From a3df0be9b7e69d92fac176661d7c95b482d35fb6 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 1 Oct 2026 13:35:32 -0400 Subject: [PATCH 01/12] fix(remote-config): support restored Node.js capability bits --- tests/parametric/capabilities.yml | 9 +++--- .../parametric/test_dynamic_configuration.py | 16 +++++------ tests/test_the_test/test_remote_config.py | 20 +++++++++++-- utils/_remote_config.py | 28 +++++++++++++------ 4 files changed, 50 insertions(+), 23 deletions(-) diff --git a/tests/parametric/capabilities.yml b/tests/parametric/capabilities.yml index 2218c856d7c..26b98ded803 100644 --- a/tests/parametric/capabilities.yml +++ b/tests/parametric/capabilities.yml @@ -70,8 +70,9 @@ 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. + '<5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0': - APM_TRACING_CUSTOM_TAGS - APM_TRACING_ENABLED - APM_TRACING_HTTP_HEADER_TAGS @@ -88,11 +89,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': - 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': - 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..4d69d0718a7 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, @@ -224,6 +225,21 @@ def test_resolve_sdk_configuration_contract(): is False ) + # dd-trace-js 6.19.0+: legacy bits are compatibility metadata, but sdk_config remains the + # only application path. The same capability combination still means lib_config for PHP. + mixed_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, + Capabilities.APM_TRACING_MULTICONFIG, + Capabilities.SDK_CONFIGURATION, + } + assert rc.resolve_sdk_configuration_contract(mixed_capabilities, library_name="nodejs") is True + assert rc.resolve_sdk_configuration_contract(mixed_capabilities, library_name="php") is False + # dd-trace-java: no SDK_CONFIGURATION at all assert ( rc.resolve_sdk_configuration_contract({Capabilities.APM_TRACING_SAMPLE_RATE, Capabilities.APM_TRACING_ENABLED}) diff --git a/utils/_remote_config.py b/utils/_remote_config.py index 77309523b80..c1e55408545 100644 --- a/utils/_remote_config.py +++ b/utils/_remote_config.py @@ -645,8 +645,9 @@ 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. +# The per-setting APM_TRACING capabilities that SDK_CONFIGURATION originally replaced. Most +# libraries on the new contract advertise the single SDK_CONFIGURATION bit instead of these; +# Node.js 6.19.0+ advertises both for backend/frontend compatibility while consuming sdk_config. LEGACY_APM_TRACING_CAPABILITIES = frozenset( { Capabilities.APM_TRACING_CUSTOM_TAGS, @@ -659,7 +660,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,15 +673,22 @@ 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, dropping the per-setting capabilities 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. """ if not capabilities & APM_TRACING_CAPABILITIES: return None + if library_name == "nodejs" and Capabilities.SDK_CONFIGURATION in capabilities: + return True + if capabilities & LEGACY_APM_TRACING_CAPABILITIES: return False @@ -711,7 +721,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 From 3d430f1fa885b7c372a2b5fde5432d793526f250 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 1 Oct 2026 13:45:37 -0400 Subject: [PATCH 02/12] fix(nodejs): scope restored capabilities to 6.x --- tests/parametric/capabilities.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/parametric/capabilities.yml b/tests/parametric/capabilities.yml index 26b98ded803..c2e00568a52 100644 --- a/tests/parametric/capabilities.yml +++ b/tests/parametric/capabilities.yml @@ -71,8 +71,9 @@ capabilities: # 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. Node.js 6.19.0 restores the - # legacy bits as compatibility metadata while continuing to consume sdk_config. - '<5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0': + # legacy bits as compatibility metadata while continuing to consume sdk_config. Keep the + # restored-bit range below 7.0.0: the main branch reports 7.0.0-pre before this change lands. + '<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 @@ -89,11 +90,11 @@ capabilities: '>=5.83.0': - APM_TRACING_MULTICONFIG - '>=5.83.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.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 || >=6.19.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: From 477b105da433e106ca070d4e2b203a3ee13eb379 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 1 Oct 2026 14:26:19 -0400 Subject: [PATCH 03/12] refactor(remote-config): clarify lib config capability fingerprint --- tests/test_the_test/test_remote_config.py | 2 +- utils/_remote_config.py | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/test_the_test/test_remote_config.py b/tests/test_the_test/test_remote_config.py index 4d69d0718a7..5f9b2bc62d4 100644 --- a/tests/test_the_test/test_remote_config.py +++ b/tests/test_the_test/test_remote_config.py @@ -281,5 +281,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 c1e55408545..087be24c9c5 100644 --- a/utils/_remote_config.py +++ b/utils/_remote_config.py @@ -645,10 +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 originally replaced. Most -# libraries on the new contract advertise the single SDK_CONFIGURATION bit instead of these; -# Node.js 6.19.0+ advertises both for backend/frontend compatibility while consuming sdk_config. -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, @@ -677,7 +678,7 @@ def resolve_sdk_configuration_contract( present. Node.js 6.19.0 restored those bits for backend/frontend compatibility without restoring the legacy `lib_config` application path. - For other tracers, dropping the per-setting capabilities is what distinguishes the two. Absence + 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 @@ -689,7 +690,7 @@ def resolve_sdk_configuration_contract( if library_name == "nodejs" and Capabilities.SDK_CONFIGURATION in capabilities: return True - if capabilities & LEGACY_APM_TRACING_CAPABILITIES: + if capabilities & LIB_CONFIG_CAPABILITY_FINGERPRINT: return False return Capabilities.SDK_CONFIGURATION in capabilities From d8aae2e952970b074763508df7348e245b13bf52 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 1 Oct 2026 14:28:55 -0400 Subject: [PATCH 04/12] test(remote-config): model exact Node capability set --- tests/test_the_test/test_remote_config.py | 35 +++++++++-------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/tests/test_the_test/test_remote_config.py b/tests/test_the_test/test_remote_config.py index 5f9b2bc62d4..951a825172c 100644 --- a/tests/test_the_test/test_remote_config.py +++ b/tests/test_the_test/test_remote_config.py @@ -208,26 +208,9 @@ 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 - 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 - ) - - # dd-trace-js 6.19.0+: legacy bits are compatibility metadata, but sdk_config remains the - # only application path. The same capability combination still means lib_config for PHP. - mixed_capabilities = { + # 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 = { Capabilities.APM_TRACING_CUSTOM_TAGS, Capabilities.APM_TRACING_ENABLED, Capabilities.APM_TRACING_HTTP_HEADER_TAGS, @@ -237,8 +220,16 @@ def test_resolve_sdk_configuration_contract(): Capabilities.APM_TRACING_MULTICONFIG, Capabilities.SDK_CONFIGURATION, } - assert rc.resolve_sdk_configuration_contract(mixed_capabilities, library_name="nodejs") is True - assert rc.resolve_sdk_configuration_contract(mixed_capabilities, library_name="php") is False + 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_6_19_apm_capabilities = php_lib_config_capabilities | { + Capabilities.APM_TRACING_ENABLE_CODE_ORIGIN, + Capabilities.APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION, + Capabilities.APM_TRACING_ENABLE_LIVE_DEBUGGING, + } + assert rc.resolve_sdk_configuration_contract(nodejs_6_19_apm_capabilities, library_name="nodejs") is True # dd-trace-java: no SDK_CONFIGURATION at all assert ( From b64c305295c13f0f7be5b79823a2a545e25cb922 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 1 Oct 2026 14:40:41 -0400 Subject: [PATCH 05/12] test(remote-config): clarify capability fixtures --- tests/test_the_test/test_remote_config.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/test_the_test/test_remote_config.py b/tests/test_the_test/test_remote_config.py index 951a825172c..397a6957a93 100644 --- a/tests/test_the_test/test_remote_config.py +++ b/tests/test_the_test/test_remote_config.py @@ -208,15 +208,18 @@ def test_resolve_sdk_configuration_contract(): } ) - # 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_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, } @@ -224,12 +227,16 @@ def test_resolve_sdk_configuration_contract(): # dd-trace-js 6.19.0+: all nine restored per-setting bits are compatibility metadata, but # sdk_config remains the only application path. - nodejs_6_19_apm_capabilities = php_lib_config_capabilities | { + 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(nodejs_6_19_apm_capabilities, library_name="nodejs") is True + assert ( + rc.resolve_sdk_configuration_contract(nodejs_sdk_config_with_legacy_capabilities, library_name="nodejs") is True + ) # dd-trace-java: no SDK_CONFIGURATION at all assert ( From 9fb3bd0e7dd67c75a2de57aa3e13194a4bc1d870 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 1 Oct 2026 14:44:57 -0400 Subject: [PATCH 06/12] fix(nodejs): apply restored capabilities to main --- tests/parametric/capabilities.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/parametric/capabilities.yml b/tests/parametric/capabilities.yml index c2e00568a52..26b98ded803 100644 --- a/tests/parametric/capabilities.yml +++ b/tests/parametric/capabilities.yml @@ -71,9 +71,8 @@ capabilities: # 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. 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: the main branch reports 7.0.0-pre before this change lands. - '<5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0 <7.0.0-0': + # legacy bits as compatibility metadata while continuing to consume sdk_config. + '<5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0': - APM_TRACING_CUSTOM_TAGS - APM_TRACING_ENABLED - APM_TRACING_HTTP_HEADER_TAGS @@ -90,11 +89,11 @@ capabilities: '>=5.83.0': - APM_TRACING_MULTICONFIG - '>=5.83.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0 <7.0.0-0': + '>=5.83.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0': - APM_TRACING_ENABLE_CODE_ORIGIN - APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION - '>=5.84.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0 <7.0.0-0': + '>=5.84.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0': - APM_TRACING_ENABLE_LIVE_DEBUGGING python: From 5ac37c0691804da3aad399ae0bf783c4e41bea4f Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 1 Oct 2026 15:06:10 -0400 Subject: [PATCH 07/12] [nodejs]: fix(remote-config): preserve main capability compatibility --- tests/parametric/capabilities.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/parametric/capabilities.yml b/tests/parametric/capabilities.yml index 26b98ded803..b096845ff28 100644 --- a/tests/parametric/capabilities.yml +++ b/tests/parametric/capabilities.yml @@ -71,8 +71,9 @@ capabilities: # 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. Node.js 6.19.0 restores the - # legacy bits as compatibility metadata while continuing to consume sdk_config. - '<5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0': + # 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 @@ -89,11 +90,11 @@ capabilities: '>=5.83.0': - APM_TRACING_MULTICONFIG - '>=5.83.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.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 || >=6.19.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: From 7435ab743d196827b9b51cd3fa70e8d5055ec7d8 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 2 Oct 2026 11:28:13 -0400 Subject: [PATCH 08/12] fix(remote-config): make SDK capability authoritative --- tests/parametric/capabilities.yml | 9 ++- .../parametric/test_dynamic_configuration.py | 16 ++--- tests/test_the_test/test_remote_config.py | 65 +++++++++---------- utils/_remote_config.py | 44 ++----------- 4 files changed, 46 insertions(+), 88 deletions(-) diff --git a/tests/parametric/capabilities.yml b/tests/parametric/capabilities.yml index b096845ff28..26b98ded803 100644 --- a/tests/parametric/capabilities.yml +++ b/tests/parametric/capabilities.yml @@ -71,9 +71,8 @@ capabilities: # 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. 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': + # legacy bits as compatibility metadata while continuing to consume sdk_config. + '<5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0': - APM_TRACING_CUSTOM_TAGS - APM_TRACING_ENABLED - APM_TRACING_HTTP_HEADER_TAGS @@ -90,11 +89,11 @@ capabilities: '>=5.83.0': - APM_TRACING_MULTICONFIG - '>=5.83.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0 <7.0.0-0': + '>=5.83.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0': - APM_TRACING_ENABLE_CODE_ORIGIN - APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION - '>=5.84.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0 <7.0.0-0': + '>=5.84.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0': - APM_TRACING_ENABLE_LIVE_DEBUGGING python: diff --git a/tests/parametric/test_dynamic_configuration.py b/tests/parametric/test_dynamic_configuration.py index d25ee305009..215d81a21e2 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 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. + 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. """ 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 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. + 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. 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 397a6957a93..a5db4ca5e4f 100644 --- a/tests/test_the_test/test_remote_config.py +++ b/tests/test_the_test/test_remote_config.py @@ -192,14 +192,8 @@ def test_build_apm_tracing_command_legacy_by_default(): @scenarios.test_the_test -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. Capability interpretation therefore also needs to account - for the tracer implementation when legacy bits and SDK_CONFIGURATION appear together. - """ - # dd-trace-js 6.17.0 and 6.18.0: the per-setting capabilities are dropped +def test_sdk_configuration_alone_selects_sdk_config(): + """SDK_CONFIGURATION without per-setting APM_TRACING capabilities selects sdk_config.""" assert rc.resolve_sdk_configuration_contract( { Capabilities.ASM_ACTIVATION, @@ -208,49 +202,50 @@ def test_resolve_sdk_configuration_contract(): } ) - core_lib_config_capabilities = { + +@scenarios.test_the_test +def test_sdk_configuration_with_apm_tracing_capabilities_selects_sdk_config(): + """SDK_CONFIGURATION alongside per-setting APM_TRACING capabilities selects sdk_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(nodejs_sdk_config_with_legacy_capabilities, library_name="nodejs") is True - ) - # dd-trace-java: no SDK_CONFIGURATION at all - assert ( - rc.resolve_sdk_configuration_contract({Capabilities.APM_TRACING_SAMPLE_RATE, Capabilities.APM_TRACING_ENABLED}) - is False - ) + assert rc.resolve_sdk_configuration_contract(capabilities) is True + + +@scenarios.test_the_test +def test_apm_tracing_capabilities_without_sdk_configuration_select_lib_config(): + """Per-setting APM_TRACING capabilities without SDK_CONFIGURATION select 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, + Capabilities.APM_TRACING_MULTICONFIG, + } + + assert rc.resolve_sdk_configuration_contract(capabilities) is False + + +@scenarios.test_the_test +def test_resolve_sdk_configuration_contract_waits_for_apm_capabilities(): + """Capability registration is inconclusive until an APM_TRACING capability is present.""" - # Only ASM capabilities registered so far: nothing to conclude, ask again later assert rc.resolve_sdk_configuration_contract({Capabilities.ASM_ACTIVATION}) is None assert rc.resolve_sdk_configuration_contract(set()) is None - # A libdatadog library mid-startup: bit 49 is registered with the other AppSec capabilities, - # before any APM_TRACING one. The absence of the per-setting bits is not evidence of the - # unified contract yet, so this must stay inconclusive rather than be cached as sdk_config. assert ( rc.resolve_sdk_configuration_contract( { @@ -262,7 +257,6 @@ def test_resolve_sdk_configuration_contract(): is None ) - # APM_TRACING registered but no SDK_CONFIGURATION: conclusively lib_config assert ( rc.resolve_sdk_configuration_contract( {Capabilities.APM_TRACING_MULTICONFIG, Capabilities.APM_TRACING_ENABLE_CODE_ORIGIN} @@ -279,5 +273,4 @@ def test_apm_tracing_capabilities_exclude_sdk_configuration(): `resolve_sdk_configuration_contract`. """ assert Capabilities.SDK_CONFIGURATION not in 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 087be24c9c5..19edf9fd400 100644 --- a/utils/_remote_config.py +++ b/utils/_remote_config.py @@ -645,54 +645,20 @@ 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 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, - Capabilities.APM_TRACING_HTTP_HEADER_TAGS, - Capabilities.APM_TRACING_LOGS_INJECTION, - Capabilities.APM_TRACING_SAMPLE_RATE, - Capabilities.APM_TRACING_SAMPLE_RULES, - } -) - -def resolve_sdk_configuration_contract( - capabilities: set[Capabilities], *, library_name: str | None = None -) -> bool | None: +def resolve_sdk_configuration_contract(capabilities: set[Capabilities]) -> 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 far cannot tell, so the caller should look again later. - The SDK_CONFIGURATION bit alone is not enough to decide. Bit 49 is SDK_CONFIGURATION in the - remote config source of truth (dd-source `remote-config/shared/libs/rc/capabilities.go`), but - 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`. - - 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. + SDK_CONFIGURATION is authoritative once the library has registered at least one APM_TRACING + capability. Waiting for an APM capability prevents an unrelated product that registers first + from deciding which APM_TRACING payload shape to use. """ if not capabilities & APM_TRACING_CAPABILITIES: return None - 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 @@ -722,7 +688,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, library_name=context.library.name) + supported = resolve_sdk_configuration_contract(capabilities) if supported is None: logger.info("No APM_TRACING capability advertised yet, sending lib_config for now") return False From 2de673903bdc23b2a3431fa119fbfa0228865c8e Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 2 Oct 2026 11:30:23 -0400 Subject: [PATCH 09/12] docs(nodejs): clarify remote config capability timeline --- tests/parametric/capabilities.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/parametric/capabilities.yml b/tests/parametric/capabilities.yml index 26b98ded803..ee0022b214a 100644 --- a/tests/parametric/capabilities.yml +++ b/tests/parametric/capabilities.yml @@ -69,9 +69,9 @@ capabilities: - ASM_ACTIVATION - 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. Node.js 6.19.0 restores the - # legacy bits as compatibility metadata while continuing to consume sdk_config. + # Node.js consumes sdk_config starting in 5.128.0 on the 5.x release line and in + # 6.17.0 on the 6.x release line. Starting in 6.19.0, it also advertises the + # per-setting APM_TRACING capabilities for backend and UI compatibility. '<5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0': - APM_TRACING_CUSTOM_TAGS - APM_TRACING_ENABLED From a7b20fddb771d2630273bffdeccf925d8edd6ba1 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 2 Oct 2026 14:24:06 -0400 Subject: [PATCH 10/12] fix(remote-config): require Node.js 6.20 for restored bits --- tests/parametric/capabilities.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/parametric/capabilities.yml b/tests/parametric/capabilities.yml index ee0022b214a..98fc857a464 100644 --- a/tests/parametric/capabilities.yml +++ b/tests/parametric/capabilities.yml @@ -70,9 +70,9 @@ capabilities: - ASM_AUTO_USER_INSTRUM_MODE # Node.js consumes sdk_config starting in 5.128.0 on the 5.x release line and in - # 6.17.0 on the 6.x release line. Starting in 6.19.0, it also advertises the + # 6.17.0 on the 6.x release line. Starting in 6.20.0, it also advertises the # per-setting APM_TRACING capabilities for backend and UI compatibility. - '<5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0': + '<5.128.0 || >=6.0.0-0 <6.17.0 || >=6.20.0': - APM_TRACING_CUSTOM_TAGS - APM_TRACING_ENABLED - APM_TRACING_HTTP_HEADER_TAGS @@ -89,11 +89,11 @@ capabilities: '>=5.83.0': - APM_TRACING_MULTICONFIG - '>=5.83.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0': + '>=5.83.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.20.0': - APM_TRACING_ENABLE_CODE_ORIGIN - APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION - '>=5.84.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0': + '>=5.84.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.20.0': - APM_TRACING_ENABLE_LIVE_DEBUGGING python: From 41470383eacf79b3e13ba25d0757444d385205e4 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 2 Oct 2026 15:07:29 -0400 Subject: [PATCH 11/12] fix(remote-config): allow Node.js 7 prerelease capability drift --- tests/parametric/capabilities.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/parametric/capabilities.yml b/tests/parametric/capabilities.yml index 98fc857a464..12567bbecd4 100644 --- a/tests/parametric/capabilities.yml +++ b/tests/parametric/capabilities.yml @@ -72,7 +72,7 @@ capabilities: # Node.js consumes sdk_config starting in 5.128.0 on the 5.x release line and in # 6.17.0 on the 6.x release line. Starting in 6.20.0, it also advertises the # per-setting APM_TRACING capabilities for backend and UI compatibility. - '<5.128.0 || >=6.0.0-0 <6.17.0 || >=6.20.0': + '<5.128.0 || >=6.0.0-0 <6.17.0 || >=6.20.0 <7.0.0-0 || >=7.0.0': - APM_TRACING_CUSTOM_TAGS - APM_TRACING_ENABLED - APM_TRACING_HTTP_HEADER_TAGS @@ -89,11 +89,11 @@ capabilities: '>=5.83.0': - APM_TRACING_MULTICONFIG - '>=5.83.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.20.0': + '>=5.83.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.20.0 <7.0.0-0 || >=7.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 || >=6.20.0': + '>=5.84.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.20.0 <7.0.0-0 || >=7.0.0': - APM_TRACING_ENABLE_LIVE_DEBUGGING python: From 1b3e5c59b3ff686a9e1f5c1e2e0c802d7680a193 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Mon, 5 Oct 2026 13:50:44 -0400 Subject: [PATCH 12/12] fix(remote-config): preserve PHP bit 49 collision --- .../parametric/test_dynamic_configuration.py | 13 +++--- tests/test_the_test/test_remote_config.py | 22 ++++++++- utils/_remote_config.py | 46 +++++++++++++++---- 3 files changed, 63 insertions(+), 18 deletions(-) diff --git a/tests/parametric/test_dynamic_configuration.py b/tests/parametric/test_dynamic_configuration.py index 215d81a21e2..0df04ad988b 100644 --- a/tests/parametric/test_dynamic_configuration.py +++ b/tests/parametric/test_dynamic_configuration.py @@ -187,22 +187,23 @@ def uses_sdk_configuration(test_agent: TestAgentAPI) -> bool: that does not poll remote config (tracing disabled by DD_TRACE_ENABLED, for instance) looks like; the shared helper turns that into the `lib_config` default. """ - return remote_config.resolve_sdk_configuration_support(lambda: test_agent.wait_for_rc_capabilities(_RC_WAIT_LOOPS)) + return remote_config.resolve_sdk_configuration_support( + lambda: test_agent.wait_for_rc_capabilities(_RC_WAIT_LOOPS), library_name=context.library.name + ) 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 may advertise that bit in + place of a per-setting capability. Since libdatadog gives the same bit a different meaning, + the shared resolver also uses the library identity when legacy capabilities remain present. """ seen_capabilities = test_agent.wait_for_rc_capabilities(wait_loops) if capability in seen_capabilities: return - assert remote_config.resolve_sdk_configuration_contract(seen_capabilities), ( + assert remote_config.resolve_sdk_configuration_contract(seen_capabilities, library_name=context.library.name), ( f"RemoteConfig capability missing: neither {capability.name} nor the SDK_CONFIGURATION " f"contract that replaces it; seen: {seen_capabilities}" ) diff --git a/tests/test_the_test/test_remote_config.py b/tests/test_the_test/test_remote_config.py index a5db4ca5e4f..878e2a70d79 100644 --- a/tests/test_the_test/test_remote_config.py +++ b/tests/test_the_test/test_remote_config.py @@ -205,7 +205,7 @@ def test_sdk_configuration_alone_selects_sdk_config(): @scenarios.test_the_test def test_sdk_configuration_with_apm_tracing_capabilities_selects_sdk_config(): - """SDK_CONFIGURATION alongside per-setting APM_TRACING capabilities selects sdk_config.""" + """Node.js uses sdk_config while continuing to advertise per-setting capabilities.""" capabilities = { Capabilities.APM_TRACING_CUSTOM_TAGS, Capabilities.APM_TRACING_ENABLED, @@ -220,7 +220,24 @@ def test_sdk_configuration_with_apm_tracing_capabilities_selects_sdk_config(): Capabilities.SDK_CONFIGURATION, } - assert rc.resolve_sdk_configuration_contract(capabilities) is True + assert rc.resolve_sdk_configuration_contract(capabilities, library_name="nodejs") is True + + +@scenarios.test_the_test +def test_php_bit_49_collision_with_apm_tracing_capabilities_selects_lib_config(): + """PHP uses bit 49 for ASM_RAW_RESPONSE_BODY and continues to consume 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, + Capabilities.APM_TRACING_MULTICONFIG, + Capabilities.SDK_CONFIGURATION, + } + + assert rc.resolve_sdk_configuration_contract(capabilities, library_name="php") is False @scenarios.test_the_test @@ -273,4 +290,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 Capabilities.APM_TRACING_MULTICONFIG in rc.APM_TRACING_CAPABILITIES diff --git a/utils/_remote_config.py b/utils/_remote_config.py index 19edf9fd400..7dc11cefca0 100644 --- a/utils/_remote_config.py +++ b/utils/_remote_config.py @@ -645,21 +645,44 @@ 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 capabilities replaced by the unified SDK_CONFIGURATION contract. Their presence +# alongside bit 49 is ambiguous: Node.js intentionally advertises both families, while libdatadog +# uses bit 49 for ASM_RAW_RESPONSE_BODY and still consumes the legacy lib_config payload. +LEGACY_APM_TRACING_CAPABILITIES = frozenset( + { + 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, + } +) + -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 far cannot tell, so the caller should look again later. - SDK_CONFIGURATION is authoritative once the library has registered at least one APM_TRACING - capability. Waiting for an APM capability prevents an unrelated product that registers first - from deciding which APM_TRACING payload shape to use. + Waiting for an APM capability prevents an unrelated product that registers first from deciding + which APM_TRACING payload shape to use. When bit 49 appears with legacy per-setting bits, only + Node.js treats it as SDK_CONFIGURATION; libdatadog libraries use the same bit for + ASM_RAW_RESPONSE_BODY and still consume lib_config. """ if not capabilities & APM_TRACING_CAPABILITIES: return None - return Capabilities.SDK_CONFIGURATION in capabilities + if Capabilities.SDK_CONFIGURATION not in capabilities: + return False + + if capabilities & LEGACY_APM_TRACING_CAPABILITIES: + return library_name == "nodejs" + + return True # Memoized once the capabilities are conclusive, both to skip re-scanning the whole /v0.7/config @@ -668,11 +691,14 @@ def resolve_sdk_configuration_contract(capabilities: set[Capabilities]) -> bool _sdk_configuration_support: dict[str, bool] = {} -def resolve_sdk_configuration_support(get_capabilities: Callable[[], set[Capabilities]]) -> bool: +def resolve_sdk_configuration_support( + get_capabilities: Callable[[], set[Capabilities]], *, library_name: str | None = None +) -> bool: """Whether the library reads its APM_TRACING settings from `sdk_config` instead of `lib_config`. - `get_capabilities` returns the capabilities the library currently advertises. How to obtain - them, and how long to wait for them, differs between the end-to-end interface and the + `get_capabilities` returns the capabilities the library currently advertises. `library_name` + resolves bit 49 when it appears alongside legacy per-setting capabilities. How to obtain the + capabilities, and how long to wait for them, differs between the end-to-end interface and the parametric test agent, so that part stays with the caller; everything after it is shared. Falls back to `lib_config` whenever the answer is not yet knowable, which is the safe @@ -688,7 +714,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=library_name) if supported is None: logger.info("No APM_TRACING capability advertised yet, sending lib_config for now") return False @@ -709,7 +735,7 @@ def library_supports_sdk_configuration() -> bool: logger.warning("No remote config request seen, assuming the library does not support SDK_CONFIGURATION") return False - return resolve_sdk_configuration_support(library.get_rc_capabilities) + return resolve_sdk_configuration_support(library.get_rc_capabilities, library_name=context.library.name) def build_apm_tracing_command(