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:
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: