diff --git a/manifests/agent.yml b/manifests/agent.yml index b1ee51a8645..7faba7fff26 100644 --- a/manifests/agent.yml +++ b/manifests/agent.yml @@ -46,6 +46,9 @@ manifest: - declaration: bug (DEBUG-5421) component_version: ">=7.79.0-devel <7.84.4" weblog: [chi, echo, fiber-v2-orchestrion, gin, net-http, net-http-orchestrion, uds-echo] + tests/ffe/test_flag_eval_evp.py::Test_FFE_EVP_Flagevaluation_SDK_Identity: + - declaration: missing_feature (Agent forwarding of EVP producer identity requires 7.84) + component_version: '<7.84.0' tests/integrations/test_open_telemetry.py::Test_MsSql::test_error_exception_event: - component_version: "<7.75.0-0" declaration: irrelevant (new behavior implement in 7.75.0) diff --git a/manifests/golang.yml b/manifests/golang.yml index bd5a9473a42..0c416b76d37 100644 --- a/manifests/golang.yml +++ b/manifests/golang.yml @@ -1771,6 +1771,7 @@ manifest: tests/ffe/test_flag_eval_evp.py::Test_FFE_EVP_Flagevaluation_ObserveFullData_Absent_Hashed: v2.11.0-dev tests/ffe/test_flag_eval_evp.py::Test_FFE_EVP_Flagevaluation_ObserveFullData_False_Hashed: v2.11.0-dev tests/ffe/test_flag_eval_evp.py::Test_FFE_EVP_Flagevaluation_ObserveFullData_True_Unhashed: v2.11.0-dev # Easy win for chi, echo, gin, net-http, net-http-orchestrion, uds-echo and version 2.10.0 + tests/ffe/test_flag_eval_evp.py::Test_FFE_EVP_Flagevaluation_SDK_Identity: v2.11.0 tests/ffe/test_flag_eval_metrics.py: v2.8.0 tests/ffe/test_flag_eval_metrics.py::Test_FFE_Eval_Metric_Parse_Error_Invalid_Regex: irrelevant (Go validates regex at config load time) tests/ffe/test_flag_eval_metrics.py::Test_FFE_Eval_Nested_Attributes_Ignored: irrelevant (FFL-1980) diff --git a/manifests/python.yml b/manifests/python.yml index fd11d834e4a..2f2b981b702 100644 --- a/manifests/python.yml +++ b/manifests/python.yml @@ -2287,6 +2287,7 @@ manifest: tests/ffe/test_flag_eval_evp.py::Test_FFE_EVP_Flagevaluation_ObserveFullData_False_Hashed: missing_feature (FFL-2446) tests/ffe/test_flag_eval_evp.py::Test_FFE_EVP_Flagevaluation_ObserveFullData_True_Unhashed: missing_feature (FFL-2446) tests/ffe/test_flag_eval_evp.py::Test_FFE_EVP_Flagevaluation_Runtime_Default: missing_feature (FFL-2446) + tests/ffe/test_flag_eval_evp.py::Test_FFE_EVP_Flagevaluation_SDK_Identity: v4.16.0 tests/ffe/test_flag_eval_metrics.py: v4.7.0 tests/ffe/test_flag_eval_metrics.py::Test_FFE_Eval_Nested_Attributes_Ignored: irrelevant (FFL-1980) tests/integration_frameworks: diff --git a/tests/ffe/README.md b/tests/ffe/README.md index 58c054e0d93..c421dc7e31d 100644 --- a/tests/ffe/README.md +++ b/tests/ffe/README.md @@ -33,6 +33,15 @@ The protobuf fixture `tests/parametric/test_ffe/rules-wire.json` comes from `ope ./run.sh FEATURE_FLAGGING_AND_EXPERIMENTATION --library ``` +## SDK identity through the Agent + +`Test_FFE_EVP_Flagevaluation_SDK_Identity` checks SDK origin and version headers on +Agent-to-intake captures while retaining the shared payload and evaluation-count checks. +The case requires Agent 7.84+ and is enabled for Go 2.11.0+, Python 4.16.0+, and +Node.js express5 under its existing version gate. Other languages retain their +current exclusions. Event-level `source` is rejected: source identity belongs in +the request headers. + ## Per-subject evaluation telemetry opt-out All four test classes report under [#649: Per-subject evaluation telemetry opt-out](https://feature-parity.us1.prod.dog/#/?feature=649). diff --git a/tests/ffe/test_flag_eval_evp.py b/tests/ffe/test_flag_eval_evp.py index bcf93e46a17..d6a35f70cb0 100644 --- a/tests/ffe/test_flag_eval_evp.py +++ b/tests/ffe/test_flag_eval_evp.py @@ -9,6 +9,7 @@ from tests.ffe.utils.evp import ( assert_agentless_evp_intake_request, assert_agentless_evp_topology, + assert_evp_sdk_identity, feature_flagging_evp_egress, register_expected_evp_capture, ) @@ -163,6 +164,7 @@ def assert_batch_context(batch: JSON) -> None: def assert_event_contract(event: JSON, flag_key: str) -> None: assert_no_reason_field(event) + assert "source" not in event, "SDK identity belongs in EVP headers, not event-level source" assert "targeting_rule" not in event, "targeting_rule must be omitted without real rule metadata" flag = event.get("flag") @@ -322,6 +324,26 @@ class Test_FFE_EVP_Flagevaluation_Egress_Datadog_Agent(FlagevaluationEgressContr pass +@scenarios.feature_flagging_and_experimentation +@features.feature_flags_evp_flagevaluation +class Test_FFE_EVP_Flagevaluation_SDK_Identity(FlagevaluationEgressContract): + # Agent 7.84+ forwards producer headers; the Agent manifest gates this case. + # Library manifests separately gate SDK support. The Agent egress test retains + # payload coverage on older Agents wherever enabled by the library manifest. + flag_key = "evp-sdk-identity-flag" + + def test_ffe_evp_flagevaluation_egress(self) -> None: + super().test_ffe_evp_flagevaluation_egress() + # interfaces.agent captures Agent-to-intake traffic, after forwarding. + for data in interfaces.agent.get_data(path_filters=EVP_FLAGEVALUATIONS_PATH): + if evp_flagevaluation_events_from_data(cast("JSON", data), self.flag_key, self.targeting_key): + assert_evp_sdk_identity( + cast("JSON", data), + library_name=context.library.name, + library_version=context.library.raw_version, + ) + + @scenario_crash @scenarios.feature_flagging_and_experimentation_agentless_direct @features.feature_flags_evp_flagevaluation diff --git a/tests/ffe/utils/evp.py b/tests/ffe/utils/evp.py index 8632b2e574f..cf35612f9bc 100644 --- a/tests/ffe/utils/evp.py +++ b/tests/ffe/utils/evp.py @@ -44,6 +44,7 @@ "golang": "dd-trace-go", "java": "dd-trace-java", "nodejs": "dd-trace-js", + "php": "dd-trace-php", "python": "dd-trace-py", "ruby": "dd-trace-rb", } @@ -134,7 +135,7 @@ def expected_evp_origin(library_name: str) -> str: def expected_evp_origin_version(library_name: str, library_version: str) -> str: - """Return the tracer's canonical wire version for direct EVP identity. + """Return the tracer's canonical wire version for EVP identity. System-tests deliberately removes Go's leading ``v`` when it parses the component version for manifest comparisons. dd-trace-go's public version @@ -161,6 +162,13 @@ def _assert_single_header(data: dict[str, Any], name: str, expected: str | set[s assert values[0] in allowed, f"expected {name} in {sorted(allowed)!r}, got {values[0]!r}" +def assert_evp_sdk_identity(data: dict[str, Any], *, library_name: str, library_version: str) -> None: + """Require the SDK source identity on an intake-side capture.""" + _assert_single_header(data, "DD-EVP-ORIGIN", expected_evp_origin(library_name)) + assert library_version.strip(), "expected library version must be non-empty" + _assert_single_header(data, "DD-EVP-ORIGIN-VERSION", expected_evp_origin_version(library_name, library_version)) + + def assert_agentless_evp_intake_request( data: dict[str, Any], *, @@ -197,12 +205,7 @@ def assert_agentless_evp_intake_request( # SDK local-route identity and credential omission remain covered by SDK unit tests. return - _assert_single_header(data, "DD-EVP-ORIGIN", expected_evp_origin(library_name)) - _assert_single_header( - data, - "DD-EVP-ORIGIN-VERSION", - expected_evp_origin_version(library_name, library_version), - ) + assert_evp_sdk_identity(data, library_name=library_name, library_version=library_version) def assert_no_evp_proxy_requests(captures: Iterable[dict[str, Any]]) -> None: