hyf

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

commit 2d1d461e482e53aa2f4dc56e97306e01431d5bb3
parent b06d3ffcdde613510a5819df5ffa3b082450c5d3
Author: triesap <tyson@radroots.org>
Date:   Thu, 18 Jun 2026 13:53:55 -0700

provider: expose assisted fallback metadata

- add top-level fallback kind and reason metadata fields
- serialize assisted fallback reasons outside provenance
- preserve provenance-gated fallback details when requested
- test fallback visibility and success metadata omission

Diffstat:
Msrc/hyf_core/capabilities/query_analysis.mojo | 4++++
Msrc/hyf_core/capabilities/query_rewrite.mojo | 2++
Msrc/hyf_core/provenance.mojo | 4++++
Msrc/hyf_stdio/meta.mojo | 8++++++++
Msrc/hyf_stdio/provider_execution.mojo | 20+++++++++++---------
Mtests/fixtures/v1/scenarios/assisted_backend_unavailable.json | 4+++-
Mtests/test_stdio_contract.mojo | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 120 insertions(+), 10 deletions(-)

diff --git a/src/hyf_core/capabilities/query_analysis.mojo b/src/hyf_core/capabilities/query_analysis.mojo @@ -363,6 +363,8 @@ def build_deterministic_meta( latency_ms=None, schema_version=Optional[Int](1), prompt_version=None, + fallback_kind=None, + fallback_reason=None, provenance=ExecutionProvenance( kind="deterministic", signal_tags=copy_string_list(signal_tags), @@ -381,5 +383,7 @@ def build_deterministic_meta( latency_ms=None, schema_version=Optional[Int](1), prompt_version=None, + fallback_kind=None, + fallback_reason=None, provenance=None, ) diff --git a/src/hyf_core/capabilities/query_rewrite.mojo b/src/hyf_core/capabilities/query_rewrite.mojo @@ -92,6 +92,8 @@ def build_query_rewrite_deterministic_fallback_meta( latency_ms=None, schema_version=Optional[Int](1), prompt_version=None, + fallback_kind=Optional[String](String(fallback_kind)), + fallback_reason=Optional[String](String(reason)), provenance=provenance^, ) diff --git a/src/hyf_core/provenance.mojo b/src/hyf_core/provenance.mojo @@ -32,6 +32,8 @@ struct CoreResponseMeta(Copyable, Movable): var latency_ms: Optional[Int] var schema_version: Optional[Int] var prompt_version: Optional[String] + var fallback_kind: Optional[String] + var fallback_reason: Optional[String] var provenance: Optional[ExecutionProvenance] @@ -45,5 +47,7 @@ def deterministic_response_meta() -> CoreResponseMeta: latency_ms=None, schema_version=None, prompt_version=None, + fallback_kind=None, + fallback_reason=None, provenance=None, ) diff --git a/src/hyf_stdio/meta.mojo b/src/hyf_stdio/meta.mojo @@ -64,6 +64,14 @@ def serialize_core_response_meta(meta: CoreResponseMeta) raises -> Value: value.set( "prompt_version", Value(String(meta.prompt_version.value())) ) + if meta.fallback_kind: + value.set( + "fallback_kind", Value(String(meta.fallback_kind.value())) + ) + if meta.fallback_reason: + value.set( + "fallback_reason", Value(String(meta.fallback_reason.value())) + ) if meta.provenance: value.set("provenance", _serialize_provenance(meta.provenance.value())) return value^ diff --git a/src/hyf_stdio/provider_execution.mojo b/src/hyf_stdio/provider_execution.mojo @@ -91,6 +91,8 @@ def _provider_meta( latency_ms=Optional[Int](result.latency_ms), schema_version=Optional[Int](result.schema_version), prompt_version=Optional[String](String(result.prompt_version)), + fallback_kind=None, + fallback_reason=None, provenance=provenance^, ) @@ -165,16 +167,16 @@ def _with_deterministic_assisted_fallback_meta( return result.copy() var meta = success.meta.value().copy() - if not meta.provenance: - return result.copy() - - var provenance = meta.provenance.value().copy() - provenance.fallback = Optional[ProvenanceFallback]( - ProvenanceFallback( - fallback_kind=String(fallback_kind), reason=String(reason) + meta.fallback_kind = Optional[String](String(fallback_kind)) + meta.fallback_reason = Optional[String](String(reason)) + if meta.provenance: + var provenance = meta.provenance.value().copy() + provenance.fallback = Optional[ProvenanceFallback]( + ProvenanceFallback( + fallback_kind=String(fallback_kind), reason=String(reason) + ) ) - ) - meta.provenance = Optional[ExecutionProvenance](provenance^) + meta.provenance = Optional[ExecutionProvenance](provenance^) return successful_capability(success.output, meta=meta^) diff --git a/tests/fixtures/v1/scenarios/assisted_backend_unavailable.json b/tests/fixtures/v1/scenarios/assisted_backend_unavailable.json @@ -22,7 +22,9 @@ "output.rewritten_text": "apples", "meta.execution_mode": "deterministic", "meta.backend": "heuristic", - "meta.schema_version": 1 + "meta.schema_version": 1, + "meta.fallback_kind": "provider_runtime", + "meta.fallback_reason": "disabled_by_runtime_config" }, "absent_paths": [ "error", diff --git a/tests/test_stdio_contract.mojo b/tests/test_stdio_contract.mojo @@ -43,6 +43,24 @@ def _array_contains_string(value: Value, expected: String) raises -> Bool: return False +def _assert_provider_runtime_fallback_meta( + response: Value, expected_reason: String +) raises: + assert_equal( + response["meta"]["fallback_kind"].string_value(), + "provider_runtime", + ) + assert_equal( + response["meta"]["fallback_reason"].string_value(), + expected_reason, + ) + + +def _assert_no_top_level_fallback_meta(response: Value) raises: + assert_true(not _has_key(response["meta"], "fallback_kind")) + assert_true(not _has_key(response["meta"], "fallback_reason")) + + def _max_local_runtime_config_toml_with_urls( base_url: String, health_url: String, request_timeout_ms: Int ) -> String: @@ -93,6 +111,18 @@ def _query_rewrite_assisted_request_json_with_deadline( ) +def _query_rewrite_assisted_request_json_without_provenance( + request_id: String +) -> String: + return ( + '{"version":1,"request_id":"' + + request_id + + '","trace_id":"' + + request_id + + '","capability":"query_rewrite","context":{"execution_mode_preference":"assisted","return_provenance":false,"deadline_ms":2500},"input":{"query":"apples near me with weekend pickup"}}' + ) + + def _semantic_rank_assisted_request_json(request_id: String) -> String: return ( '{"version":1,"request_id":"' @@ -150,6 +180,9 @@ def _assert_query_rewrite_provider_fallback_with_deadline( "heuristic", ) assert_true(not _has_key(response["meta"], "provider")) + _assert_provider_runtime_fallback_meta( + response, expected_reason + ) assert_equal( response["meta"]["provenance"]["fallback"][ "fallback_kind" @@ -202,6 +235,9 @@ def _assert_query_rewrite_runtime_config_fallback( "heuristic", ) assert_true(not _has_key(response["meta"], "provider")) + _assert_provider_runtime_fallback_meta( + response, expected_reason + ) assert_equal( response["meta"]["provenance"]["fallback"][ "fallback_kind" @@ -219,6 +255,43 @@ def _assert_query_rewrite_runtime_config_fallback( ) +def _assert_query_rewrite_runtime_config_fallback_without_provenance( + config_text: String, expected_reason: String, request_id: String +) raises: + with TemporaryDirectory() as temp_dir: + var startup_config_path = Path(temp_dir) / "explicit-hyf-config.toml" + startup_config_path.write_text(config_text) + with ScopedEnvVar(HYF_PATHS_PROFILE_ENV, "repo_local"): + with ScopedEnvVar(HYF_PATHS_REPO_LOCAL_ROOT_ENV, temp_dir): + var response = run_stdio_entrypoint( + "src/main.mojo", + _query_rewrite_assisted_request_json_without_provenance( + request_id + ), + "--config", + startup_config_path.__fspath__(), + ) + + assert_true(response["ok"].bool_value()) + assert_equal( + response["meta"]["execution_mode"].string_value(), + "deterministic", + ) + assert_equal( + response["meta"]["backend"].string_value(), + "heuristic", + ) + assert_true(not _has_key(response["meta"], "provider")) + assert_true(not _has_key(response["meta"], "provenance")) + _assert_provider_runtime_fallback_meta( + response, expected_reason + ) + assert_equal( + response["output"]["rewritten_text"].string_value(), + "apples", + ) + + def _assert_invalid_runtime_config_load_error( config_text: String, expected_error_fragment: String ) raises: @@ -1288,6 +1361,16 @@ def test_query_rewrite_falls_back_on_invalid_provider_runtime_config() raises: ) +def test_query_rewrite_fallback_metadata_is_visible_without_provenance() raises: + _assert_query_rewrite_runtime_config_fallback_without_provenance( + '[service]\ntransport = "stdio"\n\n' + + '[runtime]\ndefault_execution_mode = "deterministic"\nallow_assisted = true\n\n' + + '[assisted]\nprovider = "max_local"\n', + "provider_unconfigured", + "rewrite-assisted-no-provenance-1", + ) + + def test_assisted_semantic_rank_falls_back_as_unsupported_provider_capability() raises: with TemporaryDirectory() as temp_dir: var startup_config_path = Path(temp_dir) / "explicit-hyf-config.toml" @@ -1315,6 +1398,9 @@ def test_assisted_semantic_rank_falls_back_as_unsupported_provider_capability() "heuristic", ) assert_true(not _has_key(response["meta"], "provider")) + _assert_provider_runtime_fallback_meta( + response, "unsupported_capability" + ) assert_equal( response["meta"]["provenance"]["fallback"][ "fallback_kind" @@ -1393,6 +1479,7 @@ def test_query_rewrite_uses_max_local_provider_when_ready() raises: assert_true( response["meta"]["provenance"]["fallback"].is_null() ) + _assert_no_top_level_fallback_meta(response) assert_equal( response["output"]["rewritten_text"].string_value(), "apples pickup weekend", @@ -1667,6 +1754,7 @@ def test_query_rewrite_success() raises: assert_matches_scenario_response( response, "scenarios/query_rewrite_local_pickup_weekend.json" ) + _assert_no_top_level_fallback_meta(response) def test_query_rewrite_does_not_create_protected_local_artifacts() raises: