dd-trace 5.132.0 → 5.134.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 (231) hide show
  1. package/ci/diagnose.js +9 -14
  2. package/ci/init.js +1 -2
  3. package/ci/test-optimization-validation/ci-discovery.js +9 -4
  4. package/ci/test-optimization-validation/executable.js +1 -5
  5. package/ci/test-optimization-validation/framework-adapters/cypress.js +1 -4
  6. package/ci/test-optimization-validation/framework-adapters/vitest.js +1 -1
  7. package/ci/test-optimization-validation/generated-files.js +6 -4
  8. package/ci/test-optimization-validation/generated-test-contract.js +7 -3
  9. package/ci/test-optimization-validation/manifest-scaffold.js +7 -2
  10. package/ci/test-optimization-validation/manifest-schema.js +6 -7
  11. package/ci/test-optimization-validation/report-writer.js +6 -2
  12. package/ci/test-optimization-validation/runner-command.js +2 -4
  13. package/ci/test-optimization-validation/runner-contract.js +1 -2
  14. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +1 -15
  15. package/ci/vitest-no-worker-init-runner-setup.mjs +27 -0
  16. package/ci/vitest-no-worker-init-setup.mjs +10 -7
  17. package/index.d.ts +41 -19
  18. package/initialize.mjs +1 -3
  19. package/openfeature/rules-based.d.ts +11 -0
  20. package/openfeature/rules-based.js +7 -0
  21. package/package.json +25 -22
  22. package/packages/datadog-core/src/storage.js +16 -0
  23. package/packages/datadog-esbuild/index.js +18 -12
  24. package/packages/datadog-instrumentations/index.js +2 -1
  25. package/packages/datadog-instrumentations/src/aerospike.js +3 -3
  26. package/packages/datadog-instrumentations/src/ai.js +3 -3
  27. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +0 -6
  28. package/packages/datadog-instrumentations/src/aws-sdk.js +21 -13
  29. package/packages/datadog-instrumentations/src/cassandra-driver.js +25 -20
  30. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +9 -23
  31. package/packages/datadog-instrumentations/src/cucumber.js +65 -41
  32. package/packages/datadog-instrumentations/src/elasticsearch.js +1 -5
  33. package/packages/datadog-instrumentations/src/electron.js +6 -6
  34. package/packages/datadog-instrumentations/src/fastify.js +13 -4
  35. package/packages/datadog-instrumentations/src/fs.js +2 -2
  36. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +9 -5
  37. package/packages/datadog-instrumentations/src/grpc/client.js +2 -0
  38. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +28 -9
  39. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +1 -2
  40. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  41. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  42. package/packages/datadog-instrumentations/src/helpers/register.js +45 -1
  43. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -0
  44. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +89 -6
  45. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +5 -6
  46. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/jest.js +12 -0
  47. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +48 -31
  48. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/vitest.js +8 -0
  49. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +7 -7
  50. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +5 -0
  51. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms/postgres.js +42 -4
  52. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +7 -10
  53. package/packages/datadog-instrumentations/src/ioredis.js +7 -5
  54. package/packages/datadog-instrumentations/src/iovalkey.js +7 -5
  55. package/packages/datadog-instrumentations/src/jest.js +72 -67
  56. package/packages/datadog-instrumentations/src/knex.js +9 -5
  57. package/packages/datadog-instrumentations/src/mariadb-bundle.js +3 -3
  58. package/packages/datadog-instrumentations/src/mocha/common.js +20 -8
  59. package/packages/datadog-instrumentations/src/mocha/main.js +140 -43
  60. package/packages/datadog-instrumentations/src/mocha/utils.js +38 -4
  61. package/packages/datadog-instrumentations/src/mocha/worker.js +1 -2
  62. package/packages/datadog-instrumentations/src/mongodb-core.js +3 -3
  63. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  64. package/packages/datadog-instrumentations/src/nova-sonic/audio.js +173 -0
  65. package/packages/datadog-instrumentations/src/nova-sonic/index.js +209 -0
  66. package/packages/datadog-instrumentations/src/nova-sonic/session.js +492 -0
  67. package/packages/datadog-instrumentations/src/openai-realtime/index.js +18 -6
  68. package/packages/datadog-instrumentations/src/openai-realtime/session.js +1 -1
  69. package/packages/datadog-instrumentations/src/openai.js +7 -0
  70. package/packages/datadog-instrumentations/src/pino.js +5 -6
  71. package/packages/datadog-instrumentations/src/playwright.js +2 -0
  72. package/packages/datadog-instrumentations/src/redis.js +27 -13
  73. package/packages/datadog-instrumentations/src/router.js +30 -0
  74. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +2 -6
  75. package/packages/datadog-instrumentations/src/stripe.js +23 -12
  76. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +8 -5
  77. package/packages/datadog-instrumentations/src/vitest-main.js +16 -18
  78. package/packages/datadog-instrumentations/src/vitest-util.js +7 -4
  79. package/packages/datadog-instrumentations/src/vitest-worker.js +1 -2
  80. package/packages/datadog-instrumentations/src/vitest.js +9 -1
  81. package/packages/datadog-instrumentations/src/webdriverio.js +5 -8
  82. package/packages/datadog-instrumentations/src/ws.js +1 -4
  83. package/packages/datadog-plugin-amqplib/src/client.js +1 -2
  84. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -5
  85. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -2
  86. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -8
  87. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +18 -0
  88. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/sonic.js +45 -0
  89. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
  90. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -5
  91. package/packages/datadog-plugin-azure-durable-functions/src/index.js +5 -4
  92. package/packages/datadog-plugin-azure-functions/src/index.js +5 -3
  93. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +0 -2
  94. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +4 -5
  95. package/packages/datadog-plugin-cypress/src/index.js +6 -6
  96. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -2
  97. package/packages/datadog-plugin-cypress/src/support.js +3 -4
  98. package/packages/datadog-plugin-express/src/code_origin.js +1 -2
  99. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -2
  100. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -2
  101. package/packages/datadog-plugin-graphql/src/execute.js +5 -5
  102. package/packages/datadog-plugin-grpc/src/util.js +1 -2
  103. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -2
  104. package/packages/datadog-plugin-mocha/src/index.js +7 -10
  105. package/packages/datadog-plugin-mocha/src/webdriverio-video.js +8 -4
  106. package/packages/datadog-plugin-mongodb-core/src/query.js +8 -5
  107. package/packages/datadog-plugin-next/src/index.js +1 -2
  108. package/packages/datadog-plugin-openai/src/tracing.js +5 -2
  109. package/packages/datadog-plugin-openai-agents/src/integration.js +6 -2
  110. package/packages/datadog-plugin-openai-agents/src/processor.js +2 -4
  111. package/packages/datadog-plugin-postgres/src/index.js +24 -0
  112. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -5
  113. package/packages/datadog-plugin-rhea/src/consumer.js +1 -4
  114. package/packages/datadog-plugin-vitest/src/index.js +5 -2
  115. package/packages/datadog-shimmer/src/shimmer.js +5 -4
  116. package/packages/datadog-webpack/index.js +55 -43
  117. package/packages/datadog-webpack/src/loader.js +56 -8
  118. package/packages/dd-trace/src/aiguard/evaluation.js +143 -1
  119. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -4
  120. package/packages/dd-trace/src/aiguard/messages/utils.js +1 -2
  121. package/packages/dd-trace/src/aiguard/redaction.js +8 -2
  122. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +3 -2
  123. package/packages/dd-trace/src/appsec/api_security/sampler.js +2 -6
  124. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -9
  125. package/packages/dd-trace/src/appsec/handlers/http-request.js +3 -9
  126. package/packages/dd-trace/src/appsec/handlers/http-response.js +7 -2
  127. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +4 -3
  128. package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +1 -4
  129. package/packages/dd-trace/src/appsec/iast/path-line.js +2 -1
  130. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +7 -1
  131. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -3
  132. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -3
  133. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +1 -6
  134. package/packages/dd-trace/src/appsec/lambda.js +1 -3
  135. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +37 -27
  136. package/packages/dd-trace/src/appsec/rasp/lfi.js +9 -5
  137. package/packages/dd-trace/src/appsec/reporter.js +21 -1
  138. package/packages/dd-trace/src/appsec/stack_trace.js +4 -4
  139. package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +3 -6
  140. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +3 -2
  141. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +1 -1
  142. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -7
  143. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +1 -2
  144. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +2 -8
  145. package/packages/dd-trace/src/ci-visibility/resolve-runfile-path.js +54 -0
  146. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +5 -8
  147. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +2 -53
  148. package/packages/dd-trace/src/config/config-types.d.ts +2 -2
  149. package/packages/dd-trace/src/config/defaults.js +5 -5
  150. package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
  151. package/packages/dd-trace/src/config/helper.js +34 -3
  152. package/packages/dd-trace/src/config/index.js +55 -9
  153. package/packages/dd-trace/src/config/normalize-service.js +1 -1
  154. package/packages/dd-trace/src/config/parsers.js +2 -2
  155. package/packages/dd-trace/src/config/supported-configurations.json +44 -0
  156. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  157. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -4
  158. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +6 -5
  159. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -5
  160. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -0
  161. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +4 -6
  162. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -0
  163. package/packages/dd-trace/src/debugger/guardrail-metrics.js +2 -2
  164. package/packages/dd-trace/src/debugger/unsupported-runtime.js +16 -0
  165. package/packages/dd-trace/src/dogstatsd.js +1 -1
  166. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  167. package/packages/dd-trace/src/exporters/common/request.js +1 -5
  168. package/packages/dd-trace/src/id.js +14 -1
  169. package/packages/dd-trace/src/lambda/handler.js +179 -19
  170. package/packages/dd-trace/src/lambda/index.js +15 -1
  171. package/packages/dd-trace/src/lambda/runtime/patch.js +34 -7
  172. package/packages/dd-trace/src/llmobs/agent-manifest.js +19 -2
  173. package/packages/dd-trace/src/llmobs/audio-utils.js +3 -2
  174. package/packages/dd-trace/src/llmobs/constants/tags.js +15 -0
  175. package/packages/dd-trace/src/llmobs/experiments/experiment.js +1 -1
  176. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +15 -0
  177. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +1 -2
  178. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +1 -3
  179. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  180. package/packages/dd-trace/src/llmobs/plugins/nova-sonic.js +92 -0
  181. package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +5 -0
  182. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +64 -2
  183. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +38 -0
  184. package/packages/dd-trace/src/llmobs/prompts/manager.js +5 -7
  185. package/packages/dd-trace/src/llmobs/prompts/prompt.js +1 -1
  186. package/packages/dd-trace/src/llmobs/sdk.js +15 -4
  187. package/packages/dd-trace/src/llmobs/span_processor.js +13 -2
  188. package/packages/dd-trace/src/llmobs/tagger.js +20 -10
  189. package/packages/dd-trace/src/llmobs/util.js +11 -6
  190. package/packages/dd-trace/src/llmobs/writers/base.js +4 -5
  191. package/packages/dd-trace/src/log/log.js +1 -5
  192. package/packages/dd-trace/src/openfeature/evaluation-telemetry-opt-out-hook.js +51 -0
  193. package/packages/dd-trace/src/openfeature/flagging_provider.js +48 -21
  194. package/packages/dd-trace/src/openfeature/index.js +6 -0
  195. package/packages/dd-trace/src/openfeature/writers/util.js +4 -4
  196. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +1 -1
  197. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  198. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -2
  199. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +1 -1
  200. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +24 -24
  201. package/packages/dd-trace/src/opentracing/propagation/text_map.js +53 -13
  202. package/packages/dd-trace/src/otel-sampling.js +4 -5
  203. package/packages/dd-trace/src/otel-thread-ctx.js +79 -13
  204. package/packages/dd-trace/src/plugin_manager.js +1 -2
  205. package/packages/dd-trace/src/plugins/database.js +7 -16
  206. package/packages/dd-trace/src/plugins/tracing.js +0 -1
  207. package/packages/dd-trace/src/plugins/util/git.js +99 -59
  208. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -5
  209. package/packages/dd-trace/src/plugins/util/stacktrace.js +2 -1
  210. package/packages/dd-trace/src/plugins/util/test.js +50 -4
  211. package/packages/dd-trace/src/plugins/util/web.js +90 -20
  212. package/packages/dd-trace/src/priority_sampler.js +1 -2
  213. package/packages/dd-trace/src/profiling/profiler.js +4 -6
  214. package/packages/dd-trace/src/proxy.js +1 -2
  215. package/packages/dd-trace/src/ritm.js +4 -2
  216. package/packages/dd-trace/src/serverless.js +5 -0
  217. package/packages/dd-trace/src/span_processor.js +13 -17
  218. package/packages/dd-trace/src/span_stats.js +4 -1
  219. package/packages/dd-trace/src/telemetry/endpoints.js +1 -3
  220. package/packages/dd-trace/src/telemetry/logs/log-collector.js +1 -1
  221. package/packages/dd-trace/src/util.js +37 -4
  222. package/packages/dd-trace/src/web-tags-cache.js +6 -2
  223. package/vendor/dist/@apm-js-collab/code-transformer/index.js +8 -8
  224. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  225. package/vendor/dist/@datadog/openfeature-node-server/rules-based/LICENSE +201 -0
  226. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.d.ts +925 -0
  227. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.js +1 -0
  228. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  229. package/vendor/dist/protobufjs/index.js +1 -1
  230. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  231. package/vendor/dist/shell-quote/index.js +1 -1
@@ -102,7 +102,7 @@ const efdRetryCountByPickleId = new Map()
102
102
  const efdSlowAbortedPickleIds = new Set()
103
103
  const finishedParallelSuites = new Set()
104
104
  const numAttemptToCtx = new Map()
105
- const newTestsByTestFullname = new Map()
105
+ const efdStatusesByPickleId = new Map()
106
106
  const attemptToFixTestsByTestFullname = new Map()
107
107
  const modifiedTestsByPickleId = new Map()
108
108
  const runnerToRetryState = new WeakMap()
@@ -129,6 +129,7 @@ let earlyFlakeDetectionRetryPolicy = EMPTY_EFD_RETRY_POLICY
129
129
  let earlyFlakeDetectionFaultyThreshold = 0
130
130
  let isEarlyFlakeDetectionFaulty = false
131
131
  let isFlakyTestRetriesEnabled = false
132
+ let hasAutomaticRetries = false
132
133
  let isDynamicAtrEnabled = false
133
134
  let dynamicAtrBuckets
134
135
  let isKnownTestsEnabled = false
@@ -200,13 +201,13 @@ function getSuiteStatusFromTestStatuses (testStatuses) {
200
201
  return 'pass'
201
202
  }
202
203
 
203
- function publishWorkerEfdRetryCount (pickle, retryCount) {
204
+ function publishWorkerEfdRetryCount (pickle, retryCount, isModified) {
204
205
  const message = {
205
206
  [DD_EFD_RETRY_COUNT_MESSAGE]: {
206
207
  pickleId: pickle.id,
207
208
  retryCount,
209
+ isModified,
208
210
  testFileAbsolutePath: pickle.uri,
209
- testName: pickle.name,
210
211
  },
211
212
  }
212
213
 
@@ -220,10 +221,12 @@ function publishWorkerEfdRetryCount (pickle, retryCount) {
220
221
  }
221
222
 
222
223
  try {
223
- const { isMainThread, parentPort } = require('node:worker_threads')
224
+ const { isMainThread, parentPort, workerData } = require('node:worker_threads')
224
225
  if (isMainThread || !parentPort) return
225
226
 
226
- parentPort.postMessage(message)
227
+ // Share Cucumber's envelope port so EFD metadata arrives before test results.
228
+ const port = workerData?.port ?? parentPort
229
+ port.postMessage(message)
227
230
  } catch {
228
231
  // ignore IPC errors
229
232
  }
@@ -253,6 +256,7 @@ function configureParallelWorkerWorldParameters (options) {
253
256
  }
254
257
 
255
258
  options.worldParameters._ddIsFlakyTestRetriesEnabled = isFlakyTestRetriesEnabled
259
+ options.worldParameters._ddHasAutomaticRetries = hasAutomaticRetries
256
260
  options.worldParameters._ddNumTestRetries = numTestRetries
257
261
  options.worldParameters._ddIsDynamicAtrEnabled = isDynamicAtrEnabled
258
262
  options.worldParameters._ddDynamicAtrBuckets = dynamicAtrBuckets
@@ -296,6 +300,7 @@ function readParallelWorkerWorldParameters (options) {
296
300
  /**
297
301
  * @param {object} worldParameters
298
302
  * @param {boolean} [worldParameters._ddIsFlakyTestRetriesEnabled]
303
+ * @param {boolean} [worldParameters._ddHasAutomaticRetries]
299
304
  * @param {number} [worldParameters._ddNumTestRetries]
300
305
  * @param {boolean} [worldParameters._ddIsDynamicAtrEnabled]
301
306
  * @param {number[]} [worldParameters._ddDynamicAtrBuckets]
@@ -304,6 +309,7 @@ function readParallelWorkerWorldParameters (options) {
304
309
  */
305
310
  function readParallelWorkerAtrParameters (worldParameters) {
306
311
  isFlakyTestRetriesEnabled = !!worldParameters._ddIsFlakyTestRetriesEnabled
312
+ hasAutomaticRetries = !!worldParameters._ddHasAutomaticRetries
307
313
  numTestRetries = worldParameters._ddNumTestRetries ?? 0
308
314
  isDynamicAtrEnabled = !!worldParameters._ddIsDynamicAtrEnabled
309
315
  dynamicAtrBuckets = worldParameters._ddDynamicAtrBuckets
@@ -385,14 +391,16 @@ function handleParallelTestCaseFinished (pickle, worstTestStepResult, usesNumeri
385
391
  const testFileAbsolutePath = pickle.uri
386
392
  const finished = (pickleResultByFile[testFileAbsolutePath] ||= [])
387
393
 
388
- if (shouldRunEarlyFlakeDetection() && isNew) {
389
- const testFullname = `${pickle.uri}:${pickle.name}`
390
- let testStatuses = newTestsByTestFullname.get(testFullname)
394
+ const isAttemptToFix = isTestManagementTestsEnabled &&
395
+ getTestProperties(getTestSuitePath(testFileAbsolutePath, process.cwd()), pickle.name).attemptToFix
396
+
397
+ if (!isAttemptToFix && shouldRunEarlyFlakeDetection() && (isNew || modifiedTestsByPickleId.get(pickle.id))) {
398
+ let testStatuses = efdStatusesByPickleId.get(pickle.id)
391
399
  if (testStatuses) {
392
400
  testStatuses.push(status)
393
401
  } else {
394
402
  testStatuses = [status]
395
- newTestsByTestFullname.set(testFullname, testStatuses)
403
+ efdStatusesByPickleId.set(pickle.id, testStatuses)
396
404
  }
397
405
  let efdRetryCount = efdRetryCountByPickleId.get(pickle.id)
398
406
  if (efdRetryCount === undefined) {
@@ -404,11 +412,8 @@ function handleParallelTestCaseFinished (pickle, worstTestStepResult, usesNumeri
404
412
  efdSlowAbortedPickleIds.add(pickle.id)
405
413
  }
406
414
  }
407
- maybeRecordFinalParallelEfdStatus({ pickleId: pickle.id, testFileAbsolutePath, testFullname })
408
- } else if (
409
- isTestManagementTestsEnabled &&
410
- getTestProperties(getTestSuitePath(testFileAbsolutePath, process.cwd()), pickle.name).attemptToFix
411
- ) {
415
+ maybeRecordFinalParallelEfdStatus({ pickleId: pickle.id, testFileAbsolutePath })
416
+ } else if (isAttemptToFix) {
412
417
  const testFullname = `${pickle.uri}:${pickle.name}`
413
418
  let testStatuses = attemptToFixTestsByTestFullname.get(testFullname)
414
419
  if (testStatuses) {
@@ -495,8 +500,14 @@ function finishParallelSuiteIfDone (testFileAbsolutePath) {
495
500
  const finished = pickleResultByFile[testFileAbsolutePath]
496
501
  const expectedPickles = pickleByFile[testFileAbsolutePath]
497
502
 
498
- if (!finished || !expectedPickles || finished.length !== expectedPickles.length) return
499
- if (finishedParallelSuites.has(testFileAbsolutePath)) return
503
+ if (
504
+ !finished ||
505
+ !expectedPickles ||
506
+ finished.length !== expectedPickles.length ||
507
+ finishedParallelSuites.has(testFileAbsolutePath)
508
+ ) {
509
+ return
510
+ }
500
511
 
501
512
  finishedParallelSuites.add(testFileAbsolutePath)
502
513
  testSuiteFinishCh.publish({
@@ -505,29 +516,30 @@ function finishParallelSuiteIfDone (testFileAbsolutePath) {
505
516
  })
506
517
  }
507
518
 
508
- function maybeRecordFinalParallelEfdStatus ({ pickleId, testFileAbsolutePath, testFullname }) {
519
+ function maybeRecordFinalParallelEfdStatus ({ pickleId, testFileAbsolutePath }) {
509
520
  const efdRetryCount = efdRetryCountByPickleId.get(pickleId)
510
- const testStatuses = newTestsByTestFullname.get(testFullname)
521
+ const testStatuses = efdStatusesByPickleId.get(pickleId)
511
522
  const finished = pickleResultByFile[testFileAbsolutePath]
512
523
 
513
- if (efdRetryCount === undefined || !testStatuses || !finished) return
514
- if (testStatuses.length !== efdRetryCount + 1) return
524
+ if (efdRetryCount === undefined || !testStatuses || !finished || testStatuses.length !== efdRetryCount + 1) return
515
525
 
516
- finished.push(getTestStatusFromRetries(testStatuses))
517
- newTestsByTestFullname.delete(testFullname)
526
+ finished.push(efdRetryCount === 0 ? testStatuses[0] : getTestStatusFromRetries(testStatuses))
527
+ efdStatusesByPickleId.delete(pickleId)
518
528
  finishParallelSuiteIfDone(testFileAbsolutePath)
519
529
  }
520
530
 
521
531
  function handleEfdRetryCountMessage (message) {
522
- const { pickleId, retryCount, testFileAbsolutePath, testName } = message
532
+ const { pickleId, retryCount, isModified, testFileAbsolutePath } = message
523
533
 
524
- if (!pickleId || typeof retryCount !== 'number' || !testFileAbsolutePath || !testName) return
534
+ if (!pickleId || !testFileAbsolutePath) return
535
+
536
+ if (isModified) modifiedTestsByPickleId.set(pickleId, true)
537
+ if (typeof retryCount !== 'number') return
525
538
 
526
539
  efdRetryCountByPickleId.set(pickleId, retryCount)
527
540
  maybeRecordFinalParallelEfdStatus({
528
541
  pickleId,
529
542
  testFileAbsolutePath,
530
- testFullname: `${testFileAbsolutePath}:${testName}`,
531
543
  })
532
544
  }
533
545
 
@@ -733,9 +745,10 @@ function publishRetriedAttempt (runner, state) {
733
745
  const nextAttempt = currentAttempt + 1
734
746
  const failedAttemptCtx = numAttemptToCtx.get(currentAttempt)
735
747
  const isFirstAttempt = currentAttempt === 0
736
- const isAtrRetry = !isFirstAttempt && isFlakyTestRetriesEnabled
748
+ const isAtrRetry = !isFirstAttempt && state.hasAutomaticRetries
737
749
 
738
750
  if (isFirstAttempt && isDynamicAtrEnabled && isFlakyTestRetriesEnabled) {
751
+ state.hasAutomaticRetries = true
739
752
  state.dynamicAtrRetryCount = getDynamicAtrRetryCount(
740
753
  performance.now() - state.executionStart,
741
754
  earlyFlakeDetectionRetryPolicy,
@@ -747,7 +760,7 @@ function publishRetriedAttempt (runner, state) {
747
760
  }
748
761
 
749
762
  // ATR: record this attempt as failed so when run().finally runs (after retry) we have all statuses
750
- if (isFlakyTestRetriesEnabled) {
763
+ if (state.hasAutomaticRetries) {
751
764
  const nameForKey = getCucumberTestName(runner.pickle.name, currentAttempt > 0)
752
765
  const atrKey = `${runner.pickle.uri}:${nameForKey}`
753
766
  if (atrStatusesByScenarioKey.has(atrKey)) {
@@ -823,7 +836,7 @@ function wrapRun (pl, isLatestVersion, version) {
823
836
  }
824
837
  const ctx = testStartPayload
825
838
  const promises = {}
826
- const state = { numAttempt: 0, promises, testStartPayload }
839
+ const state = { numAttempt: 0, promises, testStartPayload, hasAutomaticRetries }
827
840
  numAttemptToCtx.set(state.numAttempt, ctx)
828
841
  runnerToRetryState.set(this, state)
829
842
  if (isTestManagementTestsEnabled && getTestProperties(testSuitePath, this.pickle.name).attemptToFix) {
@@ -912,12 +925,13 @@ function wrapRun (pl, isLatestVersion, version) {
912
925
  isNew = newTestPickleIds.has(this.pickle.id)
913
926
  }
914
927
 
915
- if (isNew || isModified) {
928
+ if (!isAttemptToFix && (isNew || isModified)) {
916
929
  isEfdRetry = numRetries > 0
917
930
  }
918
931
 
919
932
  if (
920
933
  shouldRunEarlyFlakeDetection() &&
934
+ !isAttemptToFix &&
921
935
  status !== 'skip' &&
922
936
  (isNew || isModified) &&
923
937
  !isEfdRetry &&
@@ -952,7 +966,7 @@ function wrapRun (pl, isLatestVersion, version) {
952
966
 
953
967
  // ATR: accumulate statuses by stable scenario key (uri:name) so retries are grouped.
954
968
  // Cucumber appends " (attempt N)" or " (attempt N, retried)" to the scenario name; normalize for keying.
955
- if (isFlakyTestRetriesEnabled && !isAttemptToFix && !isEfdRetry && numTestRetries > 0) {
969
+ if (state.hasAutomaticRetries && !isAttemptToFix && !isEfdRetry && numTestRetries > 0) {
956
970
  const atrKey = `${this.pickle.uri}:${testName}`
957
971
  if (atrStatusesByScenarioKey.has(atrKey)) {
958
972
  atrStatusesByScenarioKey.get(atrKey).push(status)
@@ -1001,7 +1015,7 @@ function wrapRun (pl, isLatestVersion, version) {
1001
1015
 
1002
1016
  // Notice that ATR is handled using cucumber native retries features.
1003
1017
  // Therefore, if we reach this point, we are certain that it's the last ATR execution
1004
- const isLastAtrRetry = isFlakyTestRetriesEnabled && !isAttemptToFix && !isEfdRetry &&
1018
+ const isLastAtrRetry = state.hasAutomaticRetries && !isAttemptToFix && !isEfdRetry &&
1005
1019
  numTestRetries > 0 && this.maxAttempts > 1
1006
1020
 
1007
1021
  const statuses = lastStatusByPickleId.get(this.pickle.id)
@@ -1030,8 +1044,8 @@ function wrapRun (pl, isLatestVersion, version) {
1030
1044
  error,
1031
1045
  isNew,
1032
1046
  isEfdRetry,
1033
- isFlakyRetry: state.numAttempt > 0 && isFlakyTestRetriesEnabled,
1034
- isExternalRetry: state.numAttempt > 0 && !isFlakyTestRetriesEnabled,
1047
+ isFlakyRetry: state.numAttempt > 0 && state.hasAutomaticRetries,
1048
+ isExternalRetry: state.numAttempt > 0 && !state.hasAutomaticRetries,
1035
1049
  isAttemptToFix,
1036
1050
  isAttemptToFixRetry,
1037
1051
  hasFailedAllRetries,
@@ -1252,7 +1266,9 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
1252
1266
  const processArgv = process.argv.slice(2).join(' ')
1253
1267
  const command = getEnvironmentVariable('npm_lifecycle_script') || `cucumber-js ${processArgv}`
1254
1268
 
1255
- if (isFlakyTestRetriesEnabled && !options.retry && numTestRetries > 0) {
1269
+ const originalRetryCount = options.retry
1270
+ hasAutomaticRetries = isFlakyTestRetriesEnabled && !options.retry && numTestRetries > 0
1271
+ if (hasAutomaticRetries) {
1256
1272
  options.retry = numTestRetries
1257
1273
  }
1258
1274
 
@@ -1265,7 +1281,8 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
1265
1281
  efdRetryCountByPickleId.clear()
1266
1282
  efdSlowAbortedPickleIds.clear()
1267
1283
  finishedParallelSuites.clear()
1268
- newTestsByTestFullname.clear()
1284
+ efdStatusesByPickleId.clear()
1285
+ modifiedTestsByPickleId.clear()
1269
1286
  sessionStartCh.publish({ command, frameworkVersion })
1270
1287
 
1271
1288
  if (!errorSkippableRequest && skippedSuites.length) {
@@ -1293,6 +1310,8 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
1293
1310
  log.error('Cucumber test session finalization error: %s', finalizationError)
1294
1311
  }
1295
1312
  throw error
1313
+ } finally {
1314
+ if (hasAutomaticRetries) options.retry = originalRetryCount
1296
1315
  }
1297
1316
  const success = satisfies(frameworkVersion, '>=13.1.0') ? result.success : result
1298
1317
 
@@ -1478,10 +1497,15 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
1478
1497
  }
1479
1498
  }
1480
1499
  const originalRetry = this.options.retry
1481
- const isManagedRetry = isAttemptToFix || (shouldRunEarlyFlakeDetection() && (isNew || isModified))
1500
+ const isEfdTest = !isAttemptToFix && shouldRunEarlyFlakeDetection() && (isNew || isModified)
1501
+ const isManagedRetry = isAttemptToFix || isEfdTest
1482
1502
  if (isManagedRetry) {
1483
1503
  this.options.retry = 0
1484
1504
  }
1505
+ if (isWorker && isModified && isEfdTest) {
1506
+ // The coordinator must recognize modified EFD tests before their first result arrives.
1507
+ publishWorkerEfdRetryCount(pickle, undefined, true)
1508
+ }
1485
1509
  // TODO: for >=11 we could use `runTestCaseResult` instead of accumulating results in `lastStatusByPickleId`
1486
1510
  const firstExecutionStart = performance.now()
1487
1511
  let runTestCaseResult = await runTestCaseFunction.apply(this, arguments)
@@ -1493,7 +1517,7 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
1493
1517
  const testStatuses = lastStatusByPickleId.get(pickle.id)
1494
1518
  const lastTestStatus = testStatuses.at(-1)
1495
1519
 
1496
- // New tests should not be marked as attempt to fix, so EFD + Attempt to fix should not be enabled at the same time
1520
+ // Attempt to Fix owns retries for managed tests, including modified tests.
1497
1521
  if (isAttemptToFix && lastTestStatus !== 'skip') {
1498
1522
  for (let retryIndex = 0; retryIndex < testManagementAttemptToFixRetries; retryIndex++) {
1499
1523
  numRetriesByPickleId.set(pickle.id, retryIndex + 1)
@@ -1502,8 +1526,8 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
1502
1526
  }
1503
1527
  }
1504
1528
 
1505
- // If it's a new test and it hasn't been skipped, we run it again
1506
- if (shouldRunEarlyFlakeDetection() && lastTestStatus !== 'skip' && (isNew || isModified)) {
1529
+ // Retry eligible new or modified tests unless their first attempt was skipped.
1530
+ if (isEfdTest && lastTestStatus !== 'skip') {
1507
1531
  let efdRetryCount = efdRetryCountByPickleId.get(pickle.id)
1508
1532
  if (efdRetryCount === undefined) {
1509
1533
  efdRetryCount = getEfdRetryCountForDuration(firstExecutionDurationMs, earlyFlakeDetectionRetryPolicy)
@@ -1525,7 +1549,7 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
1525
1549
  let shouldBePassedByEFD = false
1526
1550
  let shouldBePassedByTestManagement = false
1527
1551
  let shouldBeFailedByAttemptToFix = false
1528
- if ((isNew || isModified) && shouldRunEarlyFlakeDetection()) {
1552
+ if (isEfdTest) {
1529
1553
  /**
1530
1554
  * If Early Flake Detection (EFD) is enabled the logic is as follows:
1531
1555
  * - If all attempts for a test are failing, the test has failed and we will let the test process fail.
@@ -1587,7 +1611,7 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
1587
1611
  return shouldBePassedByTestManagement
1588
1612
  }
1589
1613
 
1590
- if (isNewerCucumberVersion && shouldRunEarlyFlakeDetection() && (isNew || isModified)) {
1614
+ if (isNewerCucumberVersion && isEfdTest) {
1591
1615
  return shouldBePassedByEFD
1592
1616
  }
1593
1617
 
@@ -72,11 +72,7 @@ function createWrapRequest (name) {
72
72
 
73
73
  return function wrapRequest (request) {
74
74
  return function (params, options, cb) {
75
- if (!startCh.hasSubscribers) {
76
- return request.apply(this, arguments)
77
- }
78
-
79
- if (!params) return request.apply(this, arguments)
75
+ if (!startCh.hasSubscribers || !params) return request.apply(this, arguments)
80
76
 
81
77
  const ctx = { params }
82
78
  return startCh.runStores(ctx, () => {
@@ -83,10 +83,10 @@ function createWrapAddListener (ch, mappings) {
83
83
  }
84
84
  }
85
85
 
86
- function createWrapRemoveListener (mappings) {
86
+ function createWrapRemoveListener () {
87
87
  return function wrapRemoveListener (removeListener) {
88
88
  return function (channel, listener) {
89
- const mapping = mappings[channel]
89
+ const mapping = listeners[channel]
90
90
 
91
91
  if (mapping) {
92
92
  const wrapper = mapping.get(listener)
@@ -212,10 +212,10 @@ addHook({ name: 'electron', versions: ['>=37.0.0'] }, electron => {
212
212
  wrap(ipcRenderer, 'sendToHost', createWrapSend(rendererSendCh))
213
213
 
214
214
  wrap(ipcRenderer, 'addListener', createWrapAddListener(rendererReceiveCh, listeners))
215
- wrap(ipcRenderer, 'off', createWrapRemoveListener(listeners))
215
+ wrap(ipcRenderer, 'off', createWrapRemoveListener())
216
216
  wrap(ipcRenderer, 'on', createWrapAddListener(rendererReceiveCh, listeners))
217
217
  wrap(ipcRenderer, 'once', createWrapAddListener(rendererReceiveCh, listeners))
218
- wrap(ipcRenderer, 'removeListener', createWrapRemoveListener(listeners))
218
+ wrap(ipcRenderer, 'removeListener', createWrapRemoveListener())
219
219
  wrap(ipcRenderer, 'removeAllListeners', createWrapRemoveAllListeners(listeners))
220
220
 
221
221
  ipcRenderer.send('datadog:apm:renderer:patched')
@@ -223,12 +223,12 @@ addHook({ name: 'electron', versions: ['>=37.0.0'] }, electron => {
223
223
  wrap(ipcMain, 'addListener', createWrapAddListener(mainReceiveCh, listeners))
224
224
  wrap(ipcMain, 'handle', createWrapAddListener(mainHandleCh, handlers))
225
225
  wrap(ipcMain, 'handleOnce', createWrapAddListener(mainHandleCh, handlers))
226
- wrap(ipcMain, 'off', createWrapRemoveListener(listeners))
226
+ wrap(ipcMain, 'off', createWrapRemoveListener())
227
227
  wrap(ipcMain, 'on', createWrapAddListener(mainReceiveCh, listeners))
228
228
  wrap(ipcMain, 'once', createWrapAddListener(mainReceiveCh, listeners))
229
229
  wrap(ipcMain, 'removeAllListeners', createWrapRemoveAllListeners(listeners))
230
230
  wrap(ipcMain, 'removeHandler', createWrapRemoveAllListeners(handlers))
231
- wrap(ipcMain, 'removeListener', createWrapRemoveListener(listeners))
231
+ wrap(ipcMain, 'removeListener', createWrapRemoveListener())
232
232
 
233
233
  ipcMain.once('datadog:apm:renderer:patched', event => rendererPatchedCh.publish(event))
234
234
 
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const shimmer = require('../../datadog-shimmer')
4
+ const { isEmpty } = require('../../dd-trace/src/util')
4
5
  const { addHook, channel, createErrorPublisher } = require('./helpers/instrument')
5
6
 
6
7
  const errorChannel = channel('apm:fastify:middleware:error')
@@ -186,6 +187,17 @@ function onRequest (request, reply, done) {
186
187
  return done()
187
188
  }
188
189
 
190
+ /**
191
+ * Publishing an empty body would set a WAF address for nothing, so only non-empty bodies are published.
192
+ * Strings, arrays and binary bodies are checked by length to avoid enumerating one key per character or element.
193
+ *
194
+ * @param {unknown} body
195
+ */
196
+ function hasBody (body) {
197
+ if (typeof body === 'string') return body.length > 0
198
+ return typeof body === 'object' && body !== null && !isEmpty(body)
199
+ }
200
+
189
201
  function preHandler (request, reply, done) {
190
202
  if (typeof done !== 'function') return
191
203
  if (!reply || typeof reply.send !== 'function') return done()
@@ -194,11 +206,8 @@ function preHandler (request, reply, done) {
194
206
  const res = getRes(reply)
195
207
  const ctx = { req, res }
196
208
 
197
- // eslint-disable-next-line no-restricted-syntax -- arbitrary body keys; publishing {} sets a WAF address
198
- const hasBody = request.body && Object.keys(request.body).length > 0
199
-
200
209
  // For multipart/form-data, the body is not available until after preValidation hook
201
- if (bodyParserReadCh.hasSubscribers && hasBody && !bodyPublished.has(req)) {
210
+ if (bodyParserReadCh.hasSubscribers && !bodyPublished.has(req) && hasBody(request.body)) {
202
211
  ctx.abortController = new AbortController()
203
212
  ctx.body = request.body
204
213
  bodyParserReadCh.publish(ctx)
@@ -232,9 +232,9 @@ function getMethodParamsRelationByPrefix (prefix) {
232
232
  return paramsByMethod
233
233
  }
234
234
 
235
- function createWatchWrapFunction (override = '') {
235
+ function createWatchWrapFunction () {
236
236
  return function wrapFunction (original) {
237
- const name = override || original.name
237
+ const name = original.name
238
238
  const method = name
239
239
  const operation = name
240
240
  return function (...args) {
@@ -126,10 +126,14 @@ function wrapMethod (method) {
126
126
  }
127
127
  }
128
128
 
129
- function massWrap (obj, methods, wrapper) {
129
+ /**
130
+ * @param {Record<string, Function>} obj
131
+ * @param {string[]} methods
132
+ */
133
+ function massWrap (obj, methods) {
130
134
  for (const method of methods) {
131
135
  if (typeof obj[method] === 'function') {
132
- shimmer.wrap(obj, method, wrapper)
136
+ shimmer.wrap(obj, method, wrapMethod)
133
137
  }
134
138
  }
135
139
  }
@@ -274,11 +278,11 @@ addHook({ name: '@google-cloud/pubsub', versions: ['>=1.2'] }, (obj) => {
274
278
  addHook({ name: '@google-cloud/pubsub', versions: ['>=1.2'] }, (obj) => {
275
279
  const { PublisherClient, SchemaServiceClient, SubscriberClient } = obj.v1
276
280
 
277
- massWrap(PublisherClient.prototype, publisherMethods, wrapMethod)
278
- massWrap(SubscriberClient.prototype, subscriberMethods, wrapMethod)
281
+ massWrap(PublisherClient.prototype, publisherMethods)
282
+ massWrap(SubscriberClient.prototype, subscriberMethods)
279
283
 
280
284
  if (SchemaServiceClient) {
281
- massWrap(SchemaServiceClient.prototype, schemaServiceMethods, wrapMethod)
285
+ massWrap(SchemaServiceClient.prototype, schemaServiceMethods)
282
286
  }
283
287
 
284
288
  return obj
@@ -246,6 +246,8 @@ function patch (hasPeer = false) {
246
246
  return function patch (grpc) {
247
247
  const proto = grpc.Client.prototype
248
248
 
249
+ if (instances.has(proto)) return grpc
250
+
249
251
  instances.set(proto, grpc)
250
252
 
251
253
  shimmer.wrap(proto, 'makeBidiStreamRequest', createWrapMakeRequest(types.bidi, hasPeer))
@@ -4,7 +4,7 @@ const Module = require('module')
4
4
  const dc = require('dc-polyfill')
5
5
 
6
6
  const log = require('../../../dd-trace/src/log')
7
- const { loadChannel } = require('./register.js')
7
+ const { activate, loadChannel, registeredBuiltinNames } = require('./register.js')
8
8
  const {
9
9
  filename,
10
10
  getDisabledInstrumentations,
@@ -17,25 +17,44 @@ const { isRewriteActivationEnabled } = require('./rewriter/targets')
17
17
  const disabledInstrumentations = getDisabledInstrumentations()
18
18
 
19
19
  // register.js has now set up ritm (require-in-the-middle). In bundled
20
- // environments (webpack, esbuild), Node.js built-in modules required by
21
- // dd-trace internal modules (e.g. http from request.js) may have been loaded
22
- // before ritm was active. The bundler's module cache then returns those
23
- // pre-loaded modules for any subsequent require() calls, bypassing ritm.
24
- // Re-requiring them via the real Module.prototype.require ensures ritm applies
25
- // their instrumentation hooks.
20
+ // environments (webpack, esbuild, Turbopack), Node.js built-ins required by the
21
+ // application or by dd-trace itself may have been loaded and cached by the
22
+ // bundle before ritm was active, bypassing it. Re-requiring them via the real
23
+ // Module.prototype.require lets ritm apply their hooks to the shared exports.
26
24
  //
27
25
  // In regular Node.js, `module` is an instance of Module. In bundlers, the
28
26
  // module wrapper object is a plain object (not a Module instance), so we use
29
27
  // that to detect a bundled context and avoid unintended side-effects in
30
28
  // normal Node.js (e.g. shimmer-wrapping http before ESM modules load).
31
29
  if (!(module instanceof Module)) {
32
- for (const name of ['http', 'https']) {
30
+ patchLoadedBuiltins()
31
+ }
32
+
33
+ function patchLoadedBuiltins () {
34
+ // A built-in nobody has loaded cannot be cached by the bundle yet, and a later require reaches ritm, so it is
35
+ // skipped. moduleLoadList is undocumented: if it ever disappears, every registered built-in is loaded instead.
36
+ // @ts-expect-error - process.moduleLoadList is not typed
37
+ const { moduleLoadList } = process
38
+ const loadedBuiltins = Array.isArray(moduleLoadList) ? moduleLoadList : undefined
39
+
40
+ let patched = false
41
+ for (const name of registeredBuiltinNames) {
42
+ if (loadedBuiltins?.includes(`NativeModule ${name}`) === false) continue
43
+
33
44
  try {
34
45
  Module.prototype.require.call(module, name)
46
+ patched = true
35
47
  } catch {
36
48
  // Built-in not available in this environment, skip
37
49
  }
38
50
  }
51
+
52
+ if (!patched) return
53
+ try {
54
+ Module.syncBuiltinESMExports()
55
+ } catch (error) {
56
+ log.error('Error synchronizing bundled built-in ESM exports: %s', String(error?.message ?? error), error)
57
+ }
39
58
  }
40
59
 
41
60
  const CHANNEL = 'dd-trace:bundler:load'
@@ -96,7 +115,7 @@ dc.subscribe(CHANNEL, (message) => {
96
115
  if (disabledInstrumentations.has(name)) return
97
116
 
98
117
  if (payload.activate && isRewriteActivationEnabled(name)) {
99
- loadChannel.publish({ name })
118
+ activate(name, payload.version)
100
119
  return
101
120
  }
102
121
 
@@ -132,8 +132,7 @@ module.exports.checkForPotentialConflicts = function () {
132
132
 
133
133
  for (const pathToModule of Object.keys(require.cache)) {
134
134
  const { pkg } = extractPackageAndModulePath(pathToModule)
135
- if (naughties.has(pkg)) continue
136
- if (!potentialConflicts.has(pkg)) continue
135
+ if (naughties.has(pkg) || !potentialConflicts.has(pkg)) continue
137
136
 
138
137
  warnings.push(() => `Warning: Package '${pkg}' may cause conflicts with dd-trace.`)
139
138
 
@@ -97,6 +97,7 @@ module.exports = {
97
97
  'jest-config': () => require('../jest'),
98
98
  'jest-environment-node': () => require('../jest'),
99
99
  'jest-environment-jsdom': () => require('../jest'),
100
+ 'jest-runner': () => require('../jest'),
100
101
  'jest-runtime': () => require('../jest'),
101
102
  'jest-worker': () => require('../jest'),
102
103
  knex: () => require('../knex'),
@@ -88,7 +88,7 @@ exports.getHooks = function getHooks (names) {
88
88
  for (const { module } of rewriterInstrumentations) {
89
89
  if (!requested.has(module.name)) continue
90
90
  // Fresh objects, including the versions array: callers may adjust a hook
91
- // for their own registration (the ai, claude-agent-sdk and
91
+ // for their own registration (the ai and
92
92
  // aws-durable-execution-sdk-js plugins set `hook.file = null`), which must
93
93
  // not leak into any other call.
94
94
  const hook = { name: module.name, versions: [module.versionRange], file: module.filePath }
@@ -14,6 +14,8 @@ const {
14
14
  matchesInstrumentation,
15
15
  } = require('./instrumentation-utils')
16
16
  const rewriter = require('./rewriter')
17
+ const { getActivationSetup } = require('./rewriter/instrumentation-registry')
18
+ const { isNodeBuiltinModuleName } = require('./shared-utils')
17
19
 
18
20
  const DD_TRACE_DEBUG = getValueFromEnvSources('DD_TRACE_DEBUG')
19
21
 
@@ -58,6 +60,44 @@ const instrumentedIntegrationsSuccess = new Map()
58
60
  const alreadyLoggedIncompatibleIntegrations = new Set()
59
61
  /** @type {Set<string>} */
60
62
  const compatibleOrchestrionTargets = new Set()
63
+ /** @type {Set<(activation: import('./rewriter/instrumentation-registry').Activation) => void>} */
64
+ const blockedActivationSetups = new Set()
65
+ /**
66
+ * Node.js built-ins that passed the disabled and serverless checks and received a hook.
67
+ * @type {string[]}
68
+ */
69
+ const registeredBuiltinNames = []
70
+
71
+ /**
72
+ * @param {string} moduleName
73
+ * @param {string} [version]
74
+ */
75
+ function activate (moduleName, version) {
76
+ const setup = getActivationSetup(moduleName)
77
+ if (setup) {
78
+ if (blockedActivationSetups.has(setup)) return
79
+ // Block re-entrant activation until setup completes, and never retry a failed setup.
80
+ blockedActivationSetups.add(setup)
81
+ try {
82
+ setup({ moduleName, version })
83
+ blockedActivationSetups.delete(setup)
84
+ } catch (error) {
85
+ const message = String(error?.message ?? error)
86
+ log.error('Error during activation setup of %s: %s', moduleName, message, error)
87
+ telemetry('error', [
88
+ `error_type:${error?.constructor?.name ?? typeof error}`,
89
+ `integration:${moduleName}`,
90
+ `integration_version:${version ?? 'unknown'}`,
91
+ ], {
92
+ result: 'error',
93
+ result_class: 'internal_error',
94
+ result_reason: `Error during activation of ${moduleName}: ${message}`,
95
+ })
96
+ return
97
+ }
98
+ }
99
+ loadChannel.publish({ name: moduleName })
100
+ }
61
101
 
62
102
  orchestrionLoadChannel.subscribe(({ moduleName, version, result }) => {
63
103
  const nameVersion = `${moduleName}@${version}`
@@ -72,7 +112,7 @@ orchestrionLoadChannel.subscribe(({ moduleName, version, result }) => {
72
112
  if (result === 'matched' || result === 'rewritten') {
73
113
  compatibleOrchestrionTargets.add(nameVersion)
74
114
  instrumentedIntegrationsSuccess.set(nameVersion, true)
75
- if (result === 'rewritten') loadChannel.publish({ name: moduleName })
115
+ if (result === 'rewritten') activate(moduleName, version)
76
116
  }
77
117
  })
78
118
 
@@ -90,6 +130,8 @@ for (const name of names) {
90
130
  hook = hook.fn
91
131
  }
92
132
 
133
+ if (isNodeBuiltinModuleName(name)) registeredBuiltinNames.push(name)
134
+
93
135
  Hook([name], hookOptions, (moduleExports, moduleName, moduleBaseDir, moduleVersion, isIitm) => {
94
136
  // All loaded versions are first expected to fail instrumentation.
95
137
  if (!instrumentedIntegrationsSuccess.has(`${name}@${moduleVersion}`)) {
@@ -176,7 +218,9 @@ function logAbortedIntegrations () {
176
218
  }
177
219
 
178
220
  module.exports = {
221
+ activate,
179
222
  filename,
180
223
  pathSepExpr,
181
224
  loadChannel,
225
+ registeredBuiltinNames,
182
226
  }
@@ -188,6 +188,7 @@ function createMatcher (dcModule) {
188
188
  waitForAsyncEnd,
189
189
  } = require('./transforms')
190
190
  const {
191
+ postgresQueryAcquire,
191
192
  postgresQueryHandlers,
192
193
  postgresQueryLifecycle,
193
194
  postgresQueryPreparation,
@@ -204,6 +205,7 @@ function createMatcher (dcModule) {
204
205
  matcher.addTransform('configureGraphqlJitRuntime', configureGraphqlJitRuntime)
205
206
  matcher.addTransform('configureMercuriusRequest', configureMercuriusRequest)
206
207
  matcher.addTransform('publishDurableOrchestrationFailure', publishDurableOrchestrationFailure)
208
+ matcher.addTransform('postgresQueryAcquire', postgresQueryAcquire)
207
209
  matcher.addTransform('postgresQueryHandlers', postgresQueryHandlers)
208
210
  matcher.addTransform('postgresQueryLifecycle', postgresQueryLifecycle)
209
211
  matcher.addTransform('postgresQueryPreparation', postgresQueryPreparation)