dd-trace 6.7.0 → 6.9.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 (272) 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 +14 -8
  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/playwright.js +5 -5
  20. package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
  21. package/ci/test-optimization-validation/generated-files.js +47 -37
  22. package/ci/test-optimization-validation/generated-test-contract.js +9 -7
  23. package/ci/test-optimization-validation/generated-verifier.js +24 -17
  24. package/ci/test-optimization-validation/literal-glob.js +52 -0
  25. package/ci/test-optimization-validation/manifest-loader.js +3 -0
  26. package/ci/test-optimization-validation/manifest-scaffold.js +301 -52
  27. package/ci/test-optimization-validation/manifest-schema.js +80 -17
  28. package/ci/test-optimization-validation/offline-fixtures.js +13 -4
  29. package/ci/test-optimization-validation/offline-output.js +6 -6
  30. package/ci/test-optimization-validation/package-check.js +94 -0
  31. package/ci/test-optimization-validation/plan-writer.js +183 -10
  32. package/ci/test-optimization-validation/preflight-runner.js +110 -31
  33. package/ci/test-optimization-validation/project-build-artifact.js +31 -0
  34. package/ci/test-optimization-validation/redaction.js +18 -27
  35. package/ci/test-optimization-validation/report-writer.js +182 -48
  36. package/ci/test-optimization-validation/result-semantics.js +20 -3
  37. package/ci/test-optimization-validation/runner-command.js +41 -21
  38. package/ci/test-optimization-validation/runner-contract.js +79 -43
  39. package/ci/test-optimization-validation/safe-files.js +4 -4
  40. package/ci/test-optimization-validation/scenarios/basic-reporting.js +60 -6
  41. package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
  42. package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
  43. package/ci/test-optimization-validation/scenarios/test-management.js +2 -2
  44. package/ci/test-optimization-validation/static-diagnosis.js +10 -3
  45. package/ci/test-optimization-validation/test-output.js +2 -1
  46. package/ci/test-optimization-validation/validation-blocker.js +21 -0
  47. package/ci/vitest-no-worker-init-setup.mjs +377 -84
  48. package/index.d.ts +175 -14
  49. package/init.js +1 -17
  50. package/initialize.mjs +11 -0
  51. package/loader-hook.mjs +6 -4
  52. package/package.json +12 -4
  53. package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
  54. package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
  55. package/packages/datadog-esbuild/index.js +4 -2
  56. package/packages/datadog-esbuild/src/utils.js +17 -2
  57. package/packages/datadog-instrumentations/src/ai.js +61 -34
  58. package/packages/datadog-instrumentations/src/bluebird.js +6 -6
  59. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +85 -9
  60. package/packages/datadog-instrumentations/src/cucumber.js +131 -51
  61. package/packages/datadog-instrumentations/src/dns.js +21 -2
  62. package/packages/datadog-instrumentations/src/electron.js +1 -0
  63. package/packages/datadog-instrumentations/src/fastify.js +3 -1
  64. package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
  65. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
  66. package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
  67. package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
  68. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  69. package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
  70. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +34 -9
  71. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  72. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +175 -0
  73. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
  74. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
  75. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +61 -0
  76. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
  77. package/packages/datadog-instrumentations/src/helpers/router-helper.js +38 -7
  78. package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
  79. package/packages/datadog-instrumentations/src/http/client.js +1 -3
  80. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
  81. package/packages/datadog-instrumentations/src/jest.js +986 -246
  82. package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
  83. package/packages/datadog-instrumentations/src/mocha/main.js +109 -51
  84. package/packages/datadog-instrumentations/src/mocha/utils.js +120 -32
  85. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +44 -0
  86. package/packages/datadog-instrumentations/src/mocha/worker.js +441 -43
  87. package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
  88. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  89. package/packages/datadog-instrumentations/src/next.js +155 -23
  90. package/packages/datadog-instrumentations/src/openai.js +14 -0
  91. package/packages/datadog-instrumentations/src/pg.js +180 -6
  92. package/packages/datadog-instrumentations/src/playwright.js +176 -81
  93. package/packages/datadog-instrumentations/src/promise-js.js +4 -4
  94. package/packages/datadog-instrumentations/src/promise.js +3 -3
  95. package/packages/datadog-instrumentations/src/rhea.js +1 -1
  96. package/packages/datadog-instrumentations/src/router.js +195 -19
  97. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +567 -65
  98. package/packages/datadog-instrumentations/src/vitest-main.js +438 -54
  99. package/packages/datadog-instrumentations/src/vitest-util.js +42 -7
  100. package/packages/datadog-instrumentations/src/vitest-worker.js +234 -17
  101. package/packages/datadog-instrumentations/src/webdriverio.js +1090 -0
  102. package/packages/datadog-instrumentations/src/when.js +3 -3
  103. package/packages/datadog-instrumentations/src/ws.js +5 -1
  104. package/packages/datadog-plugin-aerospike/src/index.js +1 -3
  105. package/packages/datadog-plugin-amqplib/src/client.js +1 -1
  106. package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
  107. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
  108. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
  109. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
  110. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
  111. package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
  112. package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
  113. package/packages/datadog-plugin-child_process/src/index.js +3 -3
  114. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +6 -11
  115. package/packages/datadog-plugin-cucumber/src/index.js +5 -1
  116. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +50 -46
  117. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
  118. package/packages/datadog-plugin-cypress/src/support.js +7 -4
  119. package/packages/datadog-plugin-electron/src/net.js +10 -4
  120. package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
  121. package/packages/datadog-plugin-fs/src/index.js +0 -4
  122. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
  123. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
  124. package/packages/datadog-plugin-graphql/src/execute.js +1 -0
  125. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
  126. package/packages/datadog-plugin-http/src/client.js +3 -2
  127. package/packages/datadog-plugin-http2/src/client.js +1 -1
  128. package/packages/datadog-plugin-jest/src/index.js +3 -21
  129. package/packages/datadog-plugin-jest/src/util.js +4 -1
  130. package/packages/datadog-plugin-kafkajs/src/producer.js +2 -1
  131. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  132. package/packages/datadog-plugin-mocha/src/index.js +342 -6
  133. package/packages/datadog-plugin-next/src/index.js +57 -9
  134. package/packages/datadog-plugin-openai/src/tracing.js +4 -0
  135. package/packages/datadog-plugin-pg/src/index.js +65 -1
  136. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  137. package/packages/datadog-plugin-prisma/src/index.js +5 -1
  138. package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
  139. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  140. package/packages/datadog-plugin-undici/src/index.js +2 -1
  141. package/packages/datadog-plugin-vitest/src/index.js +179 -18
  142. package/packages/datadog-shimmer/src/shimmer.js +6 -2
  143. package/packages/datadog-webpack/index.js +2 -2
  144. package/packages/dd-trace/src/aiguard/sdk.js +3 -0
  145. package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
  146. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
  147. package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
  148. package/packages/dd-trace/src/appsec/handlers/http-request.js +215 -0
  149. package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
  150. package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
  151. package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
  152. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
  153. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
  154. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
  155. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
  156. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
  157. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +2 -2
  158. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
  159. package/packages/dd-trace/src/appsec/index.js +48 -476
  160. package/packages/dd-trace/src/appsec/sdk/track_event.js +10 -1
  161. package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
  162. package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
  163. package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
  164. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
  165. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +29 -15
  166. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +19 -1
  167. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +25 -1
  168. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
  169. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
  170. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
  171. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
  172. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
  173. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
  174. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +2 -2
  175. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  176. package/packages/dd-trace/src/config/defaults.js +14 -6
  177. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -2
  178. package/packages/dd-trace/src/config/helper.js +2 -0
  179. package/packages/dd-trace/src/config/index.js +31 -5
  180. package/packages/dd-trace/src/config/parsers.js +19 -1
  181. package/packages/dd-trace/src/config/remote_config.js +1 -1
  182. package/packages/dd-trace/src/config/supported-configurations.json +13 -6
  183. package/packages/dd-trace/src/datastreams/encoding.js +3 -4
  184. package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
  185. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
  186. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
  187. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
  188. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
  189. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  190. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
  191. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  192. package/packages/dd-trace/src/exporters/common/url.js +3 -5
  193. package/packages/dd-trace/src/exporters/electron/index.js +4 -1
  194. package/packages/dd-trace/src/external-logger/src/index.js +1 -2
  195. package/packages/dd-trace/src/flare/index.js +2 -2
  196. package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
  197. package/packages/dd-trace/src/guardrails/log.js +1 -10
  198. package/packages/dd-trace/src/id.js +1 -1
  199. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
  200. package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
  201. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
  202. package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
  203. package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
  204. package/packages/dd-trace/src/llmobs/experiments/index.js +66 -19
  205. package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
  206. package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
  207. package/packages/dd-trace/src/llmobs/experiments/util.js +220 -0
  208. package/packages/dd-trace/src/llmobs/noop.js +2 -0
  209. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
  210. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  211. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
  212. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
  213. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
  214. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  215. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  216. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
  217. package/packages/dd-trace/src/llmobs/sdk.js +152 -78
  218. package/packages/dd-trace/src/llmobs/span_processor.js +9 -7
  219. package/packages/dd-trace/src/llmobs/tagger.js +1 -2
  220. package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
  221. package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
  222. package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
  223. package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
  224. package/packages/dd-trace/src/log/channels.js +1 -9
  225. package/packages/dd-trace/src/log/levels.js +12 -0
  226. package/packages/dd-trace/src/noop/proxy.js +2 -2
  227. package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
  228. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  229. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
  230. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
  231. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  232. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +3 -2
  233. package/packages/dd-trace/src/opentelemetry/span.js +4 -0
  234. package/packages/dd-trace/src/opentracing/span.js +1 -1
  235. package/packages/dd-trace/src/opentracing/span_context.js +1 -1
  236. package/packages/dd-trace/src/plugins/ci_plugin.js +124 -23
  237. package/packages/dd-trace/src/plugins/index.js +2 -0
  238. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  239. package/packages/dd-trace/src/plugins/tracing.js +1 -0
  240. package/packages/dd-trace/src/plugins/util/ci.js +13 -6
  241. package/packages/dd-trace/src/plugins/util/git.js +7 -5
  242. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +3 -3
  243. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  244. package/packages/dd-trace/src/plugins/util/llm.js +2 -2
  245. package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
  246. package/packages/dd-trace/src/plugins/util/tags.js +2 -0
  247. package/packages/dd-trace/src/plugins/util/test.js +356 -86
  248. package/packages/dd-trace/src/plugins/util/url.js +5 -5
  249. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
  250. package/packages/dd-trace/src/plugins/util/web.js +24 -2
  251. package/packages/dd-trace/src/priority_sampler.js +6 -3
  252. package/packages/dd-trace/src/profiler.js +1 -1
  253. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
  254. package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
  255. package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
  256. package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
  257. package/packages/dd-trace/src/proxy.js +1 -0
  258. package/packages/dd-trace/src/ritm.js +10 -20
  259. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
  260. package/packages/dd-trace/src/sampling_rule.js +4 -2
  261. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
  262. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
  263. package/packages/dd-trace/src/startup-log.js +3 -0
  264. package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
  265. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
  266. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  267. package/packages/dd-trace/src/util.js +16 -0
  268. package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
  269. package/vendor/dist/pprof-format/index.js +1 -1
  270. package/vendor/dist/source-map/index.js +1 -1
  271. package/version.js +10 -8
  272. /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
@@ -29,9 +29,10 @@ const RUNNER_PATTERNS = {
29
29
  * @param {object} input audit inputs
30
30
  * @param {object} input.manifest validation manifest
31
31
  * @param {object} input.framework framework entry
32
+ * @param {Map<string, Buffer|undefined>} [input.projectFileSources] approval-bound project sources
32
33
  * @returns {object} CI audit result
33
34
  */
34
- function runCiWiring ({ manifest, framework }) {
35
+ function runCiWiring ({ manifest, framework, projectFileSources }) {
35
36
  const contradiction = getFrameworkCiDiscoveryContradiction(framework, manifest)
36
37
  if (contradiction) {
37
38
  return getIncomplete(framework, contradiction.reason, {
@@ -47,6 +48,32 @@ function runCiWiring ({ manifest, framework }) {
47
48
  domain: 'ci_configuration',
48
49
  evidenceStrength: 'unknown',
49
50
  }
51
+ if (hasNoSupportedCiConfiguration(manifest, ci)) {
52
+ return getIncomplete(
53
+ framework,
54
+ 'No supported CI configuration file was found by bounded repository discovery. The CI audit is incomplete; ' +
55
+ 'this does not prove that the repository has no external CI configuration.',
56
+ {
57
+ ...evidence,
58
+ reasonCode: 'no-supported-ci-configuration',
59
+ recommendation: 'Identify the CI system and one repository-controlled test job. If CI is configured outside ' +
60
+ 'this repository, review that configuration separately.',
61
+ }
62
+ )
63
+ }
64
+ if (hasUnavailableRemoteCiCommand(ci)) {
65
+ return getIncomplete(
66
+ framework,
67
+ 'The selected CI path delegates test execution to a remote action or reusable workflow whose command is not ' +
68
+ 'available in this repository. The CI audit is incomplete.',
69
+ {
70
+ ...evidence,
71
+ reasonCode: 'remote-ci-command-unavailable',
72
+ recommendation: 'Review the referenced action or reusable workflow at its pinned revision. Keep the result ' +
73
+ 'incomplete when its final test command and effective environment cannot be bound statically.',
74
+ }
75
+ )
76
+ }
50
77
  const missing = getMissingReviewFields(ci)
51
78
  if (missing.length > 0) {
52
79
  return getIncomplete(
@@ -63,7 +90,7 @@ function runCiWiring ({ manifest, framework }) {
63
90
  )
64
91
  }
65
92
 
66
- const source = readCiSource(ci.configFile)
93
+ const source = readProjectSource(ci.configFile, projectFileSources)
67
94
  if (!source) {
68
95
  return getIncomplete(framework, 'The recorded CI configuration file is unavailable or too large to verify.', {
69
96
  ...evidence,
@@ -87,7 +114,7 @@ function runCiWiring ({ manifest, framework }) {
87
114
  }
88
115
 
89
116
  const command = ci.command.trim()
90
- const resolution = getRunnerResolution(command, framework, ci)
117
+ const resolution = getRunnerResolution(command, framework, ci, projectFileSources)
91
118
  const normalizedSource = jobSource.replaceAll('\\', '/')
92
119
  const hasInitialization = /dd-trace\/ci\/init(?:\.js)?\b/.test(normalizedSource)
93
120
  const matrixRelevant = matrixAffectsCiFacts(jobSource, command)
@@ -138,12 +165,16 @@ function runCiWiring ({ manifest, framework }) {
138
165
  if (initialization.status === 'missing' &&
139
166
  resolution.status === 'confirmed' &&
140
167
  unresolved.relevant.length === 0) {
168
+ const transportMissing = ciFacts.transport.status === 'missing'
141
169
  return getFailure(
142
170
  framework,
143
171
  'The checksum-bound CI job reaches the selected test framework through a bounded static command path, but ' +
144
- 'does not configure the dd-trace/ci/init preload. Test Optimization is not initialized in that job.',
172
+ `does not configure the dd-trace/ci/init preload${
173
+ transportMissing ? ' and declares no visible Datadog Agent or agentless reporting transport' : ''
174
+ }. Test Optimization is not configured in that job.`,
145
175
  {
146
176
  ...evidence,
177
+ ciConfigurationStatus: 'not_configured',
147
178
  ciRemediation: remediation,
148
179
  conclusion: 'confirmed_misconfigured',
149
180
  evidenceStrength: 'confirmed_static',
@@ -153,17 +184,25 @@ function runCiWiring ({ manifest, framework }) {
153
184
  }
154
185
 
155
186
  if (resolution.status !== 'confirmed') {
156
- const partial = initialization.status === 'missing'
157
- ? 'The selected job has no visible dd-trace/ci/init preload. '
158
- : ''
187
+ const visibleFacts = []
188
+ if (initialization.status === 'missing') {
189
+ visibleFacts.push('The selected job has no visible dd-trace/ci/init preload.')
190
+ }
191
+ if (ciFacts.transport.status === 'missing') {
192
+ visibleFacts.push('The recorded review found no visible Datadog Agent or agentless reporting transport.')
193
+ }
194
+ const recommendation = /working directory/i.test(resolution.reason)
195
+ ? 'Keep the CI job\'s actual working directory. Resolve the repository-root wrapper to the selected test ' +
196
+ 'runner statically; do not substitute the framework package directory merely to make the audit conclusive.'
197
+ : 'Resolve the remaining wrapper or dynamic command statically, or confirm the effective NODE_OPTIONS value ' +
198
+ 'in the final test process with DD_TRACE_DEBUG=1.'
159
199
  return getIncomplete(
160
200
  framework,
161
- `${partial}The CI audit remains incomplete because the selected command could not be resolved to the ` +
162
- `${framework.framework} runner: ${resolution.reason}.`,
201
+ 'The CI audit remains incomplete because the selected command could not be resolved to the ' +
202
+ `${framework.framework} runner: ${resolution.reason}. ${visibleFacts.join(' ')}`.trim(),
163
203
  {
164
204
  ...evidence,
165
- recommendation: 'Resolve the remaining wrapper or dynamic command statically, or confirm the effective ' +
166
- 'NODE_OPTIONS value in the final test process with DD_TRACE_DEBUG=1.',
205
+ recommendation,
167
206
  }
168
207
  )
169
208
  }
@@ -244,9 +283,10 @@ function getEffectiveNodeOptionsOverride (commandPath = []) {
244
283
  * @param {string} command selected CI command
245
284
  * @param {object} framework framework manifest entry
246
285
  * @param {object} ci selected CI evidence
286
+ * @param {Map<string, Buffer|undefined>} [projectFileSources] approval-bound project sources
247
287
  * @returns {object} static runner resolution
248
288
  */
249
- function getRunnerResolution (command, framework, ci) {
289
+ function getRunnerResolution (command, framework, ci, projectFileSources) {
250
290
  if (getDirectRunner(command, framework.framework)) {
251
291
  return {
252
292
  commandPath: [command],
@@ -266,7 +306,7 @@ function getRunnerResolution (command, framework, ci) {
266
306
  status: 'unresolved',
267
307
  }
268
308
  }
269
- const scripts = readProjectScripts(framework.project.packageJson)
309
+ const scripts = readProjectScripts(framework.project.packageJson, projectFileSources)
270
310
  if (!scripts) {
271
311
  return {
272
312
  reason: 'the approval-bound project package.json is unavailable or invalid',
@@ -283,10 +323,10 @@ function getRunnerResolution (command, framework, ci) {
283
323
  status: 'unresolved',
284
324
  }
285
325
  }
286
- const candidate = expansion.terminals.find(terminal => {
326
+ const candidates = expansion.terminals.filter(terminal => {
287
327
  return getDirectRunner(terminal.command, framework.framework)
288
328
  })
289
- if (!candidate) {
329
+ if (candidates.length === 0) {
290
330
  return {
291
331
  lifecycleScripts: expansion.lifecycleScripts,
292
332
  reason: 'no bounded local package-script path reaches the selected framework runner',
@@ -294,6 +334,15 @@ function getRunnerResolution (command, framework, ci) {
294
334
  status: 'unresolved',
295
335
  }
296
336
  }
337
+ if (candidates.length > 1) {
338
+ return {
339
+ lifecycleScripts: expansion.lifecycleScripts,
340
+ reason: 'more than one bounded local package-script path reaches the selected framework runner',
341
+ source: 'unresolved_wrapper',
342
+ status: 'unresolved',
343
+ }
344
+ }
345
+ const candidate = candidates[0]
297
346
  return {
298
347
  commandPath: candidate.path,
299
348
  lifecycleScripts: expansion.lifecycleScripts,
@@ -303,11 +352,11 @@ function getRunnerResolution (command, framework, ci) {
303
352
  }
304
353
  }
305
354
 
306
- function readProjectScripts (filename) {
355
+ function readProjectScripts (filename, projectFileSources) {
307
356
  try {
308
- const stat = fs.lstatSync(filename)
309
- if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_CI_FILE_BYTES) return
310
- const packageJson = JSON.parse(fs.readFileSync(filename, 'utf8'))
357
+ const source = readProjectSource(filename, projectFileSources)
358
+ if (source === undefined) return
359
+ const packageJson = JSON.parse(source)
311
360
  if (!packageJson.scripts || typeof packageJson.scripts !== 'object' ||
312
361
  Array.isArray(packageJson.scripts)) return {}
313
362
  return packageJson.scripts
@@ -346,18 +395,19 @@ function classifyUnresolved (ci, resolution, matrixRelevant, jobSource) {
346
395
  const ignored = []
347
396
  const unresolved = Array.isArray(ci.unresolved) ? ci.unresolved : []
348
397
  const githubHosted = /[/\\]\.github[/\\]workflows[/\\]/.test(ci.configFile) &&
349
- /^\s*runs-on:\s*(?:ubuntu|windows|macos)-/mi.test(jobSource)
398
+ /^[ \t]*runs-on:[ \t]*(?:ubuntu|windows|macos)-/mi.test(jobSource)
350
399
 
351
400
  for (const item of unresolved) {
352
401
  const isMatrix = /\bmatrix\b/i.test(item)
353
- const isResolvedPackagePath = resolution.source === 'local_package_script' &&
354
- /\b(?:lifecycle|npm|package script|pnpm|pretest|posttest|yarn)\b/i.test(item)
402
+ const isResolvedPackagePath = resolution.status === 'confirmed' &&
403
+ resolution.source === 'local_package_script' &&
404
+ /\b(?:bun|lifecycle|npm|package script|pnpm|pretest|posttest|yarn)\b/i.test(item)
355
405
  const isAmbientGithubSettings = githubHosted &&
356
- /\b(?:repository|organization|environment)[-\s,\w]*\b(?:secrets?|variables?)\b/i.test(item) &&
406
+ /\b(?:repository|organization|environment)[-\s,\w]+\b(?:secrets?|variables?)\b/i.test(item) &&
357
407
  /\b(?:inject|inherit|outside)\b/i.test(item)
358
408
  const isOtherJob = /^Other jobs?\b/i.test(item) ||
359
- /\bsecond\b.*\bjob\b.*\bonly\b.*\bselected\b/i.test(item) ||
360
- /\brelease\b.*\bcontains no test job\b/i.test(item)
409
+ /\bsecond\b.+\bjob\b.+\bonly\b.+\bselected\b/i.test(item) ||
410
+ /\brelease\b.+\bcontains no test job\b/i.test(item)
361
411
  if ((isMatrix && !matrixRelevant) ||
362
412
  isResolvedPackagePath ||
363
413
  isAmbientGithubSettings ||
@@ -373,6 +423,29 @@ function classifyUnresolved (ci, resolution, matrixRelevant, jobSource) {
373
423
  return { ignored, relevant }
374
424
  }
375
425
 
426
+ function hasNoSupportedCiConfiguration (manifest, ci) {
427
+ if (ci.configFile || ci.job || ci.command) return false
428
+ const unresolved = Array.isArray(ci.unresolved) ? ci.unresolved : []
429
+ return manifest.ciDiscovery?.found?.length === 0 ||
430
+ unresolved.some(item => /No supported CI configuration file was found/i.test(item))
431
+ }
432
+
433
+ function hasUnavailableRemoteCiCommand (ci) {
434
+ if (ci.command) return false
435
+ const evidence = [
436
+ ci.step,
437
+ ...(Array.isArray(ci.unresolved) ? ci.unresolved : []),
438
+ ].filter(Boolean)
439
+ if (evidence.some(item => {
440
+ return /\b(?:remote|external|third-party)\s+(?:action|workflow)\b/i.test(item)
441
+ })) return true
442
+
443
+ return evidence.some(item => {
444
+ const reference = /^\s*uses:\s*["']?([^"'\s]+)["']?\s*$/im.exec(item)?.[1]
445
+ return reference && !reference.startsWith('./')
446
+ })
447
+ }
448
+
376
449
  function matrixAffectsCiFacts (jobSource, command) {
377
450
  const matrixReference = /\bmatrix\s*[.[]/
378
451
  if (!matrixReference.test(jobSource)) return false
@@ -407,6 +480,8 @@ function normalizeDirectCommand (command) {
407
480
  assignments.push(...crossEnv.assignments)
408
481
  source = source.slice(crossEnv.length)
409
482
  }
483
+ source = source.replace(/^(?:(?:bunx|npx)(?:\.cmd)?|(?:pnpm|yarn)(?:\.cmd)?\s+exec)\s+/, '')
484
+ .replace(/^bun x\s+/, '')
410
485
  return { assignments, index: prefix.length, source }
411
486
  }
412
487
 
@@ -446,7 +521,7 @@ function getSelectedJobSource (source, ci) {
446
521
 
447
522
  function getYamlKeyEntry (line, index) {
448
523
  if (!line || /^\s*(?:#|$)/.test(line) || /^\s/.test(line) && line.includes('\t')) return
449
- const match = /^(\s*)(?:"([^"]+)"|'([^']+)'|([^:#][^:]*)):\s*(?:#.*)?$/.exec(line)
524
+ const match = /^(\s*)(?:"([^"]+)"|'([^']+)'|([^\s:#][^:]*)):\s*(?:#.*)?$/.exec(line)
450
525
  if (!match) return
451
526
  return {
452
527
  indent: match[1].length,
@@ -491,16 +566,23 @@ function getMissingReviewFields (ci) {
491
566
  }
492
567
 
493
568
  /**
494
- * Reads one bounded regular CI file.
569
+ * Reads one bounded regular project file or its approval-bound snapshot.
495
570
  *
496
- * @param {string} filename CI file
571
+ * @param {string} filename project file
572
+ * @param {Map<string, Buffer|undefined>} [projectFileSources] approval-bound project sources
497
573
  * @returns {string|undefined} source
498
574
  */
499
- function readCiSource (filename) {
575
+ function readProjectSource (filename, projectFileSources) {
576
+ if (typeof filename !== 'string') return
577
+ const resolved = path.resolve(filename)
578
+ if (projectFileSources) {
579
+ if (!projectFileSources.has(resolved)) return
580
+ return projectFileSources.get(resolved)?.toString('utf8')
581
+ }
500
582
  try {
501
- const stat = fs.lstatSync(filename)
583
+ const stat = fs.lstatSync(resolved)
502
584
  if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_CI_FILE_BYTES) return
503
- return fs.readFileSync(filename, 'utf8')
585
+ return fs.readFileSync(resolved, 'utf8')
504
586
  } catch {}
505
587
  }
506
588
 
@@ -519,11 +601,35 @@ function containsLiteral (source, value) {
519
601
 
520
602
  function containsExecutionCommand (source, value) {
521
603
  const command = String(value).replaceAll('\r\n', '\n').trim()
522
- if (!command || command.includes('\n')) return false
523
- return source.split(/\r?\n/).some(line => {
604
+ if (!command) return false
605
+ const lines = source.replaceAll('\r\n', '\n').split('\n')
606
+ return lines.some((line, index) => {
524
607
  if (/^\s*#/.test(line)) return false
525
- const match = /^\s*(?:-\s*)?(?:run|script):\s*(.*?)\s*$/.exec(line)
526
- return match?.[1] === command
608
+ const match = /^\s*(?:-\s*)?(?:run|script):(.*)$/.exec(line)
609
+ if (!match) return false
610
+ const scalar = match[1].trim()
611
+ if (!/^\|[+-]?$/.test(scalar)) return scalar === command
612
+
613
+ const indicatorIndent = /^\s*/.exec(line)[0].length
614
+ const block = []
615
+ let contentIndent
616
+ for (let next = index + 1; next < lines.length; next++) {
617
+ if (!lines[next].trim()) {
618
+ block.push('')
619
+ continue
620
+ }
621
+ const nextIndent = /^\s*/.exec(lines[next])[0].length
622
+ if (contentIndent === undefined) {
623
+ if (nextIndent <= indicatorIndent) break
624
+ contentIndent = nextIndent
625
+ } else if (nextIndent < contentIndent) {
626
+ break
627
+ }
628
+ block.push(lines[next])
629
+ }
630
+ if (contentIndent === undefined) return false
631
+ const blockCommand = block.map(candidate => candidate.slice(contentIndent)).join('\n').trim()
632
+ return blockCommand === command
527
633
  })
528
634
  }
529
635
 
@@ -4,6 +4,7 @@ const fs = require('node:fs')
4
4
  const path = require('path')
5
5
 
6
6
  const { getArtifactId } = require('../artifact-id')
7
+ const { BLOCKER_CATEGORIES } = require('../blocker-category')
7
8
  const { buildDatadogEnv, buildOfflineCaptureEnv, runCommand } = require('../command-runner')
8
9
  const {
9
10
  cleanupGeneratedRuntimeFiles,
@@ -19,8 +20,10 @@ const { readOfflineOutput } = require('../offline-output')
19
20
  const { sanitizeForReport, sanitizeString } = require('../redaction')
20
21
  const { getGeneratedCommand } = require('../runner-command')
21
22
  const { ensureSafeDirectory, writeFileSafely } = require('../safe-files')
23
+ const { getValidationBlockerEvidence } = require('../validation-blocker')
22
24
 
23
- const ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}${String.raw`\[[0-?]*[ -/]*[@-~]`}`, 'g')
25
+ const ANSI_PATTERN =
26
+ new RegExp(`${String.fromCharCode(27)}${String.raw`\[[\u0030-\u003F]*[\u0020-\u002F]*[\u0040-\u007E]`}`, 'g')
24
27
 
25
28
  function frameworkOutDir (out, framework, scenario) {
26
29
  return path.join(out, 'runs', getArtifactId(framework.id), scenario)
@@ -193,10 +196,11 @@ async function prepareGeneratedScenario (framework, scenarioId) {
193
196
  function requireGeneratedScenario (framework, scenarioId, scenarioName) {
194
197
  const strategy = framework.generatedTestStrategy
195
198
  if (strategy?.status === 'not_possible') {
196
- return skip(
199
+ return incomplete(
197
200
  framework,
198
201
  scenarioName,
199
- `Skipped because this advanced feature is not eligible: ${strategy.reason}`,
202
+ `The validator cannot create a collectible test for this advanced feature: ${strategy.reason} ` +
203
+ 'No conclusion was reached for this advanced feature.',
200
204
  getGeneratedStrategySkipEvidence(framework, scenarioName, scenarioId)
201
205
  )
202
206
  }
@@ -254,9 +258,9 @@ function getGeneratedStrategySkipEvidence (framework, scenarioName, scenarioId)
254
258
  reasonCode = 'generated-test-strategy-not-verified'
255
259
  }
256
260
 
257
- return {
261
+ const evidence = {
258
262
  featureEligibility: {
259
- eligible: false,
263
+ ...(reasonCode === 'generated-test-strategy-not-possible' ? {} : { eligible: false }),
260
264
  blockedBy: 'generated-test-strategy',
261
265
  reason: strategy?.reason,
262
266
  reasonCode,
@@ -265,6 +269,12 @@ function getGeneratedStrategySkipEvidence (framework, scenarioName, scenarioId)
265
269
  requiredGeneratedScenario: scenarioId,
266
270
  },
267
271
  }
272
+ if (reasonCode === 'generated-test-strategy-not-possible') {
273
+ evidence.blockerCategory = BLOCKER_CATEGORIES.VALIDATOR_LIMITATION
274
+ evidence.recommendation = 'Report that the selected runner configuration cannot collect validator-generated ' +
275
+ 'tests to validator engineering. Basic Reporting remains valid; project setup changes are not required.'
276
+ }
277
+ return evidence
268
278
  }
269
279
 
270
280
  function basicEventEvidence (events) {
@@ -442,6 +452,8 @@ function copy (target, source, key) {
442
452
 
443
453
  function summarizeDebugRerun ({ result, events, offline, outDir }) {
444
454
  const output = `${result.stdout}\n${result.stderr}`
455
+ const testEvents = eventsOfType(events, 'test')
456
+ const sourceFiles = new Set(testEvents.map(test => test.testSourceFile).filter(Boolean))
445
457
 
446
458
  return {
447
459
  ran: true,
@@ -449,8 +461,12 @@ function summarizeDebugRerun ({ result, events, offline, outDir }) {
449
461
  commandTimedOut: result.timedOut,
450
462
  debugCommandFailed: result.exitCode !== 0 || result.timedOut === true,
451
463
  offlineExporterInitialized: offline.initialized,
464
+ settingsLoadedFromCache: offline.inputs.settings?.status === 'loaded',
452
465
  artifactDirectory: outDir,
453
466
  ...basicEventEvidence(events),
467
+ testEventsWithoutSourceFile: testEvents.length - testEvents.filter(test => test.testSourceFile).length,
468
+ testSourceFileCount: sourceFiles.size,
469
+ testSourceFiles: [...sourceFiles].slice(0, 5),
454
470
  debugLines: findInterestingLines(output, [
455
471
  /dd-trace/i,
456
472
  /test optimization/i,
@@ -541,7 +557,15 @@ function inconclusive (framework, scenario, diagnosis, evidence = {}, outDir, ex
541
557
  }
542
558
 
543
559
  function error (framework, scenario, err, outDir = err?.artifactDirectory) {
544
- return result(framework, scenario, 'error', err && err.stack ? err.stack : String(err), {}, outDir)
560
+ const blockerEvidence = getValidationBlockerEvidence(err)
561
+ return result(
562
+ framework,
563
+ scenario,
564
+ blockerEvidence ? 'blocked' : 'error',
565
+ blockerEvidence ? err.message : (err && err.stack ? err.stack : String(err)),
566
+ blockerEvidence || {},
567
+ outDir
568
+ )
545
569
  }
546
570
 
547
571
  function result (framework, scenario, status, diagnosis, evidence, outDir, extraArtifacts) {
@@ -149,7 +149,7 @@ function buildQuarantinedResponse (framework, scenario, discoveredIdentities = [
149
149
  for (const identity of identities) {
150
150
  for (const suite of getSuiteCandidates(identity, scenario)) {
151
151
  for (const name of getNameCandidates(identity)) {
152
- suites[suite] = suites[suite] || { tests: {} }
152
+ suites[suite] ||= { tests: {} }
153
153
  suites[suite].tests[name] = {
154
154
  properties: {
155
155
  quarantined: true,
@@ -210,7 +210,7 @@ function summarizeManagedTests (testManagementTests) {
210
210
  }
211
211
  summary.set(displaySuite, testNames)
212
212
  }
213
- return [...summary.entries()].slice(0, 5).map(([suite, tests]) => ({
213
+ return [...summary].slice(0, 5).map(([suite, tests]) => ({
214
214
  suite,
215
215
  tests: [...tests].slice(0, 5),
216
216
  }))
@@ -8,6 +8,7 @@ const {
8
8
  getFrameworkDefinitions,
9
9
  runDiagnosis,
10
10
  } = require('../diagnose')
11
+ const { BLOCKER_CATEGORIES } = require('./blocker-category')
11
12
  const { sanitizeForReport } = require('./redaction')
12
13
  const { writeFileSafely } = require('./safe-files')
13
14
 
@@ -45,6 +46,7 @@ function runStaticDiagnosis ({ manifest, out }) {
45
46
  function getStaticBlocker (framework, diagnosis) {
46
47
  if (!SUPPORTED_FRAMEWORKS.has(framework.framework)) {
47
48
  return {
49
+ blockerCategory: BLOCKER_CATEGORIES.VALIDATOR_LIMITATION,
48
50
  reason: `Unsupported test framework detected: ${getUnsupportedFrameworkName(framework)}.`,
49
51
  recommendation: 'Choose Jest, Mocha, Cucumber, Cypress, Playwright, or Vitest for live validation.',
50
52
  }
@@ -56,6 +58,7 @@ function getStaticBlocker (framework, diagnosis) {
56
58
  const version = parseVersion(framework.frameworkVersion)
57
59
  if (version && !satisfies(version, definition.supportedRange)) {
58
60
  return {
61
+ blockerCategory: BLOCKER_CATEGORIES.UNSUPPORTED_VERSION,
59
62
  reason:
60
63
  `${definition.name} ${version} is not supported. Supported range is ${definition.supportedRange}.`,
61
64
  recommendation: definition.recommendation,
@@ -65,6 +68,7 @@ function getStaticBlocker (framework, diagnosis) {
65
68
  const staticVersionError = findStaticVersionError(definition, diagnosis, framework, version)
66
69
  if (staticVersionError) {
67
70
  return {
71
+ blockerCategory: BLOCKER_CATEGORIES.UNSUPPORTED_VERSION,
68
72
  reason: staticVersionError.title,
69
73
  recommendation: staticVersionError.recommendation || staticVersionError.message,
70
74
  }
@@ -117,8 +121,11 @@ function getExactFrameworkLocations (diagnosis, framework) {
117
121
  const locations = new Set()
118
122
  addRelativeFrameworkLocation(locations, diagnosis, framework.project?.packageJson)
119
123
 
120
- for (const configFile of framework.project?.configFiles || []) {
121
- addRelativeFrameworkLocation(locations, diagnosis, configFile)
124
+ const configFiles = framework.project?.configFiles
125
+ if (configFiles) {
126
+ for (const configFile of configFiles) {
127
+ addRelativeFrameworkLocation(locations, diagnosis, configFile)
128
+ }
122
129
  }
123
130
 
124
131
  return locations
@@ -149,7 +156,7 @@ function normalizeRelativePath (location) {
149
156
 
150
157
  function parseVersion (rawVersion) {
151
158
  if (typeof rawVersion !== 'string') return null
152
- const match = rawVersion.match(/\d+\.\d+\.\d+/)
159
+ const match = rawVersion.match(/(?<!\d)\d+\.\d+\.\d+/)
153
160
  return match ? match[0] : null
154
161
  }
155
162
 
@@ -4,7 +4,8 @@ const cypressAdapter = require('./framework-adapters/cypress')
4
4
  const cucumberAdapter = require('./framework-adapters/cucumber')
5
5
  const playwrightAdapter = require('./framework-adapters/playwright')
6
6
 
7
- const ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}${String.raw`\[[0-?]*[ -/]*[@-~]`}`, 'g')
7
+ const ANSI_PATTERN =
8
+ new RegExp(`${String.fromCharCode(27)}${String.raw`\[[\u0030-\u003F]*[\u0020-\u002F]*[\u0040-\u007E]`}`, 'g')
8
9
 
9
10
  /**
10
11
  * Extracts the final test count from common JavaScript test-runner summaries.
@@ -0,0 +1,21 @@
1
+ 'use strict'
2
+
3
+ function createValidationBlocker (message, { kind, recommendation, suppressReport = false }) {
4
+ const error = new Error(message)
5
+ error.validationBlocker = { kind, recommendation }
6
+ error.validationExitCode = 2
7
+ error.suppressReport = suppressReport
8
+ return error
9
+ }
10
+
11
+ function getValidationBlockerEvidence (error) {
12
+ if (!error?.validationBlocker) return
13
+ return {
14
+ blockerKind: error.validationBlocker.kind,
15
+ domain: 'validator_state',
16
+ recommendation: error.validationBlocker.recommendation,
17
+ validationIncomplete: true,
18
+ }
19
+ }
20
+
21
+ module.exports = { createValidationBlocker, getValidationBlockerEvidence }