commit 4c2e11761e0badc6f177eb2810039b914b19bd53
parent 41642a693248823497df69df1fdc3e17f08a4453
Author: triesap <tyson@radroots.org>
Date: Thu, 18 Jun 2026 17:43:25 -0700
provider: normalize max local fallback reasons
- map health non-2xx readiness to business provider_non_2xx
- collapse malformed provider payloads to provider_schema_invalid
- add regression coverage for public fallback reason normalization
Diffstat:
6 files changed, 53 insertions(+), 7 deletions(-)
diff --git a/src/hyf_provider/max_local.mojo b/src/hyf_provider/max_local.mojo
@@ -101,11 +101,6 @@ def max_local_query_rewrite_failure_from_error(
return MaxLocalQueryRewriteFailure(
kind="provider_payload", reason="provider_missing_content"
)
- if _matches_provider_reason(message, "provider_invalid_response"):
- return MaxLocalQueryRewriteFailure(
- kind="provider_payload", reason="provider_invalid_response"
- )
-
var lower = message.lower()
if lower.find("url") >= 0 or lower.find("scheme") >= 0:
return MaxLocalQueryRewriteFailure(
diff --git a/src/hyf_provider/result.mojo b/src/hyf_provider/result.mojo
@@ -53,7 +53,7 @@ def _first_validation_error(value: Value) raises -> String:
def extract_chat_completion_text(response: Value) raises -> String:
if not response.is_object():
- raise Error("provider_invalid_response")
+ raise Error("provider_schema_invalid")
if _has_key(response, "error"):
raise Error("provider_error_payload")
if not _has_key(response, "choices"):
diff --git a/src/hyf_stdio/provider_execution.mojo b/src/hyf_stdio/provider_execution.mojo
@@ -134,6 +134,12 @@ def _remaining_provider_budget_ms(start_ns: UInt, budget_ms: Int) -> Int:
return remaining_ms
+def _business_provider_status_reason(reason: String) -> String:
+ if reason == "non_2xx":
+ return "provider_non_2xx"
+ return String(reason)
+
+
def _query_rewrite_fallback(
input: Value,
context: RequestContext,
@@ -212,7 +218,7 @@ def _execute_query_rewrite_with_provider(
input,
context,
"provider_runtime",
- String(provider_status.reason),
+ _business_provider_status_reason(provider_status.reason),
)
var remaining_ms = _remaining_provider_budget_ms(
diff --git a/tests/max_local_process_helper.mojo b/tests/max_local_process_helper.mojo
@@ -150,6 +150,12 @@ def _handle_chat_completions(mut stream: TcpStream, mode: String) raises:
"}}"
)
_send(stream, 200, _chat_completion(body))
+ elif mode == "query_rewrite_top_level_string":
+ _send(stream, 200, '"not object"')
+ elif mode == "query_rewrite_top_level_array":
+ _send(stream, 200, "[]")
+ elif mode == "query_rewrite_top_level_null":
+ _send(stream, 200, "null")
elif mode == "query_rewrite_empty_choices":
_send(stream, 200, '{"choices":[]}')
elif mode == "query_rewrite_missing_content":
diff --git a/tests/test_provider_adapter.mojo b/tests/test_provider_adapter.mojo
@@ -230,5 +230,20 @@ def test_chat_completion_response_rejects_empty_choices() raises:
_ = parse_query_analysis_from_chat_completion(loads('{"choices":[]}'))
+def test_chat_completion_response_rejects_top_level_scalar() raises:
+ with assert_raises():
+ _ = parse_query_analysis_from_chat_completion(loads('"not object"'))
+
+
+def test_chat_completion_response_rejects_top_level_array() raises:
+ with assert_raises():
+ _ = parse_query_analysis_from_chat_completion(loads("[]"))
+
+
+def test_chat_completion_response_rejects_top_level_null() raises:
+ with assert_raises():
+ _ = parse_query_analysis_from_chat_completion(loads("null"))
+
+
def main() raises:
TestSuite.discover_tests[__functions_in_module()]().run()
diff --git a/tests/test_stdio_contract.mojo b/tests/test_stdio_contract.mojo
@@ -1518,6 +1518,12 @@ def test_query_rewrite_falls_back_when_provider_readiness_probe_times_out() rais
)
+def test_query_rewrite_falls_back_on_health_non_2xx_with_business_reason() raises:
+ _assert_query_rewrite_provider_fallback_with_requests(
+ "health_non_2xx", "provider_non_2xx", 15000, 1
+ )
+
+
def test_query_rewrite_completion_uses_remaining_deadline_after_readiness() raises:
_assert_query_rewrite_provider_fallback_with_deadline(
"query_rewrite_remaining_deadline_timeout",
@@ -1540,6 +1546,24 @@ def test_query_rewrite_falls_back_on_provider_schema_invalid_json() raises:
)
+def test_query_rewrite_falls_back_on_provider_top_level_string() raises:
+ _assert_query_rewrite_provider_fallback(
+ "query_rewrite_top_level_string", "provider_schema_invalid", 15000
+ )
+
+
+def test_query_rewrite_falls_back_on_provider_top_level_array() raises:
+ _assert_query_rewrite_provider_fallback(
+ "query_rewrite_top_level_array", "provider_schema_invalid", 15000
+ )
+
+
+def test_query_rewrite_falls_back_on_provider_top_level_null() raises:
+ _assert_query_rewrite_provider_fallback(
+ "query_rewrite_top_level_null", "provider_schema_invalid", 15000
+ )
+
+
def test_query_rewrite_falls_back_on_provider_empty_choices() raises:
_assert_query_rewrite_provider_fallback(
"query_rewrite_empty_choices", "provider_empty_choices", 15000