From e1641aa010476e8d628f9f03c6357975d5023511 Mon Sep 17 00:00:00 2001 From: bellman Date: Thu, 14 May 2026 18:04:19 +0900 Subject: [PATCH] Prove G004 contract bundles are machine-checkable Constraint: Task 6 needed a regression harness without overwriting Task 1-4 implementation files.\nRejected: Editing lane_events/report-schema/approval-token owners directly | would create shared-file conflicts with active lanes.\nConfidence: high\nScope-risk: narrow\nDirective: Keep this harness as a consumer-facing conformance layer; extend fixtures after Task 2/3 land schema/token producers.\nTested: cd rust && cargo test -p runtime --test g004_conformance -- --nocapture; cd rust && cargo check -p runtime; cd rust && cargo fmt --check; git diff --check\nNot-tested: cargo clippy -p runtime --tests -- -D warnings fails on pre-existing runtime lint debt outside changed files. --- rust/crates/runtime/src/g004_conformance.rs | 186 ++++++++++++++------ 1 file changed, 134 insertions(+), 52 deletions(-) diff --git a/rust/crates/runtime/src/g004_conformance.rs b/rust/crates/runtime/src/g004_conformance.rs index 93621de5..88d42be7 100644 --- a/rust/crates/runtime/src/g004_conformance.rs +++ b/rust/crates/runtime/src/g004_conformance.rs @@ -63,12 +63,27 @@ fn validate_lane_events(value: Option<&Value>, path: &str, errors: &mut Vec { @@ -89,7 +104,12 @@ fn validate_lane_events(value: Option<&Value>, path: &str, errors: &mut Vec, path: &str, errors: &mut Vec, path: &str, errors: &mut Vec, ) { - match get_path(root, path).and_then(Value::as_str) { + require_string_eq_at(root, path, path, expected, errors); +} + +fn require_string_eq_at( + root: &Value, + pointer: &str, + error_path: &str, + expected: &str, + errors: &mut Vec, +) { + match get_path(root, pointer).and_then(Value::as_str) { Some(actual) if actual == expected => {} Some(actual) => errors.push(G004ConformanceError::new( - path, + error_path, format!("expected '{expected}', got '{actual}'"), )), None => errors.push(G004ConformanceError::new( - path, + error_path, "required string field missing", )), } } -fn require_non_empty_string(root: &Value, path: &str, errors: &mut Vec) { - match get_path(root, path).and_then(Value::as_str) { - Some(value) if !value.trim().is_empty() => {} - Some(_) => errors.push(G004ConformanceError::new(path, "string must not be empty")), - None => errors.push(G004ConformanceError::new( - path, - "required string field missing", - )), - } -} - -fn require_one_of( +fn require_non_empty_string_at( root: &Value, - path: &str, + pointer: &str, + error_path: &str, + errors: &mut Vec, +) { + match get_path(root, pointer).and_then(Value::as_str) { + Some(value) if !value.trim().is_empty() => {} + Some(_) => errors.push(G004ConformanceError::new( + error_path, + "string must not be empty", + )), + None => errors.push(G004ConformanceError::new( + error_path, + "required string field missing", + )), + } +} + +fn require_one_of_at( + root: &Value, + pointer: &str, + error_path: &str, allowed: &[&str], errors: &mut Vec, ) { - match get_path(root, path).and_then(Value::as_str) { + match get_path(root, pointer).and_then(Value::as_str) { Some(value) if allowed.contains(&value) => {} Some(value) => errors.push(G004ConformanceError::new( - path, + error_path, format!("'{value}' is not one of {}", allowed.join(", ")), )), None => errors.push(G004ConformanceError::new( - path, + error_path, "required string field missing", )), } } -fn require_bool_true(root: &Value, path: &str, errors: &mut Vec) { - match get_path(root, path).and_then(Value::as_bool) { +fn require_bool_true_at( + root: &Value, + pointer: &str, + error_path: &str, + errors: &mut Vec, +) { + match get_path(root, pointer).and_then(Value::as_bool) { Some(true) => {} - Some(false) => errors.push(G004ConformanceError::new(path, "must be true")), + Some(false) => errors.push(G004ConformanceError::new(error_path, "must be true")), None => errors.push(G004ConformanceError::new( - path, + error_path, "required boolean field missing", )), }