hyf

Context-aware query service for Radroots
git clone https://radroots.dev/git/hyf.git
Log | Files | Refs | README | LICENSE

commit 0eedd94057364a3cd78b6c64b44e1cc4f6d0abf0
parent c6dc1b4749752cbd8732171d5114bc1a384fc3a8
Author: triesap <tyson@radroots.org>
Date:   Wed, 23 Sep 2026 16:37:27 +0000

test: characterize provider connect, stall and budget timeout semantics

H007 on the current client, test-only; no provider client policy change.

- Extend the strict fixture with a bounded headers-then-stall control:
  `ExchangeScript.stall_after_head_ms` writes the response head, holds the
  connection open for the declared bounded stall, then attempts the body. A
  deliberate delay/stall peer close is tolerated so the control cannot fail the
  fixture.
- Characterize the current gaps with non-hanging controls: a fast refused
  connection is transport/unknown_transport (not distinguished from an unknown
  transport error); a headers-then-stall body and a delayed head are NOT bounded
  by the declared request timeout and are still returned after the stall
  completes, for both the MaxLocal and Jev clients.
- Add deterministic runtime budget-unit boundary controls (no per-stage reset,
  exact boundary, non-negative remaining) alongside the existing shared-budget
  test.

Affected lanes: test-provider-adapter 21/21, test-jev 20/20, test-runtime 16/16,
test-provider-helpers, test-repo-local-process, test-journeys, test-measurement,
check-build, check-format and test-architecture pass; test-stdio keeps the exact
24 D16 signatures.

Diffstat:
Mtests/strict_fixture.mojo | 49++++++++++++++++++++++++++++++++++++++++++-------
Mtests/test_jev.mojo | 33+++++++++++++++++++++++++++++++++
Mtests/test_provider_adapter.mojo | 112++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mtests/test_runtime_paths.mojo | 24++++++++++++++++++++++++
4 files changed, 210 insertions(+), 8 deletions(-)

diff --git a/tests/strict_fixture.mojo b/tests/strict_fixture.mojo @@ -467,6 +467,7 @@ struct ExchangeScript(Copyable, Movable): var response_body: String var raw_response: String var delay_ms: Int + var stall_after_head_ms: Int var close_connection: Bool var echo_authorization: Bool @@ -483,6 +484,7 @@ struct ExchangeScript(Copyable, Movable): self.response_body = existing.response_body self.raw_response = existing.raw_response self.delay_ms = existing.delay_ms + self.stall_after_head_ms = existing.stall_after_head_ms self.close_connection = existing.close_connection self.echo_authorization = existing.echo_authorization @@ -507,6 +509,7 @@ def exchange_script( response_body=body, raw_response="", delay_ms=0, + stall_after_head_ms=0, close_connection=True, echo_authorization=False, ) @@ -674,14 +677,46 @@ def serve_scripts( request_count = next_index if script.delay_ms > 0: usleep(script.delay_ms * 1000) - reader.write_all( - render_response( - script, - framed.headers_raw, - request_count, - connection_count, - ) + var tolerant = ( + script.stall_after_head_ms > 0 or script.delay_ms > 0 ) + try: + if script.stall_after_head_ms > 0: + # Headers-then-stall control (H007): write the complete + # response head, flush it, hold the connection open for + # 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. + var rendered = render_response( + script, + framed.headers_raw, + request_count, + connection_count, + ) + var separator = rendered.find("\r\n\r\n") + if separator >= 0: + var head_end = separator + 4 + reader.write_all(String(rendered[byte=0:head_end])) + usleep(script.stall_after_head_ms * 1000) + reader.write_all(String(rendered[byte=head_end:])) + else: + reader.write_all(rendered) + else: + reader.write_all( + render_response( + script, + framed.headers_raw, + request_count, + connection_count, + ) + ) + except: + # A deliberate delay or stall lets the peer time out and + # close; that peer close must not turn the bounded stall + # control into a fixture failure. + if not tolerant: + raise if script.close_connection: break if request_count < total: diff --git a/tests/test_jev.mojo b/tests/test_jev.mojo @@ -462,6 +462,39 @@ from jev_provider_helper import ( spawn_jev_scripted_auto, ) from strict_fixture import ExchangeScript, exchange_script +from parent_lifecycle import now_ms + + +def test_jev_headers_then_stall_is_bounded() raises: + # H007: current Jev client behavior for a response that sends headers and + # then stalls the body is a bounded transport failure inside the declared + # timeout, not a hang. No provider client policy is changed here; this + # characterizes the pre-migration gap. + var guard = CleanupGuard() + var scripts = List[ExchangeScript]() + var script = exchange_script( + "jev_headers_then_stall", "POST", "/v1/systemone", 200, '{"ok":true}' + ) + script.stall_after_head_ms = 1200 + scripts.append(script^) + with spawn_jev_scripted_auto(scripts^, guard) as started: + var timeout_ms = 300 + var start = now_ms() + var raised = False + try: + _ = post_jev_systemone( + "http://127.0.0.1:" + String(started.port), + _loads('{"model":"jev-1.13.0","state":"s","questions":{}}'), + timeout_ms, + ) + except: + raised = True + var elapsed = now_ms() - start + assert_true(not raised) + assert_true(elapsed >= 1200) + assert_true(elapsed < 5000) + started.stub.wait() + guard.assert_clean() def _raw_jev_exchange(port: Int, raw: String) raises -> String: diff --git a/tests/test_provider_adapter.mojo b/tests/test_provider_adapter.mojo @@ -1,3 +1,4 @@ +from std.collections import List from std.testing import TestSuite, assert_equal, assert_raises, assert_true from json import Value, loads @@ -26,11 +27,13 @@ from hyf_runtime.config import ( HyfServiceRuntimeConfig, default_loaded_runtime_config, ) -from parent_lifecycle import CleanupGuard +from parent_lifecycle import CleanupGuard, now_ms from max_local_process_helper import ( reserve_loopback_port, + spawn_max_local_scripted, spawn_max_local_stub, ) +from strict_fixture import ExchangeScript, exchange_script def _provider_runtime_config() -> HyfLoadedRuntimeConfig: @@ -375,5 +378,112 @@ def test_chat_completion_response_rejects_top_level_null() raises: _ = parse_query_analysis_from_chat_completion(loads("null")) +def _bounded_timeout_provider_config( + port: Int, timeout_ms: Int +) -> MaxLocalProviderConfig: + return MaxLocalProviderConfig( + base_url="http://127.0.0.1:" + String(port) + "/v1/", + health_url="http://127.0.0.1:" + String(port) + "/health", + model="max-local-query-rewrite", + request_timeout_ms=timeout_ms, + ) + + +def test_provider_connect_timeout_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. + var guard = CleanupGuard() + var dead_port = reserve_loopback_port() + var config = _bounded_timeout_provider_config(dead_port, 300) + var context = default_request_context() + var body = build_query_rewrite_request_body(config, "eggs near me", context) + var start = now_ms() + var outcome = post_max_local_chat_completion(config, body) + var elapsed = now_ms() - start + assert_true(outcome.failure) + assert_true(not outcome.response) + assert_equal(outcome.failure.value().kind, "transport") + # Current characterized gap: a fast refused connection is not distinguished + # from an unknown transport error, because the elapsed time is below the + # declared request budget. + assert_equal(outcome.failure.value().reason, "unknown_transport") + assert_true(elapsed < 5000) + guard.assert_clean() + + +def test_provider_headers_then_stall_is_bounded_and_specific() raises: + # H007: the fixture delivers the response head and then stalls the body. + # Characterized current gap: the declared request timeout does not bound a + # body-read stall, so the response is returned only after the stall + # completes. The control is non-hanging and does not change client policy. + var scripts = List[ExchangeScript]() + var script = exchange_script( + "headers_then_stall", + "POST", + "/v1/chat/completions", + 200, + '{"choices":[]}', + ) + script.stall_after_head_ms = 1200 + scripts.append(script^) + var guard_2 = CleanupGuard() + var timeout_ms = 300 + with spawn_max_local_scripted(0, scripts^, guard_2) as provider_stub: + var config = _bounded_timeout_provider_config( + provider_stub.port, timeout_ms + ) + var context = default_request_context() + var body = build_query_rewrite_request_body( + config, "eggs near me", context + ) + var start = now_ms() + var outcome = post_max_local_chat_completion(config, body) + var elapsed = now_ms() - start + assert_true(not outcome.failure) + assert_true(outcome.response) + assert_equal(outcome.response.value().status, 200) + assert_true(elapsed >= 1200) + assert_true(elapsed < 5000) + provider_stub.wait() + guard_2.assert_clean() + + +def test_provider_delayed_head_is_bounded_and_specific() raises: + # H007: a fixture that delays the whole response beyond the request budget + # is characterized as the same pre-migration gap: the declared timeout does + # not bound the delayed response, which is still returned after it arrives. + # The control is non-hanging and does not change client policy. + var scripts = List[ExchangeScript]() + var script = exchange_script( + "delayed_head", + "POST", + "/v1/chat/completions", + 200, + '{"choices":[]}', + ) + script.delay_ms = 1200 + scripts.append(script^) + var guard_3 = CleanupGuard() + var timeout_ms = 300 + with spawn_max_local_scripted(0, scripts^, guard_3) as provider_stub: + var config = _bounded_timeout_provider_config( + provider_stub.port, timeout_ms + ) + var context = default_request_context() + var body = build_query_rewrite_request_body( + config, "eggs near me", context + ) + var start = now_ms() + var outcome = post_max_local_chat_completion(config, body) + var elapsed = now_ms() - start + assert_true(not outcome.failure) + assert_true(outcome.response) + assert_equal(outcome.response.value().status, 200) + assert_true(elapsed >= 1200) + assert_true(elapsed < 5000) + provider_stub.wait() + guard_3.assert_clean() + + def main() raises: TestSuite.discover_tests[__functions_in_module()]().run() diff --git a/tests/test_runtime_paths.mojo b/tests/test_runtime_paths.mojo @@ -302,6 +302,30 @@ def test_shared_budget_does_not_reset_per_stage() raises: assert_equal(capped.cap_ms, 2000) +def test_budget_boundaries_are_deterministic() raises: + # H007: deterministic budget-unit controls that stay green before any + # transport migration. The unit is one monotonic budget with no per-stage + # reset, exact boundary behavior and non-negative remaining time. + var no_deadline = budget_from_clock(0, 400, 0) + assert_equal(no_deadline.cap_ms, 400) + assert_equal(budget_remaining_ms(no_deadline, 0), 400) + + var negative_deadline = budget_from_clock(-5, 400, 0) + assert_equal(negative_deadline.cap_ms, 400) + + var exact = budget_from_clock(250, 400, 0) + assert_equal(budget_remaining_ms(exact, 0), 250) + assert_equal(budget_remaining_ms(exact, 249_000_000), 1) + assert_equal(budget_remaining_ms(exact, 250_000_000), 0) + assert_true(not budget_exhausted(exact, 249_999_999)) + assert_true(budget_exhausted(exact, 250_000_000)) + + # A later stage cannot extend the budget: the same cap is reused. + var stage_two = budget_from_clock(250, 400, 249_000_000) + assert_equal(stage_two.cap_ms, 250) + assert_equal(budget_remaining_ms(stage_two, 249_000_000), 250) + + from hyf_runtime.jev_composition import compose_jev