fix(runtime): reconcile lifecycle CI contracts

- Regenerate website/configuration-reference.md: Wave 5B moved the
  ODYSSEUS_BROWSER_SCREENSHOT_DIR read in web_tools.py (3458 -> 3479).
- Give the Chrome sweep regression fixture a real process identity (stat
  start time, boot id, process_ownership.PROC_ROOT). The sweep now signals
  only verified identities; the old cmdline-only fixture borrowed the
  identity of whatever real process held pid 101 on the host, so it passed
  or failed depending on the machine.
- Import pytest in test_workspace_artifact_tool_floor.py: its existing
  bubblewrap capability skip raised NameError on hosts without functional
  namespaces.

No production code changes. Required containment still fails closed.
This commit is contained in:
Alexandre Teixeira
2026-10-02 02:23:07 +01:00
parent b4b5412cda
commit 1bf45b9fed
3 changed files with 23 additions and 2 deletions
+20 -1
View File
@@ -243,6 +243,9 @@ def test_native_terminal_runtime_adds_offered_tools_deliberately():
# belonging to the user, or to another worktree, must survive it.
def test_chrome_sweep_kills_only_this_runtimes_profile(monkeypatch, tmp_path):
import shutil
from src import process_ownership
from src.agent_tools.web_tools import PrivateBrowserTool
proc = tmp_path / "proc"
@@ -250,9 +253,19 @@ def test_chrome_sweep_kills_only_this_runtimes_profile(monkeypatch, tmp_path):
tmpdir.mkdir()
ours = str(tmpdir.resolve() / "agent-browser-chrome-")
# A fake process needs an identity, not only a command line: the sweep
# signals a process only after verifying the start token it matched on.
# Without a stat here the token would be read from the host's real /proc,
# so the outcome would depend on whether this pid happens to exist.
boot = proc / "sys/kernel/random/boot_id"
boot.parent.mkdir(parents=True)
boot.write_text("fake-boot\n")
def _pid(pid, cmdline):
entry = proc / pid
entry.mkdir(parents=True)
starttime = " ".join(["0"] * 15 + [str(1000 + int(pid))])
(entry / "stat").write_text(f"{pid} (chrome) S 1 {pid} {pid} {starttime}")
(entry / "cmdline").write_bytes(cmdline.replace(" ", "\0").encode())
_pid("101", f"chrome --user-data-dir={ours}session-a")
@@ -261,8 +274,14 @@ def test_chrome_sweep_kills_only_this_runtimes_profile(monkeypatch, tmp_path):
(proc / "self").mkdir()
monkeypatch.setattr(platform_compat, "PROC_ROOT", proc)
monkeypatch.setattr(process_ownership, "PROC_ROOT", proc)
killed = []
monkeypatch.setattr(al_web.os, "kill", lambda pid, sig: killed.append(pid))
def _kill(pid, sig):
killed.append(pid)
shutil.rmtree(proc / str(pid), ignore_errors=True)
monkeypatch.setattr(al_web.os, "kill", _kill)
PrivateBrowserTool._terminate_owned_chrome({"TMPDIR": str(tmpdir)})
@@ -1,5 +1,7 @@
from pathlib import Path
import pytest
def test_unoffered_artifact_recovery_is_bounded():
from src.agent_loop import _artifact_unoffered_recovery_exhausted
+1 -1
View File
@@ -90,7 +90,7 @@ The source tree reads **109** `ODYSSEUS_*` variables: 79 an operator may want to
| `ODYSSEUS_BROWSER_MCP_REQUIRE_CACHE` | `''` | `src/builtin_mcp.py:90` | Truthy refuses to start the browser MCP server unless its npm package is already in the npx cache, instead of installing it at startup. |
| `ODYSSEUS_BROWSER_NAMESPACE` | `'odysseus-ui'` | `src/agent_tools/web_tools.py:100` (+3 more) | Namespace for the detached agent-browser daemon's pid files, so two runtimes on one machine do not terminate each other's browsers. |
| `ODYSSEUS_BROWSER_NO_SANDBOX` | `'1'` | `src/builtin_mcp.py:142` | Security-relevant. On by default, adding `--no-sandbox` because the Docker image cannot use the Chromium sandbox. Set 0, false or no to keep it. |
| `ODYSSEUS_BROWSER_SCREENSHOT_DIR` | *unset* | `src/agent_tools/web_tools.py:3458` | Where private-browser screenshots are written. Falls back to the container path, then the system temp directory. |
| `ODYSSEUS_BROWSER_SCREENSHOT_DIR` | *unset* | `src/agent_tools/web_tools.py:3479` | Where private-browser screenshots are written. Falls back to the container path, then the system temp directory. |
### Container and workspace mounts