commit 00e388437301229880c97bca03540c32ba732191
parent 8d0e7300bb65431cbe9306a07d1853f17317822a
Author: triesap <tyson@radroots.org>
Date: Thu, 24 Sep 2026 15:58:32 +0000
C004: parse corrected operation context and guard pre-activation dispatch
Diffstat:
4 files changed, 741 insertions(+), 2 deletions(-)
diff --git a/src/hyf_core/operation_context.mojo b/src/hyf_core/operation_context.mojo
@@ -0,0 +1,318 @@
+# ADR-0025 D45 CB01-CB05 — corrected hyf_ops_v2 capability context.
+#
+# This module owns only the strict, capability-aware operation context for the
+# three corrected operations selected by ``context.versions.schema ==
+# "hyf_ops_v2"``. It does not implement the operation pipelines (C008/C009),
+# typed semantic consistency (C023) or activation (C042-C046). A recognized v2
+# request is parsed here and then refused by the pre-activation guard in
+# hyf_stdio.server; it must never reach the legacy shortcut handlers.
+
+from std.collections import List, Optional
+
+from json import Value
+from json.deserialize import get_bool, get_int, get_string
+
+
+comptime OPERATION_CONTRACT_SCHEMA_V2: String = "hyf_ops_v2"
+comptime OPERATION_CONTEXT_DEFAULT_DEADLINE_MS: Int = 2500
+
+
+def corrected_operation_selector() -> String:
+ return OPERATION_CONTRACT_SCHEMA_V2
+
+
+def is_corrected_operation(capability: String) -> Bool:
+ return (
+ capability == "farm_update.interpret"
+ or capability == "buyer_request.interpret"
+ or capability == "buyer_request.match"
+ )
+
+
+def corrected_operation_requires_farm_id(capability: String) -> Bool:
+ return capability == "farm_update.interpret"
+
+
+def corrected_operation_activation_enabled() -> Bool:
+ """Activation boundary owned by C042-C046; C004 binds but never activates.
+ """
+ return False
+
+
+def _has_key(value: Value, key: String) -> Bool:
+ for candidate in value.object_keys():
+ if candidate == key:
+ return True
+ return False
+
+
+def _require_object(value: Value, context: String) raises:
+ if not value.is_object():
+ raise Error(context + " must be a JSON object")
+
+
+def _require_no_duplicate_keys(value: Value, context: String) raises:
+ var keys = value.object_keys()
+ for left in range(len(keys)):
+ for right in range(left + 1, len(keys)):
+ if keys[left] == keys[right]:
+ raise Error(
+ context + " contains duplicate field '" + keys[left] + "'"
+ )
+
+
+def _require_allowed_keys(
+ value: Value, allowed_keys: List[String], context: String
+) raises:
+ for key in value.object_keys():
+ var allowed = False
+ for allowed_key in allowed_keys:
+ if key == allowed_key:
+ allowed = True
+ break
+ if not allowed:
+ raise Error(context + " contains unexpected field '" + key + "'")
+
+
+def _require_non_empty(value: String, context: String) raises:
+ if value == "":
+ raise Error(context + " must not be empty")
+
+
+def _has_nonblank(value: String) -> Bool:
+ return String(value).strip().byte_length() > 0
+
+
+def _require_nonblank(value: String, context: String) raises:
+ if not _has_nonblank(value):
+ raise Error(context + " must not be blank")
+
+
+def _optional_string(
+ value: Value, key: String, context: String
+) raises -> Optional[String]:
+ if not _has_key(value, key):
+ return None
+ var raw = get_string(value, key)
+ _require_nonblank(raw, context)
+ return String(raw)
+
+
+def _required_string(
+ value: Value, key: String, context: String
+) raises -> String:
+ if not _has_key(value, key):
+ raise Error(context + " is required")
+ var raw = get_string(value, key)
+ _require_nonblank(raw, context)
+ return String(raw)
+
+
+def _object_or_empty(value: Value, key: String) -> Value:
+ if _has_key(value, key):
+ return value[key].clone()
+ return Value(None)
+
+
+@fieldwise_init
+struct OperationVersions(Copyable, Movable):
+ var schema: String
+ var taxonomy: String
+ var normalization: String
+ var review_policy: String
+ var ranking_policy: String
+ var question_bundle: String
+ var model: String
+
+
+def _parse_operation_versions(
+ json: Value, context: String
+) raises -> OperationVersions:
+ _require_object(json, context)
+ _require_no_duplicate_keys(json, context)
+
+ var allowed_keys = List[String]()
+ for key in [
+ "schema",
+ "taxonomy",
+ "normalization",
+ "review_policy",
+ "ranking_policy",
+ "question_bundle",
+ "model",
+ ]:
+ allowed_keys.append(key)
+ _require_allowed_keys(json, allowed_keys, context)
+
+ var schema = _required_string(json, "schema", context + " schema")
+ if schema != OPERATION_CONTRACT_SCHEMA_V2:
+ raise Error(
+ context
+ + " selects unsupported operation contract schema '"
+ + schema
+ + "'"
+ )
+
+ return OperationVersions(
+ schema=schema,
+ taxonomy=_required_string(json, "taxonomy", context + " taxonomy"),
+ normalization=_required_string(
+ json, "normalization", context + " normalization"
+ ),
+ review_policy=_required_string(
+ json, "review_policy", context + " review_policy"
+ ),
+ ranking_policy=_required_string(
+ json, "ranking_policy", context + " ranking_policy"
+ ),
+ question_bundle=_required_string(
+ json, "question_bundle", context + " question_bundle"
+ ),
+ model=_required_string(json, "model", context + " model"),
+ )
+
+
+@fieldwise_init
+struct OperationContext(Copyable, Movable):
+ var consumer: String
+ var execution_mode_preference: String
+ var deadline_ms: Int
+ var evaluation_time: String
+ var timezone: Optional[String]
+ var locale: Optional[String]
+ var versions: OperationVersions
+ var return_provenance: Bool
+ var actor_id: String
+ var farm_id: Optional[String]
+
+
+def default_operation_context() -> OperationContext:
+ return OperationContext(
+ consumer="unknown",
+ execution_mode_preference="deterministic",
+ deadline_ms=OPERATION_CONTEXT_DEFAULT_DEADLINE_MS,
+ evaluation_time="",
+ timezone=None,
+ locale=None,
+ versions=OperationVersions(
+ schema="",
+ taxonomy="",
+ normalization="",
+ review_policy="",
+ ranking_policy="",
+ question_bundle="",
+ model="",
+ ),
+ return_provenance=False,
+ actor_id="",
+ farm_id=None,
+ )
+
+
+def operation_context_selects_v2(context_json: Value) raises -> Bool:
+ """True only for a well-typed ``context.versions.schema == hyf_ops_v2``.
+
+ Missing, null, wrong-type and unknown selectors are not recognized here;
+ they fall through to the unchanged legacy parser, which rejects
+ ``versions`` for unrelated capabilities and returns invalid_request.
+ """
+ if not context_json.is_object():
+ return False
+ if not _has_key(context_json, "versions"):
+ return False
+ var versions = context_json["versions"]
+ if not versions.is_object():
+ return False
+ if not _has_key(versions, "schema"):
+ return False
+ var schema = versions["schema"]
+ if not schema.is_string():
+ return False
+ return String(schema.string_value()) == OPERATION_CONTRACT_SCHEMA_V2
+
+
+def parse_operation_context(
+ json: Value, capability: String
+) raises -> OperationContext:
+ if not is_corrected_operation(capability):
+ raise Error(
+ "operation context is only defined for corrected operations, not '"
+ + capability
+ + "'"
+ )
+
+ _require_object(json, "operation context")
+ _require_no_duplicate_keys(json, "operation context")
+
+ var requires_farm = corrected_operation_requires_farm_id(capability)
+ var allowed_keys = List[String]()
+ for key in [
+ "consumer",
+ "execution_mode_preference",
+ "deadline_ms",
+ "evaluation_time",
+ "timezone",
+ "locale",
+ "versions",
+ "return_provenance",
+ "actor_id",
+ ]:
+ allowed_keys.append(key)
+ if requires_farm:
+ allowed_keys.append("farm_id")
+ _require_allowed_keys(json, allowed_keys, "operation context")
+
+ var context = default_operation_context()
+
+ var consumer = _optional_string(
+ json, "consumer", "operation context consumer"
+ )
+ if consumer:
+ context.consumer = consumer.value()
+
+ if _has_key(json, "execution_mode_preference"):
+ var preference = get_string(json, "execution_mode_preference")
+ if preference != "deterministic" and preference != "assisted":
+ raise Error(
+ "operation context execution_mode_preference must be"
+ " 'deterministic' or 'assisted'"
+ )
+ context.execution_mode_preference = String(preference)
+
+ if _has_key(json, "deadline_ms"):
+ context.deadline_ms = get_int(json, "deadline_ms")
+ if context.deadline_ms <= 0:
+ raise Error(
+ "operation context deadline_ms must be greater than zero"
+ )
+
+ context.evaluation_time = _required_string(
+ json, "evaluation_time", "operation context evaluation_time"
+ )
+
+ context.timezone = _optional_string(
+ json, "timezone", "operation context timezone"
+ )
+ context.locale = _optional_string(
+ json, "locale", "operation context locale"
+ )
+
+ if not _has_key(json, "versions"):
+ raise Error("operation context versions is required")
+ context.versions = _parse_operation_versions(
+ json["versions"].clone(), "operation context versions"
+ )
+
+ if _has_key(json, "return_provenance"):
+ context.return_provenance = get_bool(json, "return_provenance")
+
+ context.actor_id = _required_string(
+ json, "actor_id", "operation context actor_id"
+ )
+
+ if requires_farm:
+ context.farm_id = _required_string(
+ json, "farm_id", "operation context farm_id"
+ )
+
+ return context^
diff --git a/src/hyf_stdio/envelope.mojo b/src/hyf_stdio/envelope.mojo
@@ -4,7 +4,17 @@ from json import Value, loads
from json.deserialize import Deserializable, get_string
from hyf_core.metadata import hyf_protocol_version
-from hyf_core.request_context import RequestContext, parse_request_context
+from hyf_core.operation_context import (
+ OperationContext,
+ is_corrected_operation,
+ operation_context_selects_v2,
+ parse_operation_context,
+)
+from hyf_core.request_context import (
+ RequestContext,
+ default_request_context,
+ parse_request_context,
+)
from hyf_stdio.errors import WireError
@@ -88,6 +98,10 @@ struct WireRequest(Copyable, Deserializable, Movable):
var capability: String
var context: RequestContext
var input: Value
+ # ADR-0025 D45 CB01: present only for a recognized hyf_ops_v2 request to one
+ # of the three corrected operations. Legacy requests leave this None and
+ # keep the unchanged legacy context.
+ var operation_context: Optional[OperationContext]
@staticmethod
def from_json(json: Value) raises -> Self:
@@ -107,7 +121,16 @@ struct WireRequest(Copyable, Deserializable, Movable):
if _has_key(json, "context"):
context_json = json["context"].clone()
- var context = parse_request_context(context_json)
+ var context = default_request_context()
+ var operation_context: Optional[OperationContext] = None
+ if is_corrected_operation(capability) and operation_context_selects_v2(
+ context_json
+ ):
+ operation_context = parse_operation_context(
+ context_json.clone(), capability
+ )
+ else:
+ context = parse_request_context(context_json)
var input = _require_input_value(json)
return Self(
@@ -117,6 +140,7 @@ struct WireRequest(Copyable, Deserializable, Movable):
capability=capability,
context=context^,
input=input^,
+ operation_context=operation_context^,
)
diff --git a/src/hyf_stdio/server.mojo b/src/hyf_stdio/server.mojo
@@ -196,6 +196,13 @@ def _route_business_capability(
request_id: String,
runtime_context: RuntimeStartupContext,
) raises -> String:
+ # ADR-0025 D45 CB01 pre-activation guard: a recognized hyf_ops_v2 request has
+ # already passed the strict capability-context parse, but activation belongs
+ # to C042-C046, so it must not reach the legacy shortcut handlers even when
+ # their legacy enable flag is set. C008/C009 retain this guard.
+ if request.operation_context:
+ return encode_error(_unavailable_response(request))
+
if is_gated_operation(request.capability):
if not operation_enabled(runtime_context.config, request.capability):
return encode_error(_disabled_response(request))
diff --git a/tests/test_hyf.mojo b/tests/test_hyf.mojo
@@ -1498,3 +1498,393 @@ def test_stdio_envelope_boundary_rejects_null_and_numeric_fields() raises:
assert_true(messages[2].find("'version' is required") >= 0)
assert_true(messages[3].find("not a string") >= 0)
assert_true(messages[4].find("not a string") >= 0)
+
+
+# ADR-0025 D45 C004: strict hyf_ops_v2 capability context and pre-activation guard.
+from hyf_core.operation_context import (
+ corrected_operation_activation_enabled,
+ is_corrected_operation,
+ operation_context_selects_v2,
+)
+from hyf_runtime.config import operation_enabled as _operation_enabled
+
+
+def _v2_versions_json() -> String:
+ return (
+ '"versions":{"schema":"hyf_ops_v2",'
+ '"taxonomy":"hyf_ops_v2.taxonomy.v1",'
+ '"normalization":"hyf_ops_v2.normalization.v1",'
+ '"review_policy":"hyf_ops_v2.review_policy.v1",'
+ '"ranking_policy":"hyf_ops_v2.ranking_policy.v1",'
+ '"question_bundle":"hyf_ops_v2.question_bundle.v1",'
+ '"model":"jev-1.13.0"}'
+ )
+
+
+def _v2_farm_references_json() -> String:
+ return (
+ '"references":{"taxonomy":{"version":"hyf_ops_v2.taxonomy.v1",'
+ '"provenance":"host_supplied","products":['
+ '{"catalogue_id":"tomato.roma","terms":["Roma tomatoes"]}]},'
+ '"normalization":{"version":"hyf_ops_v2.normalization.v1",'
+ '"provenance":"host_supplied","units":[{"unit":"lb","dimension":"mass"}],'
+ '"conversions":[],"packs":[]}}'
+ )
+
+
+def _v2_farm_source_json() -> String:
+ return (
+ '"source":{"source_id":"s1","revision":"r1",'
+ '"text":"80 lb of Roma tomatoes",'
+ '"source_time":"2026-09-24T08:30:00-07:00",'
+ '"timezone":"America/Vancouver","actor_id":"farm-1","farm_id":"farm-1"}'
+ )
+
+
+def _v2_farm_request() -> String:
+ return (
+ '{"version":1,"request_id":"v2-farm-1",'
+ '"capability":"farm_update.interpret",'
+ '"context":{"consumer":"radroots-cli",'
+ '"execution_mode_preference":"deterministic","deadline_ms":2500,'
+ '"evaluation_time":"2026-09-24T09:00:00-07:00",'
+ '"timezone":"America/Vancouver","locale":"en-CA",'
+ + _v2_versions_json()
+ + ',"return_provenance":true,"actor_id":"farm-1","farm_id":"farm-1"},'
+ '"input":{'
+ + _v2_farm_source_json()
+ + ","
+ + _v2_farm_references_json()
+ + "}}"
+ )
+
+
+def _v2_farm_request_with_context(context_body: String) -> String:
+ return (
+ '{"version":1,"request_id":"v2-farm-variant",'
+ '"capability":"farm_update.interpret","context":{'
+ + context_body
+ + '},"input":{'
+ + _v2_farm_source_json()
+ + ","
+ + _v2_farm_references_json()
+ + "}}"
+ )
+
+
+def _decode_error_message(line: String) -> String:
+ try:
+ _ = decode_request(line)
+ except e:
+ return String(e)
+ return ""
+
+
+def test_c004_corrected_operation_registry_and_activation_boundary() raises:
+ assert_true(is_corrected_operation("farm_update.interpret"))
+ assert_true(is_corrected_operation("buyer_request.interpret"))
+ assert_true(is_corrected_operation("buyer_request.match"))
+ assert_true(not is_corrected_operation("query_rewrite"))
+ # C042-C046 own activation; C004 binds the guard but never activates.
+ assert_true(not corrected_operation_activation_enabled())
+
+
+def test_c004_decode_request_parses_operation_v2_context() raises:
+ var request = decode_request(_v2_farm_request())
+ assert_equal(request.capability, "farm_update.interpret")
+ assert_true(request.operation_context)
+ var context = request.operation_context.value().copy()
+ assert_equal(context.consumer, "radroots-cli")
+ assert_equal(context.execution_mode_preference, "deterministic")
+ assert_equal(context.deadline_ms, 2500)
+ assert_equal(context.evaluation_time, "2026-09-24T09:00:00-07:00")
+ assert_equal(context.timezone.value(), "America/Vancouver")
+ assert_equal(context.locale.value(), "en-CA")
+ assert_equal(context.return_provenance, True)
+ assert_equal(context.actor_id, "farm-1")
+ assert_equal(context.farm_id.value(), "farm-1")
+ assert_equal(context.versions.schema, "hyf_ops_v2")
+ assert_equal(context.versions.taxonomy, "hyf_ops_v2.taxonomy.v1")
+ assert_equal(context.versions.normalization, "hyf_ops_v2.normalization.v1")
+ assert_equal(context.versions.review_policy, "hyf_ops_v2.review_policy.v1")
+ assert_equal(
+ context.versions.ranking_policy, "hyf_ops_v2.ranking_policy.v1"
+ )
+ assert_equal(
+ context.versions.question_bundle, "hyf_ops_v2.question_bundle.v1"
+ )
+ assert_equal(context.versions.model, "jev-1.13.0")
+
+
+def test_c004_operation_v2_context_defaults_are_not_host_guesses() raises:
+ var request = decode_request(
+ _v2_farm_request_with_context(
+ '"evaluation_time":"2026-09-24T09:00:00-07:00",'
+ + _v2_versions_json()
+ + ',"actor_id":"farm-1","farm_id":"farm-1"'
+ )
+ )
+ assert_true(request.operation_context)
+ var context = request.operation_context.value().copy()
+ assert_equal(context.consumer, "unknown")
+ assert_equal(context.execution_mode_preference, "deterministic")
+ assert_equal(context.deadline_ms, 2500)
+ assert_equal(context.return_provenance, False)
+ # Absent timezone/locale stay unknown; they are not filled from the host clock.
+ assert_true(not context.timezone)
+ assert_true(not context.locale)
+
+
+def test_c004_operation_v2_context_is_strict() raises:
+ var missing_actor = _v2_farm_request_with_context(
+ '"evaluation_time":"2026-09-24T09:00:00-07:00",'
+ + _v2_versions_json()
+ + ',"farm_id":"farm-1"'
+ )
+ assert_true(_decode_error_message(missing_actor).find("actor_id") >= 0)
+
+ var duplicate_actor = _v2_farm_request_with_context(
+ '"evaluation_time":"2026-09-24T09:00:00-07:00",'
+ + _v2_versions_json()
+ + ',"actor_id":"farm-1","actor_id":"farm-2","farm_id":"farm-1"'
+ )
+ assert_true(_decode_error_message(duplicate_actor).find("duplicate") >= 0)
+
+ var missing_farm = _v2_farm_request_with_context(
+ '"evaluation_time":"2026-09-24T09:00:00-07:00",'
+ + _v2_versions_json()
+ + ',"actor_id":"farm-1"'
+ )
+ assert_true(_decode_error_message(missing_farm).find("farm_id") >= 0)
+
+ var null_timezone = _v2_farm_request_with_context(
+ '"evaluation_time":"2026-09-24T09:00:00-07:00","timezone":null,'
+ + _v2_versions_json()
+ + ',"actor_id":"farm-1","farm_id":"farm-1"'
+ )
+ assert_true(_decode_error_message(null_timezone).find("string") >= 0)
+
+ var unknown_field = _v2_farm_request_with_context(
+ '"evaluation_time":"2026-09-24T09:00:00-07:00","planner":"strict",'
+ + _v2_versions_json()
+ + ',"actor_id":"farm-1","farm_id":"farm-1"'
+ )
+ assert_true(_decode_error_message(unknown_field).find("unexpected") >= 0)
+
+ var empty_actor = _v2_farm_request_with_context(
+ '"evaluation_time":"2026-09-24T09:00:00-07:00",'
+ + _v2_versions_json()
+ + ',"actor_id":"","farm_id":"farm-1"'
+ )
+ assert_true(_decode_error_message(empty_actor).find("actor_id") >= 0)
+
+
+def test_c004_operation_v2_versions_are_closed_and_complete() raises:
+ var incomplete = _v2_farm_request_with_context(
+ '"evaluation_time":"2026-09-24T09:00:00-07:00","versions":{'
+ '"schema":"hyf_ops_v2","taxonomy":"t","normalization":"n",'
+ '"review_policy":"r","ranking_policy":"k","model":"jev-1.13.0"},'
+ '"actor_id":"farm-1","farm_id":"farm-1"'
+ )
+ assert_true(_decode_error_message(incomplete).find("question_bundle") >= 0)
+
+ var unknown_axis = _v2_farm_request_with_context(
+ '"evaluation_time":"2026-09-24T09:00:00-07:00","versions":{'
+ '"schema":"hyf_ops_v2","taxonomy":"t","normalization":"n",'
+ '"review_policy":"r","ranking_policy":"k","question_bundle":"q",'
+ '"model":"jev-1.13.0","extra":"x"},'
+ '"actor_id":"farm-1","farm_id":"farm-1"'
+ )
+ assert_true(_decode_error_message(unknown_axis).find("unexpected") >= 0)
+
+
+def test_c004_operation_v2_buyer_forbids_farm_identity() raises:
+ var buyer_with_farm = (
+ '{"version":1,"request_id":"v2-buyer-farm",'
+ '"capability":"buyer_request.interpret",'
+ '"context":{"evaluation_time":"2026-09-24T09:00:00-07:00",'
+ + _v2_versions_json()
+ + ',"actor_id":"buyer-7","farm_id":"farm-1"},'
+ '"input":{"source":{"source_id":"s1","revision":"r1","text":"25 lb",'
+ '"source_time":"2026-09-24T08:45:00-07:00","actor_id":"buyer-7"},'
+ + _v2_farm_references_json()
+ + "}}"
+ )
+ assert_true(_decode_error_message(buyer_with_farm).find("unexpected") >= 0)
+
+ var buyer_ok = (
+ '{"version":1,"request_id":"v2-buyer-ok",'
+ '"capability":"buyer_request.interpret",'
+ '"context":{"evaluation_time":"2026-09-24T09:00:00-07:00",'
+ + _v2_versions_json()
+ + ',"actor_id":"buyer-7"},'
+ '"input":{"source":{"source_id":"s1","revision":"r1","text":"25 lb",'
+ '"source_time":"2026-09-24T08:45:00-07:00","actor_id":"buyer-7"},'
+ + _v2_farm_references_json()
+ + "}}"
+ )
+ var request = decode_request(buyer_ok)
+ assert_true(request.operation_context)
+ assert_true(not request.operation_context.value().copy().farm_id)
+ assert_equal(request.operation_context.value().copy().actor_id, "buyer-7")
+
+
+def test_c004_selector_detection_does_not_broaden_legacy_context() raises:
+ var context = loads("{}")
+ context.set("versions", loads('{"schema":"hyf_ops_v2"}'))
+ assert_true(operation_context_selects_v2(context))
+ assert_true(not operation_context_selects_v2(loads('{"consumer":"cli"}')))
+ assert_true(
+ not operation_context_selects_v2(
+ loads('{"versions":{"schema":"hyf_ops_v3"}}')
+ )
+ )
+ assert_true(not operation_context_selects_v2(loads('{"versions":1}')))
+
+ # An unrelated capability keeps the unchanged legacy admission and rejects
+ # the advertised-but-unsupported `versions` field.
+ var legacy_with_versions = (
+ '{"version":1,"request_id":"legacy-versions",'
+ '"capability":"query_rewrite","context":{'
+ + _v2_versions_json()
+ + '},"input":{"query":"eggs"}}'
+ )
+ assert_true(
+ _decode_error_message(legacy_with_versions).find("unexpected") >= 0
+ )
+
+ # The unchanged legacy envelope still parses its original context.
+ var legacy_request = decode_request(
+ '{"version":1,"request_id":"legacy-ok","capability":"query_rewrite",'
+ '"context":{"consumer":"radroots-cli","deadline_ms":2500},'
+ '"input":{"query":"eggs"}}'
+ )
+ assert_true(not legacy_request.operation_context)
+ assert_equal(legacy_request.context.consumer, "radroots-cli")
+
+
+def test_c004_operation_v2_guard_blocks_shortcut_even_when_enabled() raises:
+ with SafeTempDir() as temp_dir:
+ var runtime_context = resolve_startup_context(
+ RuntimeStartupInput(
+ env_paths_profile="repo_local",
+ env_repo_local_base_root=temp_dir,
+ user_home="/home/unused",
+ argv=List[String](),
+ )
+ )
+ runtime_context.config.effective.runtime.enable_farm_update_interpret = (
+ True
+ )
+ assert_true(
+ _operation_enabled(runtime_context.config, "farm_update.interpret")
+ )
+ var response = loads(
+ handle_request_line_with_runtime_context(
+ _v2_farm_request(), runtime_context
+ )
+ )
+ assert_equal(response["ok"].bool_value(), False)
+ assert_equal(
+ response["error"]["code"].string_value(), "capability_unavailable"
+ )
+ # Never the placeholder shortcut output, and zero provider calls.
+ assert_true(not _has_key(response, "output"))
+
+
+def test_c004_legacy_gated_operation_still_executes_when_enabled() raises:
+ with SafeTempDir() as temp_dir:
+ var runtime_context = resolve_startup_context(
+ RuntimeStartupInput(
+ env_paths_profile="repo_local",
+ env_repo_local_base_root=temp_dir,
+ user_home="/home/unused",
+ argv=List[String](),
+ )
+ )
+ runtime_context.config.effective.runtime.enable_farm_update_interpret = (
+ True
+ )
+ var legacy = (
+ '{"version":1,"request_id":"legacy-farm-1",'
+ '"capability":"farm_update.interpret","input":{'
+ + _v2_farm_source_json()
+ + "}}"
+ )
+ var response = loads(
+ handle_request_line_with_runtime_context(legacy, runtime_context)
+ )
+ assert_equal(response["ok"].bool_value(), True)
+ assert_true(_has_key(response["output"], "claims"))
+
+
+def test_c004_v2_schema_assets_and_manifest_integrity() raises:
+ var schema_dir = _dir_of_current_file() / ".." / "schemas" / "hyf_ops_v2"
+ var manifest = loads((schema_dir / "manifest.json").read_text())
+ assert_equal(manifest["selector"]["value"].string_value(), "hyf_ops_v2")
+ assert_equal(Int(manifest["envelope_version"].int_value()), 1)
+ assert_equal(
+ manifest["activation"]["guard_error_code"].string_value(),
+ "capability_unavailable",
+ )
+ assert_equal(
+ manifest["activation"]["state"].string_value(), "pre_activation"
+ )
+
+ var versions = loads((schema_dir / "version_manifest.json").read_text())[
+ "versions"
+ ]
+ var axes = List[String]()
+ axes.append("schema")
+ axes.append("taxonomy")
+ axes.append("normalization")
+ axes.append("review_policy")
+ axes.append("ranking_policy")
+ axes.append("question_bundle")
+ axes.append("model")
+ for axis in axes:
+ assert_true(_has_key(versions, axis))
+ assert_true(versions[axis].string_value() != "")
+ assert_equal(versions["schema"].string_value(), "hyf_ops_v2")
+
+ var operations = manifest["operations"]
+ for operation in operations.object_keys():
+ var entry = operations[operation]
+ var request_schema = loads(
+ (schema_dir / entry["request_schema"].string_value()).read_text()
+ )
+ var response_schema = loads(
+ (schema_dir / entry["response_schema"].string_value()).read_text()
+ )
+ assert_equal(
+ request_schema["properties"]["capability"]["const"].string_value(),
+ operation,
+ )
+ var context_def = request_schema["$defs"][
+ "farm_context" if operation
+ == "farm_update.interpret" else "buyer_context"
+ ]
+ var context_required = List[String]()
+ for value in context_def["required"].array_items():
+ context_required.append(value.string_value())
+ assert_true("actor_id" in context_required)
+ assert_true("versions" in context_required)
+ assert_true("evaluation_time" in context_required)
+ var context_properties = context_def["properties"]
+ if operation == "farm_update.interpret":
+ assert_true(_has_key(context_properties, "farm_id"))
+ else:
+ assert_true(not _has_key(context_properties, "farm_id"))
+ var response_required = List[String]()
+ for required in response_schema["required"].array_items():
+ response_required.append(required.string_value())
+ assert_equal(len(response_required), 3)
+ assert_true("version" in response_required)
+ assert_true("request_id" in response_required)
+ assert_true("ok" in response_required)
+
+ var corpus = loads((schema_dir / "examples" / "corpus.json").read_text())
+ var entries = corpus["entries"].array_items()
+ assert_true(len(entries) >= 30)
+ for entry in entries:
+ assert_true(exists(schema_dir / entry["file"].string_value()))