Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ All notable changes to OriginWeave are documented in this file. The format follo

## [Unreleased]

- Integrated the sandbox-preserving browser-crash parent into ordinary shared-deadline teardown; all four controlled browser lanes retain the sandbox requirement and the existing cleanup-evidence checks. Real pinned-browser startup remains unaccepted until the workflow prerequisite is repaired and verified.
- Integrated the current cleanup-evidence prerequisites into ordinary shared-deadline teardown and kept inherited failure tests on the actual observation path; incomplete process sampling still cannot become complete cleanup evidence.
- Browser-crash trials no longer disable Chromium's sandbox. Rejected session startup remains one failed attempt with driver and temporary-profile cleanup, without an unsandboxed retry; live pinned-browser acceptance is still required.
- Browser-crash failure evidence now preserves the first causal browser failure when session cleanup or ChromeDriver teardown also fails, retains secondary cleanup only as bounded exception-type fields, and excludes raw exception text from the emitted artifact. Cleanup-only failures remain fail-closed as primary failures.

Expand All @@ -13,6 +15,8 @@ All notable changes to OriginWeave are documented in this file. The format follo
- Controlled browser trials reject malformed pre-shutdown exit counts, retain validated driver and cleanup outcomes after ordinary task failure, and report mid-request protocol failures without remote diagnostic text; failed trials remain failures, startup retries keep their existing narrow scope, and obsolete HTTP-body close signals are no longer accepted.
- Failed controlled Agent Task runs now report whether their original browser process ended after shutdown, alongside temporary-profile cleanup; a failed task never becomes a pass merely because cleanup succeeded. If process observation itself fails, termination remains unproven. This covers the original browser process only, not all descendants or arbitrary browser recovery.
- Controlled pinned-Chromium Agent Task success now binds the ChromeDriver browser root to its exact Linux `/proc/<pid>/stat` start-time identity, binds every still-live PID from the already sampled bounded Chromium root-plus-descendant set before shutdown, explicitly records descendants that already exited between the `/proc` lineage snapshot and identity capture, and fails closed unless every retained exact identity terminates after session/driver shutdown; root disappearance or identity change remains an error, PID reuse counts only as termination of the original identity, and this does not attest cgroup/task ownership, processes appearing only after the sample, or OS-wide orphan absence.
- The controlled forced-close Agent Task recovery probe now binds the ChromeDriver browser root and its already sampled bounded Chromium descendant set to exact Linux PID/start-time identities before forcing the disposable context closed, and successful recovery is accepted only after session/driver shutdown proves root and sampled-set termination under one shared bounded monotonic deadline; this remains bounded compatibility evidence and does not attest cgroup ownership, post-snapshot processes, cross-platform supervision, or OS-wide orphan absence.
- Controlled ordinary Agent Task teardown now observes the exact browser root and already sampled Chromium process set under one shared bounded monotonic deadline after session/driver shutdown, preserving separate root/process-set termination evidence and retaining `None` when no full process set was captured; this prevents sequential waiters from extending a nominal teardown budget while making no claim about cgroup ownership, post-snapshot processes, cross-platform supervision, or OS-wide orphan absence.
- Failed ordinary and forced-close Agent Task browser trials now retain credential-free temporary-profile cleanup evidence after bounded browser errors, and separate aggregate compatibility gates require cleanup proof from every trial rather than filtering unsuccessful trials out; this does not attest adversarial filesystem erasure, process termination, or arbitrary browser recovery.
- Failed Manifest V3 restart trials now retain credential-free temporary-profile cleanup evidence after bounded browser errors, including reviewed ChromeDriver process-teardown `TimeoutExpired` failures; successful trials record the same cleanup fact, and an aggregate compatibility gate requires teardown proof from every MV3 trial before repeatability acceptance without retaining exception messages or command paths; this does not attest adversarial filesystem erasure, browser-process termination, or cleanup outside the controlled temporary profile.
- Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules.
Expand Down
14 changes: 14 additions & 0 deletions docs/doctoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ At #147 `af1b98ba377c73b88baa9633e2232e7f76e76f36`, all six parent review-eviden

The existing test now specifies each lane's actual observer, return value and exact arguments. Ordinary trials keep the individual `False` result. Forced-close trials inject `(False, False)` from the shared observer and require `(321, 654, ((321, 654),))`, preserving root-only capture rather than inventing a complete descendant set. Every existing failed-trial, profile-cleanup, live-root, driver-termination, error-type and redaction assertion remains. No production code, deadline, retry, failed denominator or coverage gate changes. This repair does not address the separate ordinary-pass two-deadline finding or prove real Linux/pinned-Chromium acceptance.

### Ordinary shared-deadline parent integration

PR #150 ordinarily adopts #147 `3dff28d9bf2dd27b72507e39979d51b8bf140fb4` while retaining its existing ordinary shared-deadline production delta and two child-owned tests from `c208668ce3aa079059acc1016646ed7240797ac0`. Before integration, discovery collected none of the six current-parent review-evidence methods. After their restoration, two protocol-fault subcases failed because the inherited ordinary-lane test still injected the individual root waiter; the full 229-test suite found three more cases with the same stale injection in the failure-process test.

Both existing tests now target the actual shared observer, return paired root/set outcomes and require the exact root-only identity tuple when capture fails early. Every failure, cleanup, error-type and diagnostic-redaction assertion remains. An additional assertion forbids inventing complete sampled-set evidence from root-only capture. Production deadlines, polling, success predicates, retry rules and failed-trial denominators are unchanged by this reconciliation. The parent's four deterministic shared-observer tests and the child's ordinary call-order contract remain independently executable. Passing injected observations does not prove live Chromium teardown, cgroup ownership or OS-wide orphan absence; the #147 ordinary-wait finding remains unresolved on that parent until the #150 delta is actually integrated there.

### Browser-crash sandbox conformance

PR #148 adopts the current #147 parent `3dff28d9bf2dd27b72507e39979d51b8bf140fb4` by ordinary merge, retaining the crash/pidfd delta and all six inherited cleanup-evidence regressions. A new regression executes the actual outer crash trial and intercepts its session-start request: before the repair it fails because that request disables the Chromium sandbox. Removing that one crash-lane argument restores the existing sandbox invariant without introducing a configuration switch or fallback path.
Expand All @@ -132,6 +138,14 @@ The same regression requires one failed startup attempt, driver reaping and actu

Supplemental verification used the existing Colima Linux kernel `6.8.0-117-generic` and Python `3.12.3`, without installing dependencies or changing VM configuration. All 247 Python contracts execute there with no skips, including the three real pidfd cases skipped on macOS: killed-but-unreaped child, non-terminating signal and stale identity. Initial host-path discovery failed because this VM does not expose the host worktree. The first streamed archive then added AppleDouble `._*.rs` files, causing two TLS source-read errors. A fresh export of only Git-tracked paths with `COPYFILE_DISABLE=1 tar --no-xattrs --no-acls --no-fflags` removes that packaging artifact at its producer; no test filter or source exception was added. Source and regression-file SHA-256 hashes match across hosts. This proves the supplemental Linux contracts, not pinned-Chromium execution, hosted approval or release acceptance.

### Ordinary shared-deadline adoption of the sandbox-preserving parent

On September 6, 2026, #150 predecessor `d3c29359fd10e540ca9b3b2723bde6f94866cdb6` replayed the unchanged four-lane sandbox contract from #148 `0135984f1bc1f68d89d7777f49c4999474105a12`. It failed on three sandbox-disabled launch paths and the absent browser-crash lane. RED commit `f0b2ab90` records that test before the ordinary parent merge. The merge retains every parent production change and adds only #150's existing ordinary shared-deadline production delta; its original child regressions and the reconciled inherited failure injections remain intact. Root-only observation still cannot establish complete sampled-process cleanup.

This integration supersedes the earlier crash-only scope note above: all four launch lanes now omit the sandbox-disabling argument. It does not supersede the parent's runtime failure. Exact-parent MV3 run `33990522248`, job `101371812631`, artifact `9977680352` (SHA-256 `5430103b6bae59acc0d62d3c21cce559b27a15eadc6f049a857551d9a92ef643`) reports zero successful trials out of three in each of the ordinary MV3, ordinary Agent Task, forced-close and browser-crash lanes. Crash trials fail during session creation; temporary-profile cleanup does not establish successful browser startup. Issue #212 owns the canonical sandbox-helper workflow prerequisite. No workflow, browser pin, retry, deadline, acceptance predicate or denominator is changed here, and #150 remains Draft. Its predecessor's successful unsandboxed run cannot be transferred to this source tree. The #147 ordinary-deadline review finding remains open until this successor delta is actually integrated there.

The combined source passes all 253 Python contracts on existing Colima Linux `6.8.0-117-generic` / Python `3.12.3`, with no skips or dependency installation. macOS discovery runs the same 253 contracts and skips exactly the three Linux-only pidfd cases, which are covered by that Linux execution rather than counted as macOS passes. Full Rust `1.97.1` formatting, check, workspace tests, strict Clippy and rustdoc pass. Pinned `nightly-2026-08-01` measures 415/415 functions, 3555/3555 lines, 4444/4444 regions and 476/476 branches; the verifier accepts all four metrics at 100%. The separate `warning: --branch option is unstable` is retained. These are local source and contract results, not a replacement for fresh hosted checks or real pinned-browser acceptance.

## References

Amazon Web Services. (n.d.). *Set up the Amazon EKS Pod Identity Agent*. Retrieved August 6, 2026, from https://docs.aws.amazon.com/eks/latest/userguide/pod-id-agent-setup.html
Expand Down
21 changes: 14 additions & 7 deletions scripts/ci/run_mv3_compatibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -1565,15 +1565,22 @@ def _run_agent_task_browser_pass(

if browser_process_id is None or browser_process_start_time_ticks is None:
raise RuntimeError("Agent Task browser process identity was not captured")
browser_process_terminated = _wait_for_linux_process_identity_exit(
browser_process_id,
browser_process_start_time_ticks,
full_process_set_captured = chromium_process_identities is not None
teardown_identities = (
chromium_process_identities
if chromium_process_identities is not None
else ((browser_process_id, browser_process_start_time_ticks),)
)
chromium_process_set_terminated: bool | None = None
if chromium_process_identities is not None:
chromium_process_set_terminated = _wait_for_linux_process_identity_set_exit(
chromium_process_identities
browser_process_terminated, observed_process_set_terminated = (
_wait_for_linux_process_teardown(
browser_process_id,
browser_process_start_time_ticks,
teardown_identities,
)
)
chromium_process_set_terminated: bool | None = (
observed_process_set_terminated if full_process_set_captured else None
)
Comment thread
seonghobae marked this conversation as resolved.
if (
browser_failure_type is not None
or session_cleanup_failure_type is not None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ def test_browser_pass_retains_sampled_process_set_teardown_after_failure(self) -
end = runner.index("\ndef _run_agent_task_trial(", start)
browser_pass = runner[start:end]
for expected in (
"if chromium_process_identities is not None:",
"chromium_process_set_terminated = _wait_for_linux_process_identity_set_exit(",
"full_process_set_captured = chromium_process_identities is not None",
"_wait_for_linux_process_teardown(",
"observed_process_set_terminated if full_process_set_captured else None",
'failure_evidence["chromium_process_set_terminated"]',
):
with self.subTest(expected=expected):
Expand Down Expand Up @@ -91,4 +92,4 @@ def fail_before_process_set_capture(


if __name__ == "__main__":
unittest.main()
unittest.main()
7 changes: 4 additions & 3 deletions tests/test_agent_task_failure_process_termination_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ def request(_port, method, target, *_args):
raise RuntimeError("private controlled browser failure")
return {}

exit_wait = mock.Mock(return_value=exit_observation)
exit_wait = mock.Mock(return_value=(exit_observation, exit_observation))
if isinstance(exit_observation, Exception):
exit_wait.side_effect = exit_observation
replacements = {
"_free_loopback_port": lambda: 12345,
"_wait_for_driver": lambda _port: None,
"_json_request": request,
"_read_linux_proc_stat_process_identity": lambda _pid: (321, 654),
"_wait_for_linux_process_identity_exit": exit_wait,
"_wait_for_linux_process_teardown": exit_wait,
}
with mock.patch.dict(browser_pass.__globals__, replacements), mock.patch.object(
namespace["subprocess"], "Popen", return_value=driver
Expand All @@ -82,9 +82,10 @@ def request(_port, method, target, *_args):

driver.terminate.assert_called_once_with()
driver.wait.assert_called_once_with(timeout=5)
exit_wait.assert_called_once_with(321, 654)
exit_wait.assert_called_once_with(321, 654, ((321, 654),))
self.assertIs(result["passed"], False)
self.assertIs(result["profile_cleaned"], True)
self.assertNotIn("chromium_process_set_terminated", result)
self.assertNotIn("private controlled browser failure", repr(result))
if isinstance(exit_observation, Exception):
self.assertEqual(result["failure_type"], "PermissionError")
Expand Down
5 changes: 4 additions & 1 deletion tests/test_agent_task_review_evidence_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ def test_protocol_faults_keep_real_browser_cleanup_evidence(self) -> None:
"""Mid-pass protocol faults stay terminal and preserve observed root cleanup."""

for lane, waiter_name, observed_exit, expected_args in (
("agent_task", "_wait_for_linux_process_identity_exit", False, (321, 654)),
(
"agent_task", "_wait_for_linux_process_teardown",
(False, False), (321, 654, ((321, 654),)),
),
(
"agent_task_forced_close", "_wait_for_linux_process_teardown",
(False, False), (321, 654, ((321, 654),)),
Expand Down
37 changes: 37 additions & 0 deletions tests/test_agent_task_shared_teardown_deadline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Contract for one total post-shutdown teardown deadline in the ordinary Agent Task lane."""

from __future__ import annotations

import pathlib
import unittest

ROOT = pathlib.Path(__file__).resolve().parents[1]
RUNNER = ROOT / "scripts" / "ci" / "run_mv3_compatibility.py"


class AgentTaskSharedTeardownDeadlineContractTests(unittest.TestCase):
"""Prevent ordinary Agent Task teardown polling from multiplying the budget."""

def test_browser_pass_uses_only_the_combined_teardown_waiter(self) -> None:
"""Root and sampled-set evidence must share one timeout authority after shutdown."""

runner = RUNNER.read_text(encoding="utf-8")
start = runner.index("def _run_agent_task_browser_pass(")
end = runner.index("\ndef _run_agent_task_trial(", start)
browser_pass = runner[start:end]

self.assertIn("_wait_for_linux_process_teardown(", browser_pass)
self.assertNotIn("_wait_for_linux_process_identity_exit(", browser_pass)
self.assertNotIn("_wait_for_linux_process_identity_set_exit(", browser_pass)

shutdown = browser_pass.index("_terminate_owned_process_bounded(")
teardown_wait = browser_pass.index("_wait_for_linux_process_teardown(")
failure_return = browser_pass.index(
"or driver_cleanup_failure_type is not None"
)
self.assertLess(shutdown, teardown_wait)
self.assertLess(teardown_wait, failure_return)


if __name__ == "__main__":
unittest.main()
Loading