commit 4b513e66d3b3e81e73b94b8262b89824f4c6acd4
parent ab73834876afd342327510849579bc572f6286d2
Author: triesap <tyson@radroots.org>
Date: Thu, 24 Sep 2026 16:05:55 +0000
C004: verify escaped duplicate keys, drop dead helpers and correct semantic-case owners
Diffstat:
3 files changed, 31 insertions(+), 12 deletions(-)
diff --git a/schemas/hyf_ops_v2/semantic-cases.json b/schemas/hyf_ops_v2/semantic-cases.json
@@ -4,7 +4,9 @@
"revision": "hyf_ops_v2",
"note": "Semantic obligations that JSON Schema alone cannot express. Each case is labelled with the enforcing owner and the C004 control that exercises it. No case is claimed enforced by a regex or by the reference validator; C008/C009/C023 own the full typed/semantic enforcement.",
"cases": [
- {"id": "duplicate_key_identity", "path": "input.source.revision", "expect": "reject", "wire": "invalid_request", "owner": "C004", "enforced_by": "strict duplicate-key detection in hyf_core.operation_context and Mojo control test"},
+ {"id": "duplicate_key_identity", "path": "context/versions duplicate object keys", "expect": "reject", "wire": "invalid_request", "owner": "C004", "enforced_by": "strict duplicate-key detection in hyf_core.operation_context and Mojo control test"},
+ {"id": "input_duplicate_key_identity", "path": "input.* duplicate object keys", "expect": "reject", "wire": "invalid_request", "owner": "C008/C009", "enforced_by": "C004 keeps input as a Value; typed strict input decode is C008/C009"},
+ {"id": "whitespace_only_identity_strings", "path": "blank-only actor_id/farm_id/version strings", "expect": "reject", "wire": "invalid_request", "owner": "C004", "enforced_by": "runtime _require_nonblank is intentionally stricter than schema minLength:1; Mojo control test"},
{"id": "escaped_duplicate_key_identity", "path": "context.actor_id vs context.\\u0061ctor_id", "expect": "reject", "wire": "invalid_request", "owner": "C004", "enforced_by": "decoded key identity in duplicate detection"},
{"id": "taxonomy_version_mismatch", "path": "context.versions.taxonomy != input.references.taxonomy.version", "expect": "reject", "wire": "invalid_request", "owner": "C023", "enforced_by": "C004 labelled semantic script; C023 typed consistency"},
{"id": "normalization_version_mismatch", "path": "context.versions.normalization != input.references.normalization.version", "expect": "reject", "wire": "invalid_request", "owner": "C023", "enforced_by": "C004 labelled semantic script; C023 typed consistency"},
diff --git a/src/hyf_core/operation_context.mojo b/src/hyf_core/operation_context.mojo
@@ -74,11 +74,6 @@ def _require_allowed_keys(
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
@@ -108,12 +103,6 @@ def _required_string(
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
diff --git a/tests/test_hyf.mojo b/tests/test_hyf.mojo
@@ -1888,3 +1888,31 @@ def test_c004_v2_schema_assets_and_manifest_integrity() raises:
assert_true(len(entries) >= 30)
for entry in entries:
assert_true(exists(schema_dir / entry["file"].string_value()))
+
+
+def test_c004_operation_v2_escaped_duplicate_key_is_rejected() raises:
+ # \u0061 is 'a'; the parser decodes key escapes, so the second key is a
+ # duplicate actor_id and must be rejected by decoded-key identity.
+ var escaped = _v2_farm_request_with_context(
+ '"evaluation_time":"2026-09-24T09:00:00-07:00",'
+ + _v2_versions_json()
+ + ',"actor_id":"farm-1","\\u0061ctor_id":"farm-2","farm_id":"farm-1"'
+ )
+ assert_true(_decode_error_message(escaped).find("duplicate") >= 0)
+
+
+def test_c004_operation_v2_whitespace_only_identity_is_rejected() raises:
+ var blank_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(blank_actor).find("blank") >= 0)
+
+ var blank_version = _v2_farm_request_with_context(
+ '"evaluation_time":"2026-09-24T09:00:00-07:00","versions":{'
+ '"schema":"hyf_ops_v2","taxonomy":" ","normalization":"n",'
+ '"review_policy":"r","ranking_policy":"k","question_bundle":"q",'
+ '"model":"m"},"actor_id":"farm-1","farm_id":"farm-1"'
+ )
+ assert_true(_decode_error_message(blank_version).find("blank") >= 0)