fix(tests): failure-writer ownership probe no longer races a real tirith download

The probe scrubs the environment down to PATH/LANG/LC_ALL/TZ, which also
dropped the harness's HERMES_DISABLE_LAZY_INSTALLS. In a fresh home with no
tirith on PATH (CI), GatewayRunner() then starts tirith's startup install on a
background thread: the PM runtime downloads and unpacks Python and uv from
GitHub while the cases run. When that unpacking overlaps the archive case, its
buffers land in the process-wide tracemalloc window and push the peak past the
8 MiB bound, so the probe reports 19/20.

The loopback fence also missed it: hermes_bootstrap's happy-eyeballs
create_connection dials with connect_ex, so the downloads reached GitHub with
blocked_external_attempts empty.

- set HERMES_DISABLE_LAZY_INSTALLS=1 in the probe environment
- fence socket.connect_ex as well as connect
- assert the receipt records no external network attempts
This commit is contained in:
teknium1
2026-10-02 03:56:08 -07:00
committed by Teknium
parent 0cdba5ec7e
commit 02fc8f7de7
3 changed files with 29 additions and 9 deletions

View File

@@ -11,8 +11,10 @@ scripts/run_tests.sh -j 1 tests/gateway/test_failure_writer_ownership.py
The first argument selects the production checkout, so the same fixture can A/B
another checkout without editing it. The receipt records its temporary HERMES_HOME.
Inherited credentials are cleared, tools are disabled, and socket connections are
restricted to loopback. No live messaging account or paid provider is contacted.
Inherited credentials are cleared, tools are disabled, lazy installs are off (so
GatewayRunner does not start tirith's background download), and socket connections
(`connect` and `connect_ex`) are restricted to loopback. No live messaging account or
paid provider is contacted.
## Fault boundaries and controls

View File

@@ -1,4 +1,4 @@
import os, sys, json, asyncio, threading, tempfile, sqlite3, socket, subprocess, tracemalloc
import os, sys, json, asyncio, threading, tempfile, sqlite3, socket, subprocess, tracemalloc, errno
from pathlib import Path
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
from types import SimpleNamespace
@@ -14,6 +14,11 @@ os.environ.update(
HOME=str(HOME),
HERMES_HOME=str(HOME),
HERMES_DISABLE_PLUGINS="1",
# The scrub above drops the test harness's hermetic switch. Without it, GatewayRunner()
# in this fresh home starts tirith's startup install on a background thread (PM runtime,
# Python and uv downloaded and unpacked while the cases run) and its buffers land in the
# process-wide tracemalloc window the archive case measures.
HERMES_DISABLE_LAZY_INSTALLS="1",
NO_PROXY="127.0.0.1,localhost",
)
sys.path.insert(0, str(ROOT))
@@ -21,23 +26,33 @@ os.chdir(HOME)
(HOME / "config.yaml").write_text(
"model:\n provider: openai-compat\n default: fixture-model\n context_length: 131072\nagent:\n max_iterations: 2\ncompression:\n enabled: false\ndatabase:\n journal_mode: delete\n"
)
# Fence all network calls to loopback, including optional discovery/aux paths.
# Fence all network calls to loopback, including optional discovery/aux paths. Both entry
# points: hermes_bootstrap's happy-eyeballs socket.create_connection dials with connect_ex.
orig_connect = socket.socket.connect
orig_connect_ex = socket.socket.connect_ex
blocked = []
def _external(address):
return isinstance(address, tuple) and address[0] not in ("127.0.0.1", "::1", "localhost")
def local_connect(self, address):
if isinstance(address, tuple) and address[0] not in (
"127.0.0.1",
"::1",
"localhost",
):
if _external(address):
blocked.append(str(address))
raise OSError("fixture forbids external network")
return orig_connect(self, address)
def local_connect_ex(self, address):
if _external(address):
blocked.append(str(address))
return errno.ENETUNREACH
return orig_connect_ex(self, address)
socket.socket.connect = local_connect
socket.socket.connect_ex = local_connect_ex
requests = []

View File

@@ -26,6 +26,9 @@ def test_gateway_failure_writer_preserves_accepted_turn_identity(tmp_path):
assert receipt.exists(), result.stdout + result.stderr
data = json.loads(receipt.read_text())
assert all(row["reached"] for row in data["observations"]), data
# A startup lazy install (tirith → PM runtime) would download over the real network on a
# background thread and pollute the archive case's process-wide tracemalloc window.
assert data["blocked_external_attempts"] == [], data["blocked_external_attempts"]
assert data["passed"] == data["total"], data["observations"]
assert result.returncode == 0, result.stdout + result.stderr