From 0b8c9c722c154a0eb08011420a4d502dc4324408 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 04:37:54 +0900 Subject: [PATCH 1/6] test(browser): require forced-close recovery evidence --- .../test_agent_task_forced_close_contract.py | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 tests/test_agent_task_forced_close_contract.py diff --git a/tests/test_agent_task_forced_close_contract.py b/tests/test_agent_task_forced_close_contract.py new file mode 100644 index 000000000..07e953439 --- /dev/null +++ b/tests/test_agent_task_forced_close_contract.py @@ -0,0 +1,118 @@ +"""Contract for deterministic forced-close failure evidence in the Agent Task lane.""" + +from __future__ import annotations + +import pathlib +import runpy +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +RUNNER = ROOT / "scripts" / "ci" / "run_mv3_compatibility.py" + + +class AgentTaskForcedCloseContractTests(unittest.TestCase): + """Require one real-browser interruption probe without normalizing failures.""" + + def test_runner_exposes_forced_close_probe(self) -> None: + """The pinned-browser lane must have an executable forced-close boundary.""" + + namespace = runpy.run_path(str(RUNNER), run_name="agent_task_forced_close_contract") + for expected in ( + "_force_close_agent_task_context", + "_run_agent_task_forced_close_browser_pass", + "_run_agent_task_forced_close_trial", + ): + with self.subTest(expected=expected): + self.assertIn(expected, namespace) + + def test_forced_close_requires_the_context_to_become_unusable(self) -> None: + """A closed top-level context must fail the next command as no-such-window.""" + + namespace = runpy.run_path(str(RUNNER), run_name="agent_task_forced_close_behavior") + force_close = namespace["_force_close_agent_task_context"] + requests: list[tuple[str, str]] = [] + + def closed_context_request( + _driver_port: int, + method: str, + path: str, + _payload: object | None = None, + **_kwargs: object, + ) -> dict[str, object]: + requests.append((method, path)) + if method == "DELETE" and path.endswith("/window"): + return {"value": []} + if method == "GET" and path.endswith("/url"): + raise RuntimeError("WebDriver error: no such window: controlled close") + raise AssertionError(f"unexpected request: {method} {path}") + + force_close.__globals__["_json_request"] = closed_context_request + self.assertTrue(force_close(4444, "session-a")) + self.assertEqual( + requests, + [ + ("DELETE", "/session/session-a/window"), + ("GET", "/session/session-a/url"), + ], + ) + + def test_forced_close_rejects_surviving_or_ambiguous_context_state(self) -> None: + """The probe must fail closed if the close did not produce exact evidence.""" + + namespace = runpy.run_path(str(RUNNER), run_name="agent_task_forced_close_fail_closed") + force_close = namespace["_force_close_agent_task_context"] + + def surviving_context_request( + _driver_port: int, + method: str, + path: str, + _payload: object | None = None, + **_kwargs: object, + ) -> dict[str, object]: + if method == "DELETE" and path.endswith("/window"): + return {"value": []} + if method == "GET" and path.endswith("/url"): + return {"value": "http://127.0.0.1/fixture"} + raise AssertionError(f"unexpected request: {method} {path}") + + force_close.__globals__["_json_request"] = surviving_context_request + with self.assertRaisesRegex(RuntimeError, "remained usable after forced close"): + force_close(4444, "session-a") + + force_close = runpy.run_path( + str(RUNNER), run_name="agent_task_forced_close_wrong_error" + )["_force_close_agent_task_context"] + + def wrong_failure_request( + _driver_port: int, + method: str, + path: str, + _payload: object | None = None, + **_kwargs: object, + ) -> dict[str, object]: + if method == "DELETE" and path.endswith("/window"): + return {"value": []} + if method == "GET" and path.endswith("/url"): + raise RuntimeError("WebDriver error: timeout: unrelated failure") + raise AssertionError(f"unexpected request: {method} {path}") + + force_close.__globals__["_json_request"] = wrong_failure_request + with self.assertRaisesRegex(RuntimeError, "timeout"): + force_close(4444, "session-a") + + def test_main_evidence_requires_forced_close_and_profile_cleanup(self) -> None: + """Compatibility success must include the interruption probe and cleanup proof.""" + + runner = RUNNER.read_text(encoding="utf-8") + for expected in ( + '"forced_close"', + '"forced_close_detected"', + '"profile_cleaned"', + "Agent Task forced-close recovery gate failed", + ): + with self.subTest(expected=expected): + self.assertIn(expected, runner) + + +if __name__ == "__main__": + unittest.main() From 138ad51b7fbc4c5b7b02c98d99ac798297049337 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 04:49:22 +0900 Subject: [PATCH 2/6] test(browser): preserve session during forced-close probe --- .../test_agent_task_forced_close_contract.py | 48 ++++++++++++++++--- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/tests/test_agent_task_forced_close_contract.py b/tests/test_agent_task_forced_close_contract.py index 07e953439..ebd25fea0 100644 --- a/tests/test_agent_task_forced_close_contract.py +++ b/tests/test_agent_task_forced_close_contract.py @@ -26,7 +26,7 @@ def test_runner_exposes_forced_close_probe(self) -> None: self.assertIn(expected, namespace) def test_forced_close_requires_the_context_to_become_unusable(self) -> None: - """A closed top-level context must fail the next command as no-such-window.""" + """A closed current context must fail as no-such-window while session survives.""" namespace = runpy.run_path(str(RUNNER), run_name="agent_task_forced_close_behavior") force_close = namespace["_force_close_agent_task_context"] @@ -41,7 +41,7 @@ def closed_context_request( ) -> dict[str, object]: requests.append((method, path)) if method == "DELETE" and path.endswith("/window"): - return {"value": []} + return {"value": ["survivor-context"]} if method == "GET" and path.endswith("/url"): raise RuntimeError("WebDriver error: no such window: controlled close") raise AssertionError(f"unexpected request: {method} {path}") @@ -56,13 +56,13 @@ def closed_context_request( ], ) - def test_forced_close_rejects_surviving_or_ambiguous_context_state(self) -> None: - """The probe must fail closed if the close did not produce exact evidence.""" + def test_forced_close_rejects_session_termination_or_surviving_context(self) -> None: + """The probe must retain another context and require the current one to be dead.""" - namespace = runpy.run_path(str(RUNNER), run_name="agent_task_forced_close_fail_closed") + namespace = runpy.run_path(str(RUNNER), run_name="agent_task_forced_close_no_survivor") force_close = namespace["_force_close_agent_task_context"] - def surviving_context_request( + def ended_session_request( _driver_port: int, method: str, path: str, @@ -71,6 +71,25 @@ def surviving_context_request( ) -> dict[str, object]: if method == "DELETE" and path.endswith("/window"): return {"value": []} + raise AssertionError(f"unexpected request after session-ending close: {method} {path}") + + force_close.__globals__["_json_request"] = ended_session_request + with self.assertRaisesRegex(RuntimeError, "no surviving browsing context"): + force_close(4444, "session-a") + + force_close = runpy.run_path( + str(RUNNER), run_name="agent_task_forced_close_survivor" + )["_force_close_agent_task_context"] + + def surviving_context_request( + _driver_port: int, + method: str, + path: str, + _payload: object | None = None, + **_kwargs: object, + ) -> dict[str, object]: + if method == "DELETE" and path.endswith("/window"): + return {"value": ["survivor-context"]} if method == "GET" and path.endswith("/url"): return {"value": "http://127.0.0.1/fixture"} raise AssertionError(f"unexpected request: {method} {path}") @@ -79,6 +98,9 @@ def surviving_context_request( with self.assertRaisesRegex(RuntimeError, "remained usable after forced close"): force_close(4444, "session-a") + def test_forced_close_rejects_unrelated_protocol_error(self) -> None: + """A timeout or other protocol failure must not masquerade as close evidence.""" + force_close = runpy.run_path( str(RUNNER), run_name="agent_task_forced_close_wrong_error" )["_force_close_agent_task_context"] @@ -91,7 +113,7 @@ def wrong_failure_request( **_kwargs: object, ) -> dict[str, object]: if method == "DELETE" and path.endswith("/window"): - return {"value": []} + return {"value": ["survivor-context"]} if method == "GET" and path.endswith("/url"): raise RuntimeError("WebDriver error: timeout: unrelated failure") raise AssertionError(f"unexpected request: {method} {path}") @@ -100,6 +122,18 @@ def wrong_failure_request( with self.assertRaisesRegex(RuntimeError, "timeout"): force_close(4444, "session-a") + def test_browser_pass_creates_and_selects_disposable_context(self) -> None: + """The real probe must preserve a session by closing only a second context.""" + + runner = RUNNER.read_text(encoding="utf-8") + for expected in ( + '"/window/new"', + '{"type": "tab"}', + '{"handle": disposable_context}', + ): + with self.subTest(expected=expected): + self.assertIn(expected, runner) + def test_main_evidence_requires_forced_close_and_profile_cleanup(self) -> None: """Compatibility success must include the interruption probe and cleanup proof.""" From 2e0fb4db866a591e7c7bce68691462130b1f4c48 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 05:11:17 +0900 Subject: [PATCH 3/6] feat(browser): prove forced-close recovery evidence --- scripts/ci/run_mv3_compatibility.py | 254 ++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) diff --git a/scripts/ci/run_mv3_compatibility.py b/scripts/ci/run_mv3_compatibility.py index db4395eac..aeca4ae07 100755 --- a/scripts/ci/run_mv3_compatibility.py +++ b/scripts/ci/run_mv3_compatibility.py @@ -1010,6 +1010,216 @@ def _run_agent_task_trial( } +def _force_close_agent_task_context(driver_port: int, session_id: str) -> bool: + """Close only the current browsing context and require no-such-window evidence.""" + + closed = _json_request( + driver_port, + "DELETE", + _webdriver_path(session_id, "/window"), + ) + surviving_contexts = closed.get("value") + if not isinstance(surviving_contexts, list): + raise RuntimeError("Agent Task forced-close returned malformed surviving contexts") + if not surviving_contexts: + raise RuntimeError("Agent Task forced-close left no surviving browsing context") + if any(not isinstance(handle, str) or not handle for handle in surviving_contexts): + raise RuntimeError("Agent Task forced-close returned invalid surviving context") + + try: + _json_request( + driver_port, + "GET", + _webdriver_path(session_id, "/url"), + ) + except RuntimeError as exc: + if "no such window" in str(exc).lower(): + return True + raise + raise RuntimeError("Agent Task context remained usable after forced close") + + +def _run_agent_task_forced_close_browser_pass( + chrome_bin: pathlib.Path, + chromedriver_bin: pathlib.Path, + fixture_url: str, + profile_dir: str, +) -> dict[str, Any]: + """Force-close a disposable real context while preserving the WebDriver session.""" + + driver_port = _free_loopback_port() + session_id: str | None = None + driver = subprocess.Popen( + [str(chromedriver_bin), f"--port={driver_port}", "--allowed-ips=127.0.0.1"], + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT, + text=True, + ) + try: + _wait_for_driver(driver_port) + session = _json_request( + driver_port, + "POST", + "/session", + { + "capabilities": { + "alwaysMatch": { + "browserName": "chrome", + "goog:chromeOptions": { + "binary": str(chrome_bin), + "args": [ + "--headless=new", + "--no-first-run", + "--disable-default-apps", + "--disable-component-update", + "--disable-sync", + "--disable-dev-shm-usage", + "--no-sandbox", + "--disable-extensions", + f"--user-data-dir={profile_dir}", + ], + }, + } + } + }, + ).get("value", {}) + if not isinstance(session, dict): + raise RuntimeError("ChromeDriver forced-close session response is malformed") + raw_session_id = session.get("sessionId") + capabilities = session.get("capabilities", {}) + if not isinstance(raw_session_id, str): + raise RuntimeError("ChromeDriver did not return a forced-close session id") + if not isinstance(capabilities, dict): + raise RuntimeError("ChromeDriver forced-close capabilities are malformed") + session_id = _path_token(raw_session_id, "session identifier") + browser_version = capabilities.get("browserVersion") + if browser_version != PINNED_CHROME_VERSION: + raise RuntimeError( + f"unexpected forced-close Chrome version: expected {PINNED_CHROME_VERSION}, " + f"got {browser_version!r}" + ) + + survivor_context = _json_request( + driver_port, + "GET", + _webdriver_path(session_id, "/window"), + ).get("value") + if not isinstance(survivor_context, str): + raise RuntimeError("ChromeDriver did not return the survivor context handle") + survivor_context = _path_token(survivor_context, "window handle") + + created = _json_request( + driver_port, + "POST", + _webdriver_path(session_id, "/window/new"), + {"type": "tab"}, + ).get("value", {}) + if not isinstance(created, dict): + raise RuntimeError("ChromeDriver returned malformed disposable context evidence") + disposable_context = created.get("handle") + if not isinstance(disposable_context, str): + raise RuntimeError("ChromeDriver did not return a disposable context handle") + disposable_context = _path_token(disposable_context, "window handle") + if disposable_context == survivor_context: + raise RuntimeError("ChromeDriver reused the survivor context as disposable context") + + _json_request( + driver_port, + "POST", + _webdriver_path(session_id, "/window"), + {"handle": disposable_context}, + ) + _json_request( + driver_port, + "POST", + _webdriver_path(session_id, "/url"), + {"url": fixture_url}, + ) + loaded_url = _json_request( + driver_port, + "GET", + _webdriver_path(session_id, "/url"), + ).get("value") + if loaded_url != fixture_url: + raise RuntimeError("Agent Task forced-close probe did not load its fixture URL") + + forced_close_detected = _force_close_agent_task_context(driver_port, session_id) + if not forced_close_detected: + raise RuntimeError("Agent Task forced-close probe did not detect the close") + + _json_request( + driver_port, + "POST", + _webdriver_path(session_id, "/window"), + {"handle": survivor_context}, + ) + surviving_url = _json_request( + driver_port, + "GET", + _webdriver_path(session_id, "/url"), + ).get("value") + if not isinstance(surviving_url, str): + raise RuntimeError("Agent Task survivor context was not usable after forced close") + + return { + "browser_version": browser_version, + "forced_close_detected": forced_close_detected, + "session_survived": True, + } + finally: + if session_id is not None: + with contextlib.suppress(Exception): + _json_request( + driver_port, + "DELETE", + _webdriver_path(session_id, ""), + {}, + ) + driver.terminate() + try: + driver.wait(timeout=5) + except subprocess.TimeoutExpired: + driver.kill() + driver.wait(timeout=5) + + +def _run_agent_task_forced_close_trial( + chrome_bin: pathlib.Path, + chromedriver_bin: pathlib.Path, + fixture_url: str, + trial_number: int, +) -> dict[str, Any]: + """Run one forced-close probe and prove its isolated browser profile is removed.""" + + trial_started = time.monotonic() + profile_path: pathlib.Path + with tempfile.TemporaryDirectory( + prefix=f"originweave-agent-task-forced-close-{trial_number}-" + ) as profile_dir: + profile_path = pathlib.Path(profile_dir) + result = _run_agent_task_forced_close_browser_pass( + chrome_bin, + chromedriver_bin, + fixture_url, + profile_dir, + ) + profile_cleaned = not profile_path.exists() + if not profile_cleaned: + raise RuntimeError( + f"Agent Task forced-close profile cleanup failed in trial {trial_number}" + ) + + return { + "trial_number": trial_number, + "passed": True, + "browser_version": result["browser_version"], + "forced_close_detected": result["forced_close_detected"], + "session_survived": result["session_survived"], + "profile_cleaned": profile_cleaned, + "duration_ms": round((time.monotonic() - trial_started) * 1000), + } + + def _start_fixture_server( directory: pathlib.Path, ) -> tuple[http.server.ThreadingHTTPServer, threading.Thread]: @@ -1122,6 +1332,26 @@ def main() -> int: } ) + forced_close_trials: list[dict[str, Any]] = [] + for trial_number in range(1, AGENT_TASK_REPEATABILITY_TRIALS + 1): + try: + forced_close_trials.append( + _run_agent_task_forced_close_trial( + chrome_bin, + chromedriver_bin, + agent_task_url, + trial_number, + ) + ) + except (OSError, ValueError, RuntimeError, json.JSONDecodeError) as exc: + forced_close_trials.append( + { + "trial_number": trial_number, + "passed": False, + "failure_type": type(exc).__name__, + } + ) + agent_task_successful_trials = sum( 1 for trial in agent_task_trials if trial.get("passed") is True ) @@ -1158,6 +1388,16 @@ def main() -> int: for trial in agent_task_trials if trial.get("passed") is True ) + forced_close_successful_trials = sum( + 1 for trial in forced_close_trials if trial.get("passed") is True + ) + forced_close_surfaces_complete = all( + trial.get("forced_close_detected") is True + and trial.get("session_survived") is True + and trial.get("profile_cleaned") is True + for trial in forced_close_trials + if trial.get("passed") is True + ) evidence = { "chrome_version": PINNED_CHROME_VERSION, @@ -1177,6 +1417,11 @@ def main() -> int: "successful_trials": agent_task_successful_trials, "trial_pass_rate": agent_task_trial_pass_rate, "trial_results": agent_task_trials, + "forced_close": { + "repeatability_trials": AGENT_TASK_REPEATABILITY_TRIALS, + "successful_trials": forced_close_successful_trials, + "trial_results": forced_close_trials, + }, }, "duration_ms": round((time.monotonic() - started) * 1000), } @@ -1196,6 +1441,15 @@ def main() -> int: ) if not agent_task_surfaces_complete: raise RuntimeError("Agent Task repeatability surfaces were incomplete") + if ( + forced_close_successful_trials != AGENT_TASK_REPEATABILITY_TRIALS + or not forced_close_surfaces_complete + ): + raise RuntimeError( + "Agent Task forced-close recovery gate failed: " + f"{forced_close_successful_trials}/{AGENT_TASK_REPEATABILITY_TRIALS} " + "trials passed" + ) return 0 finally: _stop_fixture_server(agent_task_server, agent_task_thread) From 34a29faf05794736ea3bfc6d15961a5e23a61ec8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 05:57:24 +0900 Subject: [PATCH 4/6] test(browser): reject forced-close substring false positives --- .../test_agent_task_forced_close_contract.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test_agent_task_forced_close_contract.py b/tests/test_agent_task_forced_close_contract.py index ebd25fea0..8b6507f70 100644 --- a/tests/test_agent_task_forced_close_contract.py +++ b/tests/test_agent_task_forced_close_contract.py @@ -122,6 +122,32 @@ def wrong_failure_request( with self.assertRaisesRegex(RuntimeError, "timeout"): force_close(4444, "session-a") + def test_forced_close_rejects_untyped_error_containing_no_such_window_text(self) -> None: + """Incidental error text must not be promoted into structured close evidence.""" + + force_close = runpy.run_path( + str(RUNNER), run_name="agent_task_forced_close_spoofed_text" + )["_force_close_agent_task_context"] + + def spoofed_text_request( + _driver_port: int, + method: str, + path: str, + _payload: object | None = None, + **_kwargs: object, + ) -> dict[str, object]: + if method == "DELETE" and path.endswith("/window"): + return {"value": ["survivor-context"]} + if method == "GET" and path.endswith("/url"): + raise RuntimeError( + "WebDriver transport timeout; diagnostic contained: no such window" + ) + raise AssertionError(f"unexpected request: {method} {path}") + + force_close.__globals__["_json_request"] = spoofed_text_request + with self.assertRaisesRegex(RuntimeError, "transport timeout"): + force_close(4444, "session-a") + def test_browser_pass_creates_and_selects_disposable_context(self) -> None: """The real probe must preserve a session by closing only a second context.""" From 5a352a05ade9e902e6729bcb40ad37b69b485db3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 06:09:19 +0900 Subject: [PATCH 5/6] fix(browser): require structured forced-close error evidence --- scripts/ci/run_mv3_compatibility.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/scripts/ci/run_mv3_compatibility.py b/scripts/ci/run_mv3_compatibility.py index aeca4ae07..615f4b260 100755 --- a/scripts/ci/run_mv3_compatibility.py +++ b/scripts/ci/run_mv3_compatibility.py @@ -1010,6 +1010,28 @@ def _run_agent_task_trial( } +def _is_no_such_window_runtime_error(error: RuntimeError) -> bool: + """Recognize only structured ChromeDriver no-such-window failure evidence.""" + + message = str(error) + direct_prefix = "WebDriver error: " + if message.startswith(direct_prefix): + code, separator, _detail = message[len(direct_prefix) :].partition(":") + return bool(separator) and code.strip().casefold() == "no such window" + + http_prefix = "WebDriver HTTP 404: " + if not message.startswith(http_prefix): + return False + try: + payload = json.loads(message[len(http_prefix) :]) + except json.JSONDecodeError: + return False + if not isinstance(payload, dict): + return False + value = payload.get("value") + return isinstance(value, dict) and value.get("error") == "no such window" + + def _force_close_agent_task_context(driver_port: int, session_id: str) -> bool: """Close only the current browsing context and require no-such-window evidence.""" @@ -1033,7 +1055,7 @@ def _force_close_agent_task_context(driver_port: int, session_id: str) -> bool: _webdriver_path(session_id, "/url"), ) except RuntimeError as exc: - if "no such window" in str(exc).lower(): + if _is_no_such_window_runtime_error(exc): return True raise raise RuntimeError("Agent Task context remained usable after forced close") From 4f342b8d0b6a0e4ca8bd838e5dacc78a342fb513 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 19:48:09 +0900 Subject: [PATCH 6/6] docs: record forced-close recovery evidence --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c479367bf..19a46ff3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ All notable changes to OriginWeave are documented in this file. The format follo - Rust 1.97.1 build contract, strict Clippy and rustdoc gates, and exact production function, line, region, and branch coverage enforcement. - Pinned Chrome-for-Testing Agent Task evidence now captures a bounded sampled Chromium root-plus-descendant process count and RSS total from one `/proc` status sweep, with bounded failure-type diagnostics while preserving the root-only metric and making no trusted per-task attribution claim. - Pinned Chrome-for-Testing Agent Task evidence now locates the controlled result by exact browser-computed `status`/`Task result` semantics and records only a bounded canonical SHA-256 digest plus stable field identity for the extracted synthetic value, without emitting the raw value. +- Pinned Chrome-for-Testing Agent Task semantic-observation evidence is now canonicalized as compact sorted-key UTF-8 JSON and capped at 4,096 bytes, accepting the exact limit while failing closed on empty, non-object, or oversized observations before they enter successful trial evidence. +- Pinned Chrome-for-Testing forced-close recovery evidence now closes a disposable controlled browsing context, requires structured exact `no such window` failure on the next current-context command, proves a separate survivor context remains usable in the same WebDriver session, repeats the probe with isolated profile cleanup, and fails closed on substring lookalikes or malformed error framing. - Hourly bounded OpenCode product-development workflow using `NVIDIA_NIM_API_KEY`, an unprivileged disposable workspace, loopback-only model broker, independently verified patches, and publication through a dedicated `OPENCODE_PR_TOKEN` that cannot review or merge. - Architecture, agent, security, contribution, research, database naming, roadmap, quality-gate, and TLS service-identity ADR documentation. - Authoritative product documentation graph spanning PRD, TRD, ADR lifecycle/index, product-wide UML, conceptual ERD, requirement/decision traceability, threat modeling, product-wide test strategy, operability, API/protocol, release/rollback, and current primary-source standards doctoring, with machine-checkable repository contracts that keep conversation-derived future work distinct from protected-main implementation claims.