hyf

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

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:
Msrc/hyf_provider/max_local.mojo | 5-----
Msrc/hyf_provider/result.mojo | 2+-
Msrc/hyf_stdio/provider_execution.mojo | 8+++++++-
Mtests/max_local_process_helper.mojo | 6++++++
Mtests/test_provider_adapter.mojo | 15+++++++++++++++
Mtests/test_stdio_contract.mojo | 24++++++++++++++++++++++++
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