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
@@ -5,17 +5,23 @@
5
5
  const fs = require('node:fs')
6
6
  const path = require('node:path')
7
7
 
8
- const { assertApprovalDigest } = require('./approval')
8
+ const { assertApprovalDigest, getApprovalProjectSnapshot } = require('./approval')
9
9
  const { loadApprovedPlan } = require('./approval-artifacts')
10
+ const { BLOCKER_CATEGORIES, getBlockerDomain } = require('./blocker-category')
10
11
  const { annotateCiDiscovery } = require('./ci-discovery')
11
12
  const { cleanupGeneratedFiles } = require('./generated-files')
12
13
  const { verifyGeneratedTestStrategy } = require('./generated-verifier')
14
+ const {
15
+ acquireExecutionLock,
16
+ releaseExecutionLock,
17
+ } = require('./execution-lock')
13
18
  const { loadManifest } = require('./manifest-loader')
14
19
  const { createManifestScaffold } = require('./manifest-scaffold')
15
20
  const {
16
21
  formatExecutionPlanArtifacts,
17
22
  getExecutionPlanPath,
18
23
  } = require('./plan-writer')
24
+ const { checkInstalledPackage, getInstalledPackageFailure } = require('./package-check')
19
25
  const { runFrameworkPreflight } = require('./preflight-runner')
20
26
  const { sanitizeConsoleText } = require('./redaction')
21
27
  const {
@@ -34,6 +40,7 @@ const { runEarlyFlakeDetection } = require('./scenarios/early-flake-detection')
34
40
  const { runTestManagement } = require('./scenarios/test-management')
35
41
  const { ensureSafeDirectory } = require('./safe-files')
36
42
  const { getStaticBlocker, runStaticDiagnosis } = require('./static-diagnosis')
43
+ const { getValidationBlockerEvidence } = require('./validation-blocker')
37
44
 
38
45
  const DEFAULT_MANIFEST = './dd-test-optimization-validation-manifest.json'
39
46
  const DEFAULT_OUT = './dd-test-optimization-validation-results'
@@ -111,9 +118,11 @@ function parseArgs (argv) {
111
118
  * @returns {Promise<void>} completion
112
119
  */
113
120
  async function main (argv) {
121
+ let activeApprovedPlanSha256
114
122
  let activeManifest
115
123
  let activeOut
116
124
  let cleanupOutcome = { status: 'not_started' }
125
+ let executionLock
117
126
 
118
127
  try {
119
128
  const options = parseArgs(argv)
@@ -150,8 +159,16 @@ async function main (argv) {
150
159
  keepTempFiles: options.keepTempFiles,
151
160
  verbose: options.verbose,
152
161
  })
162
+ activeApprovedPlanSha256 = options.approvedPlanSha256
163
+ const packageCheck = hasLocalScenarios(options.scenarios)
164
+ ? checkInstalledPackage()
165
+ : undefined
153
166
  options.requireExecutableApproval = true
154
167
  ensureSafeDirectory(manifest.repository.root, out, 'validation output directory', { allowRootSymlink: true })
168
+ executionLock = acquireExecutionLock({
169
+ out,
170
+ approvedPlanSha256: options.approvedPlanSha256,
171
+ })
155
172
  writePendingReport({ manifest, out })
156
173
 
157
174
  const staticDiagnosis = runStaticDiagnosis({ manifest, out })
@@ -162,7 +179,7 @@ async function main (argv) {
162
179
  for (const framework of selectedFrameworks) {
163
180
  // Each framework is independent. A setup blocker must not discard useful results from another adapter.
164
181
  // eslint-disable-next-line no-await-in-loop
165
- await validateFramework({ framework, manifest, options, out, results, staticDiagnosis })
182
+ await validateFramework({ framework, manifest, options, out, packageCheck, results, staticDiagnosis })
166
183
  }
167
184
  } finally {
168
185
  try {
@@ -186,6 +203,7 @@ async function main (argv) {
186
203
  results: annotatedResults,
187
204
  staticDiagnosis,
188
205
  runSummary: {
206
+ approvedPlanSha256: activeApprovedPlanSha256,
189
207
  checkedScenarios: [...options.scenarios],
190
208
  cleanup: cleanupOutcome,
191
209
  executionStatus,
@@ -197,34 +215,69 @@ async function main (argv) {
197
215
  validatorExitCode,
198
216
  },
199
217
  })
218
+ releaseExecutionLock(executionLock)
219
+ executionLock = undefined
200
220
  console.log(`Validation report: ${path.join(out, 'report.md')}`)
201
221
  console.log('Present the report and stop. Any correction or retry requires a fresh plan and approval.')
202
222
  process.exitCode = validatorExitCode
203
223
  } catch (error) {
204
- process.exitCode = 3
205
- if (activeManifest && activeOut) {
224
+ let reportError = error
225
+ if (executionLock) {
226
+ await publishFailureReport({
227
+ approvedPlanSha256: activeApprovedPlanSha256,
228
+ cleanup: cleanupOutcome,
229
+ error: reportError,
230
+ manifest: activeManifest,
231
+ out: activeOut,
232
+ })
233
+ }
234
+ if (executionLock) {
206
235
  try {
207
- await writeReport({
236
+ releaseExecutionLock(executionLock)
237
+ executionLock = undefined
238
+ } catch (releaseError) {
239
+ reportError = releaseError
240
+ await publishFailureReport({
241
+ approvedPlanSha256: activeApprovedPlanSha256,
242
+ cleanup: cleanupOutcome,
243
+ error: reportError,
208
244
  manifest: activeManifest,
209
245
  out: activeOut,
210
- results: [getOrchestrationFailure(error)],
211
- runSummary: {
212
- checkedScenarios: [],
213
- cleanup: cleanupOutcome,
214
- executionStatus: 'validator_error',
215
- omittedScenarios: getSelectableScenarios(),
216
- runCompleted: true,
217
- selectedFrameworkIds: [],
218
- validationCoverage: 'partial',
219
- validatorExitCode: 3,
220
- },
221
246
  })
222
- } catch {}
247
+ }
223
248
  }
224
- console.error(sanitizeConsoleText(error?.stack || error))
249
+ const validatorExitCode = reportError?.validationExitCode || 3
250
+ process.exitCode = validatorExitCode
251
+ const blockerRecommendation = reportError?.validationBlocker?.recommendation
252
+ const displayedError = reportError?.validationExitCode
253
+ ? `${reportError.message}${blockerRecommendation ? ` ${blockerRecommendation}` : ''}`
254
+ : (reportError?.stack || reportError)
255
+ console.error(sanitizeConsoleText(displayedError))
225
256
  }
226
257
  }
227
258
 
259
+ async function publishFailureReport ({ approvedPlanSha256, cleanup, error, manifest, out }) {
260
+ if (!manifest || !out || error?.suppressReport) return
261
+ try {
262
+ await writeReport({
263
+ manifest,
264
+ out,
265
+ results: [getOrchestrationFailure(error)],
266
+ runSummary: {
267
+ approvedPlanSha256,
268
+ checkedScenarios: [],
269
+ cleanup,
270
+ executionStatus: error?.validationBlocker ? 'incomplete' : 'validator_error',
271
+ omittedScenarios: getSelectableScenarios(),
272
+ runCompleted: true,
273
+ selectedFrameworkIds: [],
274
+ validationCoverage: 'partial',
275
+ validatorExitCode: error?.validationExitCode || 3,
276
+ },
277
+ })
278
+ } catch {}
279
+ }
280
+
228
281
  /**
229
282
  * Runs selected checks for one framework.
230
283
  *
@@ -233,11 +286,12 @@ async function main (argv) {
233
286
  * @param {object} input.manifest validation manifest
234
287
  * @param {object} input.options validator options
235
288
  * @param {string} input.out output directory
289
+ * @param {object|undefined} input.packageCheck installed package-load result
236
290
  * @param {object[]} input.results accumulated results
237
291
  * @param {object} input.staticDiagnosis static diagnosis
238
292
  * @returns {Promise<void>} completion
239
293
  */
240
- async function validateFramework ({ framework, manifest, options, out, results, staticDiagnosis }) {
294
+ async function validateFramework ({ framework, manifest, options, out, packageCheck, results, staticDiagnosis }) {
241
295
  let ciResult
242
296
  if (options.scenarios.has(CI_WIRING)) {
243
297
  logPhase(framework, 'CI configuration audit', 'start')
@@ -256,18 +310,38 @@ async function validateFramework ({ framework, manifest, options, out, results,
256
310
  return
257
311
  }
258
312
 
313
+ if (packageCheck?.ok === false) {
314
+ const packageFailure = getInstalledPackageFailure(framework, packageCheck)
315
+ results.push(packageFailure)
316
+ if (ciResult) results.push(ciResult)
317
+ addAdvancedNotReached(results, framework, options.scenarios, packageFailure)
318
+ return
319
+ }
320
+
259
321
  const unavailable = getUnavailableExecutable(getBasicCommand(framework))
260
322
  if (unavailable) {
261
- results.push(getUnavailableRunnerResult(framework, unavailable))
323
+ const unavailableResult = getUnavailableRunnerResult(framework, unavailable)
324
+ results.push(unavailableResult)
262
325
  if (ciResult) results.push(ciResult)
263
- addNotReachedLocalResults(results, framework, options.scenarios, 'runner-unavailable')
326
+ addNotReachedLocalResults(
327
+ results,
328
+ framework,
329
+ options.scenarios,
330
+ 'runner-unavailable',
331
+ unavailableResult.evidence.blockerCategory
332
+ )
264
333
  return
265
334
  }
266
335
 
267
336
  const blocker = getStaticBlocker(framework, staticDiagnosis.report)
268
337
  if (blocker) {
269
338
  const staticFailure = getStaticFailure(framework, blocker, staticDiagnosis.reportPath)
270
- const basicNotReached = getBasicNotReached(framework, blocker.reason, 'static-project-blocker')
339
+ const basicNotReached = getBasicNotReached(
340
+ framework,
341
+ blocker.reason,
342
+ 'static-project-blocker',
343
+ blocker.blockerCategory
344
+ )
271
345
  results.push(staticFailure, basicNotReached)
272
346
  if (ciResult) results.push(ciResult)
273
347
  addAdvancedNotReached(results, framework, options.scenarios, basicNotReached)
@@ -321,8 +395,15 @@ function initializeManifest (options) {
321
395
  if (path.dirname(manifestPath) !== process.cwd()) {
322
396
  throw new Error('The generated manifest must be stored directly in the current repository root.')
323
397
  }
398
+ if (fs.existsSync(manifestPath)) return reuseExistingManifest(manifestPath)
399
+
324
400
  const manifest = createManifestScaffold({ root: process.cwd(), frameworks: options.frameworks })
325
- fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, { flag: 'wx' })
401
+ try {
402
+ fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, { flag: 'wx' })
403
+ } catch (error) {
404
+ if (error.code === 'EEXIST') return reuseExistingManifest(manifestPath)
405
+ throw error
406
+ }
326
407
  const targets = manifest.ciDiscovery.reviewTargets
327
408
  console.log(sanitizeConsoleText([
328
409
  `Created a data-only validation manifest: ${manifestPath}`,
@@ -347,27 +428,134 @@ function initializeManifest (options) {
347
428
  function printPlan (manifest, options) {
348
429
  const out = validateOutputPath(manifest, options.out)
349
430
  const approvalManifest = getApprovalManifest(manifest, options.frameworks)
350
- const { plan } = formatExecutionPlanArtifacts({
351
- manifest: approvalManifest,
352
- out,
353
- selectedFrameworkIds: options.frameworks.size > 0
354
- ? approvalManifest.frameworks.map(framework => framework.id)
355
- : [],
356
- requestedScenario: options.requestedScenario,
357
- keepTempFiles: options.keepTempFiles,
358
- verbose: options.verbose,
359
- })
431
+ const localScenariosSelected = hasLocalScenarios(options.scenarios)
432
+ if (!localScenariosSelected ||
433
+ !approvalManifest.frameworks.some(framework => framework.status === 'runnable')) {
434
+ return writeStaticOnlyReport(approvalManifest, out, options)
435
+ }
436
+ ensureSafeDirectory(manifest.repository.root, out, 'validation output directory', { allowRootSymlink: true })
437
+ const publicationLock = acquireExecutionLock({ out, approvedPlanSha256: 'plan-publication' })
438
+ try {
439
+ const packageCheck = localScenariosSelected
440
+ ? checkInstalledPackage()
441
+ : undefined
442
+ if (packageCheck?.ok === false) throw getInstalledPackageCheckError(packageCheck)
443
+
444
+ const projectSnapshot = getApprovalProjectSnapshot(approvalManifest, {
445
+ includeLocal: options.requestedScenario !== CI_WIRING,
446
+ })
447
+ const ciPreflightResults = options.scenarios.has(CI_WIRING)
448
+ ? new Map(approvalManifest.frameworks.map(framework => [
449
+ framework.id,
450
+ runCiWiring({ manifest: approvalManifest, framework, projectFileSources: projectSnapshot.sources }),
451
+ ]))
452
+ : new Map()
453
+ const { plan } = formatExecutionPlanArtifacts({
454
+ manifest: approvalManifest,
455
+ out,
456
+ selectedFrameworkIds: options.frameworks.size > 0
457
+ ? approvalManifest.frameworks.map(framework => framework.id)
458
+ : [],
459
+ requestedScenario: options.requestedScenario,
460
+ keepTempFiles: options.keepTempFiles,
461
+ packageCheck,
462
+ ciPreflightResults,
463
+ expectedProjectFiles: projectSnapshot.projectFiles,
464
+ verbose: options.verbose,
465
+ })
466
+ console.log(sanitizeConsoleText([
467
+ '===== CUSTOMER APPROVAL PLAN =====',
468
+ plan,
469
+ '===== END CUSTOMER APPROVAL PLAN =====',
470
+ '',
471
+ `Saved execution plan: ${getExecutionPlanPath(out)}`,
472
+ 'LIVE VALIDATION HAS NOT RUN.',
473
+ 'Present the complete delimited plan and ask exactly: Approve executing exactly the plan above?',
474
+ ].join('\n')))
475
+ } finally {
476
+ releaseExecutionLock(publicationLock)
477
+ }
478
+ }
479
+
480
+ function writeStaticOnlyReport (manifest, out, options) {
481
+ const results = []
482
+ const localScenariosSelected = hasLocalScenarios(options.scenarios)
483
+ for (const framework of manifest.frameworks) {
484
+ if (localScenariosSelected) {
485
+ results.push(getFrameworkStatusResult(framework))
486
+ addNotReachedLocalResults(results, framework, options.scenarios, 'framework-not-runnable')
487
+ }
488
+ if (options.scenarios.has(CI_WIRING)) results.push(runCiWiring({ manifest, framework }))
489
+ }
490
+ const annotatedResults = annotateResults(results)
491
+ const executionStatus = getExecutionStatus(annotatedResults)
492
+ const validatorExitCode = getValidatorExitCode(annotatedResults, executionStatus)
493
+ ensureSafeDirectory(manifest.repository.root, out, 'validation output directory', { allowRootSymlink: true })
494
+ const publicationLock = acquireExecutionLock({ out, approvedPlanSha256: 'static-only-report' })
495
+ try {
496
+ writeReport({
497
+ manifest,
498
+ out,
499
+ results: annotatedResults,
500
+ runSummary: {
501
+ checkedScenarios: [...options.scenarios],
502
+ cleanup: { directoriesRemoved: 0, filesRemoved: 0, status: 'completed' },
503
+ executionStatus,
504
+ omittedScenarios: getSelectableScenarios().filter(scenario => !options.scenarios.has(scenario)),
505
+ requestedScenario: options.requestedScenario,
506
+ runCompleted: true,
507
+ selectedFrameworkIds: manifest.frameworks.map(framework => framework.id),
508
+ validationCoverage: getValidationCoverage(annotatedResults),
509
+ validatorExitCode,
510
+ },
511
+ })
512
+ } finally {
513
+ releaseExecutionLock(publicationLock)
514
+ }
515
+ const reason = localScenariosSelected
516
+ ? 'No selected framework has an eligible local command.'
517
+ : 'The selected CI-only audit does not require local execution.'
518
+ console.log(sanitizeConsoleText(
519
+ `${reason} A final static-only report was written; no new approval artifact or live validation command was ` +
520
+ 'created. Present the report and stop.'
521
+ ))
522
+ process.exitCode = validatorExitCode
523
+ }
524
+
525
+ function reuseExistingManifest (manifestPath) {
526
+ try {
527
+ const manifest = loadManifest(manifestPath)
528
+ if (fs.realpathSync(manifest.repository.root) !== fs.realpathSync(process.cwd())) {
529
+ throw new Error('repository.root does not identify the current physical repository')
530
+ }
531
+ } catch (error) {
532
+ const recovery = new Error(
533
+ `The existing validation manifest cannot be reused: ${error.message}. ` +
534
+ `Inspect and remove only ${manifestPath}, then rerun --init-manifest. The validator did not delete or replace it.`
535
+ )
536
+ recovery.validationExitCode = 2
537
+ throw recovery
538
+ }
539
+
360
540
  console.log(sanitizeConsoleText([
361
- '===== CUSTOMER APPROVAL PLAN =====',
362
- plan,
363
- '===== END CUSTOMER APPROVAL PLAN =====',
364
- '',
365
- `Saved execution plan: ${getExecutionPlanPath(out)}`,
366
- 'LIVE VALIDATION HAS NOT RUN.',
367
- 'Present the complete delimited plan and ask exactly: Approve executing exactly the plan above?',
541
+ `Existing validation manifest is valid for this repository: ${manifestPath}`,
542
+ 'Reuse it, refresh only its ciWiring evidence if needed, then run --validate-manifest and --print-plan.',
543
+ 'The validator did not overwrite the manifest or delete existing approval and report artifacts.',
368
544
  ].join('\n')))
369
545
  }
370
546
 
547
+ function hasLocalScenarios (scenarios) {
548
+ return [...scenarios].some(scenario => scenario !== CI_WIRING)
549
+ }
550
+
551
+ function getInstalledPackageCheckError (packageCheck) {
552
+ const error = new Error(
553
+ `INSTALLED DD-TRACE PACKAGE BLOCKER: ${packageCheck.diagnosis} ${packageCheck.recommendation}`
554
+ )
555
+ error.validationExitCode = 2
556
+ return error
557
+ }
558
+
371
559
  /**
372
560
  * Restores approved execution options from approval.json.
373
561
  *
@@ -442,6 +630,9 @@ function addAdvancedNotReached (results, framework, scenarios, blocker) {
442
630
  diagnosis: 'Not reached because Basic Reporting did not establish the direct reporting path.',
443
631
  evidence: {
444
632
  blockedBy: blocker?.scenario || 'basic-reporting',
633
+ ...(blocker?.evidence?.blockerCategory
634
+ ? { blockerCategory: blocker.evidence.blockerCategory }
635
+ : {}),
445
636
  validationIncomplete: true,
446
637
  },
447
638
  artifacts: [],
@@ -456,9 +647,15 @@ function addAdvancedNotReached (results, framework, scenarios, blocker) {
456
647
  * @param {object} framework framework entry
457
648
  * @param {Set<string>} scenarios selected scenarios
458
649
  * @param {string} reasonCode blocker id
650
+ * @param {string} [blockerCategoryOverride] blocker category supplied by a runtime result
459
651
  * @returns {void}
460
652
  */
461
- function addNotReachedLocalResults (results, framework, scenarios, reasonCode) {
653
+ function addNotReachedLocalResults (results, framework, scenarios, reasonCode, blockerCategoryOverride) {
654
+ const blockerCategory = blockerCategoryOverride || framework.blockerCategory || (
655
+ framework.status === 'requires_manual_setup'
656
+ ? BLOCKER_CATEGORIES.PROJECT_SETUP_REQUIRED
657
+ : BLOCKER_CATEGORIES.VALIDATOR_LIMITATION
658
+ )
462
659
  for (const scenario of scenarios) {
463
660
  if (scenario === CI_WIRING) continue
464
661
  results.push({
@@ -466,7 +663,7 @@ function addNotReachedLocalResults (results, framework, scenarios, reasonCode) {
466
663
  scenario,
467
664
  status: 'skip',
468
665
  diagnosis: 'Not reached because this framework has no available direct-runner validation target.',
469
- evidence: { reasonCode, validationIncomplete: true },
666
+ evidence: { blockerCategory, reasonCode, validationIncomplete: true },
470
667
  artifacts: [],
471
668
  })
472
669
  }
@@ -534,7 +731,7 @@ function normalizeScenarioSelection (scenario) {
534
731
  * @returns {string} normalized target
535
732
  */
536
733
  function normalizeFrameworkTarget (target) {
537
- const normalized = String(target).trim().replaceAll(/:+$/g, '')
734
+ const normalized = String(target).trim().replace(/(?<!:):+$/, '')
538
735
  if (!normalized) throw new Error('Framework target cannot be empty.')
539
736
  return normalized
540
737
  }
@@ -621,21 +818,31 @@ function logPhase (framework, phase, status) {
621
818
  * @returns {object} result
622
819
  */
623
820
  function getFrameworkStatusResult (framework) {
624
- const requiresProjectSetup = framework.status === 'requires_manual_setup'
821
+ const blockerCategory = framework.blockerCategory || (
822
+ framework.status === 'requires_manual_setup'
823
+ ? BLOCKER_CATEGORIES.PROJECT_SETUP_REQUIRED
824
+ : BLOCKER_CATEGORIES.VALIDATOR_LIMITATION
825
+ )
826
+ const domain = getBlockerDomain(blockerCategory)
625
827
  return {
626
828
  frameworkId: framework.id,
627
829
  scenario: 'all',
628
830
  status: 'skip',
629
831
  diagnosis: framework.notes?.[0] || `Framework status is ${framework.status}.`,
630
832
  evidence: {
833
+ blockerCategory,
834
+ domain,
631
835
  frameworkStatus: framework.status,
632
836
  validationIncomplete: true,
633
- ...(requiresProjectSetup
837
+ ...(blockerCategory === BLOCKER_CATEGORIES.PROJECT_SETUP_REQUIRED
634
838
  ? {
635
839
  blockedByProjectSetup: true,
636
840
  ...(framework.notes?.[0] ? { recommendation: framework.notes[0] } : {}),
637
841
  }
638
- : { validatorAdapterUnavailable: true }),
842
+ : {
843
+ validatorAdapterUnavailable: true,
844
+ ...(framework.notes?.[0] ? { recommendation: framework.notes[0] } : {}),
845
+ }),
639
846
  },
640
847
  artifacts: [],
641
848
  }
@@ -654,7 +861,12 @@ function getUnavailableRunnerResult (framework, unavailable) {
654
861
  scenario: 'all',
655
862
  status: 'skip',
656
863
  diagnosis: `The direct runner is unavailable: ${unavailable}. Complete normal project setup and retry.`,
657
- evidence: { blockedByProjectSetup: true, unavailableRunner: unavailable, validationIncomplete: true },
864
+ evidence: {
865
+ blockerCategory: BLOCKER_CATEGORIES.PROJECT_SETUP_REQUIRED,
866
+ blockedByProjectSetup: true,
867
+ unavailableRunner: unavailable,
868
+ validationIncomplete: true,
869
+ },
658
870
  artifacts: [],
659
871
  }
660
872
  }
@@ -668,12 +880,22 @@ function getUnavailableRunnerResult (framework, unavailable) {
668
880
  * @returns {object} result
669
881
  */
670
882
  function getStaticFailure (framework, blocker, reportPath) {
883
+ const blockerCategory = blocker.blockerCategory || BLOCKER_CATEGORIES.PROJECT_SETUP_REQUIRED
671
884
  return {
672
885
  frameworkId: framework.id,
673
886
  scenario: 'all',
674
887
  status: 'error',
675
888
  diagnosis: blocker.reason,
676
- evidence: { blockedByProjectSetup: true, staticDiagnosis: reportPath, validationIncomplete: true },
889
+ evidence: {
890
+ blockerCategory,
891
+ domain: getBlockerDomain(blockerCategory),
892
+ recommendation: blocker.recommendation,
893
+ staticDiagnosis: reportPath,
894
+ validationIncomplete: true,
895
+ ...(blockerCategory === BLOCKER_CATEGORIES.PROJECT_SETUP_REQUIRED
896
+ ? { blockedByProjectSetup: true }
897
+ : {}),
898
+ },
677
899
  artifacts: [reportPath],
678
900
  }
679
901
  }
@@ -684,15 +906,20 @@ function getStaticFailure (framework, blocker, reportPath) {
684
906
  * @param {object} framework framework entry
685
907
  * @param {string} diagnosis blocker diagnosis
686
908
  * @param {string} reasonCode blocker id
909
+ * @param {string | undefined} blockerCategory blocker category
687
910
  * @returns {object} result
688
911
  */
689
- function getBasicNotReached (framework, diagnosis, reasonCode) {
912
+ function getBasicNotReached (framework, diagnosis, reasonCode, blockerCategory) {
690
913
  return {
691
914
  frameworkId: framework.id,
692
915
  scenario: BASIC_REPORTING,
693
916
  status: 'skip',
694
917
  diagnosis: `Basic Reporting was not reached: ${diagnosis}`,
695
- evidence: { reasonCode, validationIncomplete: true },
918
+ evidence: {
919
+ ...(blockerCategory ? { blockerCategory } : {}),
920
+ reasonCode,
921
+ validationIncomplete: true,
922
+ },
696
923
  artifacts: [],
697
924
  }
698
925
  }
@@ -725,12 +952,13 @@ function getCleanupFailure (error, cleanup) {
725
952
  * @returns {object} result
726
953
  */
727
954
  function getOrchestrationFailure (error) {
955
+ const blockerEvidence = getValidationBlockerEvidence(error)
728
956
  return {
729
957
  frameworkId: 'validator',
730
958
  scenario: 'all',
731
- status: 'error',
959
+ status: blockerEvidence ? 'blocked' : 'error',
732
960
  diagnosis: error?.message || String(error),
733
- evidence: { validationIncomplete: true, validationOrchestrationFailed: true },
961
+ evidence: blockerEvidence || { validationIncomplete: true, validationOrchestrationFailed: true },
734
962
  artifacts: [],
735
963
  }
736
964
  }