dd-trace 6.4.0 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.electron.md +7 -0
  2. package/README.md +17 -0
  3. package/ci/diagnose.js +2100 -0
  4. package/ci/init.js +23 -2
  5. package/ci/runbook.md +198 -0
  6. package/ci/test-optimization-validation/approval-artifacts.js +143 -0
  7. package/ci/test-optimization-validation/approval.js +299 -0
  8. package/ci/test-optimization-validation/artifact-id.js +20 -0
  9. package/ci/test-optimization-validation/bounded-json.js +121 -0
  10. package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
  11. package/ci/test-optimization-validation/ci-discovery.js +181 -0
  12. package/ci/test-optimization-validation/ci-remediation.js +210 -0
  13. package/ci/test-optimization-validation/cli.js +903 -0
  14. package/ci/test-optimization-validation/command-blocker.js +81 -0
  15. package/ci/test-optimization-validation/command-output-policy.js +206 -0
  16. package/ci/test-optimization-validation/command-runner.js +707 -0
  17. package/ci/test-optimization-validation/command-suitability.js +471 -0
  18. package/ci/test-optimization-validation/executable-approval.js +48 -0
  19. package/ci/test-optimization-validation/executable.js +480 -0
  20. package/ci/test-optimization-validation/generated-file-policy.js +63 -0
  21. package/ci/test-optimization-validation/generated-files.js +351 -0
  22. package/ci/test-optimization-validation/generated-verifier.js +196 -0
  23. package/ci/test-optimization-validation/init-probe-preload.js +163 -0
  24. package/ci/test-optimization-validation/init-probe.js +246 -0
  25. package/ci/test-optimization-validation/late-initialization.js +63 -0
  26. package/ci/test-optimization-validation/local-command.js +163 -0
  27. package/ci/test-optimization-validation/manifest-loader.js +133 -0
  28. package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
  29. package/ci/test-optimization-validation/manifest-schema.js +862 -0
  30. package/ci/test-optimization-validation/offline-fixtures.js +327 -0
  31. package/ci/test-optimization-validation/offline-output.js +406 -0
  32. package/ci/test-optimization-validation/payload-normalizer.js +72 -0
  33. package/ci/test-optimization-validation/plan-writer.js +1120 -0
  34. package/ci/test-optimization-validation/preflight-runner.js +114 -0
  35. package/ci/test-optimization-validation/redaction.js +331 -0
  36. package/ci/test-optimization-validation/report-writer.js +1508 -0
  37. package/ci/test-optimization-validation/safe-files.js +203 -0
  38. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
  39. package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
  40. package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
  41. package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
  42. package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
  43. package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
  44. package/ci/test-optimization-validation/setup-runner.js +97 -0
  45. package/ci/test-optimization-validation/static-diagnosis.js +159 -0
  46. package/ci/test-optimization-validation/test-output.js +129 -0
  47. package/ci/test-optimization-validation-manifest.schema.json +1 -0
  48. package/ci/validate-test-optimization.js +3 -0
  49. package/ext/exporters.js +1 -0
  50. package/index.d.ts +121 -8
  51. package/init.js +18 -0
  52. package/initialize.mjs +1 -1
  53. package/loader-hook.mjs +28 -18
  54. package/openfeature.d.ts +1 -0
  55. package/openfeature.js +4 -0
  56. package/package.json +10 -7
  57. package/packages/datadog-instrumentations/src/ai.js +8 -2
  58. package/packages/datadog-instrumentations/src/child_process.js +1 -1
  59. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
  60. package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
  61. package/packages/datadog-instrumentations/src/cucumber.js +20 -7
  62. package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
  63. package/packages/datadog-instrumentations/src/fastify.js +7 -11
  64. package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
  65. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
  66. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
  67. package/packages/datadog-instrumentations/src/jest.js +26 -0
  68. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  69. package/packages/datadog-instrumentations/src/mocha/main.js +0 -2
  70. package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
  71. package/packages/datadog-instrumentations/src/mongodb.js +3 -3
  72. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  73. package/packages/datadog-instrumentations/src/mquery.js +2 -2
  74. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  75. package/packages/datadog-instrumentations/src/next.js +24 -0
  76. package/packages/datadog-instrumentations/src/nyc.js +0 -2
  77. package/packages/datadog-instrumentations/src/oracledb.js +2 -2
  78. package/packages/datadog-instrumentations/src/pg.js +2 -2
  79. package/packages/datadog-instrumentations/src/playwright.js +159 -22
  80. package/packages/datadog-instrumentations/src/process.js +3 -0
  81. package/packages/datadog-instrumentations/src/router.js +18 -8
  82. package/packages/datadog-instrumentations/src/selenium.js +52 -26
  83. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +2 -2
  84. package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
  85. package/packages/datadog-instrumentations/src/ws.js +2 -1
  86. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
  87. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
  88. package/packages/datadog-plugin-aws-sdk/src/util.js +2 -2
  89. package/packages/datadog-plugin-cucumber/src/index.js +1 -1
  90. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -57
  91. package/packages/datadog-plugin-cypress/src/index.js +6 -1
  92. package/packages/datadog-plugin-cypress/src/support.js +92 -26
  93. package/packages/datadog-plugin-electron/src/ipc.js +1 -1
  94. package/packages/datadog-plugin-graphql/src/execute.js +87 -45
  95. package/packages/datadog-plugin-graphql/src/validate.js +2 -2
  96. package/packages/datadog-plugin-http2/src/client.js +1 -1
  97. package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
  98. package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
  99. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  100. package/packages/datadog-plugin-playwright/src/index.js +177 -64
  101. package/packages/datadog-plugin-router/src/index.js +11 -2
  102. package/packages/datadog-plugin-selenium/src/index.js +5 -36
  103. package/packages/datadog-plugin-ws/src/close.js +2 -1
  104. package/packages/datadog-plugin-ws/src/producer.js +2 -1
  105. package/packages/datadog-plugin-ws/src/receiver.js +2 -1
  106. package/packages/datadog-plugin-ws/src/server.js +2 -1
  107. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  108. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
  109. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
  110. package/packages/dd-trace/src/azure_metadata.js +2 -1
  111. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
  112. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  113. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
  114. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
  115. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
  116. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
  117. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
  118. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +41 -3
  119. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -2
  120. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -1
  121. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
  122. package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
  123. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  124. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  125. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
  126. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +168 -8
  127. package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
  128. package/packages/dd-trace/src/config/generated-config-types.d.ts +16 -0
  129. package/packages/dd-trace/src/config/helper.js +1 -0
  130. package/packages/dd-trace/src/config/index.js +10 -0
  131. package/packages/dd-trace/src/config/supported-configurations.json +66 -0
  132. package/packages/dd-trace/src/exporter.js +2 -0
  133. package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
  134. package/packages/dd-trace/src/exporters/common/request.js +59 -30
  135. package/packages/dd-trace/src/exporters/common/url.js +15 -1
  136. package/packages/dd-trace/src/feature-registry.js +10 -3
  137. package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
  138. package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
  139. package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
  140. package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
  141. package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
  142. package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
  143. package/packages/dd-trace/src/llmobs/noop.js +6 -0
  144. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
  145. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
  146. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  147. package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
  148. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
  149. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
  150. package/packages/dd-trace/src/llmobs/sdk.js +17 -0
  151. package/packages/dd-trace/src/llmobs/tagger.js +68 -0
  152. package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
  153. package/packages/dd-trace/src/llmobs/util.js +32 -0
  154. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
  155. package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
  156. package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
  157. package/packages/dd-trace/src/openfeature/index.js +0 -2
  158. package/packages/dd-trace/src/openfeature/noop.js +1 -28
  159. package/packages/dd-trace/src/openfeature/register.js +16 -27
  160. package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
  161. package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
  162. package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
  163. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  164. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
  165. package/packages/dd-trace/src/plugins/util/git.js +3 -2
  166. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
  167. package/packages/dd-trace/src/plugins/util/test.js +56 -4
  168. package/packages/dd-trace/src/proxy.js +84 -7
  169. package/packages/dd-trace/src/span_processor.js +5 -0
  170. package/packages/dd-trace/src/standalone/index.js +0 -14
  171. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  172. package/packages/dd-trace/src/util.js +26 -0
  173. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  174. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  175. package/vendor/dist/protobufjs/index.js +1 -1
  176. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  177. package/vendor/dist/shell-quote/index.js +1 -1
  178. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
@@ -13,6 +13,25 @@ const REQUIRED_SETTINGS_KEYS = [
13
13
  'impacted_tests_enabled',
14
14
  'coverage_report_upload_enabled',
15
15
  ]
16
+ const SETTINGS_BOOLEAN_KEYS = [
17
+ 'code_coverage',
18
+ 'tests_skipping',
19
+ 'itr_enabled',
20
+ 'require_git',
21
+ 'flaky_test_retries_enabled',
22
+ 'di_enabled',
23
+ 'known_tests_enabled',
24
+ 'impacted_tests_enabled',
25
+ 'coverage_report_upload_enabled',
26
+ ]
27
+ const EARLY_FLAKE_DETECTION_KEYS = ['enabled', 'faulty_session_threshold', 'slow_test_retries']
28
+ const TEST_MANAGEMENT_KEYS = ['attempt_to_fix_retries', 'enabled']
29
+ const RETRY_THRESHOLD_PATTERN = /^\d+(?:ms|s|m|h)$/
30
+ const MAX_VALIDATION_MODULES = 1000
31
+ const MAX_VALIDATION_SUITES = 10_000
32
+ const MAX_VALIDATION_TESTS = 100_000
33
+ const MAX_VALIDATION_RETRIES = 100
34
+ const MAX_VALIDATION_STRING_BYTES = 4096
16
35
 
17
36
  function isObject (value) {
18
37
  return value !== null && typeof value === 'object' && !Array.isArray(value)
@@ -38,7 +57,7 @@ function getAttributes (response, endpoint) {
38
57
  return attributes
39
58
  }
40
59
 
41
- function validateSettingsResponse (response) {
60
+ function validateSettingsResponse (response, options = {}) {
42
61
  const attributes = response?.data?.attributes ?? response
43
62
  assertObject(attributes, 'Invalid settings response: attributes must be an object')
44
63
  assertHasKeys(attributes, REQUIRED_SETTINGS_KEYS, 'settings')
@@ -46,9 +65,27 @@ function validateSettingsResponse (response) {
46
65
  assertObject(attributes.test_management, 'Invalid settings response: test_management must be an object')
47
66
  assertHasKeys(attributes.early_flake_detection, ['enabled'], 'settings early_flake_detection')
48
67
  assertHasKeys(attributes.test_management, ['enabled'], 'settings test_management')
68
+ if (!options.validationMode) return
69
+
70
+ assertOnlyKeys(attributes, REQUIRED_SETTINGS_KEYS, 'settings')
71
+ assertOnlyKeys(attributes.early_flake_detection, EARLY_FLAKE_DETECTION_KEYS, 'settings early_flake_detection')
72
+ assertOnlyKeys(attributes.test_management, TEST_MANAGEMENT_KEYS, 'settings test_management')
73
+ assertBooleanFields(attributes, SETTINGS_BOOLEAN_KEYS, 'settings')
74
+ assertBooleanFields(attributes.early_flake_detection, ['enabled'], 'settings early_flake_detection')
75
+ assertBooleanFields(attributes.test_management, ['enabled'], 'settings test_management')
76
+ assertOptionalBoundedNumber(
77
+ attributes.early_flake_detection.faulty_session_threshold,
78
+ 'settings early_flake_detection faulty_session_threshold'
79
+ )
80
+ assertRetryMap(attributes.early_flake_detection.slow_test_retries)
81
+ assertOptionalBoundedNumber(
82
+ attributes.test_management.attempt_to_fix_retries,
83
+ 'settings test_management attempt_to_fix_retries',
84
+ { integer: true }
85
+ )
49
86
  }
50
87
 
51
- function validateKnownTestsResponse (response) {
88
+ function validateKnownTestsResponse (response, options = {}) {
52
89
  const attributes = getAttributes(response, 'known tests')
53
90
  assertHasKeys(attributes, ['tests'], 'known tests')
54
91
 
@@ -56,26 +93,46 @@ function validateKnownTestsResponse (response) {
56
93
  if (tests === null) return
57
94
  assertObject(tests, 'Invalid known tests response: tests must be an object or null')
58
95
 
59
- for (const suites of Object.values(tests)) {
96
+ let suiteCount = 0
97
+ let testCount = 0
98
+ const modules = Object.entries(tests)
99
+ if (options.validationMode && modules.length > MAX_VALIDATION_MODULES) {
100
+ throw new Error('Invalid known tests response: too many modules')
101
+ }
102
+ for (const [moduleName, suites] of modules) {
103
+ if (options.validationMode) assertValidationString(moduleName, 'known tests module')
60
104
  assertObject(suites, 'Invalid known tests response: module suites must be objects')
61
- for (const testNames of Object.values(suites)) {
105
+ for (const [suiteName, testNames] of Object.entries(suites)) {
106
+ suiteCount++
107
+ if (options.validationMode) {
108
+ if (suiteCount > MAX_VALIDATION_SUITES) throw new Error('Invalid known tests response: too many suites')
109
+ assertValidationString(suiteName, 'known tests suite')
110
+ }
62
111
  if (!Array.isArray(testNames)) {
63
112
  throw new TypeError('Invalid known tests response: suite tests must be arrays')
64
113
  }
65
114
  for (const testName of testNames) {
115
+ testCount++
66
116
  if (typeof testName !== 'string') {
67
117
  throw new TypeError('Invalid known tests response: test names must be strings')
68
118
  }
119
+ if (options.validationMode) {
120
+ if (testCount > MAX_VALIDATION_TESTS) throw new Error('Invalid known tests response: too many tests')
121
+ assertValidationString(testName, 'known test name')
122
+ }
69
123
  }
70
124
  }
71
125
  }
72
126
  }
73
127
 
74
- function validateSkippableTestsResponse (response) {
128
+ function validateSkippableTestsResponse (response, options = {}) {
75
129
  assertObject(response, 'Invalid skippable tests response: response must be an object')
76
130
  if (!Array.isArray(response.data)) {
77
131
  throw new TypeError('Invalid skippable tests response: data must be an array')
78
132
  }
133
+ if (options.validationMode && response.data.length > MAX_VALIDATION_TESTS) {
134
+ throw new Error('Invalid skippable tests response: too many items')
135
+ }
79
136
  if (response.meta !== undefined) {
80
137
  assertObject(response.meta, 'Invalid skippable tests response: meta must be an object')
81
138
  }
@@ -91,28 +148,56 @@ function validateSkippableTestsResponse (response) {
91
148
  if (typeof item.type !== 'string') {
92
149
  throw new TypeError('Invalid skippable tests response: data entry type must be a string')
93
150
  }
151
+ if (options.validationMode) assertValidationString(item.type, 'skippable test type')
94
152
  assertObject(item.attributes, 'Invalid skippable tests response: data entry attributes must be an object')
95
153
  if ((item.type === 'suite' || item.type === 'test') && typeof item.attributes.suite !== 'string') {
96
154
  throw new Error('Invalid skippable tests response: data entry suite must be a string')
97
155
  }
156
+ if (options.validationMode && item.attributes.suite !== undefined) {
157
+ assertValidationString(item.attributes.suite, 'skippable test suite')
158
+ }
98
159
  if (item.type === 'test' && typeof item.attributes.name !== 'string') {
99
160
  throw new Error('Invalid skippable tests response: data entry name must be a string')
100
161
  }
162
+ if (options.validationMode && item.attributes.name !== undefined) {
163
+ assertValidationString(item.attributes.name, 'skippable test name')
164
+ }
101
165
  }
102
166
  }
103
167
 
104
- function validateTestManagementTestsResponse (response) {
168
+ function validateTestManagementTestsResponse (response, options = {}) {
105
169
  const attributes = getAttributes(response, 'test management tests')
106
170
  assertHasKeys(attributes, ['modules'], 'test management tests')
107
171
  assertObject(attributes.modules, 'Invalid test management tests response: modules must be an object')
108
172
 
109
- for (const testModule of Object.values(attributes.modules)) {
173
+ let suiteCount = 0
174
+ let testCount = 0
175
+ const modules = Object.entries(attributes.modules)
176
+ if (options.validationMode && modules.length > MAX_VALIDATION_MODULES) {
177
+ throw new Error('Invalid test management tests response: too many modules')
178
+ }
179
+ for (const [moduleName, testModule] of modules) {
180
+ if (options.validationMode) assertValidationString(moduleName, 'test management module')
110
181
  assertObject(testModule, 'Invalid test management tests response: modules entries must be objects')
111
182
  assertObject(testModule.suites, 'Invalid test management tests response: module suites must be objects')
112
- for (const suite of Object.values(testModule.suites)) {
183
+ for (const [suiteName, suite] of Object.entries(testModule.suites)) {
184
+ suiteCount++
185
+ if (options.validationMode) {
186
+ if (suiteCount > MAX_VALIDATION_SUITES) {
187
+ throw new Error('Invalid test management tests response: too many suites')
188
+ }
189
+ assertValidationString(suiteName, 'test management suite')
190
+ }
113
191
  assertObject(suite, 'Invalid test management tests response: suites entries must be objects')
114
192
  assertObject(suite.tests, 'Invalid test management tests response: suite tests must be objects')
115
- for (const test of Object.values(suite.tests)) {
193
+ for (const [testName, test] of Object.entries(suite.tests)) {
194
+ testCount++
195
+ if (options.validationMode) {
196
+ if (testCount > MAX_VALIDATION_TESTS) {
197
+ throw new Error('Invalid test management tests response: too many tests')
198
+ }
199
+ assertValidationString(testName, 'test management test')
200
+ }
116
201
  assertObject(test, 'Invalid test management tests response: tests entries must be objects')
117
202
  assertObject(test.properties, 'Invalid test management tests response: test properties must be objects')
118
203
  }
@@ -120,6 +205,49 @@ function validateTestManagementTestsResponse (response) {
120
205
  }
121
206
  }
122
207
 
208
+ function assertBooleanFields (object, keys, endpoint) {
209
+ for (const key of keys) {
210
+ if (typeof object[key] !== 'boolean') {
211
+ throw new TypeError(`Invalid ${endpoint} response: ${key} must be a boolean`)
212
+ }
213
+ }
214
+ }
215
+
216
+ function assertOptionalBoundedNumber (value, endpoint, { integer = false } = {}) {
217
+ if (value === undefined) return
218
+ if (!Number.isFinite(value) || (integer && !Number.isInteger(value)) ||
219
+ value < 0 || value > MAX_VALIDATION_RETRIES) {
220
+ throw new TypeError(`Invalid ${endpoint}: value must be between 0 and ${MAX_VALIDATION_RETRIES}`)
221
+ }
222
+ }
223
+
224
+ function assertRetryMap (retries) {
225
+ if (retries === undefined) return
226
+ assertObject(retries, 'Invalid settings response: early_flake_detection slow_test_retries must be an object')
227
+ for (const [threshold, count] of Object.entries(retries)) {
228
+ assertValidationString(threshold, 'early flake detection retry threshold')
229
+ if (!RETRY_THRESHOLD_PATTERN.test(threshold)) {
230
+ throw new TypeError('Invalid early flake detection retry threshold: expected a duration such as 5s')
231
+ }
232
+ assertOptionalBoundedNumber(count, `early flake detection retry count ${threshold}`, { integer: true })
233
+ }
234
+ }
235
+
236
+ function assertOnlyKeys (object, keys, endpoint) {
237
+ const allowed = new Set(keys)
238
+ for (const key of Object.keys(object)) {
239
+ if (!allowed.has(key)) {
240
+ throw new Error(`Invalid ${endpoint} response: unexpected ${key}`)
241
+ }
242
+ }
243
+ }
244
+
245
+ function assertValidationString (value, field) {
246
+ if (typeof value !== 'string' || Buffer.byteLength(value) > MAX_VALIDATION_STRING_BYTES) {
247
+ throw new TypeError(`Invalid ${field}: value must be a bounded string`)
248
+ }
249
+ }
250
+
123
251
  module.exports = {
124
252
  validateKnownTestsResponse,
125
253
  validateSettingsResponse,
@@ -48,6 +48,10 @@ const SKIPPABLE_TESTS_FILE_NAME = 'skippable_tests.json'
48
48
  const TEST_MANAGEMENT_FILE_NAME = 'test_management.json'
49
49
 
50
50
  const RUNFILES_MANIFEST_SEPARATOR = ' '
51
+ const DEFAULT_VALIDATION_MAX_FILE_BYTES = 1024 * 1024
52
+ const DEFAULT_VALIDATION_MAX_ENTRIES = 100_000
53
+ const DEFAULT_VALIDATION_MAX_NESTING_DEPTH = 32
54
+ const DEFAULT_VALIDATION_MAX_STRING_BYTES = 4096
51
55
 
52
56
  function parseManifestVersion (content) {
53
57
  // Supported just the number version or 'version=x'
@@ -57,12 +61,22 @@ function parseManifestVersion (content) {
57
61
  }
58
62
 
59
63
  class TestOptimizationHttpCache {
60
- constructor ({ cwd = process.cwd(), env } = {}) {
64
+ constructor ({
65
+ cwd = process.cwd(),
66
+ env,
67
+ validationManifestPath,
68
+ maxFileBytes = DEFAULT_VALIDATION_MAX_FILE_BYTES,
69
+ } = {}) {
61
70
  this._cwd = cwd
62
71
  // This cache intentionally consumes env vars that are not tracer config keys. (??)
63
72
  // eslint-disable-next-line eslint-rules/eslint-process-env
64
73
  this._env = env ?? process.env
65
- this._manifestPath = this._resolveManifestPath()
74
+ this._validationManifestPath = validationManifestPath
75
+ this._maxFileBytes = maxFileBytes
76
+ this._lastError = undefined
77
+ this._manifestPath = validationManifestPath
78
+ ? this._resolveValidationManifestPath(validationManifestPath)
79
+ : this._resolveManifestPath()
66
80
  this._testOptimizationPath = undefined
67
81
  this._httpCachePath = undefined
68
82
  this._available = false
@@ -74,6 +88,10 @@ class TestOptimizationHttpCache {
74
88
  return this._available
75
89
  }
76
90
 
91
+ getLastError () {
92
+ return this._lastError
93
+ }
94
+
77
95
  readSettings () {
78
96
  const payload = this._readFile(SETTINGS_FILE_NAME)
79
97
  if (payload === CACHE_MISS) {
@@ -82,7 +100,11 @@ class TestOptimizationHttpCache {
82
100
  }
83
101
 
84
102
  try {
85
- const settings = parseLibraryConfigurationResponse(payload, undefined, { validateRequiredFields: true })
103
+ const settings = parseLibraryConfigurationResponse(
104
+ this._parsePayload(payload, SETTINGS_FILE_NAME),
105
+ undefined,
106
+ { validateRequiredFields: true, validationMode: Boolean(this._validationManifestPath) }
107
+ )
86
108
  incrementCountMetric(TELEMETRY_GIT_REQUESTS_SETTINGS_RESPONSE, settings)
87
109
  return settings
88
110
  } catch (err) {
@@ -97,7 +119,10 @@ class TestOptimizationHttpCache {
97
119
  if (payload === CACHE_MISS) return CACHE_MISS
98
120
 
99
121
  try {
100
- const knownTests = parseKnownTestsResponse(payload, { validateRequiredFields: true })
122
+ const knownTests = parseKnownTestsResponse(
123
+ this._parsePayload(payload, KNOWN_TESTS_FILE_NAME),
124
+ { validateRequiredFields: true, validationMode: Boolean(this._validationManifestPath) }
125
+ )
101
126
  distributionMetric(TELEMETRY_KNOWN_TESTS_RESPONSE_TESTS, {}, getNumFromKnownTests(knownTests))
102
127
  distributionMetric(TELEMETRY_KNOWN_TESTS_RESPONSE_BYTES, {}, payload.length)
103
128
  return knownTests
@@ -112,8 +137,13 @@ class TestOptimizationHttpCache {
112
137
  if (payload === CACHE_MISS) return CACHE_MISS
113
138
 
114
139
  try {
115
- const parsedResponse = JSON.parse(payload)
116
- const result = parseSkippableSuitesResponse(parsedResponse, { ...options, validateRequiredFields: true })
140
+ const boundedPayload = this._parsePayload(payload, SKIPPABLE_TESTS_FILE_NAME)
141
+ const parsedResponse = typeof boundedPayload === 'string' ? JSON.parse(boundedPayload) : boundedPayload
142
+ const result = parseSkippableSuitesResponse(parsedResponse, {
143
+ ...options,
144
+ validateRequiredFields: true,
145
+ validationMode: Boolean(this._validationManifestPath),
146
+ })
117
147
  const testLevel = options.testLevel || 'suite'
118
148
  logSkippableSuitesResponse(result, testLevel)
119
149
  const skippableItems = parsedResponse.data.filter(({ type }) => type === testLevel)
@@ -137,7 +167,10 @@ class TestOptimizationHttpCache {
137
167
  if (payload === CACHE_MISS) return false
138
168
 
139
169
  try {
140
- parseSkippableSuitesResponse(JSON.parse(payload), { ...options, validateRequiredFields: true })
170
+ parseSkippableSuitesResponse(
171
+ this._parsePayload(payload, SKIPPABLE_TESTS_FILE_NAME),
172
+ { ...options, validateRequiredFields: true, validationMode: Boolean(this._validationManifestPath) }
173
+ )
141
174
  return true
142
175
  } catch {
143
176
  return false
@@ -149,7 +182,10 @@ class TestOptimizationHttpCache {
149
182
  if (payload === CACHE_MISS) return CACHE_MISS
150
183
 
151
184
  try {
152
- const testManagementTests = parseTestManagementTestsResponse(payload, { validateRequiredFields: true })
185
+ const testManagementTests = parseTestManagementTestsResponse(
186
+ this._parsePayload(payload, TEST_MANAGEMENT_FILE_NAME),
187
+ { validateRequiredFields: true, validationMode: Boolean(this._validationManifestPath) }
188
+ )
153
189
  distributionMetric(
154
190
  TELEMETRY_TEST_MANAGEMENT_TESTS_RESPONSE_TESTS,
155
191
  {},
@@ -165,12 +201,18 @@ class TestOptimizationHttpCache {
165
201
 
166
202
  _buildReader () {
167
203
  if (!this._manifestPath) {
204
+ if (this._validationManifestPath && !this._lastError) {
205
+ this._setError('Offline Test Optimization validation manifest was not found.')
206
+ }
168
207
  log.debug('Test Optimization HTTP cache manifest not found')
169
208
  return
170
209
  }
171
210
 
172
211
  const version = this._readManifestVersion(this._manifestPath)
173
212
  if (version !== SUPPORTED_MANIFEST_VERSION) {
213
+ if (this._validationManifestPath) {
214
+ this._setError(`Unsupported offline Test Optimization validation manifest version: ${version || 'missing'}.`)
215
+ }
174
216
  log.debug('Unsupported Test Optimization HTTP cache manifest version %j at %s', version, this._manifestPath)
175
217
  return
176
218
  }
@@ -184,6 +226,9 @@ class TestOptimizationHttpCache {
184
226
 
185
227
  const settingsPath = path.join(this._httpCachePath, SETTINGS_FILE_NAME)
186
228
  if (!fs.existsSync(settingsPath)) {
229
+ if (this._validationManifestPath) {
230
+ this._setError('Offline Test Optimization validation settings fixture is missing.')
231
+ }
187
232
  log.debug('Test Optimization HTTP cache settings file not found at %s', settingsPath)
188
233
  return
189
234
  }
@@ -207,6 +252,30 @@ class TestOptimizationHttpCache {
207
252
  }
208
253
  }
209
254
 
255
+ _resolveValidationManifestPath (manifestPath) {
256
+ if (!path.isAbsolute(manifestPath)) {
257
+ this._setError('Offline Test Optimization validation manifest path must be absolute.')
258
+ return
259
+ }
260
+
261
+ const resolvedManifestPath = path.resolve(manifestPath)
262
+ const testOptimizationPath = path.dirname(resolvedManifestPath)
263
+ const fixtureRoot = path.dirname(testOptimizationPath)
264
+ if (path.basename(resolvedManifestPath) !== MANIFEST_FILE_NAME ||
265
+ path.basename(testOptimizationPath) !== PLAN_FOLDER) {
266
+ this._setError('Offline Test Optimization validation manifest must use the fixed .testoptimization layout.')
267
+ return
268
+ }
269
+
270
+ try {
271
+ assertPathComponentsAreNotSymlinks(fixtureRoot, resolvedManifestPath)
272
+ assertRegularFixtureFile(resolvedManifestPath)
273
+ return resolvedManifestPath
274
+ } catch (err) {
275
+ this._setError(err.message)
276
+ }
277
+ }
278
+
210
279
  _resolveRunfilePath (manifestFile) {
211
280
  if (fs.existsSync(manifestFile)) {
212
281
  return manifestFile
@@ -257,8 +326,10 @@ class TestOptimizationHttpCache {
257
326
 
258
327
  _readManifestVersion (manifestPath) {
259
328
  try {
329
+ this._assertValidationFixtureFile(manifestPath)
260
330
  return parseManifestVersion(fs.readFileSync(manifestPath, 'utf8'))
261
331
  } catch (err) {
332
+ if (this._validationManifestPath) this._setError(err.message)
262
333
  log.debug('Failed to read Test Optimization HTTP cache manifest %s: %s', manifestPath, err.message)
263
334
  }
264
335
  }
@@ -268,24 +339,113 @@ class TestOptimizationHttpCache {
268
339
 
269
340
  const filePath = path.join(this._httpCachePath, fileName)
270
341
  try {
342
+ this._assertValidationFixtureFile(filePath)
271
343
  log.debug('Reading Test Optimization HTTP cache file %s', filePath)
272
344
  return fs.readFileSync(filePath, 'utf8')
273
345
  } catch (err) {
346
+ if (this._validationManifestPath) this._setError(err.message)
274
347
  log.debug('Test Optimization HTTP cache file %s could not be read: %s', filePath, err.message)
275
348
  return CACHE_MISS
276
349
  }
277
350
  }
278
351
 
279
352
  _logInvalidCacheFile (fileName, err) {
353
+ if (this._validationManifestPath) {
354
+ this._setError(`Invalid offline Test Optimization ${fileName} fixture: ${err.message}`)
355
+ }
280
356
  log.debug('Test Optimization HTTP cache file %s could not be parsed: %s', fileName, err.message)
281
357
  }
282
358
 
359
+ _assertValidationFixtureFile (filePath) {
360
+ if (!this._validationManifestPath) return
361
+ const fixtureRoot = path.dirname(path.dirname(this._manifestPath))
362
+ assertPathComponentsAreNotSymlinks(fixtureRoot, filePath)
363
+ const stat = assertRegularFixtureFile(filePath)
364
+ if (stat.size > this._maxFileBytes) {
365
+ throw new Error(
366
+ `Offline Test Optimization fixture ${path.basename(filePath)} exceeds ${this._maxFileBytes} bytes.`
367
+ )
368
+ }
369
+ }
370
+
371
+ _parsePayload (payload, fileName) {
372
+ if (!this._validationManifestPath) return payload
373
+
374
+ const parsed = JSON.parse(payload)
375
+ assertBoundedValidationValue(parsed, fileName)
376
+ return parsed
377
+ }
378
+
379
+ _setError (message) {
380
+ this._lastError = new Error(message)
381
+ }
382
+
283
383
  _disable () {
284
384
  this._available = false
285
385
  }
286
386
  }
287
387
 
388
+ function assertPathComponentsAreNotSymlinks (root, filename) {
389
+ const resolvedRoot = path.resolve(root)
390
+ const resolvedFilename = path.resolve(filename)
391
+ const rootStat = fs.lstatSync(resolvedRoot)
392
+ if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
393
+ throw new Error(`Offline Test Optimization fixture root must be a regular directory: ${resolvedRoot}`)
394
+ }
395
+ const relative = path.relative(resolvedRoot, resolvedFilename)
396
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
397
+ throw new Error('Offline Test Optimization fixture path escapes its validation root.')
398
+ }
399
+
400
+ let current = resolvedRoot
401
+ for (const segment of relative ? relative.split(path.sep) : []) {
402
+ current = path.join(current, segment)
403
+ const stat = fs.lstatSync(current)
404
+ if (stat.isSymbolicLink()) {
405
+ throw new Error(`Offline Test Optimization fixture path contains a symbolic link: ${current}`)
406
+ }
407
+ }
408
+ }
409
+
410
+ function assertRegularFixtureFile (filename) {
411
+ const stat = fs.lstatSync(filename)
412
+ if (!stat.isFile()) {
413
+ throw new Error(`Offline Test Optimization fixture is not a regular file: ${filename}`)
414
+ }
415
+ if (stat.nlink > 1) {
416
+ throw new Error(`Offline Test Optimization fixture must not be hard-linked: ${filename}`)
417
+ }
418
+ return stat
419
+ }
420
+
421
+ function assertBoundedValidationValue (value, fileName) {
422
+ const pending = [{ depth: 0, value }]
423
+ let entries = 0
424
+
425
+ while (pending.length > 0) {
426
+ const current = pending.pop()
427
+ if (current.depth > DEFAULT_VALIDATION_MAX_NESTING_DEPTH) {
428
+ throw new Error(`${fileName} exceeds the validation nesting limit.`)
429
+ }
430
+ if (typeof current.value === 'string' &&
431
+ Buffer.byteLength(current.value) > DEFAULT_VALIDATION_MAX_STRING_BYTES) {
432
+ throw new Error(`${fileName} contains a string that exceeds the validation limit.`)
433
+ }
434
+ if (!current.value || typeof current.value !== 'object') continue
435
+
436
+ const values = Array.isArray(current.value) ? current.value : Object.values(current.value)
437
+ entries += values.length
438
+ if (entries > DEFAULT_VALIDATION_MAX_ENTRIES) {
439
+ throw new Error(`${fileName} exceeds the validation collection-entry limit.`)
440
+ }
441
+ for (const nestedValue of values) {
442
+ pending.push({ depth: current.depth + 1, value: nestedValue })
443
+ }
444
+ }
445
+ }
446
+
288
447
  module.exports = {
289
448
  CACHE_MISS,
449
+ DEFAULT_VALIDATION_MAX_FILE_BYTES,
290
450
  TestOptimizationHttpCache,
291
451
  }
@@ -0,0 +1,90 @@
1
+ 'use strict'
2
+
3
+ const { statSync } = require('node:fs')
4
+
5
+ const {
6
+ TEST_FAILURE_SCREENSHOT_UPLOADED,
7
+ TEST_FAILURE_SCREENSHOT_UPLOAD_ERROR,
8
+ } = require('../plugins/util/test')
9
+
10
+ const dateNow = Date.now
11
+
12
+ const SCREENSHOT_UPLOAD_RESULT_UPLOADED = 'uploaded'
13
+ const SCREENSHOT_UPLOAD_RESULT_ERROR = 'error'
14
+
15
+ /**
16
+ * Resolves a screenshot's capture time in epoch milliseconds.
17
+ *
18
+ * @param {object|string} screenshot - Framework screenshot metadata or its file path
19
+ * @param {string} filePath - Resolved screenshot file path
20
+ * @returns {number} Capture time in epoch milliseconds
21
+ */
22
+ function getScreenshotCapturedAtMs (screenshot, filePath) {
23
+ const takenAt = screenshot !== null && typeof screenshot === 'object' ? screenshot.takenAt : undefined
24
+ if (takenAt) {
25
+ const parsedMs = new Date(takenAt).getTime()
26
+ if (Number.isInteger(parsedMs) && parsedMs > 0) {
27
+ return parsedMs
28
+ }
29
+ }
30
+ try {
31
+ return Math.floor(statSync(filePath).mtimeMs)
32
+ } catch {
33
+ return dateNow()
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Combines screenshot upload results, giving errors precedence over successes.
39
+ *
40
+ * @param {Array<string|undefined>} uploadResults - Per-screenshot upload results
41
+ * @returns {string|undefined} Combined upload result
42
+ */
43
+ function getScreenshotUploadResult (uploadResults) {
44
+ let hasUploaded = false
45
+ for (const uploadResult of uploadResults) {
46
+ if (uploadResult === SCREENSHOT_UPLOAD_RESULT_ERROR) {
47
+ return SCREENSHOT_UPLOAD_RESULT_ERROR
48
+ }
49
+ if (uploadResult === SCREENSHOT_UPLOAD_RESULT_UPLOADED) {
50
+ hasUploaded = true
51
+ }
52
+ }
53
+ return hasUploaded ? SCREENSHOT_UPLOAD_RESULT_UPLOADED : undefined
54
+ }
55
+
56
+ /**
57
+ * Returns the test tag that represents an aggregate screenshot upload outcome.
58
+ *
59
+ * @param {string|undefined} uploadResult - Aggregate screenshot upload result
60
+ * @returns {string|undefined} Screenshot upload result tag
61
+ */
62
+ function getScreenshotUploadTag (uploadResult) {
63
+ if (uploadResult === SCREENSHOT_UPLOAD_RESULT_ERROR) {
64
+ return TEST_FAILURE_SCREENSHOT_UPLOAD_ERROR
65
+ }
66
+ if (uploadResult === SCREENSHOT_UPLOAD_RESULT_UPLOADED) {
67
+ return TEST_FAILURE_SCREENSHOT_UPLOADED
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Tags a test span with the aggregate screenshot upload outcome.
73
+ *
74
+ * @param {object} testSpan - Test span to tag
75
+ * @param {string|undefined} uploadResult - Aggregate screenshot upload result
76
+ * @returns {void}
77
+ */
78
+ function setScreenshotUploadTags (testSpan, uploadResult) {
79
+ const uploadTag = getScreenshotUploadTag(uploadResult)
80
+ if (uploadTag) testSpan.setTag(uploadTag, 'true')
81
+ }
82
+
83
+ module.exports = {
84
+ SCREENSHOT_UPLOAD_RESULT_ERROR,
85
+ SCREENSHOT_UPLOAD_RESULT_UPLOADED,
86
+ getScreenshotCapturedAtMs,
87
+ getScreenshotUploadResult,
88
+ getScreenshotUploadTag,
89
+ setScreenshotUploadTags,
90
+ }
@@ -16,6 +16,7 @@ export interface GeneratedConfig {
16
16
  DD_API_SECURITY_MAX_DOWNSTREAM_BODY_BYTES: number;
17
17
  DD_API_SECURITY_MAX_DOWNSTREAM_REQUEST_BODY_ANALYSIS: number;
18
18
  DD_API_SECURITY_SAMPLE_DELAY: number;
19
+ DD_APPSEC_AGENTIC_ONBOARDING: string;
19
20
  DD_APPSEC_SCA_ENABLED: boolean | undefined;
20
21
  enabled: boolean | undefined;
21
22
  eventTracking: {
@@ -431,6 +432,13 @@ export interface GeneratedConfig {
431
432
  };
432
433
  };
433
434
  };
435
+ featureFlags: {
436
+ DD_FEATURE_FLAGS_CONFIGURATION_SOURCE: "agentless" | "remote_config";
437
+ DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL: string | undefined;
438
+ DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS: number;
439
+ DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS: number;
440
+ DD_FEATURE_FLAGS_ENABLED: boolean;
441
+ };
434
442
  flushInterval: number;
435
443
  flushMinSpans: number;
436
444
  headerTags: string[];
@@ -562,6 +570,7 @@ export interface GeneratedConfig {
562
570
  DD_CIVISIBILITY_TEST_COMMAND: string | undefined;
563
571
  DD_CIVISIBILITY_TEST_MODULE_ID: string | undefined;
564
572
  DD_CIVISIBILITY_TEST_SESSION_ID: string | undefined;
573
+ DD_CODE_COVERAGE_FLAGS: string | undefined;
565
574
  DD_TEST_EARLY_FLAKE_DETECTION_RETRY_COUNT: number | undefined;
566
575
  DD_TEST_FAILED_TEST_REPLAY_ENABLED: boolean;
567
576
  DD_TEST_FAILURE_SCREENSHOTS_ENABLED: boolean | undefined;
@@ -612,6 +621,7 @@ export interface GeneratedEnvVarConfig {
612
621
  DD_APM_FLUSH_DEADLINE_MILLISECONDS: number;
613
622
  DD_APM_TRACING_ENABLED: boolean;
614
623
  DD_APP_KEY: string | undefined;
624
+ DD_APPSEC_AGENTIC_ONBOARDING: string;
615
625
  DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE: string;
616
626
  DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING: string;
617
627
  DD_APPSEC_COLLECT_ALL_HEADERS: boolean;
@@ -654,6 +664,7 @@ export interface GeneratedEnvVarConfig {
654
664
  DD_CIVISIBILITY_TEST_COMMAND: string | undefined;
655
665
  DD_CIVISIBILITY_TEST_MODULE_ID: string | undefined;
656
666
  DD_CIVISIBILITY_TEST_SESSION_ID: string | undefined;
667
+ DD_CODE_COVERAGE_FLAGS: string | undefined;
657
668
  DD_CODE_ORIGIN_FOR_SPANS_ENABLED: boolean;
658
669
  DD_CODE_ORIGIN_FOR_SPANS_EXPERIMENTAL_EXIT_SPANS_ENABLED: boolean;
659
670
  DD_CRASHTRACKING_ENABLED: boolean;
@@ -688,6 +699,11 @@ export interface GeneratedEnvVarConfig {
688
699
  DD_EXPERIMENTAL_TEST_OPT_VITEST_NO_WORKER_INIT: boolean | undefined;
689
700
  DD_EXPERIMENTAL_TEST_REQUESTS_FS_CACHE: boolean;
690
701
  DD_EXTERNAL_ENV: string | undefined;
702
+ DD_FEATURE_FLAGS_CONFIGURATION_SOURCE: "agentless" | "remote_config";
703
+ DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL: string | undefined;
704
+ DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS: number;
705
+ DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS: number;
706
+ DD_FEATURE_FLAGS_ENABLED: boolean;
691
707
  DD_GIT_BRANCH: string | undefined;
692
708
  DD_GIT_COMMIT_AUTHOR_DATE: string | undefined;
693
709
  DD_GIT_COMMIT_AUTHOR_EMAIL: string | undefined;
@@ -13,6 +13,7 @@
13
13
  * @property {string} [namespace] Nests the canonical env name under this property path (e.g. `telemetry`).
14
14
  * @property {string} [transform]
15
15
  * @property {string} [allowed]
16
+ * @property {string} [description]
16
17
  * @property {string|boolean} [deprecated]
17
18
  * @property {boolean} [sensitive] Excludes the configuration value from configuration telemetry.
18
19
  */
@@ -333,6 +333,16 @@ class Config extends ConfigBase {
333
333
  #applyCalculated () {
334
334
  undo(this, 'calculated')
335
335
 
336
+ if (this.featureFlags.DD_FEATURE_FLAGS_ENABLED &&
337
+ !trackedConfigOrigins.has('featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE') &&
338
+ trackedConfigOrigins.has('experimental.flaggingProvider.enabled')) {
339
+ if (this.experimental.flaggingProvider.enabled) {
340
+ setAndTrack(this, 'featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE', 'remote_config')
341
+ } else {
342
+ setAndTrack(this, 'featureFlags.DD_FEATURE_FLAGS_ENABLED', false)
343
+ }
344
+ }
345
+
336
346
  if (this.url ||
337
347
  os.type() !== 'Windows_NT' &&
338
348
  !trackedConfigOrigins.has('hostname') &&