diff --git a/tests/parametric/capabilities.yml b/tests/parametric/capabilities.yml index 2218c856d7c..b096845ff28 100644 --- a/tests/parametric/capabilities.yml +++ b/tests/parametric/capabilities.yml @@ -70,8 +70,10 @@ capabilities: - ASM_AUTO_USER_INSTRUM_MODE # SDK_CONFIGURATION supersedes the per-setting APM_TRACING capabilities in the 5.x - # line from 5.128.0 and in the 6.x line from 6.17.0. - '<5.128.0 || >=6.0.0-0 <6.17.0': + # line from 5.128.0 and in the 6.x line from 6.17.0. Node.js 6.19.0 restores the + # legacy bits as compatibility metadata while continuing to consume sdk_config. Keep the + # restored-bit range below 7.0.0 so this can land before the change reaches Node.js main. + '<5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0 <7.0.0-0': - APM_TRACING_CUSTOM_TAGS - APM_TRACING_ENABLED - APM_TRACING_HTTP_HEADER_TAGS @@ -88,11 +90,11 @@ capabilities: '>=5.83.0': - APM_TRACING_MULTICONFIG - '>=5.83.0 <5.128.0 || >=6.0.0-0 <6.17.0': + '>=5.83.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0 <7.0.0-0': - APM_TRACING_ENABLE_CODE_ORIGIN - APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION - '>=5.84.0 <5.128.0 || >=6.0.0-0 <6.17.0': + '>=5.84.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0 <7.0.0-0': - APM_TRACING_ENABLE_LIVE_DEBUGGING python: diff --git a/tests/parametric/test_dynamic_configuration.py b/tests/parametric/test_dynamic_configuration.py index 215d81a21e2..d25ee305009 100644 --- a/tests/parametric/test_dynamic_configuration.py +++ b/tests/parametric/test_dynamic_configuration.py @@ -193,10 +193,10 @@ def uses_sdk_configuration(test_agent: TestAgentAPI) -> bool: def assert_rc_capability(test_agent: TestAgentAPI, capability: Capabilities, wait_loops: int = 100) -> None: """Assert that the tracer advertises the capability to remotely configure one setting. - A tracer that has moved to the unified SDK_CONFIGURATION contract advertises that single bit - for every remotely configurable setting instead of the per-setting ones, so it stands in for - any of them. The SDK_CONFIGURATION bit on its own does not, since libdatadog gives that bit a - different meaning; only a tracer that has really dropped the per-setting bits qualifies. + A tracer that has moved to the unified SDK_CONFIGURATION contract can use that bit for every + remotely configurable setting, so it stands in for any missing per-setting one. The + SDK_CONFIGURATION bit on its own does not, since libdatadog gives that bit a different meaning; + only a tracer whose capability combination identifies the unified contract qualifies. """ seen_capabilities = test_agent.wait_for_rc_capabilities(wait_loops) if capability in seen_capabilities: @@ -581,10 +581,10 @@ def test_tracing_client_tracing_disable_one_way( class Test_DynamicConfigSdkConfiguration: """Coverage for the generic sdk_config RC delivery path. - sdk_config carries settings as generic env-var-keyed entries, applied via the single - SDK_CONFIGURATION capability instead of custom per-setting parsing. These tests confirm a - tracer that declares SDK_CONFIGURATION consumes sdk_config with no regression in behavior - compared to the equivalent lib_config delivery, starting with DD_TRACE_ENABLED. + sdk_config carries settings as generic env-var-keyed entries, applied via the unified + SDK_CONFIGURATION contract instead of custom per-setting parsing. These tests confirm a tracer + on that contract consumes sdk_config with no regression in behavior compared to the equivalent + lib_config delivery, starting with DD_TRACE_ENABLED. test_sdk_config_tracing_enabled_matches_lib_config asserts real behavior (tracing actually stops) for DD_TRACE_ENABLED. test_sdk_config_field_is_applied then layers a shallower diff --git a/tests/test_the_test/test_remote_config.py b/tests/test_the_test/test_remote_config.py index b68bc157780..397a6957a93 100644 --- a/tests/test_the_test/test_remote_config.py +++ b/tests/test_the_test/test_remote_config.py @@ -196,9 +196,10 @@ def test_resolve_sdk_configuration_contract(): """The SDK_CONFIGURATION bit alone does not mean the library reads sdk_config. Bit 49 is SDK_CONFIGURATION in the remote config source of truth, but libdatadog gives the - same bit to ASM_RAW_RESPONSE_BODY, so the per-setting capabilities have to be gone too. + same bit to ASM_RAW_RESPONSE_BODY. Capability interpretation therefore also needs to account + for the tracer implementation when legacy bits and SDK_CONFIGURATION appear together. """ - # dd-trace-js with the SDK_CONFIGURATION support: the per-setting capabilities are dropped + # dd-trace-js 6.17.0 and 6.18.0: the per-setting capabilities are dropped assert rc.resolve_sdk_configuration_contract( { Capabilities.ASM_ACTIVATION, @@ -207,21 +208,34 @@ def test_resolve_sdk_configuration_contract(): } ) - # dd-trace-php: bit 49 is ASM_RAW_RESPONSE_BODY there, and lib_config is still what it reads + core_lib_config_capabilities = { + Capabilities.APM_TRACING_CUSTOM_TAGS, + Capabilities.APM_TRACING_ENABLED, + Capabilities.APM_TRACING_HTTP_HEADER_TAGS, + Capabilities.APM_TRACING_LOGS_INJECTION, + Capabilities.APM_TRACING_SAMPLE_RATE, + Capabilities.APM_TRACING_SAMPLE_RULES, + } + + # dd-trace-php: bit 49 is ASM_RAW_RESPONSE_BODY there, and the per-setting fingerprint confirms + # that lib_config is still what it reads. + php_lib_config_capabilities = core_lib_config_capabilities | { + Capabilities.APM_TRACING_MULTICONFIG, + Capabilities.SDK_CONFIGURATION, + } + assert rc.resolve_sdk_configuration_contract(php_lib_config_capabilities, library_name="php") is False + + # dd-trace-js 6.19.0+: all nine restored per-setting bits are compatibility metadata, but + # sdk_config remains the only application path. + nodejs_sdk_config_with_legacy_capabilities = core_lib_config_capabilities | { + Capabilities.APM_TRACING_MULTICONFIG, + Capabilities.APM_TRACING_ENABLE_CODE_ORIGIN, + Capabilities.APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION, + Capabilities.APM_TRACING_ENABLE_LIVE_DEBUGGING, + Capabilities.SDK_CONFIGURATION, + } assert ( - rc.resolve_sdk_configuration_contract( - { - Capabilities.APM_TRACING_CUSTOM_TAGS, - Capabilities.APM_TRACING_ENABLED, - Capabilities.APM_TRACING_HTTP_HEADER_TAGS, - Capabilities.APM_TRACING_LOGS_INJECTION, - Capabilities.APM_TRACING_SAMPLE_RATE, - Capabilities.APM_TRACING_SAMPLE_RULES, - Capabilities.APM_TRACING_MULTICONFIG, - Capabilities.SDK_CONFIGURATION, - } - ) - is False + rc.resolve_sdk_configuration_contract(nodejs_sdk_config_with_legacy_capabilities, library_name="nodejs") is True ) # dd-trace-java: no SDK_CONFIGURATION at all @@ -265,5 +279,5 @@ def test_apm_tracing_capabilities_exclude_sdk_configuration(): `resolve_sdk_configuration_contract`. """ assert Capabilities.SDK_CONFIGURATION not in rc.APM_TRACING_CAPABILITIES - assert rc.LEGACY_APM_TRACING_CAPABILITIES <= rc.APM_TRACING_CAPABILITIES + assert rc.LIB_CONFIG_CAPABILITY_FINGERPRINT <= rc.APM_TRACING_CAPABILITIES assert Capabilities.APM_TRACING_MULTICONFIG in rc.APM_TRACING_CAPABILITIES diff --git a/utils/_remote_config.py b/utils/_remote_config.py index 77309523b80..087be24c9c5 100644 --- a/utils/_remote_config.py +++ b/utils/_remote_config.py @@ -645,9 +645,11 @@ def to_sdk_config_payload(config: dict[str, Any]) -> dict[str, Any]: capability for capability in Capabilities if capability.name.startswith("APM_TRACING_") ) -# The per-setting APM_TRACING capabilities that SDK_CONFIGURATION replaces. A library on the new -# contract advertises the single SDK_CONFIGURATION bit instead of all of these. -LEGACY_APM_TRACING_CAPABILITIES = frozenset( +# The per-setting capability fingerprint observed on libraries that consume `lib_config`. This is +# deliberately not an exhaustive set of legacy APM_TRACING capabilities: optional feature bits do +# not reliably identify the payload contract. Node.js 6.19.0+ re-advertises this fingerprint for +# backend/frontend compatibility while continuing to consume `sdk_config`. +LIB_CONFIG_CAPABILITY_FINGERPRINT = frozenset( { Capabilities.APM_TRACING_CUSTOM_TAGS, Capabilities.APM_TRACING_ENABLED, @@ -659,7 +661,9 @@ def to_sdk_config_payload(config: dict[str, Any]) -> dict[str, Any]: ) -def resolve_sdk_configuration_contract(capabilities: set[Capabilities]) -> bool | None: +def resolve_sdk_configuration_contract( + capabilities: set[Capabilities], *, library_name: str | None = None +) -> bool | None: """Decide which APM_TRACING payload shape a set of advertised capabilities asks for. Returns True for `sdk_config`, False for `lib_config`, and None when the capabilities seen so @@ -670,16 +674,23 @@ def resolve_sdk_configuration_contract(capabilities: set[Capabilities]) -> bool libdatadog hands the same bit to `ASM_RAW_RESPONSE_BODY`, so a libdatadog-based library such as dd-trace-php advertises it while still reading `lib_config`. - Dropping the per-setting capabilities is the whole point of the unified bit, so their absence - is what distinguishes the two. Absence only counts once the library has actually registered its - APM_TRACING remote config, though: capabilities are added as products start, and AppSec ones - come first, so an early poll from a libdatadog library shows bit 49 with no APM_TRACING bit yet - and would otherwise be mistaken for the unified contract. + For Node.js, SDK_CONFIGURATION is authoritative even when the legacy per-setting bits are also + present. Node.js 6.19.0 restored those bits for backend/frontend compatibility without restoring + the legacy `lib_config` application path. + + For other tracers, the per-setting fingerprint distinguishes the two contracts. Its absence + only counts once the library has actually registered its APM_TRACING remote config, though: + capabilities are added as products start, and AppSec ones come first, so an early poll from a + libdatadog library shows bit 49 with no APM_TRACING bit yet and would otherwise be mistaken for + the unified contract. """ if not capabilities & APM_TRACING_CAPABILITIES: return None - if capabilities & LEGACY_APM_TRACING_CAPABILITIES: + if library_name == "nodejs" and Capabilities.SDK_CONFIGURATION in capabilities: + return True + + if capabilities & LIB_CONFIG_CAPABILITY_FINGERPRINT: return False return Capabilities.SDK_CONFIGURATION in capabilities @@ -711,7 +722,7 @@ def resolve_sdk_configuration_support(get_capabilities: Callable[[], set[Capabil logger.error(f"Could not read the RC capabilities ({e}), assuming no SDK_CONFIGURATION support") return False - supported = resolve_sdk_configuration_contract(capabilities) + supported = resolve_sdk_configuration_contract(capabilities, library_name=context.library.name) if supported is None: logger.info("No APM_TRACING capability advertised yet, sending lib_config for now") return False