diff --git a/docs/runtime-telemetry-history.md b/docs/runtime-telemetry-history.md index c9e3333..8575faa 100644 --- a/docs/runtime-telemetry-history.md +++ b/docs/runtime-telemetry-history.md @@ -82,7 +82,11 @@ Newer Orchestrator feeds can also declare `edgeenv_mapping_hint` fields. EdgeEnv preserves these hints and validates them when present: Orchestrator may map only supplemental candidate operation context to `runtime_telemetry_context.candidate`, while EdgeEnv remains the owner of -`runtime_telemetry_context.history.telemetry_coverage`. +`runtime_telemetry_context.history.telemetry_coverage`. When the feed declares +`candidate_context_required_fields`, EdgeEnv checks that the mapping hint and the +candidate context still include `run_id`, `telemetry_source`, `operation`, and +`resource` before the context can reach regression reports or Lab handoff +manifests. Replay validation command: @@ -121,8 +125,15 @@ The history artifact uses this top-level shape: "not_a_comparability_gate": true, "edgeenv_mapping_hint": { "copy_candidate_context_to": "runtime_telemetry_context.candidate", + "operation_context_role": "supplemental", "coverage_summary_owner": "edgeenv", - "coverage_summary_path": "runtime_telemetry_context.history.telemetry_coverage" + "coverage_summary_path": "runtime_telemetry_context.history.telemetry_coverage", + "candidate_context_required_fields": [ + "run_id", + "telemetry_source", + "operation", + "resource" + ] } } } diff --git a/tests/test_regression.py b/tests/test_regression.py index f2687f7..622b3f2 100644 --- a/tests/test_regression.py +++ b/tests/test_regression.py @@ -16,6 +16,7 @@ ORCHESTRATOR_EDGEENV_COVERAGE_SUMMARY_OWNER, ORCHESTRATOR_EDGEENV_HISTORY_COVERAGE_PATH, ORCHESTRATOR_EDGEENV_OPERATION_CONTEXT_ROLE, + ORCHESTRATOR_EDGEENV_REQUIRED_CANDIDATE_FIELDS, ) from inferedge_env.result.writer import ResultArtifactWriter from inferedge_env.runners.base import RunnerResult @@ -271,9 +272,18 @@ def test_regression_attaches_orchestrator_feed_as_supplemental_context( assert candidate_context["orchestrator_operation_context"]["edgeenv_mapping_hint"][ "coverage_summary_path" ] == ORCHESTRATOR_EDGEENV_HISTORY_COVERAGE_PATH + assert candidate_context["orchestrator_operation_context"]["edgeenv_mapping_hint"][ + "operation_context_role" + ] == ORCHESTRATOR_EDGEENV_OPERATION_CONTEXT_ROLE + assert candidate_context["orchestrator_operation_context"]["edgeenv_mapping_hint"][ + "candidate_context_required_fields" + ] == [*ORCHESTRATOR_EDGEENV_REQUIRED_CANDIDATE_FIELDS] assert candidate_context["orchestrator_operation_context"]["candidate_context"][ "operation" ]["queue_depth"] == 7 + assert candidate_context["orchestrator_operation_context"]["candidate_context"][ + "telemetry_source" + ] == "inferedge_orchestrator_operation_summary" assert ( "Orchestrator operation context is supplemental evidence, not a regression judgement." in context["notes"] diff --git a/tests/test_runtime_intelligence_lab_handoff.py b/tests/test_runtime_intelligence_lab_handoff.py index 4b5f137..68a9e4a 100644 --- a/tests/test_runtime_intelligence_lab_handoff.py +++ b/tests/test_runtime_intelligence_lab_handoff.py @@ -166,6 +166,34 @@ def test_runtime_intelligence_lab_handoff_rejects_bad_orchestrator_mapping( ) +def test_runtime_intelligence_lab_handoff_rejects_incomplete_mapping_required_fields( + tmp_path, +): + baseline_path, candidate_path, regression_path, history_path = _write_handoff_files( + tmp_path + ) + regression = json.loads(regression_path.read_text(encoding="utf-8")) + regression["runtime_telemetry_context"]["candidate"][ + "orchestrator_operation_context" + ]["edgeenv_mapping_hint"]["candidate_context_required_fields"] = [ + "run_id", + "operation", + "resource", + ] + regression_path.write_text(json.dumps(regression), encoding="utf-8") + + with pytest.raises( + RuntimeIntelligenceLabHandoffError, + match="candidate_context_required_fields must include telemetry_source", + ): + build_runtime_intelligence_lab_handoff_manifest( + baseline_result_path=baseline_path, + candidate_result_path=candidate_path, + edgeenv_regression_report_path=regression_path, + telemetry_history_path=history_path, + ) + + def _write_handoff_files(tmp_path): baseline_path = tmp_path / "baseline-result.json" candidate_path = tmp_path / "candidate-result.json" diff --git a/tests/test_runtime_telemetry_history.py b/tests/test_runtime_telemetry_history.py index 5505a75..ac49d2e 100644 --- a/tests/test_runtime_telemetry_history.py +++ b/tests/test_runtime_telemetry_history.py @@ -14,6 +14,7 @@ ORCHESTRATOR_EDGEENV_COVERAGE_SUMMARY_OWNER, ORCHESTRATOR_EDGEENV_HISTORY_COVERAGE_PATH, ORCHESTRATOR_EDGEENV_OPERATION_CONTEXT_ROLE, + ORCHESTRATOR_EDGEENV_REQUIRED_CANDIDATE_FIELDS, ORCHESTRATOR_TELEMETRY_FEED_SCHEMA_VERSION, RUNTIME_TELEMETRY_HISTORY_SCHEMA_VERSION, RuntimeTelemetryHistoryError, @@ -201,6 +202,11 @@ def test_build_runtime_telemetry_history_attaches_orchestrator_feed_context( assert context["edgeenv_mapping_hint"]["coverage_summary_path"] == ( ORCHESTRATOR_EDGEENV_HISTORY_COVERAGE_PATH ) + assert context["edgeenv_mapping_hint"]["candidate_context_required_fields"] == [ + *ORCHESTRATOR_EDGEENV_REQUIRED_CANDIDATE_FIELDS + ] + for field in ORCHESTRATOR_EDGEENV_REQUIRED_CANDIDATE_FIELDS: + assert field in context["candidate_context"] assert "not a regression judgement" in payload["notes"][3]