1010 _map_applies_to ,
1111 _merge_results ,
1212)
13+ from agent_control .telemetry .trace_context import (
14+ clear_trace_context_provider ,
15+ set_trace_context_provider ,
16+ )
1317from agent_control_models import ControlDefinition
1418
1519# =============================================================================
@@ -326,6 +330,9 @@ def test_fallback_warning_logged_only_once(self):
326330class TestCheckEvaluationWithLocal :
327331 """Tests for check_evaluation_with_local event emission and non_matches."""
328332
333+ def teardown_method (self ) -> None :
334+ clear_trace_context_provider ()
335+
329336 @pytest .mark .asyncio
330337 async def test_emits_events_when_trace_context_provided (self ):
331338 """Should emit observability events when trace_id and span_id are passed."""
@@ -398,7 +405,7 @@ async def test_emits_events_when_trace_context_provided(self):
398405
399406 @pytest .mark .asyncio
400407 async def test_emits_events_without_trace_context (self ):
401- """Should still emit events when trace_id/span_id not provided (fallback IDs) ."""
408+ """Should resolve trace context from the provider when IDs are omitted ."""
402409 from agent_control_models import EvaluationResponse , Step
403410
404411 mock_response = EvaluationResponse (
@@ -424,6 +431,12 @@ async def test_emits_events_without_trace_context(self):
424431 client = MagicMock ()
425432 client .http_client = AsyncMock ()
426433 step = Step (type = "llm" , name = "test-step" , input = "hello" )
434+ set_trace_context_provider (
435+ lambda : {
436+ "trace_id" : "a" * 32 ,
437+ "span_id" : "b" * 16 ,
438+ }
439+ )
427440
428441 with patch ("agent_control.evaluation.ControlEngine" , return_value = mock_engine ), \
429442 patch ("agent_control.evaluation.list_evaluators" , return_value = ["regex" ]), \
@@ -438,8 +451,8 @@ async def test_emits_events_without_trace_context(self):
438451 )
439452 mock_emit .assert_called_once ()
440453 call_args = mock_emit .call_args
441- assert call_args [0 ][3 ] is None # trace_id passed as None
442- assert call_args [0 ][4 ] is None # span_id passed as None
454+ assert call_args [0 ][3 ] == "a" * 32
455+ assert call_args [0 ][4 ] == "b" * 16
443456
444457 @pytest .mark .asyncio
445458 async def test_forwards_trace_headers_to_server (self ):
@@ -492,6 +505,59 @@ async def test_forwards_trace_headers_to_server(self):
492505 assert headers ["X-Trace-Id" ] == "aaaa1111bbbb2222cccc3333dddd4444"
493506 assert headers ["X-Span-Id" ] == "eeee5555ffff6666"
494507
508+ @pytest .mark .asyncio
509+ async def test_forwards_provider_trace_headers_to_server_when_ids_omitted (self ):
510+ """Server POST should resolve trace headers from the provider when omitted."""
511+ from agent_control_models import Step
512+
513+ controls = [{
514+ "id" : 1 ,
515+ "name" : "server-ctrl" ,
516+ "control" : {
517+ "condition" : {
518+ "evaluator" : {"name" : "regex" , "config" : {"pattern" : "test" }},
519+ "selector" : {"path" : "input" },
520+ },
521+ "action" : {"decision" : "deny" },
522+ "execution" : "server" ,
523+ },
524+ }]
525+
526+ mock_http_response = MagicMock ()
527+ mock_http_response .json .return_value = {
528+ "is_safe" : True ,
529+ "confidence" : 1.0 ,
530+ "matches" : None ,
531+ "errors" : None ,
532+ "non_matches" : None ,
533+ }
534+ mock_http_response .raise_for_status = MagicMock ()
535+
536+ client = MagicMock ()
537+ client .http_client = AsyncMock ()
538+ client .http_client .post = AsyncMock (return_value = mock_http_response )
539+ step = Step (type = "llm" , name = "test-step" , input = "hello" )
540+ set_trace_context_provider (
541+ lambda : {
542+ "trace_id" : "c" * 32 ,
543+ "span_id" : "d" * 16 ,
544+ }
545+ )
546+
547+ with patch ("agent_control.evaluation.list_evaluators" , return_value = ["regex" ]):
548+ await evaluation .check_evaluation_with_local (
549+ client = client ,
550+ agent_name = "agent-000000000001" ,
551+ step = step ,
552+ stage = "pre" ,
553+ controls = controls ,
554+ )
555+
556+ call_kwargs = client .http_client .post .call_args
557+ headers = call_kwargs .kwargs .get ("headers" , {})
558+ assert headers ["X-Trace-Id" ] == "c" * 32
559+ assert headers ["X-Span-Id" ] == "d" * 16
560+
495561
496562# =============================================================================
497563# control_decorators non_matches dict conversion
0 commit comments