commit e02e759eba7d2a706e7863cd834dae4a24f311c6
parent 986fa89e553578f3686d83a5fda9f502a7fd1eff
Author: triesap <tyson@radroots.org>
Date: Wed, 23 Sep 2026 18:24:33 +0000
H007: restrict expected-close acceptance to real peer-close classes
- Require the declared expected cause to be a peer close, not a timeout.
- Derive the observed phase from the write step actually attempted.
- Rename the refused-connection control to match its characterized outcome.
- Add a control proving a declared write timeout is still rejected.
Diffstat:
2 files changed, 59 insertions(+), 8 deletions(-)
diff --git a/tests/strict_fixture.mojo b/tests/strict_fixture.mojo
@@ -639,6 +639,16 @@ def classify_write_error_cause(text: String) -> String:
return "unrelated_error"
+def is_peer_close_cause(cause: String) -> Bool:
+ """True only for the bounded peer-close classes a script may expect.
+
+ A declaration is structurally restricted to an actual peer close, so a
+ write timeout, an invalid descriptor or an unrelated handler error can
+ never be waived by naming it as the expected cause (ADR-0020 TC01).
+ """
+ return cause == "peer_reset" or cause == "broken_pipe"
+
+
def serve_scripts(
listener: TcpListener, var scripts: List[ExchangeScript], label: String
) raises -> ServeReport:
@@ -707,10 +717,7 @@ def serve_scripts(
request_count = next_index
if script.delay_ms > 0:
usleep(script.delay_ms * 1000)
- var phase = (
- "body_stall" if script.stall_after_head_ms
- > 0 else "delayed_write"
- )
+ var phase = "delayed_write"
try:
if script.stall_after_head_ms > 0:
# Headers-then-stall control (H007): write the complete
@@ -718,7 +725,10 @@ def serve_scripts(
# the declared bounded stall, then attempt the body. This
# separates a body-read stall from a connection timeout
# and from the overall budget using a bounded fixture
- # delay that never hangs the owning test.
+ # delay that never hangs the owning test. ``phase`` is
+ # the write step actually being attempted, not a script
+ # label, so a head-write failure is not reported as a
+ # body stall.
var rendered = render_response(
script,
framed.headers_raw,
@@ -728,14 +738,18 @@ def serve_scripts(
var separator = rendered.find("\r\n\r\n")
if separator >= 0:
var head_end = separator + 4
+ phase = "head_write"
reader.write_all(String(rendered[byte=0:head_end]))
+ phase = "body_stall"
usleep(script.stall_after_head_ms * 1000)
if script.inject_write_error != "":
raise Error(script.inject_write_error)
reader.write_all(String(rendered[byte=head_end:]))
else:
+ phase = "head_write"
reader.write_all(rendered)
else:
+ phase = "delayed_write"
if script.inject_write_error != "":
raise Error(script.inject_write_error)
reader.write_all(
@@ -752,10 +766,14 @@ def serve_scripts(
# whose exact bounded cause and phase match is accepted;
# unexpected/wrong-phase closes, write timeouts, invalid
# descriptors and unrelated handler errors fail the fixture
- # with a bounded, cause-specific reason.
+ # with a bounded, cause-specific reason. The declared cause
+ # is itself restricted to a real peer-close class, so a
+ # timeout or unrelated error cannot be waived by declaring
+ # it as the expected cause.
var observed = classify_write_error_cause(String(e))
if (
script.expect_peer_close
+ and is_peer_close_cause(script.expected_close_cause)
and observed == script.expected_close_cause
and phase == script.expected_close_phase
):
diff --git a/tests/test_provider_adapter.mojo b/tests/test_provider_adapter.mojo
@@ -393,9 +393,10 @@ def _bounded_timeout_provider_config(
)
-def test_provider_connect_timeout_is_bounded_and_specific() raises:
+def test_provider_refused_connection_is_bounded_and_specific() raises:
# H007: a refused connection is a bounded, cause-specific transport failure,
- # not a hang. No provider client policy is changed here.
+ # not a hang. It is explicitly characterized as a refusal, not as a real
+ # connect-timeout scenario, and no provider client policy is changed here.
var guard = CleanupGuard()
var dead_port = reserve_loopback_port()
var config = _bounded_timeout_provider_config(dead_port, 300)
@@ -700,6 +701,38 @@ def test_provider_scripted_injected_write_error_fails() raises:
guard.assert_clean()
+def test_provider_scripted_declared_non_peer_cause_is_rejected() raises:
+ # TC01: the declared expected cause is restricted to a real peer-close class,
+ # so a write timeout can never be waived by declaring it as expected.
+ var scripts = List[ExchangeScript]()
+ var script = exchange_script(
+ "declared_timeout",
+ "POST",
+ "/v1/chat/completions",
+ 200,
+ '{"choices":[]}',
+ )
+ script.stall_after_head_ms = 200
+ script.expect_peer_close = True
+ script.expected_close_cause = "write_timeout"
+ script.expected_close_phase = "body_stall"
+ script.inject_write_error = "Timeout"
+ scripts.append(script^)
+ var guard = CleanupGuard()
+ with spawn_max_local_scripted(0, scripts^, guard) as provider_stub:
+ _ = _raw_send_and_read(provider_stub.port, "/v1/chat/completions")
+ provider_stub.reap()
+ assert_true(not provider_stub.ok())
+ assert_equal(provider_stub.phase(), "peer_close")
+ assert_true(
+ provider_stub.reason().find(
+ "unexpected_write_write_timeout_body_stall"
+ )
+ >= 0
+ )
+ guard.assert_clean()
+
+
def test_provider_stalled_call_is_parent_bounded() raises:
# TC02: a real risky client call against a stalling peer runs under the
# parent deadline and is stopped/reaped by the parent instead of hanging.