From d6e1451f5b7862cea1664e04664007666f468ffa Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 04:39:51 +0800 Subject: [PATCH 01/12] test: expose permissive original mode reply boundary (refs #151) --- research/ui/origin_wrap_reference.js | 57 ++++++ research/ui/origin_wrap_reference.py | 220 ++++++++++++++++++++++ research/ui/test_origin_wrap_reference.py | 115 +++++++++++ 3 files changed, 392 insertions(+) create mode 100644 research/ui/origin_wrap_reference.js create mode 100644 research/ui/origin_wrap_reference.py create mode 100644 research/ui/test_origin_wrap_reference.py diff --git a/research/ui/origin_wrap_reference.js b/research/ui/origin_wrap_reference.js new file mode 100644 index 00000000..796ce0a2 --- /dev/null +++ b/research/ui/origin_wrap_reference.js @@ -0,0 +1,57 @@ +// Direct original DSR and document observations; no clipboard, paint or private settings. +let stage=0,busy=false,finished=false,due=0,lastPhase=-1,stable=null,stableSince=0; +let readyGrid=null,readySince=0; +const started=Date.now(),observations=[]; +for(const module of [wind,core,gui,widgets]){ + const expected=(__RUN_DIRECTORY_JSON__+'/app/'+module.name).replace(/\\/g,'/').toLowerCase(); + if(module.path.replace(/\\/g,'/').toLowerCase()!==expected)throw Error('pinned Qt module path guard'); +} +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'),lines=[];if(typeof n!=='number'||n<0||n>200)throw Error('document line bound');for(var i=0;i1024)throw Error('document text bound');lines.push(line);}return {protocol:get('protocolName'),connected:get('isConnected'),top:get('topLine'),scroll:get('scrollTop'),line_count:n,capacity:get('screenLineCapacity'),lines:lines,caret_api:{caretLine:get('caretLine'),caretColumn:get('caretColumn'),caretPosition:get('caretPosition')},cell_styles:null,current_style:null};})())`); + return raw?JSON.parse(raw):null; +} +function finish(status,error){ + if(finished)return;finished=true; + const result={status,error:error||null,observations,full_active_grid_verified:false,terminal_style_values_verified:false,local_shell_transport_verified:false,presented_done:null}; + const file=new File(__RUN_DIRECTORY_JSON__+'/origin-wrap-result.json','w');file.write(JSON.stringify(result));file.close(); + send({kind:'origin_wrap_result',...result}); +} +send({kind:'probe_ready',station_isolated:true,scenario:'origin-wrap-margin-v1'}); +Interceptor.attach(user.getExportByName('DispatchMessageW'),{onEnter(){ + if(busy||finished||Process.getCurrentThreadId()!==uiThread||Date.now()41000)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)return; + if(peer.error)throw Error('peer '+peer.error); + if(peer.complete){finish('observed-all-origin-wrap-phases',null);return;} + const terminal=currentTerminal();if(!terminal||!terminal.connected||terminal.protocol!=='Telnet')return; + if(stage===3){ + const grid=JSON.stringify([peer.columns,peer.rows,terminal.capacity]); + if(!peer.columns||peer.rows!==terminal.capacity)return; + if(grid!==readyGrid){readyGrid=grid;readySince=Date.now();return;} + if(Date.now()-readySince<600)return; + const flag=new File(__RUN_DIRECTORY_JSON__+'/fixture-ready','w');flag.write('stable-NAWS-capacity');flag.close(); + stage=4;return; + } + if(peer.phase===null)return; + if(terminal.capacity!==peer.frozen_grid[1])throw Error('terminal capacity changed after freeze'); + if(peer.phase!==lastPhase){lastPhase=peer.phase;stable=null;stableSince=Date.now();} + const record=peer.records[peer.phase];if(!record.cursor_reply)return; + const state=JSON.stringify(terminal); + if(state!==stable){stable=state;stableSince=Date.now();return;} + if(Date.now()-stableSince<400)return; + if(observations.length!==peer.phase||observations.length>=25)throw Error('phase sequence bound'); + const observation={phase:peer.phase,label:peer.phase_label,terminal,cursor_reply:record.cursor_reply,cursor_reply_private:record.cursor_reply_private,response_hex:record.response_hex,frozen_grid:peer.frozen_grid}; + observations.push(observation);send({kind:'origin_wrap_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/origin_wrap_reference.py b/research/ui/origin_wrap_reference.py new file mode 100644 index 00000000..792852d9 --- /dev/null +++ b/research/ui/origin_wrap_reference.py @@ -0,0 +1,220 @@ +"""One original origin/wrap/margin observation, guarded by an immutable run plan.""" +from __future__ import annotations +import argparse +import hashlib +import json +from pathlib import Path +import platform +import re +import subprocess +import sys + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT)) +CASE_ID = "origin-wrap-margin-v1" +SOURCE_FILES = ("research/ui/origin_wrap_reference.py", "research/ui/origin_wrap_reference.js", + "research/ui/alternate_reference.py", "research/ui/r1_launch.py", "research/ui/launch.py", + "research/ui/qt_r1_text.js", "experiments/conpty/run.py") +EXE_SHA256 = "7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0" +MANIFEST_SHA256 = "e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203" + + +def require(condition, message): + if not condition: + raise ValueError(message) + + +def phases(rows, columns): + require(type(rows) is int and type(columns) is int and 20 <= rows <= 60 and 40 <= columns <= 160, + "Controlled grid out of bounds") + esc = b"\x1b[" + cup = lambda row, column: f"\x1b[{row};{column}H".encode("ascii") + return [ + ("fresh-session-cup", esc+b"2J"+esc+b"HFT"), + ("explicit-origin-wrap-reset", esc+b"?6l"+esc+b"?7h"+esc+b"r"+esc+b"2J"+esc+b"H"), + ("main-margin-set", esc+b"2;5r"), + ("origin-reset-cup", cup(1,3)+b"D"), + ("origin-set", esc+b"?6h"), + ("origin-set-cup", cup(1,3)+b"O"), + ("origin-set-upper-relative-bound", esc+b"99A"+esc+b"99D"), + ("origin-set-lower-relative-bound", esc+b"99B"), + ("origin-reset-after-margin", esc+b"?6l"), + ("region-sentinels", esc+b"r"+esc+b"2J"+cup(1,1)+b"TP"+cup(rows,1)+b"BT"+ + cup(2,1)+b"R0"+cup(3,1)+b"R1"+cup(4,1)+b"R2"+cup(5,1)+b"R3"+esc+b"2;5r"), + ("region-bottom-newline", cup(5,1)+b"\r\nNW"), + ("above-region-newline", cup(1,1)+b"\nUP"), + ("below-region-newline", cup(rows,1)+b"\nDN"), + ("main-margin-reset", esc+b"r"), + # The two letters share a source packet with no DSR between them. + ("wrap-enabled-edge", esc+b"?7h"+cup(8,columns)+b"AB"), + ("wrap-disabled-edge", esc+b"?7l"+cup(10,columns)+b"CD"), + ("wrap-enabled-again-edge", esc+b"?7h"+cup(12,columns)+b"EF"), + ("pending-edge-disable-before-next", cup(14,columns)+b"G"+esc+b"?7lH"), + ("disabled-edge-enable-before-next", esc+b"?7l"+cup(16,columns)+b"I"+esc+b"?7hJ"), + ("main-margin-origin-before-alt", esc+b"?7h"+esc+b"2;5r"+esc+b"?6h"+cup(2,4)+b"M"), + ("alternate-enter", esc+b"?1049h"), + ("alternate-margin-origin", esc+b"3;6r"+esc+b"?6h"+cup(2,4)+b"A"), + ("alternate-exit", esc+b"?1049l"), + ("main-cup-after-alternate", cup(1,6)+b"R"), + ("final-explicit-reset", esc+b"?6l"+esc+b"?7h"+esc+b"r"+cup(1,1)+b"DONE"), + ] + + +def reply_observation(data): + """Return a complete DSR observation, or wait for a split reply prefix.""" + matches = list(re.finditer(rb"\x1b\[(\??)([0-9]+);([0-9]+)R", data)) + if not matches: + return None + match = matches[-1] + return {"cursor_reply": [int(match[2]), int(match[3])], + "cursor_reply_private": match[1] == b"?", "response_hex": data.hex()} + + +def peer_class(): + """Reuse the protected Telnet parser in a private namespace, without changing older runs.""" + from research.ui import alternate_reference + original = Path(alternate_reference.__file__) + source = original.read_text(encoding="utf-8") + replacements = { + 'if self.state["rows"] and selected is None:': + 'if self.state["rows"] and selected is None and (self.directory / "fixture-ready").exists():', + 'selected = phases(self.state["rows"])': + 'self.state["frozen_grid"] = [self.state["columns"], self.state["rows"]]\n self.state["freeze_event_count"] = len(self.state.get("naws_history", []))\n selected = phases(self.state["rows"], self.state["columns"])', + 'self.state["rows"] = int.from_bytes(sub[3:5], "big")': + 'self.state["rows"] = int.from_bytes(sub[3:5], "big")\n self._on_naws()', + ' if index >= 0:\n matches = list(re.finditer(rb"\\x1b\\[(\\??)(\\d+);(\\d+)R", bytes(received[reply_start:])))\n self.state["records"][index]["cursor_reply"] = [int(matches[-1][2]),int(matches[-1][3])] if matches else None': + ' self.state["received_hex"] = received.hex()\n if index >= 0:\n raw = bytes(received[reply_start:])\n self.state["records"][index]["response_hex"] = raw.hex()\n observed = reply_observation(raw)\n if observed is not None:\n self.state["records"][index].update(observed)', + 'if selected and (index == -1 or (self.directory / f"ack-{index}").exists()):': + 'if selected and (index == -1 or ((self.directory / f"ack-{index}").exists() and self.state["records"][index]["cursor_reply"] is not None)):', + } + for old, new in replacements.items(): + require(source.count(old) == 1, "Protected peer source changed") + source = source.replace(old, new) + namespace = {"__file__": str(original), "__name__": "termforge_origin_wrap_peer"} + exec(compile(source, str(original), "exec"), namespace) + namespace.update(phases=phases, reply_observation=reply_observation) + return namespace["Peer"] + + +class Peer(peer_class()): + def _on_naws(self): + grid = [self.state["columns"], self.state["rows"]] + history = self.state.setdefault("naws_history", []) + if len(history) >= 64: + raise RuntimeError("NAWS event bound") + history.append(grid) + self._record() + if self.state.get("frozen_grid") and self.state["frozen_grid"] != grid: + raise RuntimeError("NAWS changed after fixture freeze") + + +def sha(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def rea_identity(rea): + package = rea.parent.parent / "rea-agents/package.json" + metadata = json.loads(package.read_text(encoding="utf-8")) + require(rea.is_file() and metadata["name"] == "rea-agents" and metadata["version"] == "6.7.0" and + metadata["bin"]["rea"] == "scripts/rea.mjs", "Locked REA version/entry differs") + return {"version": "6.7.0", "shim_sha256": sha(rea), "package_json_sha256": sha(package), + "entry_sha256": sha(package.parent / "scripts/rea.mjs")} + + +def phase_records(rows=25, columns=91): + return [{"phase": index, "label": label, "source_hex": content.hex(), "source_bytes": len(content), + "source_sha256": hashlib.sha256(content).hexdigest(), "dsr_suffix_hex": "1b5b366e"} + for index, (label, content) in enumerate(phases(rows, columns))] + + +def fixed_contract(): + return {"schema": "termforge-origin-wrap-plan-v1", "issue": 151, "case_id": CASE_ID, + "maximum_original_runs": 1, "actual_original_runs": 0, "phase_count": 25, + "rea_capture_seconds": 45, "cli_wait_timeout_seconds": 60, "whole_lifecycle_bound_seconds": None, + "example_grid": [91, 25], "example_phases": phase_records(), + "reference_exe_sha256": EXE_SHA256, "reference_files_manifest_sha256": MANIFEST_SHA256} + + +def validate_fixed_contract(plan): + for name, expected in fixed_contract().items(): + require(type(plan.get(name)) is type(expected) and plan[name] == expected, "Fixed source/bounds differ") + + +def launcher_source(): + source = (ROOT / "research/ui/r1_launch.py").read_text(encoding="utf-8") + substitutions = { + "from research.ui.r1_vt_fixture import Peer": "from research.ui.origin_wrap_reference import Peer", + 'Path(__file__).with_name("r1_menu_wrap.js").read_text(encoding="utf-8")': + 'Path(__file__).with_name("origin_wrap_reference.js").read_text(encoding="utf-8")', + } + for old, new in substitutions.items(): + require(source.count(old) == 1, "Protected launcher source changed") + source = source.replace(old, new) + return source + + +def host_identity(): + return {"python_sha256": sha(Path(sys.executable)), "python_version": sys.version, + "windows_version": platform.version()} + + +def prepare_plan(source, out, rea, destination, execution_authorized=False): + source, out, rea, destination = [path.resolve() for path in (source, out, rea, destination)] + require(not out.exists() and not destination.exists(), "Plan/output must be new") + require(out != ROOT / "local" and out.is_relative_to(ROOT / "local") and + destination.is_relative_to(ROOT / "local"), "Plan/output must belong to this worktree local directory") + require(not out.is_relative_to(source) and not source.is_relative_to(out), "Source/output overlap") + require(sha(source / "WindTerm.exe") == EXE_SHA256, "Pinned original executable differs") + plan = {**fixed_contract(), "execution_authorized": execution_authorized, + "source_base_commit": subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip(), + "source": str(source), "out": str(out), "rea": str(rea), "rea_prepared_identity": rea_identity(rea), + "source_files_sha256": {name: sha(ROOT / name) for name in SOURCE_FILES}, + "launcher_source_sha256": hashlib.sha256(launcher_source().encode()).hexdigest(), **host_identity(), + "source_freeze": "Stable actual NAWS/capacity before first source; retain every complete NAWS; any subsequent grid change fails", + "scope": "Direct DSR and synthetic document text only; document is not the active screen grid", + "stop": "Identity/protocol/NAWS/stage/timeout/error stops the single attempt; no retry or budget extension"} + destination.parent.mkdir(parents=True, exist_ok=True) + with destination.open("x", encoding="utf-8", newline="\n") as handle: + handle.write(json.dumps(plan, indent=2) + "\n") + return plan + + +def run_plan(path): + path = path.resolve() + plan = json.loads(path.read_text(encoding="utf-8")) + require(plan.get("execution_authorized") is True, "Offline proposal only; original execution has not been authorized") + validate_fixed_contract(plan) + require(plan["source_base_commit"] == subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip(), + "Source commit changed after plan") + require(plan["source_files_sha256"] == {name: sha(ROOT / name) for name in SOURCE_FILES}, "Probe source changed after plan") + require(plan["launcher_source_sha256"] == hashlib.sha256(launcher_source().encode()).hexdigest(), "Launcher source differs") + require(all(plan[key] == value for key, value in host_identity().items()), "Execution host/runtime differs") + source, out, rea = [Path(plan[key]).resolve() for key in ("source", "out", "rea")] + require(path.is_relative_to(ROOT / "local") and out != ROOT / "local" and out.is_relative_to(ROOT / "local") and + not out.exists(), "This run plan/output is not new/local") + require(plan["rea_prepared_identity"] == rea_identity(rea), "Prepared REA entry changed") + require(sha(source / "WindTerm.exe") == EXE_SHA256, "Pinned original executable differs") + # The inherited copier validates the complete 4,047-file distribution before launch. + with path.with_suffix(".attempted").open("x", encoding="utf-8") as marker: + marker.write("One original launch attempt; retain after failure and never retry this plan.\n") + namespace = {"__file__": str(ROOT / "research/ui/r1_launch.py"), "__name__": "termforge_origin_wrap_launcher"} + exec(compile(launcher_source(), namespace["__file__"], "exec"), namespace) + namespace["run"](source, out, rea, 45, False, "menu-wrap") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--plan", type=Path, required=True) + parser.add_argument("--prepare", action="store_true") + parser.add_argument("--authorize-execution", action="store_true") + for name in ("source", "out", "rea"): + parser.add_argument("--" + name, type=Path) + args = parser.parse_args() + if args.prepare: + require(args.source and args.out and args.rea, "Source/output/REA required") + prepare_plan(args.source, args.out, args.rea, args.plan, args.authorize_execution) + print(json.dumps({"prepared": True, "phase_count": 25, "maximum_original_runs": 1, + "execution_authorized": args.authorize_execution})) + else: + require(not args.authorize_execution, "Authorization belongs to preparation of a fresh plan") + run_plan(args.plan) diff --git a/research/ui/test_origin_wrap_reference.py b/research/ui/test_origin_wrap_reference.py new file mode 100644 index 00000000..34550e5b --- /dev/null +++ b/research/ui/test_origin_wrap_reference.py @@ -0,0 +1,115 @@ +"""Pure protocol/plan counterexamples; never execute a target or native runtime.""" +from __future__ import annotations +import copy +import hashlib +import json +from pathlib import Path +import tempfile +import unittest + +from research.ui import origin_wrap_reference as probe + + +class ProtocolTests(unittest.TestCase): + def test_fixed_input_covers_fresh_and_explicit_origin_without_inserting_dsr(self): + rows = probe.phases(25, 91) + self.assertEqual(len(rows), 25) + self.assertEqual(rows[0], ("fresh-session-cup", b"\x1b[2J\x1b[HFT")) + self.assertNotIn(b"\x1b[?6", rows[0][1]) + self.assertEqual(rows[4], ("origin-set", b"\x1b[?6h")) + self.assertEqual(rows[14], ("wrap-enabled-edge", b"\x1b[?7h\x1b[8;91HAB")) + self.assertEqual(rows[15], ("wrap-disabled-edge", b"\x1b[?7l\x1b[10;91HCD")) + self.assertEqual(rows[19][0], "main-margin-origin-before-alt") + self.assertEqual(rows[20], ("alternate-enter", b"\x1b[?1049h")) + self.assertEqual(rows[22], ("alternate-exit", b"\x1b[?1049l")) + self.assertEqual(len({name for name, _ in rows}), len(rows)) + for _, content in rows: + self.assertNotIn(b"\x1b[6n", content) + + def test_grid_limits_and_dynamic_edge_sources(self): + for grid in ((19, 91), (61, 91), (25, 39), (25, 161), (True, 91)): + with self.subTest(grid=grid), self.assertRaises(ValueError): + probe.phases(*grid) + cases = dict(probe.phases(30, 120)) + self.assertIn(b"\x1b[30;1HBT", cases["region-sentinels"]) + self.assertEqual(cases["wrap-enabled-edge"], b"\x1b[?7h\x1b[8;120HAB") + + def test_exact_reply_is_preserved_and_split_prefix_waits(self): + for raw in (b"", b"\x1b", b"\x1b[", b"\x1b[?", b"\x1b[12;", b"\x1b[12;3"): + with self.subTest(raw=raw): + self.assertIsNone(probe.reply_observation(raw)) + self.assertEqual(probe.reply_observation(b"\x1b[3;7R"), + {"cursor_reply": [3, 7], "cursor_reply_private": False, "response_hex": "1b5b333b3752"}) + self.assertEqual(probe.reply_observation(b"\x1b[?0;5R")["cursor_reply"], [0, 5]) + self.assertTrue(probe.reply_observation(b"\x1b[?0;5R")["cursor_reply_private"]) + + def test_duplicate_reply_or_unrelated_bytes_never_become_success(self): + for raw in (b"\x1b[3;7R\x1b[3;7R", b"private-path", b"\x1b[3;7Rtail", b"\x1b[;7R", b"\x1b[3;7m"): + with self.subTest(raw=raw), self.assertRaises(ValueError): + probe.reply_observation(raw) + + def test_naws_transient_change_is_retained_and_rejected(self): + peer = probe.Peer.__new__(probe.Peer) + peer.state = {"columns": 91, "rows": 25} + peer._record = lambda: None + peer._on_naws() + peer.state["columns"] = 92 + peer._on_naws() + peer.state["columns"] = 91 + peer._on_naws() + self.assertEqual(peer.state["naws_history"], [[91, 25], [92, 25], [91, 25]]) + peer.state["frozen_grid"] = [91, 25] + peer._on_naws() + peer.state["columns"] = 92 + with self.assertRaisesRegex(RuntimeError, "NAWS changed"): + peer._on_naws() + self.assertEqual(peer.state["naws_history"][-1], [92, 25]) + + def test_peer_namespace_does_not_mutate_older_cases(self): + from research.ui import alternate_reference, alternate_style_reference + self.assertIsNot(probe.Peer._serve.__globals__, alternate_reference.Peer._serve.__globals__) + self.assertIs(probe.Peer._serve.__globals__["phases"], probe.phases) + self.assertIs(alternate_style_reference.Peer._serve.__globals__["phases"], alternate_style_reference.phases) + + +class PlanTests(unittest.TestCase): + def test_false_proposal_does_not_create_attempt_or_target_directory(self): + with tempfile.TemporaryDirectory() as temporary: + path = Path(temporary) / "proposal.json" + path.write_text(json.dumps({"issue": 151, "case_id": probe.CASE_ID, + "maximum_original_runs": 1, "execution_authorized": False})) + with self.assertRaisesRegex(ValueError, "not been authorized"): + probe.run_plan(path) + self.assertEqual([p.name for p in path.parent.iterdir()], ["proposal.json"]) + + def test_plan_source_and_deadline_changes_are_rejected(self): + plan = probe.fixed_contract() + probe.validate_fixed_contract(plan) + for key, value in (("phase_count", 26), ("rea_capture_seconds", 46), ("cli_wait_timeout_seconds", 61), + ("maximum_original_runs", True), ("whole_lifecycle_bound_seconds", 45)): + changed = copy.deepcopy(plan) + changed[key] = value + with self.subTest(key=key), self.assertRaises(ValueError): + probe.validate_fixed_contract(changed) + changed = copy.deepcopy(plan) + changed["example_phases"][0]["source_hex"] = "00" + with self.assertRaises(ValueError): + probe.validate_fixed_contract(changed) + + def test_launcher_and_input_identity_keep_protected_older_files(self): + root = Path(__file__).resolve().parents[2] + paths = [root / name for name in ("research/ui/alternate_reference.py", "research/ui/r1_launch.py", + "research/ui/qt_r1_text.js")] + before = [hashlib.sha256(path.read_bytes()).hexdigest() for path in paths] + script = probe.launcher_source() + self.assertIn("from research.ui.origin_wrap_reference import Peer", script) + self.assertIn('with_name("origin_wrap_reference.js")', script) + self.assertIn('job.assign(info.pid)', script) + self.assertEqual([hashlib.sha256(path.read_bytes()).hexdigest() for path in paths], before) + records = probe.phase_records(25, 91) + self.assertEqual(records[0]["source_hex"], "1b5b324a1b5b484654") + self.assertEqual(records[0]["dsr_suffix_hex"], "1b5b366e") + + +if __name__ == "__main__": + unittest.main() From 9f2bf13549227bc40843d9140aeed68c676fbde4 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 04:42:56 +0800 Subject: [PATCH 02/12] research: freeze bounded original origin-wrap observation (refs #151) --- docs/research/windterm-origin-wrap-plan.md | 69 +++++++++++++++++ research/ui/origin_wrap_reference.js | 2 +- research/ui/origin_wrap_reference.py | 13 ++-- research/ui/test_origin_wrap_reference.py | 88 ++++++++++++++++++++++ 4 files changed, 166 insertions(+), 6 deletions(-) create mode 100644 docs/research/windterm-origin-wrap-plan.md diff --git a/docs/research/windterm-origin-wrap-plan.md b/docs/research/windterm-origin-wrap-plan.md new file mode 100644 index 00000000..99623abc --- /dev/null +++ b/docs/research/windterm-origin-wrap-plan.md @@ -0,0 +1,69 @@ +# WindTerm 2.7.0:origin/wrap 与滚动区域取证计划 + +状态:**离线准备,原版运行次数为 0,尚无行为结论**。本项由 [#151](https://github.com/JavaZeroo/termforge/issues/151) 承接 [#27](https://github.com/JavaZeroo/termforge/issues/27) 的直接观察缺口;没有改动自主 C++ 核心或产品模式。通用规范、旧候选和本地控制台结果均不作为原版行为。 + +目标固定为官方 WindTerm 2.7.0 Windows x64,EXE SHA256 `7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0`;受保护复制器在创建自有副本后校验 4,047 文件分发清单 `e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203`,不改动官方原件。 + +## 固定观察边界 + +[Python 执行器](../../research/ui/origin_wrap_reference.py)复用既有 loopback Telnet 解析器和 `r1_launch.py`,但使用独立命名空间和本项 [Qt 观察脚本](../../research/ui/origin_wrap_reference.js)。原有执行器、探针和旧证据不变。单个合成 profile 位于新 `local/` 目录;原版窗口在独立非输入 WindowStation/Desktop 挂起创建,分配自有 Job 后恢复。REA CLI 树也沿用既有自有 Job/句柄监督。本流程没有产品 stdin GO 协议,不把挂起创建写成 GO 握手。 + +先等待实际 NAWS 和 `screenLineCapacity` 相符且稳定 600ms,再允许 peer 冻结完整网格。接受范围为 40–160 列、20–60 行;实例计划展示 91×25,但实际输入按冻结后的实际尺寸生成并逐阶段保存。每个完整 NAWS 事件均保留,包括同一次 recv 中瞬时改变后恢复的事件;冻结后任意改变立即失败。Qt capacity 改变同样失败。 + +每阶段仅发送表中输入和一个末尾 `CSI 6n`;等待一条完整 DSR 及稳定 400ms 的 Qt 文档快照,再写独立 ack 推进下一阶段。完整接收字节、DSR 私有前缀和行列分别记录;分包前缀可等待,重复回复、附加字节和错误格式不能被“取最后一条回复”掩盖。允许报告的 0 坐标被保留,不预设原版始终采用一基原点。DSR 不插入同一阶段末列两个字母之间。 + +Qt 的 `caret_api`、文档行号/文字、DSR 行列各自保留;**文档数组不等于活动屏幕网格**。本次不调用未知的样式接口,也不捕获像素或声称实际上屏。 + +## 25 个阶段 + +`r` 表示 `CSI r`,`CUP(r,c)` 表示显式光标定位。详细十六进制、字节数和 SHA256 由 `phase_records()` 写入执行前计划,实际源再按冻结网格存入 raw peer。 + +| 序号 | 阶段 | 合成输入与要观察的问题 | +|---|---|---| +| 0 | fresh-session-cup | ED2、Home、`FT`;不先改 origin/wrap,保留新会话入口观察 | +| 1 | explicit-origin-wrap-reset | `?6l`、`?7h`、`r`、ED2、Home,建立显式控制状态 | +| 2 | main-margin-set | `2;5r` 后的光标回复 | +| 3 | origin-reset-cup | CUP(1,3)、`D` | +| 4 | origin-set | 单独 `?6h` 后的光标回复 | +| 5 | origin-set-cup | CUP(1,3)、`O` | +| 6 | origin-set-upper-relative-bound | CUU99、CUB99 | +| 7 | origin-set-lower-relative-bound | CUD99 | +| 8 | origin-reset-after-margin | 单独 `?6l` | +| 9 | region-sentinels | `r`、ED2;顶行 `TP`、最底行 `BT`;第2–5行 `R0`–`R3`;再设置 `2;5r` | +| 10 | region-bottom-newline | CUP(5,1)、CRLF、`NW`,读取区域内/外文字变化 | +| 11 | above-region-newline | CUP(1,1)、LF、`UP` | +| 12 | below-region-newline | CUP(实际最底行,1)、LF、`DN` | +| 13 | main-margin-reset | 单独 `r` | +| 14 | wrap-enabled-edge | `?7h`、CUP(8,末列)、`AB` | +| 15 | wrap-disabled-edge | `?7l`、CUP(10,末列)、`CD` | +| 16 | wrap-enabled-again-edge | `?7h`、CUP(12,末列)、`EF` | +| 17 | pending-edge-disable-before-next | CUP(14,末列)、`G`、`?7l`、`H` | +| 18 | disabled-edge-enable-before-next | `?7l`、CUP(16,末列)、`I`、`?7h`、`J` | +| 19 | main-margin-origin-before-alt | `?7h`、`2;5r`、`?6h`、CUP(2,4)、`M` | +| 20 | alternate-enter | `?1049h` | +| 21 | alternate-margin-origin | `3;6r`、`?6h`、CUP(2,4)、`A` | +| 22 | alternate-exit | `?1049l` | +| 23 | main-cup-after-alternate | CUP(1,6)、`R`,读取特定返回路径的 origin/margin 影响 | +| 24 | final-explicit-reset | `?6l`、`?7h`、`r`、CUP(1,1)、`DONE` | + +这些是待回答的问题和确定输入,不是预先规定的原版答案。样式、完整网格、历史选择、复杂文字、resize、全套保存恢复、其他模式、固定 ConPTY 本地路径、不同 DPI、物理输入/实际呈现和性能均未由本项验收。 + +## 次数、期限与停止 + +每份计划最多一次原版尝试。false proposal 默认禁止执行,且不能创建 attempt marker 或目标目录。执行前重新核对源码 commit/逐文件 SHA、生成后的 launcher SHA、Python 文件和版本、Windows 版本,以及现有 REA 6.7.0 shim/package/entry SHA。正式尝试先用排他方式创建 `.attempted`,失败后保留,不重试或覆盖目录。该内部防重复记录不要求新的逐次用户确认;实际执行由父代理完成预审后推进。 + +原预算保持:peer 接收循环43秒、JS阶段检查41秒/计时器42秒、REA 捕获45秒、CLI 等待60秒。原版在 CLI 前已恢复,另有2秒启动间隔;**45/60秒不是整个进程或启动器的墙钟上限**,`whole_lifecycle_bound_seconds=null`。继承的清理含 peer join3秒、原版句柄等待5秒和 Job drain 自身边界,不能把这些局部预算拼成一个已保证的整程期限。挂起创建/分配失败仍沿原句柄回收分支;任何结构、身份、NAWS、协议、阶段、stderr/REA或清理失败保留原件并停止。 + +本次准备只运行 [纯 Python 合成测试](../../research/ui/test_origin_wrap_reference.py):内存假连接覆盖完整实际接收循环、分包DSR、瞬时NAWS变化和重复回复;没有打开套接字、运行 Node/Qt/native/guest 或启动原版。宽松末条匹配的 red 记录有9项测试、5个失败子例;严格边界修复后最终普通/`-O`各12项通过。早先缺模块的初始 red 仅为脚手架检查,不能当作协议反例。 + +## 复现准备与后续执行 + +在本工作树使用已存在的 Python 和 REA 路径;不安装、升级或探测连接配置。以下命令中的 `<...>` 由父代理替换为已有私有绝对路径,所有计划和原件留在 `local/`。 + +```powershell +python -B -X utf8 -m unittest research.ui.test_origin_wrap_reference -v +python -B -O -X utf8 -m unittest research.ui.test_origin_wrap_reference -v +python -B -X utf8 research/ui/origin_wrap_reference.py --prepare --plan local/origin-wrap-proposal.json --source <固定官方包> --out local/origin-wrap-run-0 --rea <现有REA6.7.0-shim> +``` + +上述 prepare 默认 `execution_authorized=false`,不启动任何目标。审查通过后,在新计划路径加入 `--authorize-execution` 冻结同一源码和期限,再用 `--plan <新计划>`执行;不得将旧 false 提案改写为已执行或复用其 attempt。实际运行后先审计原始阶段、DSR、NAWS、profile/Job,再实现严格离线白名单导出并验证普通/`-O`一致;raw文档、PID/路径、REA原始产物、专有副本均不入 git。没有这一步就不公开原始结果或关闭本项。 diff --git a/research/ui/origin_wrap_reference.js b/research/ui/origin_wrap_reference.js index 796ce0a2..148cf125 100644 --- a/research/ui/origin_wrap_reference.js +++ b/research/ui/origin_wrap_reference.js @@ -28,7 +28,7 @@ Interceptor.attach(user.getExportByName('DispatchMessageW'),{onEnter(){ else{ const peer=peerState();if(!peer||!peer.connected)return; if(peer.error)throw Error('peer '+peer.error); - if(peer.complete){finish('observed-all-origin-wrap-phases',null);return;} + if(peer.complete){if(observations.length!==25)throw Error('incomplete observation sequence');finish('observed-all-origin-wrap-phases',null);return;} const terminal=currentTerminal();if(!terminal||!terminal.connected||terminal.protocol!=='Telnet')return; if(stage===3){ const grid=JSON.stringify([peer.columns,peer.rows,terminal.capacity]); diff --git a/research/ui/origin_wrap_reference.py b/research/ui/origin_wrap_reference.py index 792852d9..989595c6 100644 --- a/research/ui/origin_wrap_reference.py +++ b/research/ui/origin_wrap_reference.py @@ -14,7 +14,7 @@ CASE_ID = "origin-wrap-margin-v1" SOURCE_FILES = ("research/ui/origin_wrap_reference.py", "research/ui/origin_wrap_reference.js", "research/ui/alternate_reference.py", "research/ui/r1_launch.py", "research/ui/launch.py", - "research/ui/qt_r1_text.js", "experiments/conpty/run.py") + "research/ui/qt_r1_text.js", "research/ui/r1_vt_fixture.py", "experiments/conpty/run.py") EXE_SHA256 = "7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0" MANIFEST_SHA256 = "e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203" @@ -62,10 +62,13 @@ def phases(rows, columns): def reply_observation(data): """Return a complete DSR observation, or wait for a split reply prefix.""" - matches = list(re.finditer(rb"\x1b\[(\??)([0-9]+);([0-9]+)R", data)) - if not matches: - return None - match = matches[-1] + match = re.fullmatch(rb"\x1b\[(\??)([0-9]{1,5});([0-9]{1,5})R", data) + if match is None: + # A receive can split inside the ESC/CSI header or either coordinate. + # Other bytes, additional replies and tails must never be discarded. + if data in (b"", b"\x1b") or re.fullmatch(rb"\x1b\[\??[0-9]{0,5}(?:;[0-9]{0,5})?", data): + return None + raise ValueError("Unexpected DSR response bytes") return {"cursor_reply": [int(match[2]), int(match[3])], "cursor_reply_private": match[1] == b"?", "response_hex": data.hex()} diff --git a/research/ui/test_origin_wrap_reference.py b/research/ui/test_origin_wrap_reference.py index 34550e5b..0d5ab4fa 100644 --- a/research/ui/test_origin_wrap_reference.py +++ b/research/ui/test_origin_wrap_reference.py @@ -10,7 +10,95 @@ from research.ui import origin_wrap_reference as probe +def naws(columns, rows): + return bytes([255, 250, 31]) + columns.to_bytes(2, "big") + rows.to_bytes(2, "big") + bytes([255, 240]) + + +class SyntheticConnection: + """Deterministic recv fragments; this object never opens a socket.""" + def __init__(self, peer, mode): + self.peer, self.mode = peer, mode + self.initial, self.fragment, self.sent = True, None, [] + + def settimeout(self, _): + pass + + def close(self): + pass + + def sendall(self, data): + self.sent.append(data) + + def recv(self, _): + if self.initial: + self.initial = False + return naws(91, 25) + if self.mode == "transient-naws": + return naws(92, 25) + naws(91, 25) + row = self.peer.state["records"][-1] + if row["cursor_reply"] is not None: + (self.peer.directory / f'ack-{row["phase"]}').write_text("observed") + import socket + raise socket.timeout() + if self.mode == "duplicate-dsr": + return b"\x1b[3;7R\x1b[4;8R" + if self.fragment is not None: + value, self.fragment = self.fragment, None + return value + self.fragment = f'{row["phase"]+1};7R'.encode() + return b"\x1b[" + + +class SyntheticListener: + def __init__(self, connection): + self.connection = connection + + def accept(self): + return self.connection, ("127.0.0.1", 12345) + + +def receive_fixture(mode): + with tempfile.TemporaryDirectory() as temporary: + peer = probe.Peer.__new__(probe.Peer) + peer.directory = Path(temporary) + (peer.directory / "fixture-ready").write_text("stable-NAWS-capacity") + peer.state = {"connected": False, "columns": None, "rows": None, "phase": None, "records": []} + peer.stop = type("NeverStopped", (), {"is_set": lambda _: False})() + connection = SyntheticConnection(peer, mode) + peer.listener = SyntheticListener(connection) + peer._serve() + return peer.state, connection.sent + + class ProtocolTests(unittest.TestCase): + def test_receive_loop_retains_split_replies_for_every_actual_source(self): + state, sent = receive_fixture("normal") + self.assertTrue(state["complete"]) + self.assertTrue(state["closed"]) + self.assertNotIn("error", state) + self.assertEqual(state["naws_history"], [[91, 25]]) + self.assertEqual(state["freeze_event_count"], 1) + self.assertEqual(state["frozen_grid"], [91, 25]) + self.assertEqual(len(state["records"]), 25) + self.assertEqual([r["cursor_reply"] for r in state["records"]], [[n+1, 7] for n in range(25)]) + self.assertEqual(sent[0], bytes([255, 251, 1, 255, 251, 3, 255, 253, 31])) + self.assertEqual(sent[1], b"\x1b[2J\x1b[HFT\x1b[6n") + self.assertEqual(len(sent), 26) + + def test_receive_loop_rejects_transient_naws_before_advancing(self): + state, _ = receive_fixture("transient-naws") + self.assertEqual(state["error"], "NAWS changed after fixture freeze") + self.assertEqual(state["naws_history"], [[91, 25], [92, 25]]) + self.assertEqual(len(state["records"]), 1) + self.assertFalse(state.get("complete", False)) + + def test_receive_loop_keeps_duplicate_raw_and_fails_without_ack(self): + state, _ = receive_fixture("duplicate-dsr") + self.assertEqual(state["error"], "Unexpected DSR response bytes") + self.assertEqual(state["records"][0]["response_hex"], b"\x1b[3;7R\x1b[4;8R".hex()) + self.assertEqual(state["received_hex"], b"\x1b[3;7R\x1b[4;8R".hex()) + self.assertFalse(state.get("complete", False)) + def test_fixed_input_covers_fresh_and_explicit_origin_without_inserting_dsr(self): rows = probe.phases(25, 91) self.assertEqual(len(rows), 25) From e133b5122136c7309d4af6e2c9ce5bd72f91ee8d Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 04:45:11 +0800 Subject: [PATCH 03/12] research: reject incomplete original mode observation mechanics (refs #151) --- docs/research/windterm-origin-wrap-plan.md | 4 +- research/ui/origin_wrap_reference.py | 53 ++++++++++++++++++++++ research/ui/test_origin_wrap_reference.py | 37 +++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/docs/research/windterm-origin-wrap-plan.md b/docs/research/windterm-origin-wrap-plan.md index 99623abc..4104bf7e 100644 --- a/docs/research/windterm-origin-wrap-plan.md +++ b/docs/research/windterm-origin-wrap-plan.md @@ -54,7 +54,9 @@ Qt 的 `caret_api`、文档行号/文字、DSR 行列各自保留;**文档数 原预算保持:peer 接收循环43秒、JS阶段检查41秒/计时器42秒、REA 捕获45秒、CLI 等待60秒。原版在 CLI 前已恢复,另有2秒启动间隔;**45/60秒不是整个进程或启动器的墙钟上限**,`whole_lifecycle_bound_seconds=null`。继承的清理含 peer join3秒、原版句柄等待5秒和 Job drain 自身边界,不能把这些局部预算拼成一个已保证的整程期限。挂起创建/分配失败仍沿原句柄回收分支;任何结构、身份、NAWS、协议、阶段、stderr/REA或清理失败保留原件并停止。 -本次准备只运行 [纯 Python 合成测试](../../research/ui/test_origin_wrap_reference.py):内存假连接覆盖完整实际接收循环、分包DSR、瞬时NAWS变化和重复回复;没有打开套接字、运行 Node/Qt/native/guest 或启动原版。宽松末条匹配的 red 记录有9项测试、5个失败子例;严格边界修复后最终普通/`-O`各12项通过。早先缺模块的初始 red 仅为脚手架检查,不能当作协议反例。 +本次准备只运行 [纯 Python 合成测试](../../research/ui/test_origin_wrap_reference.py):内存假连接覆盖完整实际接收循环、分包DSR、瞬时NAWS变化和重复回复;没有打开套接字、运行 Node/Qt/native/guest 或启动原版。宽松末条匹配的 red 记录有9项测试、5个失败子例;严格边界及执行后机械审计完成后最终普通/`-O`各13项通过。早先缺模块的初始 red 仅为脚手架检查,不能当作协议反例。 + +REA CLI 返回0之后还必须由 `audit_mechanics()`检查25阶段/源字节/逐条DSR/Qt快照/ack和NAWS、空stderr、单个合成profile及Job active0,再写本地 `mechanics-check.json`;partial或失败不能借CLI0过关。该检查的 `semantic_acceptance=false`,不预先认可原版光标语义,不代替后续REA完整Evidence审计、来源复核和白名单导出。 ## 复现准备与后续执行 diff --git a/research/ui/origin_wrap_reference.py b/research/ui/origin_wrap_reference.py index 989595c6..ffceae10 100644 --- a/research/ui/origin_wrap_reference.py +++ b/research/ui/origin_wrap_reference.py @@ -161,6 +161,56 @@ def host_identity(): "windows_version": platform.version()} +def audit_mechanics(directory): + """Check observation completeness, without inventing any terminal-semantic answer.""" + read = lambda name: json.loads((directory / name).read_text(encoding="utf-8")) + require((directory / "stderr.txt").read_bytes() == b"" and not (directory / "launcher-failure.json").exists(), + "REA stderr/launcher failure retained") + peer, result, cleanup, profile = [read(name) for name in + ("peer.json", "origin-wrap-result.json", "cleanup.json", "profile-check.json")] + require(all(peer.get(name) is True for name in ("connected", "closed", "complete")) and not peer.get("error"), + "Peer did not complete cleanly") + require(result.get("status") == "observed-all-origin-wrap-phases" and result.get("error") is None and + result.get("presented_done") is None and all(result.get(name) is False for name in + ("full_active_grid_verified", "terminal_style_values_verified", "local_shell_transport_verified")), + "UI observation is partial or claims an unknown endpoint") + require(type(cleanup.get("active")) is int and cleanup["active"] == 0 and + all(type(cleanup.get(name)) is int and cleanup[name] >= 0 for name in ("total", "terminated")) and + profile.get("only_synthetic_session") is True and type(profile.get("session_count")) is int and + profile["session_count"] == 1, "Owned Job/profile cleanup differs") + grid = peer["frozen_grid"] + require(type(grid) is list and len(grid) == 2, "Frozen grid differs") + expected = phase_records(grid[1], grid[0]) + history, freeze = peer["naws_history"], peer["freeze_event_count"] + require(type(history) is list and 1 <= len(history) <= 64 and type(freeze) is int and + 1 <= freeze <= len(history) and all(type(event) is list and len(event) == 2 and + all(type(n) is int and 0 <= n <= 65535 for n in event) for event in history) and + history[freeze-1] == grid and all(event == grid for event in history[freeze:]), "NAWS history changed or is incomplete") + records, observations = peer["records"], result["observations"] + require(len(records) == len(observations) == 25 and peer["phase_count"] == 25 and peer["phase"] == 24 and + peer["phase_label"] == expected[-1]["label"], "Stage sequence is incomplete") + replies = bytearray() + for source, row, observation in zip(expected, records, observations): + require(all(row.get(key) == value for key, value in source.items()), "Actual frozen source differs") + raw = bytes.fromhex(row["response_hex"]) + reply = reply_observation(raw) + require(reply is not None and all(row.get(key) == value for key, value in reply.items()), "DSR observation differs") + replies.extend(raw) + require(all(observation.get(key) == row[key] for key in ("phase", "label", *reply)) and + observation["frozen_grid"] == grid, "UI/peer phase observations differ") + terminal = observation["terminal"] + require(terminal["connected"] is True and terminal["protocol"] == "Telnet" and + type(terminal["capacity"]) is int and terminal["capacity"] == grid[1] and + type(terminal["line_count"]) is int and 0 <= terminal["line_count"] <= 200 and + type(terminal["lines"]) is list and len(terminal["lines"]) == terminal["line_count"] and + all(type(line) is str and len(line) <= 1024 for line in terminal["lines"]), "Document observation bounds differ") + require((directory / f'ack-{row["phase"]}').read_text(encoding="utf-8") == "observed", "UI acknowledgment differs") + require(peer["received_hex"] == replies.hex(), "Unassigned received bytes retained") + return {"schema": "termforge-origin-wrap-mechanics-v1", "stage_count": 25, "dsr_count": 25, + "frozen_grid": grid, "naws_events": len(history), "job_active": 0, + "semantic_acceptance": False, "presented_done": None} + + def prepare_plan(source, out, rea, destination, execution_authorized=False): source, out, rea, destination = [path.resolve() for path in (source, out, rea, destination)] require(not out.exists() and not destination.exists(), "Plan/output must be new") @@ -203,6 +253,9 @@ def run_plan(path): namespace = {"__file__": str(ROOT / "research/ui/r1_launch.py"), "__name__": "termforge_origin_wrap_launcher"} exec(compile(launcher_source(), namespace["__file__"], "exec"), namespace) namespace["run"](source, out, rea, 45, False, "menu-wrap") + require(plan["source_files_sha256"] == {name: sha(ROOT / name) for name in SOURCE_FILES}, "Probe source changed during execution") + mechanics = audit_mechanics(out) + (out / "mechanics-check.json").write_text(json.dumps(mechanics, indent=2) + "\n", encoding="utf-8", newline="\n") if __name__ == "__main__": diff --git a/research/ui/test_origin_wrap_reference.py b/research/ui/test_origin_wrap_reference.py index 0d5ab4fa..d2639d1f 100644 --- a/research/ui/test_origin_wrap_reference.py +++ b/research/ui/test_origin_wrap_reference.py @@ -161,6 +161,43 @@ def test_peer_namespace_does_not_mutate_older_cases(self): class PlanTests(unittest.TestCase): + def test_mechanical_audit_refuses_partial_or_changed_observation_despite_cli_exit_zero(self): + state, _ = receive_fixture("normal") + observations = [{key: row[key] for key in ("phase", "label", "cursor_reply", "cursor_reply_private", "response_hex")} + for row in state["records"]] + for observation in observations: + observation.update(frozen_grid=[91, 25], terminal={"connected": True, "protocol": "Telnet", "capacity": 25, + "line_count": 1, "lines": ["synthetic"]}) + result = {"status": "observed-all-origin-wrap-phases", "error": None, "observations": observations, + "presented_done": None, "full_active_grid_verified": False, "terminal_style_values_verified": False, + "local_shell_transport_verified": False} + data = {"peer.json": state, "origin-wrap-result.json": result, "cleanup.json": {"active": 0, "total": 3, "terminated": 0}, + "profile-check.json": {"only_synthetic_session": True, "session_count": 1}} + with tempfile.TemporaryDirectory() as temporary: + directory = Path(temporary) + for row in state["records"]: + (directory / f'ack-{row["phase"]}').write_text("observed") + (directory / "stderr.txt").write_bytes(b"") + def save(values): + for name, value in values.items(): + (directory / name).write_text(json.dumps(value), encoding="utf-8") + save(data) + self.assertFalse(probe.audit_mechanics(directory)["semantic_acceptance"]) + for key, field, value in (("origin-wrap-result.json", "status", "failed"), + ("origin-wrap-result.json", "observations", observations[:-1]), + ("peer.json", "received_hex", state["received_hex"] + "00"), + ("cleanup.json", "active", 1), + ("profile-check.json", "only_synthetic_session", False)): + changed = copy.deepcopy(data) + changed[key][field] = value + save(changed) + with self.subTest(field=field), self.assertRaises(ValueError): + probe.audit_mechanics(directory) + save(data) + (directory / "stderr.txt").write_bytes(b"controlled error") + with self.assertRaises(ValueError): + probe.audit_mechanics(directory) + def test_false_proposal_does_not_create_attempt_or_target_directory(self): with tempfile.TemporaryDirectory() as temporary: path = Path(temporary) / "proposal.json" From 04cd906c0820990979f6f21c5a07643ff63c6c57 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 04:55:01 +0800 Subject: [PATCH 04/12] test: reproduce original mode Node selection drift (refs #151) --- research/ui/test_origin_wrap_reference.py | 81 +++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/research/ui/test_origin_wrap_reference.py b/research/ui/test_origin_wrap_reference.py index d2639d1f..9daeabc3 100644 --- a/research/ui/test_origin_wrap_reference.py +++ b/research/ui/test_origin_wrap_reference.py @@ -1,11 +1,13 @@ """Pure protocol/plan counterexamples; never execute a target or native runtime.""" from __future__ import annotations import copy +from contextlib import contextmanager, ExitStack import hashlib import json from pathlib import Path import tempfile import unittest +from unittest.mock import patch, Mock from research.ui import origin_wrap_reference as probe @@ -70,6 +72,53 @@ def receive_fixture(mode): return peer.state, connection.sent +@contextmanager +def synthetic_run_plan(): + """Exercise the actual run_plan/compiled-launcher seam with inert fake files.""" + with tempfile.TemporaryDirectory() as temporary, ExitStack() as patches: + directory = Path(temporary).resolve() + (directory / "local").mkdir() + (directory / "source").mkdir() + (directory / "source/WindTerm.exe").write_bytes(b"inert synthetic original") + (directory / "probe.py").write_bytes(b"fixed synthetic source") + nodes = [directory / f"node-{name}.exe" for name in ("a", "b")] + for index, node in enumerate(nodes): + node.write_bytes(f"inert synthetic Node {index}".encode()) + rea = directory / "tools/node_modules/.bin/rea.cmd" + rea.parent.mkdir(parents=True) + rea.write_bytes(b"inert synthetic REA shim") + package = rea.parent.parent / "rea-agents" + (package / "scripts").mkdir(parents=True) + (package / "package.json").write_text(json.dumps({"name": "rea-agents", "version": "6.7.0", "bin": {"rea": "scripts/rea.mjs"}})) + (package / "scripts/rea.mjs").write_bytes(b"inert synthetic REA entry") + launcher = '''import shutil +def cli_command(rea): + return [shutil.which("node"), str(rea.parent.parent / "rea-agents/scripts/rea.mjs")] +def run(source, out, rea, seconds, hosted, scenario): + from research.ui import origin_wrap_reference as probe + probe.capture_test_cli_command(lambda: cli_command(rea), out, seconds, hosted, scenario) +''' + patches.enter_context(patch.object(probe, "ROOT", directory)) + patches.enter_context(patch.object(probe, "SOURCE_FILES", ("probe.py",))) + patches.enter_context(patch.object(probe, "EXE_SHA256", probe.sha(directory / "source/WindTerm.exe"))) + patches.enter_context(patch.object(probe, "launcher_source", return_value=launcher)) + patches.enter_context(patch.object(probe, "host_identity", return_value={"python_sha256": "a"*64, + "python_version": "synthetic", "windows_version": "synthetic"})) + patches.enter_context(patch.object(probe.subprocess, "check_output", return_value="b"*40)) + # Patch the shared stdlib selection seam used by both the original and fixed launcher. + which = patches.enter_context(patch("shutil.which", return_value=str(nodes[0]))) + observed = [] + def record(command, out, *settings): + observed.append((command(), settings)) + out.mkdir() + capture = patches.enter_context(patch.object(probe, "capture_test_cli_command", create=True, + new=Mock(side_effect=record))) + patches.enter_context(patch.object(probe, "audit_mechanics", return_value={"synthetic_only": True})) + path, out = directory / "local/plan.json", directory / "local/out" + plan = probe.prepare_plan(directory / "source", out, rea, path, execution_authorized=True) + yield path, out, plan, nodes, which, capture, observed + + class ProtocolTests(unittest.TestCase): def test_receive_loop_retains_split_replies_for_every_actual_source(self): state, sent = receive_fixture("normal") @@ -161,6 +210,38 @@ def test_peer_namespace_does_not_mutate_older_cases(self): class PlanTests(unittest.TestCase): + def test_node_selection_path_drift_is_rejected_before_attempt(self): + with synthetic_run_plan() as (path, out, _, nodes, which, capture, _): + which.return_value = str(nodes[1]) + with self.assertRaisesRegex(ValueError, "REA entry changed"): + probe.run_plan(path) + self.assertFalse(path.with_suffix(".attempted").exists()) + self.assertFalse(out.exists()) + capture.assert_not_called() + + def test_selected_node_file_drift_is_rejected_before_attempt(self): + with synthetic_run_plan() as (path, out, _, nodes, _, capture, _): + nodes[0].write_bytes(b"changed inert Node bytes") + with self.assertRaisesRegex(ValueError, "REA entry changed"): + probe.run_plan(path) + self.assertFalse(path.with_suffix(".attempted").exists()) + self.assertFalse(out.exists()) + capture.assert_not_called() + + def test_actual_launcher_uses_recorded_executable_after_path_changes_late(self): + with synthetic_run_plan() as (path, out, plan, nodes, which, capture, observed): + def change_path_then_record(command, destination, *settings): + which.return_value = str(nodes[1]) + observed.append((command(), settings)) + destination.mkdir() + capture.side_effect = change_path_then_record + probe.run_plan(path) + self.assertEqual(observed, [([str(nodes[0]), str(Path(plan["rea"]).parent.parent / "rea-agents/scripts/rea.mjs")], + (45, False, "menu-wrap"))]) + self.assertEqual(which.call_count, 2) # preparation and pre-marker verification only + self.assertTrue(path.with_suffix(".attempted").is_file()) + self.assertTrue((out / "mechanics-check.json").is_file()) + def test_mechanical_audit_refuses_partial_or_changed_observation_despite_cli_exit_zero(self): state, _ = receive_fixture("normal") observations = [{key: row[key] for key in ("phase", "label", "cursor_reply", "cursor_reply_private", "response_hex")} From 21fc745d5a25339a4ce8f11ba1e1090a81fa1cab Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 04:55:43 +0800 Subject: [PATCH 05/12] fix: bind original mode observation to prepared Node executable (refs #151) --- docs/research/windterm-origin-wrap-plan.md | 4 ++-- research/ui/origin_wrap_reference.py | 12 +++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/research/windterm-origin-wrap-plan.md b/docs/research/windterm-origin-wrap-plan.md index 4104bf7e..5f35f3e6 100644 --- a/docs/research/windterm-origin-wrap-plan.md +++ b/docs/research/windterm-origin-wrap-plan.md @@ -50,11 +50,11 @@ Qt 的 `caret_api`、文档行号/文字、DSR 行列各自保留;**文档数 ## 次数、期限与停止 -每份计划最多一次原版尝试。false proposal 默认禁止执行,且不能创建 attempt marker 或目标目录。执行前重新核对源码 commit/逐文件 SHA、生成后的 launcher SHA、Python 文件和版本、Windows 版本,以及现有 REA 6.7.0 shim/package/entry SHA。正式尝试先用排他方式创建 `.attempted`,失败后保留,不重试或覆盖目录。该内部防重复记录不要求新的逐次用户确认;实际执行由父代理完成预审后推进。 +每份计划最多一次原版尝试。false proposal 默认禁止执行,且不能创建 attempt marker 或目标目录。执行前重新核对源码 commit/逐文件 SHA、生成后的 launcher SHA、Python 文件和版本、Windows 版本,以及现有 REA 6.7.0 shim/package/entry SHA。准备阶段还只读解析实际 `node` 绝对路径并计算文件SHA;attempt之前复验路径选择和文件字节,随后在本项独立执行 namespace 中固定 `cli_command` 使用已记录绝对Node文件和REA entry,不在真实启动时重新从PATH选择。没有调用Node或 `--version`,此记录只是文件身份,**没有取得Node进程版本**。旧受保护 `r1_launch.py` 不变,namespace接线由本项源码SHA绑定。正式尝试先用排他方式创建 `.attempted`,失败后保留,不重试或覆盖目录。该内部防重复记录不要求新的逐次用户确认;实际执行由父代理完成预审后推进。 原预算保持:peer 接收循环43秒、JS阶段检查41秒/计时器42秒、REA 捕获45秒、CLI 等待60秒。原版在 CLI 前已恢复,另有2秒启动间隔;**45/60秒不是整个进程或启动器的墙钟上限**,`whole_lifecycle_bound_seconds=null`。继承的清理含 peer join3秒、原版句柄等待5秒和 Job drain 自身边界,不能把这些局部预算拼成一个已保证的整程期限。挂起创建/分配失败仍沿原句柄回收分支;任何结构、身份、NAWS、协议、阶段、stderr/REA或清理失败保留原件并停止。 -本次准备只运行 [纯 Python 合成测试](../../research/ui/test_origin_wrap_reference.py):内存假连接覆盖完整实际接收循环、分包DSR、瞬时NAWS变化和重复回复;没有打开套接字、运行 Node/Qt/native/guest 或启动原版。宽松末条匹配的 red 记录有9项测试、5个失败子例;严格边界及执行后机械审计完成后最终普通/`-O`各13项通过。早先缺模块的初始 red 仅为脚手架检查,不能当作协议反例。 +本次准备只运行 [纯 Python 合成测试](../../research/ui/test_origin_wrap_reference.py):内存假连接覆盖完整实际接收循环、分包DSR、瞬时NAWS变化和重复回复;没有打开套接字、运行 Node/Qt/native/guest 或启动原版。宽松末条匹配的 red 记录有9项测试、5个失败子例;严格边界及执行后机械审计完成时普通/`-O`各13项通过。执行前审查另发现Node依赖未绑定,新增三个真实 `run_plan`+编译后假launcher的纯反例全部red:路径漂移、原文件字节漂移、守卫后PATH再改变。修正后最终普通/`-O`各16项通过;前两种漂移在attempt/out创建前拒绝,后一种仍使用已绑定Node。假launcher只调用Python记录函数,所有exe/entry均为惰性合成字节,没有启动进程。原false proposal及既有red/green原件保持,只为修订源码创建独立新false proposal。早先缺模块的初始 red 仅为脚手架检查,不能当作协议反例。 REA CLI 返回0之后还必须由 `audit_mechanics()`检查25阶段/源字节/逐条DSR/Qt快照/ack和NAWS、空stderr、单个合成profile及Job active0,再写本地 `mechanics-check.json`;partial或失败不能借CLI0过关。该检查的 `semantic_acceptance=false`,不预先认可原版光标语义,不代替后续REA完整Evidence审计、来源复核和白名单导出。 diff --git a/research/ui/origin_wrap_reference.py b/research/ui/origin_wrap_reference.py index ffceae10..93b71b01 100644 --- a/research/ui/origin_wrap_reference.py +++ b/research/ui/origin_wrap_reference.py @@ -6,6 +6,7 @@ from pathlib import Path import platform import re +import shutil import subprocess import sys @@ -120,8 +121,13 @@ def rea_identity(rea): metadata = json.loads(package.read_text(encoding="utf-8")) require(rea.is_file() and metadata["name"] == "rea-agents" and metadata["version"] == "6.7.0" and metadata["bin"]["rea"] == "scripts/rea.mjs", "Locked REA version/entry differs") + selected = shutil.which("node") + require(selected is not None, "Prepared Node executable is unavailable") + node = Path(selected).resolve() + require(node.is_file(), "Prepared Node executable is not a file") return {"version": "6.7.0", "shim_sha256": sha(rea), "package_json_sha256": sha(package), - "entry_sha256": sha(package.parent / "scripts/rea.mjs")} + "entry_sha256": sha(package.parent / "scripts/rea.mjs"), + "node_executable": str(node), "node_executable_sha256": sha(node)} def phase_records(rows=25, columns=91): @@ -252,6 +258,10 @@ def run_plan(path): marker.write("One original launch attempt; retain after failure and never retry this plan.\n") namespace = {"__file__": str(ROOT / "research/ui/r1_launch.py"), "__name__": "termforge_origin_wrap_launcher"} exec(compile(launcher_source(), namespace["__file__"], "exec"), namespace) + # The inherited run() resolves this name in its own namespace. After the + # pre-attempt checks it must use the bound executable, never select PATH again. + namespace["cli_command"] = lambda _: [plan["rea_prepared_identity"]["node_executable"], + str(rea.parent.parent / "rea-agents/scripts/rea.mjs")] namespace["run"](source, out, rea, 45, False, "menu-wrap") require(plan["source_files_sha256"] == {name: sha(ROOT / name) for name in SOURCE_FILES}, "Probe source changed during execution") mechanics = audit_mechanics(out) From c973f6ff86e79caf06b5fbc4e7f2a56677dd2c17 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 05:13:23 +0800 Subject: [PATCH 06/12] test: reproduce original mode pre-freeze stability gaps (refs #151) --- research/ui/test_origin_wrap_stage3.js | 66 ++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 research/ui/test_origin_wrap_stage3.js diff --git a/research/ui/test_origin_wrap_stage3.js b/research/ui/test_origin_wrap_stage3.js new file mode 100644 index 00000000..2b437f4c --- /dev/null +++ b/research/ui/test_origin_wrap_stage3.js @@ -0,0 +1,66 @@ +// Pure V8 tests of the actual stage-3 body; no target, socket or child process. +'use strict'; +const fs=require('fs'),vm=require('vm'),assert=require('assert/strict'),crypto=require('crypto'); +const source=fs.readFileSync(process.argv[2],'utf8'); +const cases=[]; +function fixture(){ + const state={now:0,peer:{connected:true,columns:91,rows:25,phase:null,naws_history:[[91,25]]}, + terminal:{connected:true,protocol:'Telnet',capacity:25,line_count:1,lines:['synthetic']}}; + const files=new Map(),messages=[],root='/owned/origin-wrap'; + const module=name=>({name,path:root+'/app/'+name,base:{add:()=>({readU8:()=>0})}}); + const context={__RUN_DIRECTORY_JSON__:root,uiThread:1, + wind:module('WindTerm.exe'),core:module('Qt5Core.dll'),gui:module('Qt5Gui.dll'),widgets:module('Qt5Widgets.dll'), + user:{getExportByName:()=>1},Process:{getCurrentThreadId:()=>1}, + Date:{now:()=>state.now},send:value=>messages.push(value),setTimeout:()=>1, + File:class{constructor(name){this.name=name;this.data='';}write(data){this.data+=data;}close(){files.set(this.name,{at:state.now,data:this.data});}}, + peerState:()=>state.peer,evalText:()=>JSON.stringify(state.terminal), + ready:()=>true,resize:()=>{},scan:()=>{},expandAll:()=>{},focus:()=>{},key:()=>{},main:1,tree:2}; + context.Interceptor={attach:(_,callbacks)=>{context.dispatch=callbacks.onEnter;}}; + vm.createContext(context); + vm.runInContext(source,context,{timeout:1000}); + function tick(now){state.now=now;vm.runInContext('dispatch()',context,{timeout:100});} + function ready(){return files.get(root+'/fixture-ready');} + function noReady(){assert.equal(ready(),undefined,'continuous stable interval has not elapsed');} + tick(0);tick(100);tick(200); // Drive the real startup transitions, without writing stage. + assert.equal(vm.runInContext('stage',context),3); + return {state,tick,ready,noReady,messages}; +} +function test(name,run){cases.push({name,run});} +test('continuous matching grid waits exactly 600ms',()=>{ + const h=fixture();h.tick(1000);h.tick(1599);h.noReady();h.tick(1600); + assert.equal(h.ready().at,1600); +}); +test('identical duplicate NAWS does not restart stable wait',()=>{ + const h=fixture();h.tick(1000);h.state.peer.naws_history.push([91,25]);h.tick(1550);h.tick(1600); + assert.equal(h.ready().at,1600); +}); +test('transient NAWS change and return restarts on observation',()=>{ + const h=fixture();h.tick(1000);h.state.peer.naws_history.push([92,25],[91,25]);h.tick(1550); + h.tick(1600);h.noReady();h.tick(2149);h.noReady();h.tick(2150); + assert.equal(h.ready().at,2150); +}); +test('batched NAWS change first seen at old deadline restarts wait',()=>{ + const h=fixture();h.tick(1000);h.state.peer.naws_history.push([92,25],[91,25]); + h.tick(1600);h.noReady();h.tick(2199);h.noReady();h.tick(2200); + assert.equal(h.ready().at,2200); +}); +for(const [name,invalidate,restore] of [ + ['capacity mismatch',s=>{s.terminal.capacity=26;},s=>{s.terminal.capacity=25;}], + ['terminal disconnected',s=>{s.terminal.connected=false;},s=>{s.terminal.connected=true;}], + ['peer disconnected',s=>{s.peer.connected=false;},s=>{s.peer.connected=true;}], + ['peer temporarily missing',s=>{s.savedPeer=s.peer;s.peer=null;},s=>{s.peer=s.savedPeer;}], + ['terminal temporarily missing',s=>{s.savedTerminal=s.terminal;s.terminal=null;},s=>{s.terminal=s.savedTerminal;}], + ['terminal protocol mismatch',s=>{s.terminal.protocol='not-Telnet';},s=>{s.terminal.protocol='Telnet';}], +])test(name+' clears prior stable interval',()=>{ + const h=fixture();h.tick(1000);invalidate(h.state);h.tick(1550);restore(h.state);h.tick(1600); + h.noReady();h.tick(2199);h.noReady();h.tick(2200);assert.equal(h.ready().at,2200); +}); +const results=[]; +for(const entry of cases){try{entry.run();results.push({case:entry.name,status:'passed'});} + catch(error){results.push({case:entry.name,status:'failed',error:String(error.message)});}} +const failed=results.filter(row=>row.status==='failed').length; +process.stdout.write(JSON.stringify({schema:'termforge-origin-wrap-stage3-test-v1',node_version:process.version, + node_arch:process.arch,node_platform:process.platform,actual_source_sha256:crypto.createHash('sha256').update(source).digest('hex'), + actual_stage3_executed:true,child_processes_spawned:0,sockets_opened:0,target_runs:0, + case_count:results.length,failed,results})+'\n'); +process.exitCode=failed?1:0; From 72e661d964b771eae226e3bc301ed213c5e55ce8 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 05:16:16 +0800 Subject: [PATCH 07/12] fix: restart original mode readiness after observed instability (refs #151) --- docs/research/windterm-origin-wrap-plan.md | 16 +++++++++++++--- research/ui/origin_wrap_reference.js | 22 ++++++++++++++++++---- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/docs/research/windterm-origin-wrap-plan.md b/docs/research/windterm-origin-wrap-plan.md index 5f35f3e6..c1bdbe45 100644 --- a/docs/research/windterm-origin-wrap-plan.md +++ b/docs/research/windterm-origin-wrap-plan.md @@ -8,7 +8,7 @@ [Python 执行器](../../research/ui/origin_wrap_reference.py)复用既有 loopback Telnet 解析器和 `r1_launch.py`,但使用独立命名空间和本项 [Qt 观察脚本](../../research/ui/origin_wrap_reference.js)。原有执行器、探针和旧证据不变。单个合成 profile 位于新 `local/` 目录;原版窗口在独立非输入 WindowStation/Desktop 挂起创建,分配自有 Job 后恢复。REA CLI 树也沿用既有自有 Job/句柄监督。本流程没有产品 stdin GO 协议,不把挂起创建写成 GO 握手。 -先等待实际 NAWS 和 `screenLineCapacity` 相符且稳定 600ms,再允许 peer 冻结完整网格。接受范围为 40–160 列、20–60 行;实例计划展示 91×25,但实际输入按冻结后的实际尺寸生成并逐阶段保存。每个完整 NAWS 事件均保留,包括同一次 recv 中瞬时改变后恢复的事件;冻结后任意改变立即失败。Qt capacity 改变同样失败。 +先等待实际 NAWS 和 `screenLineCapacity` 相符且稳定 600ms,再允许 peer 冻结完整网格。冻结前每次读取都处理新增 NAWS 历史:任何真实网格改变(包括同一次 recv 内改变后恢复)、无效 capacity、peer/terminal 缺失或断开、协议不匹配都清除此前稳定等待;恢复匹配后重新等待完整600ms。完全相同的重复NAWS不重置。接受范围为 40–160 列、20–60 行;实例计划展示 91×25,但实际输入按冻结后的实际尺寸生成并逐阶段保存。每个完整 NAWS 事件均保留;冻结后任意改变立即失败。Qt capacity 改变同样失败。 每阶段仅发送表中输入和一个末尾 `CSI 6n`;等待一条完整 DSR 及稳定 400ms 的 Qt 文档快照,再写独立 ack 推进下一阶段。完整接收字节、DSR 私有前缀和行列分别记录;分包前缀可等待,重复回复、附加字节和错误格式不能被“取最后一条回复”掩盖。允许报告的 0 坐标被保留,不预设原版始终采用一基原点。DSR 不插入同一阶段末列两个字母之间。 @@ -50,14 +50,24 @@ Qt 的 `caret_api`、文档行号/文字、DSR 行列各自保留;**文档数 ## 次数、期限与停止 -每份计划最多一次原版尝试。false proposal 默认禁止执行,且不能创建 attempt marker 或目标目录。执行前重新核对源码 commit/逐文件 SHA、生成后的 launcher SHA、Python 文件和版本、Windows 版本,以及现有 REA 6.7.0 shim/package/entry SHA。准备阶段还只读解析实际 `node` 绝对路径并计算文件SHA;attempt之前复验路径选择和文件字节,随后在本项独立执行 namespace 中固定 `cli_command` 使用已记录绝对Node文件和REA entry,不在真实启动时重新从PATH选择。没有调用Node或 `--version`,此记录只是文件身份,**没有取得Node进程版本**。旧受保护 `r1_launch.py` 不变,namespace接线由本项源码SHA绑定。正式尝试先用排他方式创建 `.attempted`,失败后保留,不重试或覆盖目录。该内部防重复记录不要求新的逐次用户确认;实际执行由父代理完成预审后推进。 +每份计划最多一次原版尝试。false proposal 默认禁止执行,且不能创建 attempt marker 或目标目录。执行前重新核对源码 commit/逐文件 SHA、生成后的 launcher SHA、Python 文件和版本、Windows 版本,以及现有 REA 6.7.0 shim/package/entry SHA。准备阶段还只读解析实际 `node` 绝对路径并计算文件SHA;attempt之前复验路径选择和文件字节,随后在本项独立执行 namespace 中固定 `cli_command` 使用已记录绝对Node文件和REA entry,不在真实启动时重新从PATH选择。**生成计划本身**不调用Node或 `--version`,该计划记录只是文件身份;后述纯JS测试另有真实Node运行,记录其 `process.version`,不能转记为REA/原版运行结果。旧受保护 `r1_launch.py` 不变,namespace接线由本项源码SHA绑定。正式尝试先用排他方式创建 `.attempted`,失败后保留,不重试或覆盖目录。该内部防重复记录不要求新的逐次用户确认;实际执行由父代理完成预审后推进。 原预算保持:peer 接收循环43秒、JS阶段检查41秒/计时器42秒、REA 捕获45秒、CLI 等待60秒。原版在 CLI 前已恢复,另有2秒启动间隔;**45/60秒不是整个进程或启动器的墙钟上限**,`whole_lifecycle_bound_seconds=null`。继承的清理含 peer join3秒、原版句柄等待5秒和 Job drain 自身边界,不能把这些局部预算拼成一个已保证的整程期限。挂起创建/分配失败仍沿原句柄回收分支;任何结构、身份、NAWS、协议、阶段、stderr/REA或清理失败保留原件并停止。 -本次准备只运行 [纯 Python 合成测试](../../research/ui/test_origin_wrap_reference.py):内存假连接覆盖完整实际接收循环、分包DSR、瞬时NAWS变化和重复回复;没有打开套接字、运行 Node/Qt/native/guest 或启动原版。宽松末条匹配的 red 记录有9项测试、5个失败子例;严格边界及执行后机械审计完成时普通/`-O`各13项通过。执行前审查另发现Node依赖未绑定,新增三个真实 `run_plan`+编译后假launcher的纯反例全部red:路径漂移、原文件字节漂移、守卫后PATH再改变。修正后最终普通/`-O`各16项通过;前两种漂移在attempt/out创建前拒绝,后一种仍使用已绑定Node。假launcher只调用Python记录函数,所有exe/entry均为惰性合成字节,没有启动进程。原false proposal及既有red/green原件保持,只为修订源码创建独立新false proposal。早先缺模块的初始 red 仅为脚手架检查,不能当作协议反例。 +[纯 Python 合成测试](../../research/ui/test_origin_wrap_reference.py)用内存假连接覆盖完整实际接收循环、分包DSR、瞬时NAWS变化和重复回复,初版准备阶段没有打开套接字或运行Node/Qt/native/guest/原版。宽松末条匹配的 red 记录有9项测试、5个失败子例;严格边界及执行后机械审计完成时普通/`-O`各13项通过。执行前审查另发现Node依赖未绑定,新增三个真实 `run_plan`+编译后假launcher的纯反例全部red:路径漂移、原文件字节漂移、守卫后PATH再改变。修正后普通/`-O`各16项通过;前两种漂移在attempt/out创建前拒绝,后一种仍使用已绑定Node。假launcher只调用Python记录函数,所有exe/entry均为惰性合成字节,没有启动进程。稳定性修订后再次执行普通/`-O`各16项通过。旧false proposal及既有red/green原件保持,修订源码使用独立新false proposal。早先缺模块的初始 red 仅为脚手架检查,不能当作协议反例。 REA CLI 返回0之后还必须由 `audit_mechanics()`检查25阶段/源字节/逐条DSR/Qt快照/ack和NAWS、空stderr、单个合成profile及Job active0,再写本地 `mechanics-check.json`;partial或失败不能借CLI0过关。该检查的 `semantic_acceptance=false`,不预先认可原版光标语义,不代替后续REA完整Evidence审计、来源复核和白名单导出。 +## 真实 JS stage 3 的纯 Node 反例 + +Spec 执行前审查发现,初版只比较最新网格,瞬时NAWS改变后恢复或capacity不匹配区间会沿用旧 `readySince`。本次另新增 [纯V8测试](../../research/ui/test_origin_wrap_stage3.js),加载本项**实际JS正文**,用假 Date、peer、terminal、File 驱动真实启动状态到stage3,不直接改stage,不使用Python镜像或生产测试钩子。隔离上下文中没有Qt、原版、进程或网络API;文件只在内存记录。 + +按执行前限定只调用Node两次:red一次、green一次,每次沿原纯fixture10秒期限,未重试或扩预算。10个场景包含连续600ms正例、相同重复NAWS、同批92→91变化、首次在旧期限观察到变化,以及capacity/terminal/peer/协议无效后恢复。red的8个反例失败、2个正例通过;green10/10,exit0,均未超时。新模块的实际Node执行与 #144 旧CI诊断分列,不据此解释或关闭旧超时。 + +Node进程实际报告 `v22.21.1`、win32/x64,文件SHA256 `471961cb355311c9a9dd8ba417eca8269ead32a2231653084112554cda52e8b3`。测试正文SHA256 `17fe62980a026e6e8086070addcbae3a3ffb6a3b7e1ac9b4335743a3d7c43b95`;red生产JS为 `b56756ffc9455651c4c082f940789b43913d6481bab38f2bb907e679409ed0ee`,green为 `d93d9c4e7c84c220be9f6911f4f04f2c12dee939cefe20854e0d9285d6f38ff5`,每次执行前后字节均相同。red执行提交为 `c973f6ff86e79caf06b5fbc4e7f2a56677dd2c17`;green读取该基点上的未提交JS修正,**以完整JS SHA为执行身份**,不把基点HEAD误写成修正已提交的执行源码。随后提交保留同一green字节,不为提交身份再跑一遍Node。 + +全部命令、原stdout/stderr、期限及源码/runtime身份仅留 `local/origin-wrap-preflight/`;本节公开的是合成反例事实。没有启动Node子进程、socket、Qt/native、guest或原版,原版运行次数仍为0。这些V8测试不证明真实Frida/Qt消息调度或原版行为,仍须完成原版实际观察和来源审计。 + ## 复现准备与后续执行 在本工作树使用已存在的 Python 和 REA 路径;不安装、升级或探测连接配置。以下命令中的 `<...>` 由父代理替换为已有私有绝对路径,所有计划和原件留在 `local/`。 diff --git a/research/ui/origin_wrap_reference.js b/research/ui/origin_wrap_reference.js index 148cf125..c8384afc 100644 --- a/research/ui/origin_wrap_reference.js +++ b/research/ui/origin_wrap_reference.js @@ -1,6 +1,6 @@ // Direct original DSR and document observations; no clipboard, paint or private settings. let stage=0,busy=false,finished=false,due=0,lastPhase=-1,stable=null,stableSince=0; -let readyGrid=null,readySince=0; +let readyGrid=null,readySince=0,readyNawsCount=0,readyNawsGrid=null; const started=Date.now(),observations=[]; for(const module of [wind,core,gui,widgets]){ const expected=(__RUN_DIRECTORY_JSON__+'/app/'+module.name).replace(/\\/g,'/').toLowerCase(); @@ -16,6 +16,19 @@ function finish(status,error){ const file=new File(__RUN_DIRECTORY_JSON__+'/origin-wrap-result.json','w');file.write(JSON.stringify(result));file.close(); send({kind:'origin_wrap_result',...result}); } +function resetReadiness(){readyGrid=null;readySince=0;} +function observeReadinessNaws(peer){ + const history=peer.naws_history; + if(!Array.isArray(history)||history.length64)throw Error('NAWS readiness history bound'); + for(let i=readyNawsCount;i!Number.isInteger(n)||n<0||n>65535))throw Error('NAWS readiness event bound'); + const grid=JSON.stringify(event); + if(readyNawsGrid!==null&&grid!==readyNawsGrid)resetReadiness(); + readyNawsGrid=grid; + } + readyNawsCount=history.length; +} send({kind:'probe_ready',station_isolated:true,scenario:'origin-wrap-margin-v1'}); Interceptor.attach(user.getExportByName('DispatchMessageW'),{onEnter(){ if(busy||finished||Process.getCurrentThreadId()!==uiThread||Date.now() Date: Thu, 29 Oct 2026 05:40:32 +0800 Subject: [PATCH 08/12] research: audit and whitelist fixed original mode evidence (refs #151) --- CMakeLists.txt | 1 + research/ui/export_origin_wrap_reference.py | 236 ++++++++++++++++++ .../ui/test_export_origin_wrap_reference.py | 149 +++++++++++ 3 files changed, 386 insertions(+) create mode 100644 research/ui/export_origin_wrap_reference.py create mode 100644 research/ui/test_export_origin_wrap_reference.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c4704b0..a4d0930c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -322,6 +322,7 @@ if(BUILD_TESTING) termforge_add_python_test(windterm_alternate_export -m unittest research.ui.test_export_alternate_reference -v) termforge_add_python_test(windterm_alternate_style_reference -m unittest research.ui.test_export_alternate_style_reference research.ui.test_alternate_style_peer -v) termforge_add_python_test(windterm_raster_style_reference -m unittest research.ui.test_raster_style_reference -v) + termforge_add_python_test(windterm_origin_wrap_reference -m unittest research.ui.test_origin_wrap_reference research.ui.test_export_origin_wrap_reference -v) # Pure offline regressions only; OwnedNodeTests and frozen batches are never launched here. termforge_add_python_test(node_contract_export -m unittest research.ui.test_export_node_contract_deadline research.ui.test_node_contract_deadline.CheckpointTests research.ui.test_node_contract_deadline.LauncherVerdictTests -v) termforge_add_python_test(recovery_measure_protocol -m unittest experiments.measure.test_recovery_measure -v) diff --git a/research/ui/export_origin_wrap_reference.py b/research/ui/export_origin_wrap_reference.py new file mode 100644 index 00000000..4a82f453 --- /dev/null +++ b/research/ui/export_origin_wrap_reference.py @@ -0,0 +1,236 @@ +"""Audit the one fixed origin/wrap capture and publish a closed synthetic projection.""" +from __future__ import annotations +import argparse +from datetime import datetime +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 import origin_wrap_reference as probe + +EXECUTED_SOURCE = "72e661d964b771eae226e3bc301ed213c5e55ce8" +PLAN_SHA256 = "0bb60918f4236c36349d9b87f2cc4c0c71c5147a89a12ddeff1db7b8d6959dc6" +SNAPSHOT_SHA256 = "e5cb4a124a18774faad93bd02238a4bef7c2e031b0f9ab1add9395ac8244d437" +FALSE_PLANS = ( + ("proposal.json", "initial", "e64ca5343021e25abb3f8dc530da8c8afaa75fa3f08b00347fca93a8a371dcf1", "e133b5122136c7309d4af6e2c9ce5bd72f91ee8d"), + ("proposal-node-bound.json", "node-bound", "18b7fcfc2963ca38d13e29e3df6f97303471209473ccfdf67f7849ca83c22679", "21fc745d5a25339a4ce8f11ba1e1090a81fa1cab"), + ("proposal-stability-bound.json", "stability-bound", "50546257a4fde7e868dfb2d041d572bc632c6eca60578a12694b5683d35d7bdd", EXECUTED_SOURCE), +) +RAW_NAMES = ("evidence.json", "run.json", "probe.js", "peer.json", "origin-wrap-result.json", "files.json", + "mechanics-check.json", "stderr.txt", "profile-check.json", "cleanup.json", "fixture-ready", + *(f"ack-{index}" for index in range(25))) +SYNTHETIC_LINE = re.compile(r"(?: *(?:FT|D|O|TP|BT|R[0-3]|NW|UP|DN|DONE|R2 M|UP R|A|H)? *| {90}(?:AB|EF|IJ))") + + +def require(condition, message): + if not condition: + raise ValueError(message) + + +def read(path): + return json.loads(path.read_text(encoding="utf-8")) + + +def sha(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def bounded_integer(value, maximum, message): + require(type(value) is int and 0 <= value <= maximum, message) + return value + + +def file_identity(path): + return {"bytes": path.stat().st_size, "sha256": sha(path)} + + +def expected_probe(directory, run): + bounded_integer(run["pid"], 2**32-1, "Process identity is not an integer") + bounded_integer(run["tid"], 2**32-1, "Thread identity is not an integer") + require(type(run["window_station"]) is str and re.fullmatch(r"TermForgeR1-[0-9]+", run["window_station"]), "Owned station identity differs") + template = (ROOT / "research/ui/qt_r1_text.js").read_text(encoding="utf-8").split("// Scenario driver", 1)[0] + template += (ROOT / "research/ui/origin_wrap_reference.js").read_text(encoding="utf-8") + require(hashlib.sha256(template.encode()).hexdigest() == run["template_sha256"], "Probe template differs") + generated = template.replace("__UI_THREAD_ID__", str(run["tid"])).replace("__OWNED_STATION__", run["window_station"]) + generated = generated.replace("__RUN_DIRECTORY_JSON__", json.dumps(directory.as_posix())) + generated = generated.replace("__HOSTED_CI__", "false").replace("__SCENARIO_TIMEOUT_MS__", "43000") + # The executed Windows write_text used its default LF-to-CRLF translation. + return generated.encode().replace(b"\n", b"\r\n") + + +def capture_facts(directory): + mechanics = probe.audit_mechanics(directory) + require(read(directory / "mechanics-check.json") == mechanics, "Saved mechanics audit differs") + peer, result, run, outer = [read(directory / name) for name in + ("peer.json", "origin-wrap-result.json", "run.json", "evidence.json")] + require(run["sha256"] == probe.EXE_SHA256 and run["files_sha256"] == probe.MANIFEST_SHA256, "Original identity differs") + require(run["scenario"] == "menu-wrap" and run["profile"] == "synthetic-local-only" and + run["clipboard"] == "owned-window-station", "Launch scope differs") + require((directory / "probe.js").read_bytes() == expected_probe(directory, run) and + sha(directory / "probe.js") == run["script_sha256"], "Runtime probe bytes differ") + require(outer["ok"] is True and outer["data"]["cleanup_error"] is None, "REA outer result failed") + evidence = outer["data"]["evidence"] + normalized = evidence["normalized_result"] + require(evidence["provider"] == {"id": "frida", "name": "Frida", "version": None} and + evidence["confidence"] == "observed" and evidence["operation"] == "instrument_with_frida" and + evidence["subject"] is None and evidence["environment"] is None and evidence["raw_result"] is None, + "Provider observation identity differs") + require(type(evidence["evidence_id"]) is str and re.fullmatch(r"ev_[0-9a-f]{64}", evidence["evidence_id"]), "Evidence identity differs") + require(normalized["messages_truncated"] is False and normalized["cleanup_error"] is None and + normalized["source_sha256"] == run["script_sha256"] and normalized["mode"] == "attach" and + normalized["source_kind"] == "file" and normalized["state"] == "running" and + normalized["pid"] == run["pid"] and Path(normalized["source_path"]).resolve() == directory / "probe.js", + "Provider source/lifecycle differs or is incomplete") + scripts, messages = normalized["scripts"], normalized["messages"] + require(type(scripts) is list and len(scripts) == 1 and type(messages) is list and len(messages) == 30, + "Provider script/message count differs") + require(all(message["type"] == "send" and message["script_id"] == scripts[0]["script_id"] and + type(message["payload"]) is dict for message in messages), "Provider contains an error or unrelated script") + payloads = [message["payload"] for message in messages] + require([row.get("kind") for row in payloads] == ["probe_ready", *(["readiness"]*3), *(["origin_wrap_phase"]*25), "origin_wrap_result"], + "Provider message sequence differs") + require(payloads[0] == {"kind": "probe_ready", "station_isolated": True, "scenario": probe.CASE_ID}, "Probe ready identity differs") + for readiness in payloads[1:4]: + require(readiness["phase"] == "ui" and all(type(readiness[key]) is bool for key in + ("ready", "has_tree", "has_engine", "has_editor", "peer_ready")), "Readiness record differs") + require([{key: value for key, value in row.items() if key != "kind"} for row in payloads[4:29]] == result["observations"] and + {key: value for key, value in payloads[-1].items() if key != "kind"} == result, "Provider and saved observations differ") + require(peer["frozen_grid"] == [91, 25] and peer["naws_history"] == [[91, 24], [91, 25]] and + peer["freeze_event_count"] == 2 and (directory / "fixture-ready").read_text() == "stable-NAWS-capacity", + "Fixed observation NAWS/freeze identity differs") + phases = [] + for source, row, observation in zip(probe.phase_records(), peer["records"], result["observations"]): + terminal = observation["terminal"] + require(terminal["top"] is None and terminal["cell_styles"] is None and terminal["current_style"] is None, + "An unknown terminal endpoint changed") + require(all(type(line) is str and len(line) <= 92 and SYNTHETIC_LINE.fullmatch(line) for line in terminal["lines"]), + "Only fixed synthetic document text is publishable") + scroll = bounded_integer(terminal["scroll"], 200, "Document scroll is not a bounded integer") + caret = {key: bounded_integer(terminal["caret_api"][key], maximum, "Caret API value is not a bounded integer") + for key, maximum in (("caretLine", 200), ("caretColumn", 500), ("caretPosition", 32768))} + cursor = [bounded_integer(n, 65535, "DSR coordinate is not a bounded integer") for n in row["cursor_reply"]] + require(row["cursor_reply_private"] is False, "Fixed observation DSR prefix differs") + phases.append({**source, "cursor_reply": cursor, "response_hex": row["response_hex"], "private_dsr_prefix": False, + "document_projection": {"lines": list(terminal["lines"]), "line_count": len(terminal["lines"]), + "top": None, "scroll": scroll, "capacity": 25, "caret_api": caret}}) + cleanup = read(directory / "cleanup.json") + account = {key: bounded_integer(cleanup[key], 100, "Job account is not a bounded integer") for key in ("active", "total", "terminated")} + require(account == {"active": 0, "total": 3, "terminated": 0}, "Fixed observation Job account differs") + return {"phases": phases, "source_bytes": sum(row["source_bytes"] for row in phases), "dsr_request_bytes": 100, + "application_reply_bytes": len(bytes.fromhex(peer["received_hex"])), "naws_history": [[91, 24], [91, 25]], + "frozen_grid": [91, 25], "freeze_event_count": 2, "job_after": account, + "provider": {"id": "frida", "version": None, "confidence": "observed", "evidence_id": evidence["evidence_id"], + "subject": None, "environment": None, "messages": 30, "phase_messages": 25, + "messages_truncated": False, "cleanup_error": None}} + + +def provenance_facts(directory, plan_path): + require(sha(plan_path) == PLAN_SHA256, "Authorized plan bytes differ") + plan = read(plan_path) + probe.validate_fixed_contract(plan) + require(plan["execution_authorized"] is True and plan["source_base_commit"] == EXECUTED_SOURCE and + directory == Path(plan["out"]).resolve(), "Executed plan/source/output identity differs") + require(plan_path.with_suffix(".attempted").read_text() == "One original launch attempt; retain after failure and never retry this plan.\n", + "Single-attempt marker differs") + require(set(plan["source_files_sha256"]) == set(probe.SOURCE_FILES), "Execution source inventory differs") + source_hashes = {} + for name in probe.SOURCE_FILES: + blob = subprocess.check_output(["git", "show", f"{EXECUTED_SOURCE}:{name}"], cwd=ROOT) + require(sha(ROOT / name) == hashlib.sha256(blob).hexdigest() == plan["source_files_sha256"][name], "Executed/current source bytes differ") + source_hashes[name] = sha(ROOT / name) + require(hashlib.sha256(probe.launcher_source().encode()).hexdigest() == plan["launcher_source_sha256"], "Launcher namespace source differs") + require(probe.rea_identity(Path(plan["rea"])) == plan["rea_prepared_identity"] and + all(plan[key] == value for key, value in probe.host_identity().items()), "Current runtime/host bytes differ from plan") + require(re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", plan["windows_version"]), "Windows version is not a safe scalar") + python_version = re.match(r"[0-9]+\.[0-9]+\.[0-9]+", plan["python_version"]) + require(python_version is not None, "Python version is not a safe scalar") + files = read(directory / "files.json") + canonical = json.dumps(files, sort_keys=True, separators=(",", ":")).encode() + require(type(files) is dict and len(files) == 4047 and hashlib.sha256(canonical).hexdigest() == probe.MANIFEST_SHA256, + "Copied distribution manifest differs") + for name, expected in files.items(): + require(sha(directory / "app" / name) == sha(Path(plan["source"]) / name) == expected, "Original distribution file changed") + require(read(directory / "run.json")["fixture_sha256"] == source_hashes["research/ui/r1_vt_fixture.py"], "Legacy fixture metadata tag differs") + sessions = read(directory / "profile/.wind/profiles/default.v10/terminal/user.sessions") + require(type(sessions) is list and len(sessions) == 1 and all(sessions[0].get(key) == value for key, value in + {"session.group": "TermForge Lab", "session.label": "TF_R1_LOOPBACK", "session.protocol": "Telnet", + "session.target": "127.0.0.1", "session.uuid": "00000000-0000-4000-8000-000000000001"}.items()), "Actual profile scope differs") + folder = ROOT / "local/origin-wrap-preflight" + snapshot_path = folder / "post-run-originals-before-export.json" + require(sha(snapshot_path) == SNAPSHOT_SHA256, "Preserved original inventory changed") + snapshot = read(snapshot_path) + require(len(snapshot) == 43 and all(file_identity(ROOT / name) == identity for name, identity in snapshot.items()), "An original observation/proposal artifact changed") + false_plans = [] + for index, (name, label, expected, source) in enumerate(FALSE_PLANS): + path = folder / name + require(sha(path) == expected, "An unexecuted proposal changed") + value = read(path) + require(value["execution_authorized"] is False and value["source_base_commit"] == source and + not path.with_suffix(".attempted").exists(), "A false proposal execution state differs") + shared = Path(value["out"]).resolve() == directory + require(shared is (index == 0) and Path(value["out"]).exists() is shared, "False proposal output alias differs") + false_plans.append({"label": label, **file_identity(path), "source_base_commit": source, + "execution_authorized": False, "attempt_present": False, "output_present": shared, + "output_shared_with_authorized_plan": shared}) + wrapper = read(folder / "authorized-run-exit.json") + require(wrapper["schema"] == "termforge-private-origin-wrap-launch-exit-v1" and type(wrapper["exit_code"]) is int and + wrapper["exit_code"] == 0 and wrapper["source_commit"] == EXECUTED_SOURCE and wrapper["plan_sha256"] == PLAN_SHA256, + "Outer launch result differs") + for key in ("started_utc", "finished_utc"): + require(type(wrapper[key]) is str and re.fullmatch(r"[0-9]{4}(?:-[0-9]{2}){2}T[0-9]{2}(?::[0-9]{2}){2}\.[0-9]{7}Z", wrapper[key]), "Wrapper timestamp differs") + seconds = (datetime.fromisoformat(wrapper["finished_utc"].replace("Z", "+00:00")) - + datetime.fromisoformat(wrapper["started_utc"].replace("Z", "+00:00"))).total_seconds() + require(seconds > 0, "Wrapper interval is invalid") + tool = plan["rea_prepared_identity"] + return {"execution_source_files_sha256": source_hashes, "launcher_namespace_sha256": plan["launcher_source_sha256"], + "reference_identity": {"exe_sha256": probe.EXE_SHA256, "manifest_sha256": probe.MANIFEST_SHA256, "files_verified": 4047}, + "launch_host": {"windows_version": plan["windows_version"], "python_version_number": python_version[0], + "python_version_text_sha256": hashlib.sha256(plan["python_version"].encode()).hexdigest(), + "python_exe_sha256": plan["python_sha256"], "node_exe_sha256": tool["node_executable_sha256"], + "node_process_version_observed_in_original_run": None, + "rea_package_version": "6.7.0", "rea_shim_sha256": tool["shim_sha256"], + "rea_package_json_sha256": tool["package_json_sha256"], "rea_entry_sha256": tool["entry_sha256"]}, + "wrapper": {"exit_code": 0, "started_utc": wrapper["started_utc"], "finished_utc": wrapper["finished_utc"], + "observed_wall_seconds": seconds, "whole_lifecycle_bound_seconds": None}, + "preserved_original_inventory": {"files": 43, "sha256": SNAPSHOT_SHA256}, "unexecuted_proposals": false_plans, + "raw_files": {name: file_identity(directory / name) for name in RAW_NAMES}, + "authorized_plan": file_identity(plan_path), "outer_launch_log": file_identity(folder / "authorized-run.log"), + "outer_launch_exit": file_identity(folder / "authorized-run-exit.json")} + + +def export(directory, plan_path, destination): + directory, plan_path, destination = [path.resolve() for path in (directory, plan_path, destination)] + require(not destination.exists(), "Export destination must be new") + facts = capture_facts(directory) + provenance = provenance_facts(directory, plan_path) + commit = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip() + require(re.fullmatch(r"[0-9a-f]{40}", commit), "Export source commit is invalid") + result = {"schema": "termforge-original-origin-wrap-reference-v1", "issue": 151, + "execution_source_commit": EXECUTED_SOURCE, "offline_export_source_commit": commit, + "offline_exporter_sha256": sha(Path(__file__)), "authorized_plan_sha256": PLAN_SHA256, + "status": "bounded-direct-protocol-and-document-observation", "actual_original_runs": 1, "maximum_original_runs": 1, + "rea_capture_seconds": 45, "cli_wait_timeout_seconds": 60, "whole_lifecycle_bound_seconds": None, + "semantic_acceptance": False, "full_active_grid_verified": False, "terminal_styles_verified": False, + "local_shell_transport_verified": False, "presented_done": None, "performance_gate": "not_evaluated", + "deliberate_original_process_termination": True, "natural_gui_exit_verified": False, + "synthetic_profile_verified": True, "stderr_bytes": 0, "r2_complete": False, + "capture": facts, "provenance": provenance} + payload = (json.dumps(result, ensure_ascii=False, indent=2) + "\n").encode() + destination.parent.mkdir(parents=True, exist_ok=True) + with destination.open("xb") as handle: + handle.write(payload) + return result + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + for name in ("directory", "plan", "destination"): + parser.add_argument("--" + name, type=Path, required=True) + args = parser.parse_args() + result = export(args.directory, args.plan, args.destination) + print(json.dumps({"exported": True, "phases": len(result["capture"]["phases"]), "sha256": sha(args.destination)})) diff --git a/research/ui/test_export_origin_wrap_reference.py b/research/ui/test_export_origin_wrap_reference.py new file mode 100644 index 00000000..94f69eed --- /dev/null +++ b/research/ui/test_export_origin_wrap_reference.py @@ -0,0 +1,149 @@ +"""Synthetic publication counterexamples; no original/REA/Node/native execution.""" +from __future__ import annotations +import copy +import hashlib +import json +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + +from research.ui import export_origin_wrap_reference as exporter +from research.ui import origin_wrap_reference as probe +from research.ui.test_origin_wrap_reference import receive_fixture + + +def fixture(root): + directory = root / "capture" + directory.mkdir() + peer, _ = receive_fixture("normal") + peer.update(naws_history=[[91, 24], [91, 25]], freeze_event_count=2) + observations = [] + for row in peer["records"]: + observation = {key: row[key] for key in ("phase", "label", "cursor_reply", "cursor_reply_private", "response_hex")} + observation.update(frozen_grid=[91, 25], terminal={"connected": True, "protocol": "Telnet", "capacity": 25, + "line_count": 1, "lines": ["FT"], "top": None, "scroll": 0, + "caret_api": {"caretLine": 0, "caretColumn": 0, "caretPosition": 2}, "cell_styles": None, "current_style": None}) + observations.append(observation) + result = {"status": "observed-all-origin-wrap-phases", "error": None, "observations": observations, + "full_active_grid_verified": False, "terminal_style_values_verified": False, + "local_shell_transport_verified": False, "presented_done": None} + run = {"pid": 12, "tid": 34, "window_station": "TermForgeR1-1234", "sha256": probe.EXE_SHA256, + "files_sha256": probe.MANIFEST_SHA256, "scenario": "menu-wrap", "profile": "synthetic-local-only", + "clipboard": "owned-window-station"} + template = (exporter.ROOT / "research/ui/qt_r1_text.js").read_text(encoding="utf-8").split("// Scenario driver", 1)[0] + template += (exporter.ROOT / "research/ui/origin_wrap_reference.js").read_text(encoding="utf-8") + run["template_sha256"] = hashlib.sha256(template.encode()).hexdigest() + generated = exporter.expected_probe(directory, run) + (directory / "probe.js").write_bytes(generated) + run["script_sha256"] = hashlib.sha256(generated).hexdigest() + normalized = {"messages_truncated": False, "cleanup_error": None, "source_sha256": run["script_sha256"], + "mode": "attach", "source_kind": "file", "state": "running", "pid": 12, + "source_path": str(directory / "probe.js"), "scripts": [{"script_id": "synthetic-script", "name": "synthetic"}]} + evidence = {"provider": {"id": "frida", "name": "Frida", "version": None}, "confidence": "observed", + "operation": "instrument_with_frida", "subject": None, "environment": None, "raw_result": None, + "evidence_id": "ev_" + "a"*64, "normalized_result": normalized} + data = {"peer.json": peer, "origin-wrap-result.json": result, "run.json": run, + "cleanup.json": {"active": 0, "total": 3, "terminated": 0}, + "profile-check.json": {"only_synthetic_session": True, "session_count": 1}, + "evidence.json": {"ok": True, "data": {"cleanup_error": None, "evidence": evidence}}} + (directory / "stderr.txt").write_bytes(b"") + (directory / "fixture-ready").write_text("stable-NAWS-capacity") + for row in peer["records"]: + (directory / f'ack-{row["phase"]}').write_text("observed") + save(directory, data) + (directory / "mechanics-check.json").write_text(json.dumps(probe.audit_mechanics(directory))) + return directory, data + + +def save(directory, data, synchronize=True): + if synchronize: + normalized = data["evidence.json"]["data"]["evidence"]["normalized_result"] + payloads = [{"kind": "probe_ready", "station_isolated": True, "scenario": probe.CASE_ID}] + payloads += [{"kind": "readiness", "phase": "ui", "ready": value, "has_tree": True, + "has_engine": True, "has_editor": False, "peer_ready": False} for value in (False, False, True)] + payloads += [{"kind": "origin_wrap_phase", **copy.deepcopy(row)} for row in data["origin-wrap-result.json"]["observations"]] + payloads += [{"kind": "origin_wrap_result", **copy.deepcopy(data["origin-wrap-result.json"])}] + normalized["messages"] = [{"type": "send", "script_id": "synthetic-script", "payload": value} for value in payloads] + for name, value in data.items(): + (directory / name).write_text(json.dumps(value), encoding="utf-8") + + +class ExportTests(unittest.TestCase): + def test_valid_capture_projects_safe_fields_without_paths_or_process_ids(self): + with tempfile.TemporaryDirectory() as temporary: + directory, data = fixture(Path(temporary)) + result = exporter.capture_facts(directory) + self.assertEqual(len(result["phases"]), 25) + self.assertEqual(result["source_bytes"], 334) + self.assertEqual(result["job_after"], {"active": 0, "total": 3, "terminated": 0}) + payload = json.dumps(result) + for private in (str(directory), "TermForgeR1-1234", "source_path", '"pid"', '"tid"'): + self.assertNotIn(private, payload) + self.assertFalse(result["provider"]["messages_truncated"]) + + def test_unknown_terminal_and_job_fields_are_not_forwarded(self): + with tempfile.TemporaryDirectory() as temporary: + directory, data = fixture(Path(temporary)) + before = exporter.capture_facts(directory) + data["origin-wrap-result.json"]["observations"][0]["terminal"]["private_note"] = "DO_NOT_PUBLISH" + data["cleanup.json"]["private_path"] = "C:/private/DO_NOT_PUBLISH" + save(directory, data) + self.assertEqual(exporter.capture_facts(directory), before) + + def test_private_text_and_string_caret_are_rejected_after_evidence_synchronization(self): + for field in ("lines", "caret"): + with self.subTest(field=field), tempfile.TemporaryDirectory() as temporary: + directory, data = fixture(Path(temporary)) + terminal = data["origin-wrap-result.json"]["observations"][0]["terminal"] + if field == "lines": + terminal["lines"] = ["DO_NOT_PUBLISH_PRIVATE_TEXT"] + else: + terminal["caret_api"]["caretLine"] = "C:/private/DO_NOT_PUBLISH" + save(directory, data) + with self.assertRaises(ValueError): + exporter.capture_facts(directory) + + def test_wrong_source_missing_stage_extra_reply_and_cleanup_are_refused(self): + for case in ("source", "missing-stage", "extra-reply", "cleanup", "truncated", "script-error"): + with self.subTest(case=case), tempfile.TemporaryDirectory() as temporary: + directory, data = fixture(Path(temporary)) + normalized = data["evidence.json"]["data"]["evidence"]["normalized_result"] + if case == "source": + data["run.json"]["sha256"] = "0"*64 + elif case == "missing-stage": + data["origin-wrap-result.json"]["observations"].pop() + elif case == "extra-reply": + data["peer.json"]["received_hex"] += "1b5b313b3152" + elif case == "cleanup": + data["cleanup.json"]["active"] = 1 + elif case == "truncated": + normalized["messages_truncated"] = True + save(directory, data) + if case == "script-error": + normalized["messages"][1]["type"] = "error" + save(directory, data, synchronize=False) + with self.assertRaises(ValueError): + exporter.capture_facts(directory) + + def test_public_cli_refuses_private_text_in_normal_and_optimized_python_without_output(self): + with tempfile.TemporaryDirectory() as temporary: + directory, data = fixture(Path(temporary)) + data["origin-wrap-result.json"]["observations"][0]["terminal"]["lines"] = ["DO_NOT_PUBLISH"] + save(directory, data) + plan = Path(temporary) / "plan.json" + plan.write_text("{}") + for optimized in (False, True): + destination = Path(temporary) / ("optimized/out.json" if optimized else "normal/out.json") + command = [sys.executable, "-B", *(["-O"] if optimized else []), "-X", "utf8", "-m", + "research.ui.export_origin_wrap_reference", "--directory", str(directory), "--plan", str(plan), + "--destination", str(destination)] + result = subprocess.run(command, cwd=exporter.ROOT, capture_output=True, text=True, encoding="utf-8", timeout=10) + self.assertNotEqual(result.returncode, 0) + self.assertIn("Only fixed synthetic document text", result.stderr) + self.assertFalse(destination.parent.exists()) + + +if __name__ == "__main__": + unittest.main() From 01a979da926c05c8e6c34e3f5e5ed7b8d6916de1 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 05:47:53 +0800 Subject: [PATCH 09/12] docs: publish bounded original origin-wrap evidence (refs #151) --- docs/EVIDENCE.md | 2 + docs/FEATURES.md | 2 +- docs/research/windterm-origin-wrap-plan.md | 2 +- ...ndterm-origin-wrap-reference-2026-10-29.md | 53 + .../README.md | 19 + .../publication-audit.json | 26 + .../validation.json | 1451 +++++++++++++++++ 7 files changed, 1553 insertions(+), 2 deletions(-) create mode 100644 docs/research/windterm-origin-wrap-reference-2026-10-29.md create mode 100644 research/ui/results/2026-10-29-origin-wrap-reference/README.md create mode 100644 research/ui/results/2026-10-29-origin-wrap-reference/publication-audit.json create mode 100644 research/ui/results/2026-10-29-origin-wrap-reference/validation.json diff --git a/docs/EVIDENCE.md b/docs/EVIDENCE.md index df4f080f..5b4cda58 100644 --- a/docs/EVIDENCE.md +++ b/docs/EVIDENCE.md @@ -24,6 +24,8 @@ R1 固定总出口的源码为 `ce9b24d`,早期 `2dc8d6d` / `9b16acc` 仍分 原版模式依据见 [1049 直达终端观察](research/windterm-alternate-reference-2026-10-28.md)与[验证身份](../research/ui/results/2026-10-28-alternate-reference/validation.json):26 阶段精确输入、DSR 和文档投影相互绑定,原版重复进入的光标行为与通用幂等假设不同;样式、完整 active grid 和本地 Shell 运输继续保留未知。 +[#151 origin/wrap/margin有限观察](research/windterm-origin-wrap-reference-2026-10-29.md)取得一次固定91×25直达终端的25阶段、25条bareDSR及25文档快照,[数据与发布审计](../research/ui/results/2026-10-29-origin-wrap-reference/README.md)保留完整NAWS、源/回复/文档分层、Node与源码身份、主动终止和旧false输出路径别名。严格出口普通/-O逐字节相同;这只为自主C++下一切片提供有限依据,不是完整active grid、产品模式、固定ConPTY或性能验收。 + [#146 SGR1 固定样本调查](../research/ui/results/2026-10-29-sgr1-reference/README.md)取得五阶段/五 grab、28 对 H/space 完整有序像素签名;SGR0/SGR1 控制在采样条件下均不可分,进入/重复进入/返回无唯一分类。内部字重、SGR1 是否受字体/profile 偏好影响及全部非颜色属性仍未知;没有据此更改 C++ 策略或验收 #123/#126/#27。 原版输出自动化见 [cmd 正确性工具](../benchmarks/tools/WINDTERM-CMD.md)及[固定结果](../benchmarks/tools/results/2026-10-28-windterm-cmd/README.md):小 ASCII 全文、生产事件和 Job 回收已有无人值守闭环,路径元字符修复及各执行源码分列。它没有取得 C0、完整历史、消费/真实呈现端点或正式性能基线。 diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 480ed54f..9351990b 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -25,7 +25,7 @@ |---|---|---|---| | 自主 C++ / Qt / 固定微软 ConPTY | **实现中,未验收**:[显式 Qt 入口](../src/qt/README.md)提供默认 80×24、随可用区域变化的 cmd 标签、基本 ASCII 与[限定 Unicode 文本字节](../experiments/shell/results/2026-10-29-qt-unicode-input/README.md)、16 色及各会话独立有界队列;[基础动态尺寸验证](../experiments/shell/results/2026-10-29-qt-resize/README.md)分离窗口/核心/transport 与真实 child,覆盖极小、重复、隐藏页和关闭交叉,并复验原标签算术及事件循环回收。678 原生极窄失败保留;0ff 仅修订字面子区域/顶层实际区域夹具,另有[冻结包一次隔离原生成功](../experiments/shell/results/2026-10-29-qt-resize/native-validation.json):实际顶层区域、core/transport/child 一致,窗口仅 DPI 96。旧尺寸结果保留各自源码;[Unicode 联合源码的一次完整61项](../experiments/shell/results/2026-10-29-qt-unicode-input/validation.json)分开 Qt3项与其余58项既有/独立诊断,13条内部skip保留 | 分屏、其他 shell、工作区恢复、完整 resize/reflow、历史/选择/Find、完整 Unicode 字素/绘制与 IME、真实 DPI、应用兼容与性能仍需实现或迁移;物理裁剪不恢复丢弃正文,完整 WindTerm 标签/尺寸行为对照未完成;当前不是可发布开发包 | R2 | -自主 C++ 核心已有 [#130 双缓冲存储基础](../experiments/core/results/2026-10-29-buffer-state-r2/README.md),并接入 [#135 限定 1049 协议](../experiments/core/results/2026-10-29-cpp-1049/README.md):strict/recovery 识别 `?1049h/l`,重复进入归首格、清旧备用文本和复位写入颜色,返回恢复主屏文字/光标/颜色及 pending-wrap。共享行 ID、双帧物理 resize 与逻辑 epoch 防止旧位置、选择、搜索及 viewport 复活,包括清屏后重写相同文字。原版完整网格/背景、非颜色属性及其他模式仍未知;本地55项中 Qt1项与其余54项诊断分列,13条内部跳过保留,不表示完整 [#27](https://github.com/JavaZeroo/termforge/issues/27)、真实 TUI/Qt 交互或系统剪贴板已验收。 +自主 C++ 核心已有 [#130 双缓冲存储基础](../experiments/core/results/2026-10-29-buffer-state-r2/README.md),并接入 [#135 限定 1049 协议](../experiments/core/results/2026-10-29-cpp-1049/README.md):strict/recovery 识别 `?1049h/l`,重复进入归首格、清旧备用文本和复位写入颜色,返回恢复主屏文字/光标/颜色及 pending-wrap。共享行 ID、双帧物理 resize 与逻辑 epoch 防止旧位置、选择、搜索及 viewport 复活,包括清屏后重写相同文字。原版完整网格/背景、非颜色属性及其他模式的完整行为仍未知;[origin/wrap/margin有限直接观察](research/windterm-origin-wrap-reference-2026-10-29.md)只提供后续实现依据,尚未接入产品。本地55项中 Qt1项与其余54项诊断分列,13条内部跳过保留,不表示完整 [#27](https://github.com/JavaZeroo/termforge/issues/27)、真实 TUI/Qt 交互或系统剪贴板已验收。 Qt 构建显式准备并部署[固定微软 CRT 14.51.36247.0](../third_party/msvc_runtime/README.md),不安装到 System32。隔离 Windows 的[限定加载与 cmd 验证](../experiments/shell/results/2026-10-29-qt-crt-deploy/README.md)保留原包失败、原 52 文件不变且仅追加 CRT/原许可后的成功,以及新源码包的独立验证;它不解除完整产品打包、Qt/第三方分发审计、实际 DPI/IME 或物理呈现的验收缺口。 diff --git a/docs/research/windterm-origin-wrap-plan.md b/docs/research/windterm-origin-wrap-plan.md index c1bdbe45..3b860f4e 100644 --- a/docs/research/windterm-origin-wrap-plan.md +++ b/docs/research/windterm-origin-wrap-plan.md @@ -1,6 +1,6 @@ # WindTerm 2.7.0:origin/wrap 与滚动区域取证计划 -状态:**离线准备,原版运行次数为 0,尚无行为结论**。本项由 [#151](https://github.com/JavaZeroo/termforge/issues/151) 承接 [#27](https://github.com/JavaZeroo/termforge/issues/27) 的直接观察缺口;没有改动自主 C++ 核心或产品模式。通用规范、旧候选和本地控制台结果均不作为原版行为。 +状态:**本计划已在执行源72e661d上完成唯一原版尝试**,[有限结果与原始来源审计](windterm-origin-wrap-reference-2026-10-29.md)另列,不倒改以下执行前输入、次数和期限。本项由 [#151](https://github.com/JavaZeroo/termforge/issues/151) 承接 [#27](https://github.com/JavaZeroo/termforge/issues/27) 的直接观察缺口;没有改动自主 C++ 核心或产品模式。通用规范、旧候选和本地控制台结果均不作为原版行为。 目标固定为官方 WindTerm 2.7.0 Windows x64,EXE SHA256 `7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0`;受保护复制器在创建自有副本后校验 4,047 文件分发清单 `e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203`,不改动官方原件。 diff --git a/docs/research/windterm-origin-wrap-reference-2026-10-29.md b/docs/research/windterm-origin-wrap-reference-2026-10-29.md new file mode 100644 index 00000000..36ea5303 --- /dev/null +++ b/docs/research/windterm-origin-wrap-reference-2026-10-29.md @@ -0,0 +1,53 @@ +# WindTerm 2.7.0:origin/wrap 与滚动区域的有限直接观察 + +**一次直达终端观察完成,未实现或验收 TermForge 模式。** 固定91×25网格下取得25阶段、25条bare DSR和25份Qt文档快照,可用于自主C++的下一项有限实现。完整活动网格、样式、所有模式交叉和本地ConPTY路径仍未由本项验证;#27/#41/#56/R2保持开放。 + +输入、次数和期限先按 [#151计划](windterm-origin-wrap-plan.md)固定,再由根代理执行唯一原版尝试。[完整白名单数据](../../research/ui/results/2026-10-29-origin-wrap-reference/validation.json)和[独立发布审计](../../research/ui/results/2026-10-29-origin-wrap-reference/publication-audit.json)保留实际来源,公开内容只有原创合成字节、回复、合成文档及脱敏元数据。 + +## 来源与完整性 + +- 原版执行源:`72e661d964b771eae226e3bc301ed213c5e55ce8`。官方2.7.0 x64 EXE为 `7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0`;4,047文件清单为 `e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203`。副本和原官方源逐文件复核均相同。 +- 唯一正式计划SHA256:`0bb60918f4236c36349d9b87f2cc4c0c71c5147a89a12ddeff1db7b8d6959dc6`。marker保留,原版运行次数1;未重试。独立非输入WindowStation/Desktop、单个合成Telnet profile及自有Job复核通过。 +- 完整REA Evidence:`ev_9f414d87b06374e853efc468a6ed115f754c869745e0a75b554d17274fa6247d`。30个send恰为1个ready、3个readiness、25个phase和1个final;单个script,阶段/末态与保存文件逐项相同,无截断、脚本error或两层cleanup_error。provider subject/environment/version均为null,不由这些字段推断环境;启动器另有独立来源验证。 +- NAWS完整事件为 `[91,24]→[91,25]`;UI稳定后在第二事件冻结91×25,25次capacity均为25,冻结后没有新NAWS。实际合成输入334B,加25个DSR请求100B,共发送434B;收到157B,恰好25条bare DSR,无额外应用字节。每个阶段均有独立ack,TCP分块未预设。 +- probe模板和运行脚本均重建一致。Windows默认写入将189个LF精确转换为CRLF;实际probe SHA256为 `3800ba21b6b4a5e91e8ecc4b75f428d2cb659d8412b1d4323a162852d5e1f51e`。旧launcher的 `fixture_sha256` 是未执行的通用fixture元数据标签;本项真实peer/JS由正式计划的8源SHA独立绑定,不能把旧标签当成本项输入来源。 +- Node/Python/REA当前文件、Python/Windows版本与执行前计划相同,8项执行源码当前字节及72e git blob均与计划相同。这是前置计划与本次离线复核的一致性,不声称全生命期连续监控文件。原版Node进程版本没有被捕获;准备时纯V8测试的v22.21.1不转记为原版运行证据。 + +## 可支持的有限行为 + +下表位置均为**原版主动发送的DSR行、列**。开启origin后的行号在本样例表现为区域相对坐标;文档行号、caret API与DSR是不同端点,不能互相代替。 + +| 阶段 | 确定输入与直接观察 | 可用边界 | +|---|---|---| +| 0–3 | 新会话Home写`FT`回复(1,3);显式`?6l/?7h/r`重置后(1,1);`2;5r`后仍(1,1);CUP(1,3)写`D`后(1,4) | 新会话本次入口及显式控制状态,不外推所有默认设置 | +| 4–8 | `?6h`后(1,1),CUP(1,3)写`O`后(1,4),但`O`的文档位置比此前`D`低一行;CUU99+CUB99得到(1,1),CUD99得到(4,1);`?6l`后(1,1) | 支持`2;5r`下origin偏移和上下界的固定输入;不能由相同DSR推断同一物理位置 | +| 9–10 | 区域第2–5行的`R0,R1,R2,R3`,在第5行CRLF写`NW`后变为`R1,R2,R3,NW`,回复(5,3);区域外`TP/BT`保持原位置 | 只对该四行区域、CRLF及ASCII样例证明内部滚动与外部保持 | +| 11–13 | 在区域上方第1行LF写`UP`得到(2,3);在区域下方第25行LF写`DN`得到(25,3),中间`R2,R3,NW`未滚动;`r`后(1,1) | 两次区域外LF有各自预期覆写,不能继续声称所有哨兵从未变化 | +| 14 | wrap开启,在第8行末列一次发送`AB`,回复(9,2);文档为90空格后连续`AB` | 文档软折行投影不是两个活动屏物理行数组 | +| 15–16 | wrap关闭后第10行末列`CD`只留下`D`,回复(10,91);再开启,第12行末列`EF`得到(13,2),文档保留`EF` | 仅约束所发送完整序列,不推定内部pending字段 | +| 17 | 末列`G`紧接`?7l`和`H`,最后只留`H`,回复(14,91) | 中间没有插入DSR | +| 18 | wrap关闭时末列`I`紧接`?7h`和`J`,文档保留`IJ`,回复(17,2) | 中间没有插入DSR;不能按一般规范猜测未观测状态 | +| 19–22 | 主屏`2;5r/?6h`、CUP(2,4)写`M`后(2,5);1049进入后(1,1);备用`3;6r/?6h`写`A`后(2,5);退出后主屏完整文档、caret API和DSR均恢复到阶段19 | 主备用两边都设origin开启,无法区分origin位是否分属各屏;返回后的margin下界未测 | +| 23–24 | 返回后CUP(1,6)写`R`,`R`出现在原`UP`行,回复(1,7);最终显式reset、Home写`DONE`后(1,5) | 支持此返回路径的主margin上界恢复;不代表全套保存恢复已完成 | + +本次 `caretColumn` 始终为0,虽然DSR列在1–91之间变化,所以不把该API当作终端列坐标。`lineText`包含保留文档:备用期间仍出现主屏旧文字,部分软折行文字长度92超过91列。它没有提供完整active grid、全部空白格或逐格样式。 + +下一C++切片可直接锁定上述固定输入、DSR及独立文字预期,再验证行身份、历史隔离和分块;未取证的非法/退化margin、更多origin/wrap/resize交叉、每屏origin位与下界恢复不能计为原版匹配。来源观察与TermForge容器/状态策略分别记录,重要差异仍按ADR-0006提交用户确认。 + +## 回收与期限 + +最终Job为 `active0/total3/terminated0`,stderr为空,受控profile实际仍为一个loopback Telnet会话。**启动器主动终止原版窗口**,该Job计数不是自然GUI退出证明。Frida CLI正常返回也不等于所有进程自然结束。 + +外层wrapper记录2026-10-28 21:23:17.0405260Z至21:24:13.3076732Z,观察wall约56.267147秒;它包括本次准备、执行和收尾,不是吞吐或呈现指标。原43秒peer、41/42秒JS、45秒REA捕获、60秒CLI等待各属局部期限;原版在CLI前已恢复且有单独2秒间隔,**没有把这些期限拼成整程45/60秒硬上限**,`whole_lifecycle_bound_seconds=null`。 + +三个false提案原字节仍保留,均未执行、无自身attempt。初版提案与正式计划共用一个预留out;该目录现已由正式计划创建,所以此时 `output_present=true`、`output_shared_with_authorized_plan=true`。另两份false的out仍不存在。这不改写此前准备时三者均无out的事实,也不能把目录存在当成false提案执行过。 + +## 离线发布验证与剩余范围 + +离线实现源 `fb1a40a2b6efea4ae5cf528607cd1d853e6ecd4e` 独立于原版执行源72e;其中已整合main `7e896cb75283fd0d984c936500ba8d1de4ceaf46`,8执行源逐项无差异,没有因此重新执行原版。新增CTest只注册纯Python观察/出口测试,不执行原版或纯Node fixture。 + +[严格出口](../../research/ui/export_origin_wrap_reference.py)逐层重建白名单,不转发原始terminal/provider/Job对象。5项新增出口测试覆盖私密文字、字符串caret、错身份、缺阶段、额外回复、未回收Job、截断和script error;未知terminal/Job字段不转发。普通/`-O`的真实公共CLI拒绝私密文本且不创建输出目录,相关纯测试总计各21项通过;未跑完整本地矩阵。 + +实际raw分别用普通和`-O`导出,41,269B逐字节相同,SHA256 `9774e25bab9f5f751368e19f915e5d1ec8f4332be606070f15b5ffb33ce5b675`。公开前再独立核对25份文档/DSR投影、8源和43份原件;绝对路径、PID/TID、station/session/script ID及原始Evidence/profile/资源均未进入公开数据。这一离线阶段没有新增原版、REA、Node、Qt/native或guest运行。 + +本项是有限原版合同输入,`semantic_acceptance=false`、`presented_done=null`。完整模式、固定ConPTY本地运输、真实TUI、所有字体/复杂字、DPI、持续稳定及性能仍按各责任issue验收。没有因此修复旧CI事件、批准性能容差或宣布R2完成。 diff --git a/research/ui/results/2026-10-29-origin-wrap-reference/README.md b/research/ui/results/2026-10-29-origin-wrap-reference/README.md new file mode 100644 index 00000000..2697c4a6 --- /dev/null +++ b/research/ui/results/2026-10-29-origin-wrap-reference/README.md @@ -0,0 +1,19 @@ +# WindTerm 2.7.0 origin/wrap/margin:一次有限观察 + +[完整报告](../../../../docs/research/windterm-origin-wrap-reference-2026-10-29.md)解释确定输入、DSR、文档投影及未知边界;[执行前计划](../../../../docs/research/windterm-origin-wrap-plan.md)保留25阶段和原期限。 + +- 原版执行源 `72e661d964b771eae226e3bc301ed213c5e55ce8`,唯一正式计划SHA `0bb60918f4236c36349d9b87f2cc4c0c71c5147a89a12ddeff1db7b8d6959dc6`;原版运行一次。 +- 离线审计/出口源 `fb1a40a2b6efea4ae5cf528607cd1d853e6ecd4e`,整合main7e896cb后8执行源字节不变。没有重新运行原版或Node。 +- [validation.json](validation.json):25阶段/25个bare DSR/25份文档快照;334B输入+100B请求,157B回复;完整NAWS91×24→91×25,第二事件冻结91×25。普通/`-O`出口逐字节相同,41,269B,SHA256 `9774e25bab9f5f751368e19f915e5d1ec8f4332be606070f15b5ffb33ce5b675`。 +- [publication-audit.json](publication-audit.json):4,047分发文件、8源码、43原件和25投影独立复核;相关纯测试普通/`-O`各21项通过。旧false三份均无自身attempt;初版out已由正式trueplan创建,别名明确保留。 + +Job0/3/0、stderr0及合成单会话复核通过;启动器主动终止原版,不能称自然GUI退出。文档不是active grid,caretColumn不是DSR列。原版完整grid/样式、每屏origin位、返回后margin下界、其他尺寸/分块、固定ConPTY、实际呈现及性能均未验收。 + +原43/41/42/45/60秒局部期限不构成整程硬上限,`whole_lifecycle_bound_seconds=null`。本记录不实现产品模式,也不关闭#27/#41/#56/R2;实际专有副本、原始Evidence/profile和所有私有标识只留local。 + +```powershell +python -B -X utf8 -m unittest research.ui.test_origin_wrap_reference research.ui.test_export_origin_wrap_reference -v +python -B -O -X utf8 -m unittest research.ui.test_origin_wrap_reference research.ui.test_export_origin_wrap_reference -v +# 对已有私有原件的只读审计导出;不启动原版,输出必须是新路径。 +python -B -X utf8 -m research.ui.export_origin_wrap_reference --directory local/origin-wrap-run-0 --plan local/origin-wrap-preflight/authorized-20261029.json --destination local/new-origin-wrap-export.json +``` diff --git a/research/ui/results/2026-10-29-origin-wrap-reference/publication-audit.json b/research/ui/results/2026-10-29-origin-wrap-reference/publication-audit.json new file mode 100644 index 00000000..1d7109ee --- /dev/null +++ b/research/ui/results/2026-10-29-origin-wrap-reference/publication-audit.json @@ -0,0 +1,26 @@ +{ + "schema": "termforge-origin-wrap-publication-audit-v1", + "execution_source_commit": "72e661d964b771eae226e3bc301ed213c5e55ce8", + "offline_export_source_commit": "fb1a40a2b6efea4ae5cf528607cd1d853e6ecd4e", + "public_sha256": "9774e25bab9f5f751368e19f915e5d1ec8f4332be606070f15b5ffb33ce5b675", + "normal_and_optimized_byte_equal": true, + "phases_audited": 25, + "document_and_dsr_projections_unchanged": true, + "copied_and_official_distribution_files_verified": 4047, + "executed_source_files_verified": 8, + "preserved_original_files": 43, + "preserved_original_inventory_sha256": "e5cb4a124a18774faad93bd02238a4bef7c2e031b0f9ab1add9395ac8244d437", + "ordinary_pure_tests_passed": 21, + "optimized_pure_tests_passed": 21, + "new_export_tests": 5, + "full_matrix_executed": false, + "privacy_scan_passed": true, + "unexecuted_false_proposals": 3, + "false_proposal_attempts_present": 0, + "initial_false_proposal_output_now_shared_with_authorized_plan": true, + "new_original_runs_in_offline_export_delta": 0, + "new_node_runs_in_offline_export_delta": 0, + "whole_lifecycle_bound_seconds": null, + "natural_gui_exit_verified": false, + "presented_done": null +} diff --git a/research/ui/results/2026-10-29-origin-wrap-reference/validation.json b/research/ui/results/2026-10-29-origin-wrap-reference/validation.json new file mode 100644 index 00000000..09d9c2fc --- /dev/null +++ b/research/ui/results/2026-10-29-origin-wrap-reference/validation.json @@ -0,0 +1,1451 @@ +{ + "schema": "termforge-original-origin-wrap-reference-v1", + "issue": 151, + "execution_source_commit": "72e661d964b771eae226e3bc301ed213c5e55ce8", + "offline_export_source_commit": "fb1a40a2b6efea4ae5cf528607cd1d853e6ecd4e", + "offline_exporter_sha256": "0fa64c1115cbf3e9b6d0437ce790578bc1a79473c0f722a7b70cf164dd7008e2", + "authorized_plan_sha256": "0bb60918f4236c36349d9b87f2cc4c0c71c5147a89a12ddeff1db7b8d6959dc6", + "status": "bounded-direct-protocol-and-document-observation", + "actual_original_runs": 1, + "maximum_original_runs": 1, + "rea_capture_seconds": 45, + "cli_wait_timeout_seconds": 60, + "whole_lifecycle_bound_seconds": null, + "semantic_acceptance": false, + "full_active_grid_verified": false, + "terminal_styles_verified": false, + "local_shell_transport_verified": false, + "presented_done": null, + "performance_gate": "not_evaluated", + "deliberate_original_process_termination": true, + "natural_gui_exit_verified": false, + "synthetic_profile_verified": true, + "stderr_bytes": 0, + "r2_complete": false, + "capture": { + "phases": [ + { + "phase": 0, + "label": "fresh-session-cup", + "source_hex": "1b5b324a1b5b484654", + "source_bytes": 9, + "source_sha256": "fbed2c2e7bdeb58598a7207603f1971783e2517b6d6965648767deeab84e8e30", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 3 + ], + "response_hex": "1b5b313b3352", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT" + ], + "line_count": 1, + "top": null, + "scroll": 0, + "capacity": 25, + "caret_api": { + "caretLine": 0, + "caretColumn": 0, + "caretPosition": 2 + } + } + }, + { + "phase": 1, + "label": "explicit-origin-wrap-reset", + "source_hex": "1b5b3f366c1b5b3f37681b5b721b5b324a1b5b48", + "source_bytes": 20, + "source_sha256": "293ed28475b1485f3ce6bf9d612453acb113870c942335e71650f8c737213a6f", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 1 + ], + "response_hex": "1b5b313b3152", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + "" + ], + "line_count": 2, + "top": null, + "scroll": 1, + "capacity": 25, + "caret_api": { + "caretLine": 1, + "caretColumn": 0, + "caretPosition": 3 + } + } + }, + { + "phase": 2, + "label": "main-margin-set", + "source_hex": "1b5b323b3572", + "source_bytes": 6, + "source_sha256": "77711f76f33683bf00ae55191c7f063208fc14c8076fc5c8136787e11e293d32", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 1 + ], + "response_hex": "1b5b313b3152", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + "" + ], + "line_count": 2, + "top": null, + "scroll": 1, + "capacity": 25, + "caret_api": { + "caretLine": 1, + "caretColumn": 0, + "caretPosition": 3 + } + } + }, + { + "phase": 3, + "label": "origin-reset-cup", + "source_hex": "1b5b313b334844", + "source_bytes": 7, + "source_sha256": "76445508d6944b7ec27c77724961cd37c98800296eddbc5464bfcf8f6e38b943", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 4 + ], + "response_hex": "1b5b313b3452", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D" + ], + "line_count": 2, + "top": null, + "scroll": 1, + "capacity": 25, + "caret_api": { + "caretLine": 1, + "caretColumn": 0, + "caretPosition": 6 + } + } + }, + { + "phase": 4, + "label": "origin-set", + "source_hex": "1b5b3f3668", + "source_bytes": 5, + "source_sha256": "ab7004944136d39a0189dbdf820e78ab7a9950c4ee083f2f6f7146f9cc62a2b4", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 1 + ], + "response_hex": "1b5b313b3152", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + "" + ], + "line_count": 3, + "top": null, + "scroll": 1, + "capacity": 25, + "caret_api": { + "caretLine": 2, + "caretColumn": 0, + "caretPosition": 7 + } + } + }, + { + "phase": 5, + "label": "origin-set-cup", + "source_hex": "1b5b313b33484f", + "source_bytes": 7, + "source_sha256": "202666677a58212288eadacfeaf43034f76b6c38da69f9e49cd33279f3ed8c4f", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 4 + ], + "response_hex": "1b5b313b3452", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O" + ], + "line_count": 3, + "top": null, + "scroll": 1, + "capacity": 25, + "caret_api": { + "caretLine": 2, + "caretColumn": 0, + "caretPosition": 10 + } + } + }, + { + "phase": 6, + "label": "origin-set-upper-relative-bound", + "source_hex": "1b5b3939411b5b393944", + "source_bytes": 10, + "source_sha256": "bf9a40e3c4dcc81fab85b7c0595af435b3febe72578ee3b89e60497401a4912c", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 1 + ], + "response_hex": "1b5b313b3152", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O" + ], + "line_count": 3, + "top": null, + "scroll": 1, + "capacity": 25, + "caret_api": { + "caretLine": 2, + "caretColumn": 0, + "caretPosition": 7 + } + } + }, + { + "phase": 7, + "label": "origin-set-lower-relative-bound", + "source_hex": "1b5b393942", + "source_bytes": 5, + "source_sha256": "73cc631bfa1641e4bc86c6c963ee94f130ae93c846dc43e2e8498727175334f1", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 4, + 1 + ], + "response_hex": "1b5b343b3152", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O", + "", + "", + "" + ], + "line_count": 6, + "top": null, + "scroll": 1, + "capacity": 25, + "caret_api": { + "caretLine": 5, + "caretColumn": 0, + "caretPosition": 13 + } + } + }, + { + "phase": 8, + "label": "origin-reset-after-margin", + "source_hex": "1b5b3f366c", + "source_bytes": 5, + "source_sha256": "12c9df545ffbef2349f537419d67dec47ba4dcbadf3f83e95c5c7dd4cc17d11d", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 1 + ], + "response_hex": "1b5b313b3152", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O", + "", + "", + "" + ], + "line_count": 6, + "top": null, + "scroll": 1, + "capacity": 25, + "caret_api": { + "caretLine": 1, + "caretColumn": 0, + "caretPosition": 3 + } + } + }, + { + "phase": 9, + "label": "region-sentinels", + "source_hex": "1b5b721b5b324a1b5b313b314854501b5b32353b314842541b5b323b314852301b5b333b314852311b5b343b314852321b5b353b314852331b5b323b3572", + "source_bytes": 62, + "source_sha256": "83b2fea420f0cb996943499fea0432501809a3754fa0224cd32881b0ab339a46", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 1 + ], + "response_hex": "1b5b313b3152", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O", + "TP", + "R0", + "R1", + "R2", + "R3", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "BT" + ], + "line_count": 28, + "top": null, + "scroll": 3, + "capacity": 25, + "caret_api": { + "caretLine": 3, + "caretColumn": 0, + "caretPosition": 11 + } + } + }, + { + "phase": 10, + "label": "region-bottom-newline", + "source_hex": "1b5b353b31480d0a4e57", + "source_bytes": 10, + "source_sha256": "42f02f7ec819dda757d1f8e54077a6f2a65452b0c89636ced636d377c54f3638", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 5, + 3 + ], + "response_hex": "1b5b353b3352", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O", + "TP", + "R1", + "R2", + "R3", + "NW", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "BT" + ], + "line_count": 28, + "top": null, + "scroll": 3, + "capacity": 25, + "caret_api": { + "caretLine": 7, + "caretColumn": 0, + "caretPosition": 25 + } + } + }, + { + "phase": 11, + "label": "above-region-newline", + "source_hex": "1b5b313b31480a5550", + "source_bytes": 9, + "source_sha256": "0158f7c5bb58988fb69103568497c816b92874f50730db61b4c1a3164d428f97", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 2, + 3 + ], + "response_hex": "1b5b323b3352", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O", + "TP", + "UP", + "R2", + "R3", + "NW", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "BT" + ], + "line_count": 28, + "top": null, + "scroll": 3, + "capacity": 25, + "caret_api": { + "caretLine": 4, + "caretColumn": 0, + "caretPosition": 16 + } + } + }, + { + "phase": 12, + "label": "below-region-newline", + "source_hex": "1b5b32353b31480a444e", + "source_bytes": 10, + "source_sha256": "f72a428b4175c877885e2292ddde1ba35f6dc18b7ea7ab3741fedd6ce5feb80c", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 25, + 3 + ], + "response_hex": "1b5b32353b3352", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O", + "TP", + "UP", + "R2", + "R3", + "NW", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "DN" + ], + "line_count": 28, + "top": null, + "scroll": 3, + "capacity": 25, + "caret_api": { + "caretLine": 27, + "caretColumn": 0, + "caretPosition": 47 + } + } + }, + { + "phase": 13, + "label": "main-margin-reset", + "source_hex": "1b5b72", + "source_bytes": 3, + "source_sha256": "869eaff68d67f4cf7f214506fee2ed20e21ccb853376379de9383ab39929e81f", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 1 + ], + "response_hex": "1b5b313b3152", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O", + "TP", + "UP", + "R2", + "R3", + "NW", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "DN" + ], + "line_count": 28, + "top": null, + "scroll": 3, + "capacity": 25, + "caret_api": { + "caretLine": 3, + "caretColumn": 0, + "caretPosition": 11 + } + } + }, + { + "phase": 14, + "label": "wrap-enabled-edge", + "source_hex": "1b5b3f37681b5b383b3931484142", + "source_bytes": 14, + "source_sha256": "48efccaf61ca02c2b5936d7c36e40f7190f08f9a6ff3d12a6810346daa189cde", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 9, + 2 + ], + "response_hex": "1b5b393b3252", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O", + "TP", + "UP", + "R2", + "R3", + "NW", + "", + "", + " AB", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "DN", + "" + ], + "line_count": 28, + "top": null, + "scroll": 3, + "capacity": 25, + "caret_api": { + "caretLine": 10, + "caretColumn": 0, + "caretPosition": 120 + } + } + }, + { + "phase": 15, + "label": "wrap-disabled-edge", + "source_hex": "1b5b3f376c1b5b31303b3931484344", + "source_bytes": 15, + "source_sha256": "789c7ae3561a9db1aad81d09d76ecc2327ae2303a17151eeefb2aef5b065ade6", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 10, + 91 + ], + "response_hex": "1b5b31303b393152", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O", + "TP", + "UP", + "R2", + "R3", + "NW", + "", + "", + " AB", + " D", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "DN", + "" + ], + "line_count": 28, + "top": null, + "scroll": 3, + "capacity": 25, + "caret_api": { + "caretLine": 11, + "caretColumn": 0, + "caretPosition": 211 + } + } + }, + { + "phase": 16, + "label": "wrap-enabled-again-edge", + "source_hex": "1b5b3f37681b5b31323b3931484546", + "source_bytes": 15, + "source_sha256": "8010047940ffb6932e5d7db2064fc5f63de11171c284a961aa0879fc95023e7d", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 13, + 2 + ], + "response_hex": "1b5b31333b3252", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O", + "TP", + "UP", + "R2", + "R3", + "NW", + "", + "", + " AB", + " D", + "", + " EF", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "DN", + "", + "" + ], + "line_count": 28, + "top": null, + "scroll": 3, + "capacity": 25, + "caret_api": { + "caretLine": 13, + "caretColumn": 0, + "caretPosition": 306 + } + } + }, + { + "phase": 17, + "label": "pending-edge-disable-before-next", + "source_hex": "1b5b31343b393148471b5b3f376c48", + "source_bytes": 15, + "source_sha256": "00a13cb42e37c1356e4be732d728ed4866a9d59c107be259a99bdf6aae7e94bf", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 14, + 91 + ], + "response_hex": "1b5b31343b393152", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O", + "TP", + "UP", + "R2", + "R3", + "NW", + "", + "", + " AB", + " D", + "", + " EF", + " H", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "DN", + "", + "" + ], + "line_count": 28, + "top": null, + "scroll": 3, + "capacity": 25, + "caret_api": { + "caretLine": 14, + "caretColumn": 0, + "caretPosition": 397 + } + } + }, + { + "phase": 18, + "label": "disabled-edge-enable-before-next", + "source_hex": "1b5b3f376c1b5b31363b393148491b5b3f37684a", + "source_bytes": 20, + "source_sha256": "a32f88ab4e1517a6bf16f00a3493127badb47e7b7c85c7afcbae21842f79eee7", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 17, + 2 + ], + "response_hex": "1b5b31373b3252", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O", + "TP", + "UP", + "R2", + "R3", + "NW", + "", + "", + " AB", + " D", + "", + " EF", + " H", + "", + " IJ", + "", + "", + "", + "", + "", + "", + "", + "DN", + "", + "", + "" + ], + "line_count": 28, + "top": null, + "scroll": 3, + "capacity": 25, + "caret_api": { + "caretLine": 16, + "caretColumn": 0, + "caretPosition": 492 + } + } + }, + { + "phase": 19, + "label": "main-margin-origin-before-alt", + "source_hex": "1b5b3f37681b5b323b35721b5b3f36681b5b323b34484d", + "source_bytes": 23, + "source_sha256": "fd0ae0fe72873d22d4be24d73ca955b8641fc7ab128ac21696dd4ebbe52d0c46", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 2, + 5 + ], + "response_hex": "1b5b323b3552", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O", + "TP", + "UP", + "R2 M", + "R3", + "NW", + "", + "", + " AB", + " D", + "", + " EF", + " H", + "", + " IJ", + "", + "", + "", + "", + "", + "", + "", + "DN", + "", + "", + "" + ], + "line_count": 28, + "top": null, + "scroll": 3, + "capacity": 25, + "caret_api": { + "caretLine": 5, + "caretColumn": 0, + "caretPosition": 21 + } + } + }, + { + "phase": 20, + "label": "alternate-enter", + "source_hex": "1b5b3f3130343968", + "source_bytes": 8, + "source_sha256": "b7c9b6f9082bb2a92f63566d8800008f96b5dee9d380c2d460b2466ad1496440", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 1 + ], + "response_hex": "1b5b313b3152", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O", + "TP", + "UP", + "R2 M", + "R3", + "NW", + "", + "", + " AB", + " D", + "", + " EF", + " H", + "", + " IJ", + "", + "", + "", + "", + "", + "", + "", + "DN", + "", + "", + "", + "" + ], + "line_count": 29, + "top": null, + "scroll": 4, + "capacity": 25, + "caret_api": { + "caretLine": 3, + "caretColumn": 0, + "caretPosition": 11 + } + } + }, + { + "phase": 21, + "label": "alternate-margin-origin", + "source_hex": "1b5b333b36721b5b3f36681b5b323b344841", + "source_bytes": 18, + "source_sha256": "16eb874957325ad3fbe03ed113331c482abb42a88fd089c327504e222a8be051", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 2, + 5 + ], + "response_hex": "1b5b323b3552", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O", + "TP", + "UP", + "R2 M", + "R3", + "NW", + "", + "", + " AB", + " D", + "", + " EF", + " H", + "", + " IJ", + "", + "", + "", + "", + "", + "", + "", + "DN", + "", + "", + "", + " A", + "", + "", + "" + ], + "line_count": 32, + "top": null, + "scroll": 28, + "capacity": 25, + "caret_api": { + "caretLine": 28, + "caretColumn": 0, + "caretPosition": 512 + } + } + }, + { + "phase": 22, + "label": "alternate-exit", + "source_hex": "1b5b3f313034396c", + "source_bytes": 8, + "source_sha256": "36a3d2013e70720f8d7c0105d9b9c23acfa208b175099330c7c820ad3e1e1df5", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 2, + 5 + ], + "response_hex": "1b5b323b3552", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O", + "TP", + "UP", + "R2 M", + "R3", + "NW", + "", + "", + " AB", + " D", + "", + " EF", + " H", + "", + " IJ", + "", + "", + "", + "", + "", + "", + "", + "DN", + "", + "", + "" + ], + "line_count": 28, + "top": null, + "scroll": 3, + "capacity": 25, + "caret_api": { + "caretLine": 5, + "caretColumn": 0, + "caretPosition": 21 + } + } + }, + { + "phase": 23, + "label": "main-cup-after-alternate", + "source_hex": "1b5b313b364852", + "source_bytes": 7, + "source_sha256": "8eb86ce63e869f604316346705430b0588d85cd503ea93f981d0b0bc67ed7059", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 7 + ], + "response_hex": "1b5b313b3752", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O", + "TP", + "UP R", + "R2 M", + "R3", + "NW", + "", + "", + " AB", + " D", + "", + " EF", + " H", + "", + " IJ", + "", + "", + "", + "", + "", + "", + "", + "DN", + "", + "", + "" + ], + "line_count": 28, + "top": null, + "scroll": 3, + "capacity": 25, + "caret_api": { + "caretLine": 4, + "caretColumn": 0, + "caretPosition": 20 + } + } + }, + { + "phase": 24, + "label": "final-explicit-reset", + "source_hex": "1b5b3f366c1b5b3f37681b5b721b5b313b3148444f4e45", + "source_bytes": 23, + "source_sha256": "f45dfc22cd655dcf31ca27d1c2141a935e1c8ab966caf40824f91d0fedbe1c5f", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 5 + ], + "response_hex": "1b5b313b3552", + "private_dsr_prefix": false, + "document_projection": { + "lines": [ + "FT", + " D", + " O", + "DONE", + "UP R", + "R2 M", + "R3", + "NW", + "", + "", + " AB", + " D", + "", + " EF", + " H", + "", + " IJ", + "", + "", + "", + "", + "", + "", + "", + "DN", + "", + "", + "" + ], + "line_count": 28, + "top": null, + "scroll": 3, + "capacity": 25, + "caret_api": { + "caretLine": 3, + "caretColumn": 0, + "caretPosition": 15 + } + } + } + ], + "source_bytes": 334, + "dsr_request_bytes": 100, + "application_reply_bytes": 157, + "naws_history": [ + [ + 91, + 24 + ], + [ + 91, + 25 + ] + ], + "frozen_grid": [ + 91, + 25 + ], + "freeze_event_count": 2, + "job_after": { + "active": 0, + "total": 3, + "terminated": 0 + }, + "provider": { + "id": "frida", + "version": null, + "confidence": "observed", + "evidence_id": "ev_9f414d87b06374e853efc468a6ed115f754c869745e0a75b554d17274fa6247d", + "subject": null, + "environment": null, + "messages": 30, + "phase_messages": 25, + "messages_truncated": false, + "cleanup_error": null + } + }, + "provenance": { + "execution_source_files_sha256": { + "research/ui/origin_wrap_reference.py": "66b500df34d88b68f2b3224a92f956f63829a0f16555f17dbf244ce3576def8e", + "research/ui/origin_wrap_reference.js": "d93d9c4e7c84c220be9f6911f4f04f2c12dee939cefe20854e0d9285d6f38ff5", + "research/ui/alternate_reference.py": "94b85d49347e0e9813f7acddf0244f58520a7d1fce14084677c91cf4ddafffea", + "research/ui/r1_launch.py": "54fb41872c6abcc48242a0938044184854b016785cc13659c66b1bf404da0d8a", + "research/ui/launch.py": "6970e0d4f2fe5e7ec4827df7aa1026cd0e74aeb828cdc9b7a0b42b3c887eb4e6", + "research/ui/qt_r1_text.js": "f5820d413fdd72c13dffcefbf4d0ade615abfe15754c6b0b52e1e098814eea6f", + "research/ui/r1_vt_fixture.py": "5853f684d5b8a4c17a30f22a536f04f0eb212813b8d6b3cc3c197df392eb732a", + "experiments/conpty/run.py": "c8eef1e36b6d200b2d508cef91eaa5ca79a91fa87c5daf6265b2475c31dce329" + }, + "launcher_namespace_sha256": "7db16b829cc26c4b7157dfc9a36c6015f78ed6c3fd4d26369286c02feab02e34", + "reference_identity": { + "exe_sha256": "7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0", + "manifest_sha256": "e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203", + "files_verified": 4047 + }, + "launch_host": { + "windows_version": "10.0.26200", + "python_version_number": "3.12.10", + "python_version_text_sha256": "f0823692b73f6cc9b2412e30deb4aecc1f01e6368a93b3ca3e19ea3b586b08df", + "python_exe_sha256": "0b471133e110cfb53a061cad528ce8e517d7b9ac41a0a396c39ad795a487fc14", + "node_exe_sha256": "471961cb355311c9a9dd8ba417eca8269ead32a2231653084112554cda52e8b3", + "node_process_version_observed_in_original_run": null, + "rea_package_version": "6.7.0", + "rea_shim_sha256": "d092352a4088e7df223e9fe39039d703411dc5cbd39990f69756ade448ef1de4", + "rea_package_json_sha256": "3f6c47fb12c8388f291a21e7f99a876495d03aee671b3b6a0708dd12d6ebb570", + "rea_entry_sha256": "ed06cee49505b95a3cc5fceb1b4b279167e26df86227c644bb366ffffdbaf738" + }, + "wrapper": { + "exit_code": 0, + "started_utc": "2026-10-28T21:23:17.0405260Z", + "finished_utc": "2026-10-28T21:24:13.3076732Z", + "observed_wall_seconds": 56.267147, + "whole_lifecycle_bound_seconds": null + }, + "preserved_original_inventory": { + "files": 43, + "sha256": "e5cb4a124a18774faad93bd02238a4bef7c2e031b0f9ab1add9395ac8244d437" + }, + "unexecuted_proposals": [ + { + "label": "initial", + "bytes": 9598, + "sha256": "e64ca5343021e25abb3f8dc530da8c8afaa75fa3f08b00347fca93a8a371dcf1", + "source_base_commit": "e133b5122136c7309d4af6e2c9ce5bd72f91ee8d", + "execution_authorized": false, + "attempt_present": false, + "output_present": true, + "output_shared_with_authorized_plan": true + }, + { + "label": "node-bound", + "bytes": 9769, + "sha256": "18b7fcfc2963ca38d13e29e3df6f97303471209473ccfdf67f7849ca83c22679", + "source_base_commit": "21fc745d5a25339a4ce8f11ba1e1090a81fa1cab", + "execution_authorized": false, + "attempt_present": false, + "output_present": false, + "output_shared_with_authorized_plan": false + }, + { + "label": "stability-bound", + "bytes": 9774, + "sha256": "50546257a4fde7e868dfb2d041d572bc632c6eca60578a12694b5683d35d7bdd", + "source_base_commit": "72e661d964b771eae226e3bc301ed213c5e55ce8", + "execution_authorized": false, + "attempt_present": false, + "output_present": false, + "output_shared_with_authorized_plan": false + } + ], + "raw_files": { + "evidence.json": { + "bytes": 35070, + "sha256": "9a067916d6bd7559d4582fe13d5f854760b5f9e0b21a1b03ed37d4d8934b1efa" + }, + "run.json": { + "bytes": 620, + "sha256": "3fe2f4a0f5627f89d90fbb9c67cd67982174e33d4b3b1e04f2641fe2994b8a99" + }, + "probe.js": { + "bytes": 20628, + "sha256": "3800ba21b6b4a5e91e8ecc4b75f428d2cb659d8412b1d4323a162852d5e1f51e" + }, + "peer.json": { + "bytes": 8479, + "sha256": "13d297a0245a2bd6cdbc0e952b4c61123021766e662d4deb9a654936bde8e9a8" + }, + "origin-wrap-result.json": { + "bytes": 15227, + "sha256": "7ae991a712eadb5cbc981d1aff53d01ce2a900551fc56ea1e3c253e2e76b8a9b" + }, + "files.json": { + "bytes": 637666, + "sha256": "a25c87cba935294d5bfd3bbc76c650d31a8473809564d798ad5a889e4aea00e8" + }, + "mechanics-check.json": { + "bytes": 229, + "sha256": "534b0f0102a6841888b067beecc7308cbe7915ad52660ad2d10f244c73769d59" + }, + "stderr.txt": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "profile-check.json": { + "bytes": 52, + "sha256": "5e522a64e98197722ccd084639b1ce50258b238e03be2f016e81c8af023b5ee1" + }, + "cleanup.json": { + "bytes": 42, + "sha256": "8400f93b6a4a4a50cde35b5d474660590ed215732f55772a9d8afac138da4f1d" + }, + "fixture-ready": { + "bytes": 20, + "sha256": "622d3895d17f825845b8a486e94c55b2db0fa84777cb6a4d3c2f72414fd5469f" + }, + "ack-0": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-1": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-2": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-3": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-4": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-5": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-6": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-7": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-8": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-9": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-10": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-11": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-12": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-13": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-14": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-15": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-16": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-17": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-18": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-19": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-20": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-21": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-22": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-23": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + }, + "ack-24": { + "bytes": 8, + "sha256": "604cee807f644af47487bf2bbab442b94212ac5119f36f995f78e9e4694dae8c" + } + }, + "authorized_plan": { + "bytes": 9757, + "sha256": "0bb60918f4236c36349d9b87f2cc4c0c71c5147a89a12ddeff1db7b8d6959dc6" + }, + "outer_launch_log": { + "bytes": 687, + "sha256": "9400e2f17a06ebed0ac48996d7487fbb56fa309fd800aee8aba9a57a902c1e47" + }, + "outer_launch_exit": { + "bytes": 336, + "sha256": "7b0c4a819f9c6659deb4ea285f0083dfc709ea4a269d1a14110aec6e32eefd13" + } + } +} From 9d36d4efa02a70ab6d7585d50f1e42df32ebedc6 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 06:04:00 +0800 Subject: [PATCH 10/12] test: reproduce original mode export fixture path alias failure (refs #151) --- .../ui/test_export_origin_wrap_reference.py | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/research/ui/test_export_origin_wrap_reference.py b/research/ui/test_export_origin_wrap_reference.py index 94f69eed..04237927 100644 --- a/research/ui/test_export_origin_wrap_reference.py +++ b/research/ui/test_export_origin_wrap_reference.py @@ -71,6 +71,18 @@ def save(directory, data, synchronize=True): class ExportTests(unittest.TestCase): + def test_existing_parent_alias_fixture_is_valid_for_direct_capture(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary).resolve() + alias_component = root / "existing path spelling component" + alias_component.mkdir() + alias = alias_component / ".." + self.assertNotEqual(alias, alias.resolve()) + directory, _ = fixture(alias) + result = exporter.capture_facts(directory) + self.assertEqual(len(result["phases"]), 25) + self.assertEqual(directory, directory.resolve()) + def test_valid_capture_projects_safe_fields_without_paths_or_process_ids(self): with tempfile.TemporaryDirectory() as temporary: directory, data = fixture(Path(temporary)) @@ -130,12 +142,26 @@ def test_wrong_source_missing_stage_extra_reply_and_cleanup_are_refused(self): def test_public_cli_refuses_private_text_in_normal_and_optimized_python_without_output(self): with tempfile.TemporaryDirectory() as temporary: directory, data = fixture(Path(temporary)) - data["origin-wrap-result.json"]["observations"][0]["terminal"]["lines"] = ["DO_NOT_PUBLISH"] - save(directory, data) - plan = Path(temporary) / "plan.json" - plan.write_text("{}") - for optimized in (False, True): - destination = Path(temporary) / ("optimized/out.json" if optimized else "normal/out.json") + self.check_private_cli_refusals(directory, data, Path(temporary)) + + def test_existing_parent_alias_private_text_reaches_public_cli_whitelist(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary).resolve() + alias_component = root / "existing path spelling component" + alias_component.mkdir() + alias = alias_component / ".." + self.assertNotEqual(alias, alias.resolve()) + directory, data = fixture(alias) + self.check_private_cli_refusals(directory, data, root) + + def check_private_cli_refusals(self, directory, data, root): + data["origin-wrap-result.json"]["observations"][0]["terminal"]["lines"] = ["DO_NOT_PUBLISH"] + save(directory, data) + plan = root / "plan.json" + plan.write_text("{}") + for optimized in (False, True): + with self.subTest(optimized=optimized): + destination = root / ("optimized/out.json" if optimized else "normal/out.json") command = [sys.executable, "-B", *(["-O"] if optimized else []), "-X", "utf8", "-m", "research.ui.export_origin_wrap_reference", "--directory", str(directory), "--plan", str(plan), "--destination", str(destination)] From 74d4cadd9fa90fd6b6a1de6c61e96f8369e67951 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 06:04:07 +0800 Subject: [PATCH 11/12] fix: canonicalize original mode export fixture directory (refs #151) --- research/ui/test_export_origin_wrap_reference.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/research/ui/test_export_origin_wrap_reference.py b/research/ui/test_export_origin_wrap_reference.py index 04237927..4d80e3d4 100644 --- a/research/ui/test_export_origin_wrap_reference.py +++ b/research/ui/test_export_origin_wrap_reference.py @@ -15,7 +15,7 @@ def fixture(root): - directory = root / "capture" + directory = (root / "capture").resolve() directory.mkdir() peer, _ = receive_fixture("normal") peer.update(naws_history=[[91, 24], [91, 25]], freeze_event_count=2) From ec0f1993c4b54f8b6a3193d8bbdfc4db29dfb652 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 06:07:45 +0800 Subject: [PATCH 12/12] docs: preserve original mode CI failures and path fixture validation (refs #151) --- docs/EVIDENCE.md | 2 +- ...ndterm-origin-wrap-reference-2026-10-29.md | 10 +++ .../README.md | 2 + .../fixture-path-validation.json | 67 +++++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 research/ui/results/2026-10-29-origin-wrap-reference/fixture-path-validation.json diff --git a/docs/EVIDENCE.md b/docs/EVIDENCE.md index 5b4cda58..44c9caa3 100644 --- a/docs/EVIDENCE.md +++ b/docs/EVIDENCE.md @@ -24,7 +24,7 @@ R1 固定总出口的源码为 `ce9b24d`,早期 `2dc8d6d` / `9b16acc` 仍分 原版模式依据见 [1049 直达终端观察](research/windterm-alternate-reference-2026-10-28.md)与[验证身份](../research/ui/results/2026-10-28-alternate-reference/validation.json):26 阶段精确输入、DSR 和文档投影相互绑定,原版重复进入的光标行为与通用幂等假设不同;样式、完整 active grid 和本地 Shell 运输继续保留未知。 -[#151 origin/wrap/margin有限观察](research/windterm-origin-wrap-reference-2026-10-29.md)取得一次固定91×25直达终端的25阶段、25条bareDSR及25文档快照,[数据与发布审计](../research/ui/results/2026-10-29-origin-wrap-reference/README.md)保留完整NAWS、源/回复/文档分层、Node与源码身份、主动终止和旧false输出路径别名。严格出口普通/-O逐字节相同;这只为自主C++下一切片提供有限依据,不是完整active grid、产品模式、固定ConPTY或性能验收。 +[#151 origin/wrap/margin有限观察](research/windterm-origin-wrap-reference-2026-10-29.md)取得一次固定91×25直达终端的25阶段、25条bareDSR及25文档快照,[数据与发布审计](../research/ui/results/2026-10-29-origin-wrap-reference/README.md)保留完整NAWS、源/回复/文档分层、Node与源码身份、主动终止和旧false输出路径别名。严格出口普通/-O逐字节相同;初次Windows夹具CI红、真实路径别名反例及仅fixture规范化的修正另列,原公开字节保留,不推定CI使用8.3。它只为自主C++下一切片提供有限依据,不是完整active grid、产品模式、固定ConPTY或性能验收。 [#146 SGR1 固定样本调查](../research/ui/results/2026-10-29-sgr1-reference/README.md)取得五阶段/五 grab、28 对 H/space 完整有序像素签名;SGR0/SGR1 控制在采样条件下均不可分,进入/重复进入/返回无唯一分类。内部字重、SGR1 是否受字体/profile 偏好影响及全部非颜色属性仍未知;没有据此更改 C++ 策略或验收 #123/#126/#27。 diff --git a/docs/research/windterm-origin-wrap-reference-2026-10-29.md b/docs/research/windterm-origin-wrap-reference-2026-10-29.md index 36ea5303..b063a8bb 100644 --- a/docs/research/windterm-origin-wrap-reference-2026-10-29.md +++ b/docs/research/windterm-origin-wrap-reference-2026-10-29.md @@ -51,3 +51,13 @@ 实际raw分别用普通和`-O`导出,41,269B逐字节相同,SHA256 `9774e25bab9f5f751368e19f915e5d1ec8f4332be606070f15b5ffb33ce5b675`。公开前再独立核对25份文档/DSR投影、8源和43份原件;绝对路径、PID/TID、station/session/script ID及原始Evidence/profile/资源均未进入公开数据。这一离线阶段没有新增原版、REA、Node、Qt/native或guest运行。 本项是有限原版合同输入,`semantic_acceptance=false`、`presented_done=null`。完整模式、固定ConPTY本地运输、真实TUI、所有字体/复杂字、DPI、持续稳定及性能仍按各责任issue验收。没有因此修复旧CI事件、批准性能容差或宣布R2完成。 + +## 初次 Windows CI 红与夹具路径修正 + +发布HEAD01a979d的 [push/default Windows](https://github.com/JavaZeroo/termforge/actions/runs/40774903422/job/123121533676)为59/60、292.34s,仅新增 `windterm_origin_wrap_reference` 失败;[PR/default Windows](https://github.com/JavaZeroo/termforge/actions/runs/40774909044/job/123121553707)为58/60、283.98s,除该测试外还有独立的owned-desktop收尾事件,由#38另行接续。原job和artifact保留;未重跑旧run,本修正不解释或修复独立desktop事件。artifact12853994007 / SHA `1861c7fee6a58c9eb96898ec52eac77e4f2becc7073c005788e6f0a8c9ad2960`、artifact12854726855 / SHA `05392812a79a0b38d48384b47905c85c9f46285cbedcf0b4afa2275550c9efbb`由根代理下载保留,本项未重复下载。 + +已用实际存在的子目录后接 `..` 构造同一目录的不同拼写,复现直接调用的 `Provider source/lifecycle differs or is incomplete`,以及公共CLI在私密文本门槛前遇到 `Runtime probe bytes differ`。前者是夹具目录未规范化、provider路径却resolve后比较;后者是夹具将未规范化目录嵌入probe,而真实公共入口先resolve,再重建probe。**已证明的是该夹具缺陷;旧CI未确定TEMP的具体拼写,不能据此声称它实际使用8.3或哪一种别名。** + +反例内容提交 `9d36d4efa02a70ab6d7585d50f1e42df32ebedc6`;最小修正 `74d4cadd9fa90fd6b6a1de6c61e96f8369e67951` 仅将夹具目录改为 `(root / "capture").resolve()`,在生成probe和source元数据前与真实launcher一致。生产exporter、source/path/lifecycle校验、189行LF→CRLF及私密文字白名单均未改。两个新增回归直接驱动真实capture/publicCLI:red为1个Provider错误和普通/-O两个CLI失配;green相关纯测试普通/-O各23项通过,别名下有效记录通过,私密文本仍在正确白名单门槛拒绝且不创建输出。 + +[路径修正审计](../../research/ui/results/2026-10-29-origin-wrap-reference/fixture-path-validation.json)单列本次反例、源码、日志和派生出口。原件43项、原版执行源72e、三个false及旧公开 `9774e25…` 原字节保持。修正后的实际raw普通/-O重导仍相同,新SHA `6ad6820884d4f94b9a42fd1a669253c72df38512b9f8dadb461250e3425e4557`;与旧公开文件只差 `offline_export_source_commit` 由fb1a40a变为74d4cad,其余投影和字节完全相同。这是调用HEAD元数据变化,不是新观测、数据修正或新原版运行。新source的自动CI及最终审查另行核对;没有用本地green覆盖旧CI红。 diff --git a/research/ui/results/2026-10-29-origin-wrap-reference/README.md b/research/ui/results/2026-10-29-origin-wrap-reference/README.md index 2697c4a6..c1bd7977 100644 --- a/research/ui/results/2026-10-29-origin-wrap-reference/README.md +++ b/research/ui/results/2026-10-29-origin-wrap-reference/README.md @@ -7,6 +7,8 @@ - [validation.json](validation.json):25阶段/25个bare DSR/25份文档快照;334B输入+100B请求,157B回复;完整NAWS91×24→91×25,第二事件冻结91×25。普通/`-O`出口逐字节相同,41,269B,SHA256 `9774e25bab9f5f751368e19f915e5d1ec8f4332be606070f15b5ffb33ce5b675`。 - [publication-audit.json](publication-audit.json):4,047分发文件、8源码、43原件和25投影独立复核;相关纯测试普通/`-O`各21项通过。旧false三份均无自身attempt;初版out已由正式trueplan创建,别名明确保留。 +初次Windows CI的新测试失败与后续夹具修正见 [fixture-path-validation.json](fixture-path-validation.json)和完整报告末节。真实子目录/`..`复现路径未规范化问题;只修夹具resolve,生产gate未变。新增反例后普通/`-O`各23项通过,旧CI未重跑。旧公开数据仍为上述`9774e25…`;重导新SHA`6ad68208…`仅因`offline_export_source_commit`更新,其余投影/字节不变,原版没有重测。CI实际TEMP拼写仍未知,不宣称使用8.3;另一次owned-desktop收尾失败独立保留。 + Job0/3/0、stderr0及合成单会话复核通过;启动器主动终止原版,不能称自然GUI退出。文档不是active grid,caretColumn不是DSR列。原版完整grid/样式、每屏origin位、返回后margin下界、其他尺寸/分块、固定ConPTY、实际呈现及性能均未验收。 原43/41/42/45/60秒局部期限不构成整程硬上限,`whole_lifecycle_bound_seconds=null`。本记录不实现产品模式,也不关闭#27/#41/#56/R2;实际专有副本、原始Evidence/profile和所有私有标识只留local。 diff --git a/research/ui/results/2026-10-29-origin-wrap-reference/fixture-path-validation.json b/research/ui/results/2026-10-29-origin-wrap-reference/fixture-path-validation.json new file mode 100644 index 00000000..94eac0b6 --- /dev/null +++ b/research/ui/results/2026-10-29-origin-wrap-reference/fixture-path-validation.json @@ -0,0 +1,67 @@ +{ + "schema": "termforge-origin-wrap-ci-path-fixture-validation-v1", + "issue": 151, + "previous_publication_head": "01a979da926c05c8e6c34e3f5e5ed7b8d6916de1", + "red_committed_reproducer": "9d36d4efa02a70ab6d7585d50f1e42df32ebedc6", + "green_committed_fixture_fix": "74d4cadd9fa90fd6b6a1de6c61e96f8369e67951", + "red_runtime_base_commit": "01a979da926c05c8e6c34e3f5e5ed7b8d6916de1", + "red_runtime_worktree_test_bytes_match_committed_reproducer": true, + "green_runtime_base_commit": "9d36d4efa02a70ab6d7585d50f1e42df32ebedc6", + "green_runtime_worktree_test_bytes_match_committed_fix": true, + "reproducer": "Existing real directory followed by parent component; no assumed 8.3 spelling", + "original_ci_actual_temporary_path_spelling": null, + "red_tests": 2, + "red_errors": 1, + "red_failing_subcases": 2, + "ordinary_pure_tests_passed": 23, + "optimized_pure_tests_passed": 23, + "production_exporter_sha256": "0fa64c1115cbf3e9b6d0437ce790578bc1a79473c0f722a7b70cf164dd7008e2", + "production_exporter_unchanged": true, + "old_publication_sha256": "9774e25bab9f5f751368e19f915e5d1ec8f4332be606070f15b5ffb33ce5b675", + "derived_normal_and_optimized_sha256": "6ad6820884d4f94b9a42fd1a669253c72df38512b9f8dadb461250e3425e4557", + "normal_and_optimized_byte_equal": true, + "changed_public_fields": [ + "offline_export_source_commit" + ], + "original_projection_and_other_bytes_unchanged": true, + "preserved_original_files": 43, + "preserved_original_inventory_sha256": "e5cb4a124a18774faad93bd02238a4bef7c2e031b0f9ab1add9395ac8244d437", + "new_original_runs": 0, + "new_node_runs": 0, + "old_ci_retries": 0, + "full_matrix_executed": false, + "ci_failures": [ + { + "event": "push", + "run": 40774903422, + "job": 123121533676, + "windows_passed": 59, + "windows_total": 60, + "artifact": 12853994007, + "artifact_sha256": "1861c7fee6a58c9eb96898ec52eac77e4f2becc7073c005788e6f0a8c9ad2960" + }, + { + "event": "pull_request", + "run": 40774909044, + "job": 123121553707, + "windows_passed": 58, + "windows_total": 60, + "artifact": 12854726855, + "artifact_sha256": "05392812a79a0b38d48384b47905c85c9f46285cbedcf0b4afa2275550c9efbb" + } + ], + "private_logs": { + "ci-path-alias-red.log": { + "bytes": 5395, + "sha256": "464d3c84c807150169b20cedb02c87cf6d605410ffdab4e60ee3871490c3e4df" + }, + "ci-path-alias-green.log": { + "bytes": 4438, + "sha256": "2323a92b192acc50169f590165b1e31157572a4a963cf13ea7758a97e3fd8425" + }, + "ci-path-alias-green-O.log": { + "bytes": 4438, + "sha256": "cf9198c5b1c6a061642ad05904c8421fd36eeac166c2c86cb0db1e37d2897dea" + } + } +}