dd-trace 6.8.0 → 6.10.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 (209) hide show
  1. package/ci/diagnose.js +27 -25
  2. package/ci/init.js +6 -6
  3. package/ci/runbook.md +114 -20
  4. package/ci/test-optimization-validation/approval-artifacts.js +36 -3
  5. package/ci/test-optimization-validation/approval.js +78 -22
  6. package/ci/test-optimization-validation/blocker-category.js +24 -0
  7. package/ci/test-optimization-validation/ci-discovery.js +23 -17
  8. package/ci/test-optimization-validation/ci-package-scripts.js +34 -6
  9. package/ci/test-optimization-validation/ci-remediation.js +1 -1
  10. package/ci/test-optimization-validation/cli.js +280 -52
  11. package/ci/test-optimization-validation/command-blocker.js +159 -20
  12. package/ci/test-optimization-validation/command-output-policy.js +9 -3
  13. package/ci/test-optimization-validation/command-runner.js +9 -7
  14. package/ci/test-optimization-validation/environment.js +4 -2
  15. package/ci/test-optimization-validation/executable.js +15 -13
  16. package/ci/test-optimization-validation/execution-lock.js +70 -0
  17. package/ci/test-optimization-validation/framework-adapters/cucumber-validation.json +3 -0
  18. package/ci/test-optimization-validation/framework-adapters/cucumber.js +657 -5
  19. package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
  20. package/ci/test-optimization-validation/generated-files.js +38 -28
  21. package/ci/test-optimization-validation/generated-test-contract.js +8 -4
  22. package/ci/test-optimization-validation/generated-verifier.js +24 -17
  23. package/ci/test-optimization-validation/literal-glob.js +52 -0
  24. package/ci/test-optimization-validation/manifest-loader.js +3 -0
  25. package/ci/test-optimization-validation/manifest-scaffold.js +300 -50
  26. package/ci/test-optimization-validation/manifest-schema.js +80 -17
  27. package/ci/test-optimization-validation/offline-fixtures.js +13 -4
  28. package/ci/test-optimization-validation/package-check.js +94 -0
  29. package/ci/test-optimization-validation/plan-writer.js +183 -10
  30. package/ci/test-optimization-validation/preflight-runner.js +110 -31
  31. package/ci/test-optimization-validation/project-build-artifact.js +31 -0
  32. package/ci/test-optimization-validation/redaction.js +18 -27
  33. package/ci/test-optimization-validation/report-writer.js +182 -48
  34. package/ci/test-optimization-validation/result-semantics.js +20 -3
  35. package/ci/test-optimization-validation/runner-command.js +40 -20
  36. package/ci/test-optimization-validation/runner-contract.js +79 -43
  37. package/ci/test-optimization-validation/scenarios/basic-reporting.js +59 -5
  38. package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
  39. package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
  40. package/ci/test-optimization-validation/static-diagnosis.js +10 -3
  41. package/ci/test-optimization-validation/test-output.js +2 -1
  42. package/ci/test-optimization-validation/validation-blocker.js +21 -0
  43. package/ci/vitest-no-worker-init-setup.mjs +140 -42
  44. package/index.d.ts +140 -1
  45. package/init.js +1 -17
  46. package/initialize.mjs +11 -0
  47. package/loader-hook.mjs +6 -4
  48. package/package.json +8 -5
  49. package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
  50. package/packages/datadog-esbuild/src/utils.js +16 -1
  51. package/packages/datadog-instrumentations/src/ai.js +61 -34
  52. package/packages/datadog-instrumentations/src/bluebird.js +6 -6
  53. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +81 -5
  54. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
  55. package/packages/datadog-instrumentations/src/cucumber.js +124 -47
  56. package/packages/datadog-instrumentations/src/dns.js +21 -2
  57. package/packages/datadog-instrumentations/src/electron.js +1 -0
  58. package/packages/datadog-instrumentations/src/fastify.js +3 -1
  59. package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
  60. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
  61. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  62. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +629 -0
  63. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +34 -9
  64. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +199 -0
  65. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
  66. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
  67. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +62 -0
  68. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
  69. package/packages/datadog-instrumentations/src/helpers/router-helper.js +37 -6
  70. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
  71. package/packages/datadog-instrumentations/src/jest.js +980 -234
  72. package/packages/datadog-instrumentations/src/mocha/main.js +108 -10
  73. package/packages/datadog-instrumentations/src/mocha/utils.js +66 -50
  74. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +25 -0
  75. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -18
  76. package/packages/datadog-instrumentations/src/mysql.js +46 -16
  77. package/packages/datadog-instrumentations/src/mysql2.js +119 -18
  78. package/packages/datadog-instrumentations/src/next.js +155 -23
  79. package/packages/datadog-instrumentations/src/openai.js +14 -0
  80. package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
  81. package/packages/datadog-instrumentations/src/pg.js +209 -11
  82. package/packages/datadog-instrumentations/src/playwright.js +173 -79
  83. package/packages/datadog-instrumentations/src/promise-js.js +4 -4
  84. package/packages/datadog-instrumentations/src/promise.js +3 -3
  85. package/packages/datadog-instrumentations/src/router.js +195 -19
  86. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +85 -13
  87. package/packages/datadog-instrumentations/src/vitest-main.js +528 -72
  88. package/packages/datadog-instrumentations/src/vitest-util.js +106 -12
  89. package/packages/datadog-instrumentations/src/vitest-worker.js +382 -40
  90. package/packages/datadog-instrumentations/src/webdriverio.js +212 -22
  91. package/packages/datadog-instrumentations/src/when.js +3 -3
  92. package/packages/datadog-instrumentations/src/ws.js +5 -1
  93. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
  94. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
  95. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -5
  96. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
  97. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -3
  98. package/packages/datadog-plugin-cucumber/src/index.js +5 -1
  99. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +51 -47
  100. package/packages/datadog-plugin-cypress/src/support.js +7 -4
  101. package/packages/datadog-plugin-electron/src/net.js +10 -4
  102. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
  103. package/packages/datadog-plugin-http/src/client.js +1 -1
  104. package/packages/datadog-plugin-http2/src/client.js +1 -1
  105. package/packages/datadog-plugin-jest/src/index.js +6 -22
  106. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
  107. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
  108. package/packages/datadog-plugin-kafkajs/src/producer.js +5 -3
  109. package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
  110. package/packages/datadog-plugin-mocha/src/index.js +832 -4
  111. package/packages/datadog-plugin-mysql/src/index.js +37 -0
  112. package/packages/datadog-plugin-next/src/index.js +51 -21
  113. package/packages/datadog-plugin-openai/src/tracing.js +4 -0
  114. package/packages/datadog-plugin-openai-agents/src/integration.js +278 -69
  115. package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
  116. package/packages/datadog-plugin-pg/src/index.js +66 -1
  117. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  118. package/packages/datadog-plugin-vitest/src/index.js +103 -27
  119. package/packages/datadog-plugin-ws/src/util.js +2 -1
  120. package/packages/datadog-shimmer/src/shimmer.js +6 -2
  121. package/packages/dd-trace/src/aiguard/sdk.js +3 -0
  122. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
  123. package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
  124. package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
  125. package/packages/dd-trace/src/appsec/handlers/http-request.js +228 -0
  126. package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
  127. package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
  128. package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
  129. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
  130. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  131. package/packages/dd-trace/src/appsec/index.js +48 -476
  132. package/packages/dd-trace/src/appsec/sdk/track_event.js +9 -0
  133. package/packages/dd-trace/src/carrier.js +356 -0
  134. package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
  135. package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
  136. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +27 -13
  137. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +17 -0
  138. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +21 -1
  139. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
  140. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
  141. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
  142. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
  143. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  144. package/packages/dd-trace/src/config/generated-config-types.d.ts +5 -0
  145. package/packages/dd-trace/src/config/helper.js +2 -0
  146. package/packages/dd-trace/src/config/index.js +16 -5
  147. package/packages/dd-trace/src/config/remote_config.js +1 -1
  148. package/packages/dd-trace/src/config/supported-configurations.json +18 -0
  149. package/packages/dd-trace/src/datastreams/context.js +6 -1
  150. package/packages/dd-trace/src/datastreams/manager.js +5 -1
  151. package/packages/dd-trace/src/datastreams/pathway.js +23 -22
  152. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  153. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  154. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
  155. package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
  156. package/packages/dd-trace/src/guardrails/log.js +1 -10
  157. package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
  158. package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
  159. package/packages/dd-trace/src/llmobs/experiments/index.js +33 -25
  160. package/packages/dd-trace/src/llmobs/experiments/util.js +4 -2
  161. package/packages/dd-trace/src/llmobs/index.js +34 -2
  162. package/packages/dd-trace/src/llmobs/noop.js +2 -0
  163. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
  164. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  165. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
  166. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
  167. package/packages/dd-trace/src/llmobs/sdk.js +149 -68
  168. package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
  169. package/packages/dd-trace/src/llmobs/tagger.js +96 -0
  170. package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
  171. package/packages/dd-trace/src/llmobs/util.js +78 -0
  172. package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
  173. package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
  174. package/packages/dd-trace/src/log/channels.js +1 -9
  175. package/packages/dd-trace/src/log/levels.js +12 -0
  176. package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
  177. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  178. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
  179. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
  180. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  181. package/packages/dd-trace/src/opentelemetry/span.js +4 -0
  182. package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
  183. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
  184. package/packages/dd-trace/src/plugin_manager.js +2 -0
  185. package/packages/dd-trace/src/plugins/ci_plugin.js +21 -5
  186. package/packages/dd-trace/src/plugins/index.js +1 -0
  187. package/packages/dd-trace/src/plugins/util/ci.js +6 -0
  188. package/packages/dd-trace/src/plugins/util/git.js +6 -4
  189. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +2 -2
  190. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  191. package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +3 -62
  192. package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
  193. package/packages/dd-trace/src/plugins/util/test.js +397 -74
  194. package/packages/dd-trace/src/plugins/util/url.js +1 -1
  195. package/packages/dd-trace/src/plugins/util/web.js +76 -3
  196. package/packages/dd-trace/src/profiler.js +1 -1
  197. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
  198. package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
  199. package/packages/dd-trace/src/proxy.js +1 -0
  200. package/packages/dd-trace/src/ritm.js +5 -0
  201. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
  202. package/packages/dd-trace/src/sampling_rule.js +4 -2
  203. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
  204. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
  205. package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
  206. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  207. package/vendor/dist/pprof-format/index.js +1 -1
  208. package/version.js +10 -8
  209. /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
@@ -0,0 +1,356 @@
1
+ 'use strict'
2
+
3
+ /** @typedef {Record<string, unknown>} Carrier */
4
+ /** @typedef {string | Buffer} PathwayValue */
5
+
6
+ const FIELD_READ = 1
7
+ const FIELD_WRITE = 2
8
+ const FIELD_HAS = 4
9
+ const FIELD_DELETE = 8
10
+ const FIELD_NAME_LENGTH = 16
11
+
12
+ /** @type {Record<string, unknown>} */
13
+ const carrierOperations = {}
14
+
15
+ /**
16
+ * @typedef {object} CarrierCapability
17
+ * @property {unknown} [read]
18
+ * @property {unknown} [write]
19
+ * @property {unknown} [has]
20
+ * @property {unknown} [delete]
21
+ * @property {unknown} [nameLength]
22
+ */
23
+
24
+ /**
25
+ * @template Value
26
+ * @typedef {object} CarrierField
27
+ * @property {(carrier: Carrier) => Value | undefined} read
28
+ * @property {(carrier: Carrier, value: Value) => void} write
29
+ * @property {(carrier: Carrier) => boolean} has
30
+ * @property {(carrier: Carrier) => void} delete
31
+ * @property {(carrier: Carrier, target: Carrier) => void} copy
32
+ * @property {number} nameLength
33
+ */
34
+
35
+ /**
36
+ * @template {CarrierCapability} Capability
37
+ * @param {string} name
38
+ * @param {number} operations
39
+ * @param {Capability} capability
40
+ * @returns {Capability}
41
+ */
42
+ function defineCapability (name, operations, capability) {
43
+ const suffix = name[0].toUpperCase() + name.slice(1)
44
+ if (operations & FIELD_READ) carrierOperations[`read${suffix}`] = capability.read
45
+ if (operations & FIELD_WRITE) carrierOperations[`write${suffix}`] = capability.write
46
+ if (operations & FIELD_HAS) carrierOperations[`has${suffix}`] = capability.has
47
+ if (operations & FIELD_DELETE) carrierOperations[`delete${suffix}`] = capability.delete
48
+ if (operations & FIELD_NAME_LENGTH) carrierOperations[`${name}NameLength`] = capability.nameLength
49
+ return capability
50
+ }
51
+
52
+ /**
53
+ * @template Value
54
+ * @param {string} fieldName
55
+ * @param {string} headerName
56
+ * @param {(value: unknown) => Value | undefined} resolve
57
+ * @param {number} operations
58
+ * @returns {CarrierField<Value>}
59
+ */
60
+ function defineField (fieldName, headerName, resolve, operations) {
61
+ return defineCapability(fieldName, operations, {
62
+ /**
63
+ * @param {Carrier} carrier
64
+ * @returns {Value | undefined}
65
+ */
66
+ read (carrier) {
67
+ return resolve(carrier[headerName])
68
+ },
69
+
70
+ /**
71
+ * @param {Carrier} carrier
72
+ * @param {Value} value
73
+ * @returns {void}
74
+ */
75
+ write (carrier, value) {
76
+ carrier[headerName] = value
77
+ },
78
+
79
+ /**
80
+ * @param {Carrier} carrier
81
+ * @returns {boolean}
82
+ */
83
+ has (carrier) {
84
+ return headerName in carrier
85
+ },
86
+
87
+ /**
88
+ * @param {Carrier} carrier
89
+ * @returns {void}
90
+ */
91
+ delete (carrier) {
92
+ delete carrier[headerName]
93
+ },
94
+
95
+ /**
96
+ * @param {Carrier} carrier
97
+ * @param {Carrier} target
98
+ * @returns {void}
99
+ */
100
+ copy (carrier, target) {
101
+ if (Object.hasOwn(carrier, headerName)) target[headerName] = carrier[headerName]
102
+ },
103
+
104
+ nameLength: headerName.length,
105
+ })
106
+ }
107
+
108
+ /**
109
+ * Skipping non-strings keeps a Symbol or a throwing `toString` from aborting
110
+ * extraction, which discards the whole context rather than one field.
111
+ *
112
+ * @param {unknown} value
113
+ * @returns {string | undefined}
114
+ */
115
+ function readListText (value) {
116
+ if (typeof value === 'string') return value
117
+ if (!Array.isArray(value)) return
118
+
119
+ let joined = ''
120
+ let first = true
121
+
122
+ for (const item of value) {
123
+ if (typeof item !== 'string') continue
124
+ if (first) {
125
+ joined = item
126
+ first = false
127
+ } else {
128
+ joined += `,${item}`
129
+ }
130
+ }
131
+
132
+ return joined
133
+ }
134
+
135
+ /**
136
+ * @param {unknown} value
137
+ * @returns {string | undefined}
138
+ */
139
+ function readLastText (value) {
140
+ if (typeof value === 'string') return value
141
+ if (!Array.isArray(value)) return
142
+
143
+ let result
144
+ for (let i = value.length - 1; i >= 0; i--) {
145
+ if (typeof value[i] === 'string') {
146
+ result = value[i]
147
+ break
148
+ }
149
+ }
150
+ return result
151
+ }
152
+
153
+ /**
154
+ * @param {unknown} value
155
+ * @returns {string | undefined}
156
+ */
157
+ function readUniqueText (value) {
158
+ let result
159
+ if (typeof value === 'string') {
160
+ result = value
161
+ } else if (Array.isArray(value) && value.length === 1 && typeof value[0] === 'string') {
162
+ result = value[0]
163
+ }
164
+ return result
165
+ }
166
+
167
+ /**
168
+ * @param {unknown} value
169
+ * @returns {PathwayValue | undefined}
170
+ */
171
+ function readLastPathway (value) {
172
+ if (typeof value === 'string' || Buffer.isBuffer(value)) return value
173
+ if (!Array.isArray(value)) return
174
+
175
+ let result
176
+ for (let i = value.length - 1; i >= 0; i--) {
177
+ const item = value[i]
178
+ if (typeof item === 'string' || Buffer.isBuffer(item)) {
179
+ result = item
180
+ break
181
+ }
182
+ }
183
+ return result
184
+ }
185
+
186
+ const datadogTraceId = defineField(
187
+ 'datadogTraceId',
188
+ 'x-datadog-trace-id',
189
+ readLastText,
190
+ FIELD_READ | FIELD_WRITE
191
+ )
192
+ const datadogParentId = defineField(
193
+ 'datadogParentId',
194
+ 'x-datadog-parent-id',
195
+ readLastText,
196
+ FIELD_READ | FIELD_WRITE | FIELD_DELETE
197
+ )
198
+ const datadogOrigin = defineField(
199
+ 'datadogOrigin',
200
+ 'x-datadog-origin',
201
+ readLastText,
202
+ FIELD_READ | FIELD_WRITE
203
+ )
204
+ const datadogSamplingPriority = defineField(
205
+ 'datadogSamplingPriority',
206
+ 'x-datadog-sampling-priority',
207
+ readLastText,
208
+ FIELD_READ | FIELD_WRITE
209
+ )
210
+ defineField('datadogTags', 'x-datadog-tags', readListText, FIELD_READ | FIELD_WRITE)
211
+ defineField('baggage', 'baggage', readListText, FIELD_READ | FIELD_WRITE)
212
+ const b3TraceId = defineField('b3TraceId', 'x-b3-traceid', readLastText, FIELD_READ | FIELD_WRITE)
213
+ const b3SpanId = defineField('b3SpanId', 'x-b3-spanid', readLastText, FIELD_READ | FIELD_WRITE)
214
+ const b3ParentId = defineField('b3ParentId', 'x-b3-parentspanid', readLastText, FIELD_WRITE)
215
+ const b3Sampled = defineField('b3Sampled', 'x-b3-sampled', readLastText, FIELD_READ | FIELD_WRITE)
216
+ const b3Flags = defineField('b3Flags', 'x-b3-flags', readLastText, FIELD_READ | FIELD_WRITE)
217
+ const b3 = defineField('b3', 'b3', readLastText, FIELD_READ | FIELD_WRITE)
218
+ defineField('sqsd', 'x-aws-sqsd-attr-_datadog', readLastText, FIELD_READ)
219
+ const traceparent = defineField('traceparent', 'traceparent', readUniqueText, FIELD_READ | FIELD_WRITE)
220
+ const tracestate = defineField('tracestate', 'tracestate', readListText, FIELD_READ | FIELD_WRITE)
221
+ const dsmBase64 = defineField(
222
+ 'dsmBase64',
223
+ 'dd-pathway-ctx-base64',
224
+ readLastPathway,
225
+ FIELD_READ | FIELD_WRITE | FIELD_HAS | FIELD_NAME_LENGTH
226
+ )
227
+ const dsmBinary = defineField('dsmBinary', 'dd-pathway-ctx', readLastPathway, FIELD_READ | FIELD_HAS)
228
+
229
+ const legacyBaggagePrefix = 'ot-baggage-'
230
+ // RFC 7230 `token`, compatible with Node.js header-name validation.
231
+ const httpHeaderNameExpr = /^[0-9A-Za-z!#$%&'*+\-.^_`|~]+$/
232
+
233
+ defineCapability('legacyBaggage', FIELD_READ | FIELD_WRITE, {
234
+ /**
235
+ * @param {Record<string, string> | undefined} carrier
236
+ * @param {string} key
237
+ * @param {string} value
238
+ * @returns {Record<string, string> | undefined}
239
+ */
240
+ write (carrier, key, value) {
241
+ const name = legacyBaggagePrefix + key
242
+ if (!httpHeaderNameExpr.test(name)) return
243
+
244
+ carrier ??= {}
245
+ carrier[name] = value
246
+ return carrier
247
+ },
248
+
249
+ /**
250
+ * @param {Carrier} carrier
251
+ * @param {Record<string, string>} target
252
+ * @returns {void}
253
+ */
254
+ read (carrier, target) {
255
+ for (const name of Object.keys(carrier)) {
256
+ if (!name.startsWith(legacyBaggagePrefix)) continue
257
+ const key = name.slice(legacyBaggagePrefix.length)
258
+ if (!key) continue
259
+ const value = readLastText(carrier[name])
260
+ if (value !== undefined) target[key] = value
261
+ }
262
+ },
263
+ })
264
+
265
+ const textMapLogFields = [
266
+ datadogTraceId,
267
+ datadogParentId,
268
+ datadogSamplingPriority,
269
+ datadogOrigin,
270
+ b3TraceId,
271
+ b3SpanId,
272
+ b3ParentId,
273
+ b3Sampled,
274
+ b3Flags,
275
+ b3,
276
+ traceparent,
277
+ tracestate,
278
+ ]
279
+ const dsmLogFields = [dsmBinary, dsmBase64]
280
+
281
+ /**
282
+ * @param {Carrier} carrier
283
+ * @param {Array<Pick<CarrierField<unknown>, 'copy'>>} selectedFields
284
+ * @returns {Carrier}
285
+ */
286
+ function pick (carrier, selectedFields) {
287
+ /** @type {Carrier} */
288
+ const result = {}
289
+ for (const field of selectedFields) field.copy(carrier, result)
290
+ return result
291
+ }
292
+
293
+ /**
294
+ * @param {Carrier} carrier
295
+ * @returns {Carrier}
296
+ */
297
+ function pickTextMap (carrier) {
298
+ return pick(carrier, textMapLogFields)
299
+ }
300
+
301
+ /**
302
+ * @param {Carrier} carrier
303
+ * @returns {Carrier}
304
+ */
305
+ function pickDsm (carrier) {
306
+ return pick(carrier, dsmLogFields)
307
+ }
308
+
309
+ /**
310
+ * @typedef {object} CarrierOperations
311
+ * @property {typeof datadogParentId.delete} deleteDatadogParentId
312
+ * @property {typeof dsmBase64.nameLength} dsmBase64NameLength
313
+ * @property {typeof dsmBase64.has} hasDsmBase64
314
+ * @property {typeof dsmBinary.has} hasDsmBinary
315
+ * @property {typeof pickDsm} pickDsm
316
+ * @property {typeof pickTextMap} pickTextMap
317
+ * @property {(carrier: Carrier) => string | undefined} readBaggage
318
+ * @property {typeof b3.read} readB3
319
+ * @property {typeof b3Flags.read} readB3Flags
320
+ * @property {typeof b3Sampled.read} readB3Sampled
321
+ * @property {typeof b3SpanId.read} readB3SpanId
322
+ * @property {typeof b3TraceId.read} readB3TraceId
323
+ * @property {typeof datadogOrigin.read} readDatadogOrigin
324
+ * @property {typeof datadogParentId.read} readDatadogParentId
325
+ * @property {typeof datadogSamplingPriority.read} readDatadogSamplingPriority
326
+ * @property {(carrier: Carrier) => string | undefined} readDatadogTags
327
+ * @property {typeof datadogTraceId.read} readDatadogTraceId
328
+ * @property {typeof dsmBase64.read} readDsmBase64
329
+ * @property {typeof dsmBinary.read} readDsmBinary
330
+ * @property {(carrier: Carrier, target: Record<string, string>) => void} readLegacyBaggage
331
+ * @property {(carrier: Carrier) => string | undefined} readSqsd
332
+ * @property {typeof traceparent.read} readTraceparent
333
+ * @property {typeof tracestate.read} readTracestate
334
+ * @property {(carrier: Carrier, value: string) => void} writeBaggage
335
+ * @property {typeof b3.write} writeB3
336
+ * @property {typeof b3Flags.write} writeB3Flags
337
+ * @property {typeof b3ParentId.write} writeB3ParentId
338
+ * @property {typeof b3Sampled.write} writeB3Sampled
339
+ * @property {typeof b3SpanId.write} writeB3SpanId
340
+ * @property {typeof b3TraceId.write} writeB3TraceId
341
+ * @property {typeof datadogOrigin.write} writeDatadogOrigin
342
+ * @property {typeof datadogParentId.write} writeDatadogParentId
343
+ * @property {typeof datadogSamplingPriority.write} writeDatadogSamplingPriority
344
+ * @property {(carrier: Carrier, value: string) => void} writeDatadogTags
345
+ * @property {typeof datadogTraceId.write} writeDatadogTraceId
346
+ * @property {typeof dsmBase64.write} writeDsmBase64
347
+ * @property {(carrier: Record<string, string> | undefined, key: string, value: string) =>
348
+ * Record<string, string> | undefined} writeLegacyBaggage
349
+ * @property {typeof traceparent.write} writeTraceparent
350
+ * @property {typeof tracestate.write} writeTracestate
351
+ */
352
+
353
+ carrierOperations.pickDsm = pickDsm
354
+ carrierOperations.pickTextMap = pickTextMap
355
+
356
+ module.exports = /** @type {CarrierOperations} */ (carrierOperations)
@@ -5,6 +5,8 @@ const path = require('node:path')
5
5
 
6
6
  const log = require('../log')
7
7
 
8
+ const BIGINT_LSTAT_OPTIONS = { bigint: true, throwIfNoEntry: false }
9
+
8
10
  /**
9
11
  * Coverage report file patterns to search for
10
12
  * Each entry contains the relative path from root and the format identifier
@@ -38,7 +40,7 @@ const COVERAGE_REPORT_PATTERNS = [
38
40
  /**
39
41
  * Discovers code coverage report files in the given root directory
40
42
  * @param {string} rootDir - The root directory to search for coverage reports
41
- * @returns {Array<{filePath: string, format: string}>} Array of discovered coverage reports
43
+ * @returns {Array<{filePath: string, fileDevice: bigint, fileInode: bigint, format: string}>}
42
44
  */
43
45
  function discoverCoverageReports (rootDir) {
44
46
  if (!rootDir) {
@@ -46,27 +48,57 @@ function discoverCoverageReports (rootDir) {
46
48
  return []
47
49
  }
48
50
 
51
+ let resolvedRoot
52
+ try {
53
+ const unresolvedRoot = path.resolve(rootDir)
54
+ const rootStats = fs.lstatSync(unresolvedRoot, BIGINT_LSTAT_OPTIONS)
55
+ if (rootStats?.isSymbolicLink() || !rootStats?.isDirectory()) {
56
+ log.debug('Coverage report root is not a regular directory: %s', unresolvedRoot)
57
+ return []
58
+ }
59
+ resolvedRoot = fs.realpathSync(unresolvedRoot)
60
+ const resolvedRootStats = fs.lstatSync(resolvedRoot, BIGINT_LSTAT_OPTIONS)
61
+ if (!resolvedRootStats || resolvedRootStats.dev !== rootStats.dev || resolvedRootStats.ino !== rootStats.ino) {
62
+ log.debug('Coverage report root changed while resolving it: %s', unresolvedRoot)
63
+ return []
64
+ }
65
+ } catch (error) {
66
+ log.debug('Error checking coverage report root %s: %s', rootDir, error.message)
67
+ return []
68
+ }
69
+
49
70
  const discoveredReports = []
50
71
 
51
72
  for (const pattern of COVERAGE_REPORT_PATTERNS) {
52
- const fullPath = path.join(rootDir, pattern.path)
73
+ let currentPath = resolvedRoot
53
74
 
54
75
  try {
55
- if (fs.existsSync(fullPath)) {
56
- const stats = fs.statSync(fullPath)
57
-
58
- // Only include regular files, not directories or symlinks
59
- if (stats.isFile()) {
60
- discoveredReports.push({
61
- filePath: fullPath,
62
- format: pattern.format,
63
- })
64
- log.debug('Found coverage report: %s (format: %s)', fullPath, pattern.format)
76
+ let stats
77
+ for (const pathSegment of pattern.path.split('/')) {
78
+ currentPath = path.join(currentPath, pathSegment)
79
+ stats = fs.lstatSync(currentPath, BIGINT_LSTAT_OPTIONS)
80
+ if (!stats || stats.isSymbolicLink()) break
81
+ }
82
+
83
+ // A hard link can name a file outside the root without changing its real path.
84
+ if (stats?.isFile() && stats.nlink === 1n) {
85
+ const resolvedPath = fs.realpathSync(currentPath)
86
+ const pathFromRoot = path.relative(resolvedRoot, resolvedPath)
87
+ if (pathFromRoot === '..' || pathFromRoot.startsWith(`..${path.sep}`) || path.isAbsolute(pathFromRoot)) {
88
+ log.debug('Coverage report resolved outside the root directory: %s', currentPath)
89
+ continue
65
90
  }
91
+ discoveredReports.push({
92
+ filePath: resolvedPath,
93
+ fileDevice: stats.dev,
94
+ fileInode: stats.ino,
95
+ format: pattern.format,
96
+ })
97
+ log.debug('Found coverage report: %s (format: %s)', resolvedPath, pattern.format)
66
98
  }
67
- } catch (err) {
99
+ } catch (error) {
68
100
  // Log but don't fail if we can't access a file
69
- log.debug('Error checking coverage report path %s: %s', fullPath, err.message)
101
+ log.debug('Error checking coverage report path %s: %s', currentPath, error.message)
70
102
  }
71
103
  }
72
104
 
@@ -0,0 +1,89 @@
1
+ 'use strict'
2
+
3
+ const EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS = [
4
+ { limitMs: 5000, key: '5s' },
5
+ { limitMs: 10_000, key: '10s' },
6
+ { limitMs: 30_000, key: '30s' },
7
+ { limitMs: 300_000, key: '5m' },
8
+ ]
9
+
10
+ // A bucket the settings validator accepts but this module ignores silently loses its retries.
11
+ const EARLY_FLAKE_DETECTION_RETRY_BUCKETS =
12
+ Object.freeze(EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS.map(({ key }) => key))
13
+
14
+ /**
15
+ * @typedef {object} EfdDurationRetryCount
16
+ * @property {number} durationLimitMs
17
+ * @property {number} retryCount
18
+ */
19
+
20
+ /**
21
+ * @typedef {object} EfdRetryPolicy
22
+ * @property {readonly EfdDurationRetryCount[]} durationRetryCounts
23
+ * @property {number} schedulingRetryCount
24
+ */
25
+
26
+ /**
27
+ * @param {number} durationMs
28
+ * @param {EfdRetryPolicy} retryPolicy
29
+ * @returns {number}
30
+ */
31
+ function getEfdRetryCountForDuration (durationMs, retryPolicy) {
32
+ for (const { durationLimitMs, retryCount } of retryPolicy.durationRetryCounts) {
33
+ if (durationMs < durationLimitMs) {
34
+ return retryCount
35
+ }
36
+ }
37
+ return 0
38
+ }
39
+
40
+ /**
41
+ * @param {Record<string, unknown> | undefined} retriesByDuration
42
+ * @returns {EfdRetryPolicy}
43
+ */
44
+ function createEfdRetryPolicy (retriesByDuration = {}) {
45
+ const durationRetryCounts = []
46
+ let schedulingRetryCount = 0
47
+ for (const { limitMs: durationLimitMs, key } of EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS) {
48
+ const configuredRetryCount = retriesByDuration[key]
49
+ const retryCount = Number.isSafeInteger(configuredRetryCount) && configuredRetryCount >= 0
50
+ ? configuredRetryCount
51
+ : 0
52
+ durationRetryCounts.push(Object.freeze({ durationLimitMs, retryCount }))
53
+ if (retryCount > schedulingRetryCount) {
54
+ schedulingRetryCount = retryCount
55
+ }
56
+ }
57
+ return Object.freeze({
58
+ durationRetryCounts: Object.freeze(durationRetryCounts),
59
+ schedulingRetryCount,
60
+ })
61
+ }
62
+
63
+ /**
64
+ * @param {EfdRetryPolicy | undefined} retryPolicy
65
+ * @returns {boolean}
66
+ */
67
+ function hasEfdRetries (retryPolicy) {
68
+ return (retryPolicy?.schedulingRetryCount ?? 0) > 0
69
+ }
70
+
71
+ /**
72
+ * @param {number} retryIndex
73
+ * @param {number | undefined} retryCount
74
+ * @returns {boolean}
75
+ */
76
+ function shouldSkipEfdRetry (retryIndex, retryCount) {
77
+ return retryCount !== undefined && retryIndex > retryCount
78
+ }
79
+
80
+ const EMPTY_EFD_RETRY_POLICY = createEfdRetryPolicy()
81
+
82
+ module.exports = {
83
+ EARLY_FLAKE_DETECTION_RETRY_BUCKETS,
84
+ EMPTY_EFD_RETRY_POLICY,
85
+ createEfdRetryPolicy,
86
+ getEfdRetryCountForDuration,
87
+ hasEfdRetries,
88
+ shouldSkipEfdRetry,
89
+ }
@@ -4,6 +4,7 @@ const { hostname: getHostname } = require('node:os')
4
4
  const URL = require('url').URL
5
5
 
6
6
  const { version: tracerVersion } = require('../../../../../package.json')
7
+ const { EMPTY_EFD_RETRY_POLICY, createEfdRetryPolicy } = require('../efd-retry-policy')
7
8
  const { getLibraryConfiguration: getLibraryConfigurationRequest } = require('../requests/get-library-configuration')
8
9
  const { getSkippableSuites: getSkippableSuitesRequest } = require('../intelligent-test-runner/get-skippable-suites')
9
10
  const { getKnownTests: getKnownTestsRequest } = require('../early-flake-detection/get-known-tests')
@@ -23,6 +24,18 @@ const { sendGitMetadata: sendGitMetadataRequest } = require('./git/git_metadata'
23
24
  const hostname = getHostname()
24
25
  const EMPTY_SETTINGS = Object.freeze({})
25
26
 
27
+ /**
28
+ * Test session identity sent with every request. Fields are optional because the CI provider,
29
+ * the git repository or the runtime may not expose them.
30
+ *
31
+ * @typedef {{
32
+ * repositoryUrl?: string, sha?: string, branch?: string, tag?: string, testLevel?: string,
33
+ * osVersion?: string, osPlatform?: string, osArchitecture?: string,
34
+ * runtimeName?: string, runtimeVersion?: string, commitMessage?: string,
35
+ * pullRequestBaseSha?: string, commitHeadSha?: string, commitHeadMessage?: string,
36
+ * }} TestConfiguration
37
+ */
38
+
26
39
  function getTestConfigurationTags (tags) {
27
40
  if (!tags) {
28
41
  return {}
@@ -190,6 +203,7 @@ class CiVisibilityExporter extends BufferingExporter {
190
203
  const cachedSkippableSuites = this._testOptimizationHttpCache.readSkippableSuites({
191
204
  testLevel: requestConfiguration.testLevel,
192
205
  isCoverageReportUploadEnabled: requestConfiguration.isCoverageReportUploadEnabled,
206
+ isLineCoverageSupported: requestConfiguration.isLineCoverageSupported,
193
207
  })
194
208
  if (cachedSkippableSuites !== CACHE_MISS) {
195
209
  const { skippableSuites, correlationId, coverage } = cachedSkippableSuites
@@ -238,6 +252,10 @@ class CiVisibilityExporter extends BufferingExporter {
238
252
  /**
239
253
  * We can't request library configuration until we know whether we can use the
240
254
  * CI Visibility Protocol, hence the this._canUseCiVisProtocol promise.
255
+ *
256
+ * @param {TestConfiguration} testConfiguration
257
+ * @param {(error: Error | null, libraryConfig?: Readonly<Record<string, unknown>>) => void} callback
258
+ * @returns {void}
241
259
  */
242
260
  getLibraryConfiguration (testConfiguration, callback) {
243
261
  const { repositoryUrl } = testConfiguration
@@ -321,21 +339,14 @@ class CiVisibilityExporter extends BufferingExporter {
321
339
  DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES: configuredAttemptToFixRetries = 0,
322
340
  DD_TEST_MANAGEMENT_ENABLED: isTestManagementAllowed,
323
341
  } = testOptimization
324
- const hasEarlyFlakeDetectionRetryCount = earlyFlakeDetectionRetryCount !== undefined
325
- const configuredSlowTestRetries = remoteConfiguration.earlyFlakeDetectionSlowTestRetries ?? EMPTY_SETTINGS
326
- const earlyFlakeDetectionSlowTestRetries = hasEarlyFlakeDetectionRetryCount
327
- ? Object.freeze({
342
+ const earlyFlakeDetectionRetryPolicy = earlyFlakeDetectionRetryCount === undefined
343
+ ? remoteConfiguration.earlyFlakeDetectionRetryPolicy ?? EMPTY_EFD_RETRY_POLICY
344
+ : createEfdRetryPolicy({
328
345
  '5s': earlyFlakeDetectionRetryCount,
329
346
  '10s': earlyFlakeDetectionRetryCount,
330
347
  '30s': earlyFlakeDetectionRetryCount,
331
348
  '5m': earlyFlakeDetectionRetryCount,
332
349
  })
333
- : Object.isFrozen(configuredSlowTestRetries)
334
- ? configuredSlowTestRetries
335
- : Object.freeze({ ...configuredSlowTestRetries })
336
- const earlyFlakeDetectionNumRetries = hasEarlyFlakeDetectionRetryCount
337
- ? earlyFlakeDetectionRetryCount
338
- : remoteConfiguration.earlyFlakeDetectionNumRetries ?? 0
339
350
  const testManagementAttemptToFixRetries =
340
351
  remoteConfiguration.testManagementAttemptToFixRetries ?? configuredAttemptToFixRetries
341
352
 
@@ -346,8 +357,7 @@ class CiVisibilityExporter extends BufferingExporter {
346
357
  requireGit: remoteConfiguration.requireGit === true,
347
358
  isEarlyFlakeDetectionEnabled:
348
359
  remoteConfiguration.isEarlyFlakeDetectionEnabled === true && isEarlyFlakeDetectionAllowed === true,
349
- earlyFlakeDetectionNumRetries,
350
- earlyFlakeDetectionSlowTestRetries,
360
+ earlyFlakeDetectionRetryPolicy,
351
361
  earlyFlakeDetectionFaultyThreshold: remoteConfiguration.earlyFlakeDetectionFaultyThreshold ?? 30,
352
362
  isFlakyTestRetriesEnabled:
353
363
  remoteConfiguration.isFlakyTestRetriesEnabled === true && isFlakyTestRetriesAllowed === true,
@@ -521,17 +531,21 @@ class CiVisibilityExporter extends BufferingExporter {
521
531
  * Uploads a single coverage report to the CI intake.
522
532
  * @param {object} options - Upload options
523
533
  * @param {string} options.filePath - Path to the coverage report file
534
+ * @param {bigint} options.fileDevice - Device containing the discovered report
535
+ * @param {bigint} options.fileInode - Inode of the discovered report
524
536
  * @param {string} options.format - Format of the coverage report
525
537
  * @param {object} options.testEnvironmentMetadata - Test environment metadata containing git/CI tags
526
538
  * @param {(error: Error|null) => void} callback - Callback function
527
539
  */
528
- uploadCoverageReport ({ filePath, format, testEnvironmentMetadata }, callback) {
540
+ uploadCoverageReport ({ filePath, fileDevice, fileInode, format, testEnvironmentMetadata }, callback) {
529
541
  if (!this._codeCoverageReportUrl) {
530
542
  return callback(new Error('Coverage report upload URL not configured'))
531
543
  }
532
544
 
533
545
  uploadCoverageReportRequest({
534
546
  filePath,
547
+ fileDevice,
548
+ fileInode,
535
549
  format,
536
550
  flags: this._coverageReportFlags,
537
551
  testEnvironmentMetadata,
@@ -32,6 +32,9 @@ const {
32
32
  TELEMETRY_GIT_REQUESTS_OBJECT_PACKFILES_BYTES,
33
33
  } = require('../../../ci-visibility/telemetry')
34
34
 
35
+ /** @typedef {{ isEvpProxy: boolean, evpProxyPrefix: string }} EvpProxyConfiguration */
36
+ /** @typedef {EvpProxyConfiguration & { url: URL, repositoryUrl: string }} GitUploadTarget */
37
+
35
38
  const isValidSha1 = (sha) => /^[0-9a-f]{40}$/.test(sha)
36
39
  const isValidSha256 = (sha) => /^[0-9a-f]{64}$/.test(sha)
37
40
 
@@ -62,6 +65,10 @@ function getCommonRequestOptions (url) {
62
65
  * This function posts the SHAs of the commits of the last month
63
66
  * The response are the commits for which the backend already has information
64
67
  * This response is used to know which commits can be ignored from there on
68
+ *
69
+ * @param {GitUploadTarget & { latestCommits: string[] }} options
70
+ * @param {(error: Error | null, commitsToUpload?: string[]) => void} callback
71
+ * @returns {void}
65
72
  */
66
73
  function getCommitsToUpload ({ url, repositoryUrl, latestCommits, isEvpProxy, evpProxyPrefix }, callback) {
67
74
  const commonOptions = getCommonRequestOptions(url)
@@ -127,6 +134,10 @@ function getCommitsToUpload ({ url, repositoryUrl, latestCommits, isEvpProxy, ev
127
134
 
128
135
  /**
129
136
  * This function uploads a git packfile
137
+ *
138
+ * @param {GitUploadTarget & { packFileToUpload: string, headCommit: string }} options
139
+ * @param {(error: Error | null, uploadSize?: number) => void} callback
140
+ * @returns {void}
130
141
  */
131
142
  function uploadPackFile ({ url, isEvpProxy, evpProxyPrefix, packFileToUpload, repositoryUrl, headCommit }, callback) {
132
143
  const form = new FormData()
@@ -245,6 +256,12 @@ function generateAndUploadPackFiles ({
245
256
 
246
257
  /**
247
258
  * This function uploads git metadata to CI Visibility's backend.
259
+ *
260
+ * @param {URL} url
261
+ * @param {EvpProxyConfiguration} evpProxyConfiguration
262
+ * @param {string | undefined} configRepositoryUrl repository URL from the configuration, if set
263
+ * @param {(error?: Error | null) => void} callback
264
+ * @returns {void}
248
265
  */
249
266
  function sendGitMetadata (url, { isEvpProxy, evpProxyPrefix }, configRepositoryUrl, callback) {
250
267
  if (!isGitAvailable()) {