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.
- package/ci/diagnose.js +27 -25
- package/ci/init.js +6 -6
- package/ci/runbook.md +114 -20
- package/ci/test-optimization-validation/approval-artifacts.js +36 -3
- package/ci/test-optimization-validation/approval.js +78 -22
- package/ci/test-optimization-validation/blocker-category.js +24 -0
- package/ci/test-optimization-validation/ci-discovery.js +23 -17
- package/ci/test-optimization-validation/ci-package-scripts.js +34 -6
- package/ci/test-optimization-validation/ci-remediation.js +1 -1
- package/ci/test-optimization-validation/cli.js +280 -52
- package/ci/test-optimization-validation/command-blocker.js +159 -20
- package/ci/test-optimization-validation/command-output-policy.js +14 -8
- package/ci/test-optimization-validation/command-runner.js +9 -7
- package/ci/test-optimization-validation/environment.js +4 -2
- package/ci/test-optimization-validation/executable.js +15 -13
- package/ci/test-optimization-validation/execution-lock.js +70 -0
- package/ci/test-optimization-validation/framework-adapters/cucumber-validation.json +3 -0
- package/ci/test-optimization-validation/framework-adapters/cucumber.js +657 -5
- package/ci/test-optimization-validation/framework-adapters/playwright.js +5 -5
- package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
- package/ci/test-optimization-validation/generated-files.js +47 -37
- package/ci/test-optimization-validation/generated-test-contract.js +9 -7
- package/ci/test-optimization-validation/generated-verifier.js +24 -17
- package/ci/test-optimization-validation/literal-glob.js +52 -0
- package/ci/test-optimization-validation/manifest-loader.js +3 -0
- package/ci/test-optimization-validation/manifest-scaffold.js +301 -52
- package/ci/test-optimization-validation/manifest-schema.js +80 -17
- package/ci/test-optimization-validation/offline-fixtures.js +13 -4
- package/ci/test-optimization-validation/offline-output.js +6 -6
- package/ci/test-optimization-validation/package-check.js +94 -0
- package/ci/test-optimization-validation/plan-writer.js +183 -10
- package/ci/test-optimization-validation/preflight-runner.js +110 -31
- package/ci/test-optimization-validation/project-build-artifact.js +31 -0
- package/ci/test-optimization-validation/redaction.js +18 -27
- package/ci/test-optimization-validation/report-writer.js +182 -48
- package/ci/test-optimization-validation/result-semantics.js +20 -3
- package/ci/test-optimization-validation/runner-command.js +41 -21
- package/ci/test-optimization-validation/runner-contract.js +79 -43
- package/ci/test-optimization-validation/safe-files.js +4 -4
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +60 -6
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
- package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
- package/ci/test-optimization-validation/scenarios/test-management.js +2 -2
- package/ci/test-optimization-validation/static-diagnosis.js +10 -3
- package/ci/test-optimization-validation/test-output.js +2 -1
- package/ci/test-optimization-validation/validation-blocker.js +21 -0
- package/ci/vitest-no-worker-init-setup.mjs +377 -84
- package/index.d.ts +175 -14
- package/init.js +1 -17
- package/initialize.mjs +11 -0
- package/loader-hook.mjs +6 -4
- package/package.json +12 -4
- package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
- package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
- package/packages/datadog-esbuild/index.js +4 -2
- package/packages/datadog-esbuild/src/utils.js +17 -2
- package/packages/datadog-instrumentations/src/ai.js +61 -34
- package/packages/datadog-instrumentations/src/bluebird.js +6 -6
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +85 -9
- package/packages/datadog-instrumentations/src/cucumber.js +131 -51
- package/packages/datadog-instrumentations/src/dns.js +21 -2
- package/packages/datadog-instrumentations/src/electron.js +1 -0
- package/packages/datadog-instrumentations/src/fastify.js +3 -1
- package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
- package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
- package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
- package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +34 -9
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +175 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +61 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +38 -7
- package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
- package/packages/datadog-instrumentations/src/http/client.js +1 -3
- package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +986 -246
- package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
- package/packages/datadog-instrumentations/src/mocha/main.js +109 -51
- package/packages/datadog-instrumentations/src/mocha/utils.js +120 -32
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +44 -0
- package/packages/datadog-instrumentations/src/mocha/worker.js +441 -43
- package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
- package/packages/datadog-instrumentations/src/mysql.js +1 -1
- package/packages/datadog-instrumentations/src/next.js +155 -23
- package/packages/datadog-instrumentations/src/openai.js +14 -0
- package/packages/datadog-instrumentations/src/pg.js +180 -6
- package/packages/datadog-instrumentations/src/playwright.js +176 -81
- package/packages/datadog-instrumentations/src/promise-js.js +4 -4
- package/packages/datadog-instrumentations/src/promise.js +3 -3
- package/packages/datadog-instrumentations/src/rhea.js +1 -1
- package/packages/datadog-instrumentations/src/router.js +195 -19
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +567 -65
- package/packages/datadog-instrumentations/src/vitest-main.js +438 -54
- package/packages/datadog-instrumentations/src/vitest-util.js +42 -7
- package/packages/datadog-instrumentations/src/vitest-worker.js +234 -17
- package/packages/datadog-instrumentations/src/webdriverio.js +1090 -0
- package/packages/datadog-instrumentations/src/when.js +3 -3
- package/packages/datadog-instrumentations/src/ws.js +5 -1
- package/packages/datadog-plugin-aerospike/src/index.js +1 -3
- package/packages/datadog-plugin-amqplib/src/client.js +1 -1
- package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
- package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
- package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
- package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
- package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
- package/packages/datadog-plugin-child_process/src/index.js +3 -3
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +6 -11
- package/packages/datadog-plugin-cucumber/src/index.js +5 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +50 -46
- package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
- package/packages/datadog-plugin-cypress/src/support.js +7 -4
- package/packages/datadog-plugin-electron/src/net.js +10 -4
- package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
- package/packages/datadog-plugin-fs/src/index.js +0 -4
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
- package/packages/datadog-plugin-graphql/src/execute.js +1 -0
- package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
- package/packages/datadog-plugin-http/src/client.js +3 -2
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +3 -21
- package/packages/datadog-plugin-jest/src/util.js +4 -1
- package/packages/datadog-plugin-kafkajs/src/producer.js +2 -1
- package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
- package/packages/datadog-plugin-mocha/src/index.js +342 -6
- package/packages/datadog-plugin-next/src/index.js +57 -9
- package/packages/datadog-plugin-openai/src/tracing.js +4 -0
- package/packages/datadog-plugin-pg/src/index.js +65 -1
- package/packages/datadog-plugin-playwright/src/index.js +1 -1
- package/packages/datadog-plugin-prisma/src/index.js +5 -1
- package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
- package/packages/datadog-plugin-rhea/src/producer.js +1 -1
- package/packages/datadog-plugin-undici/src/index.js +2 -1
- package/packages/datadog-plugin-vitest/src/index.js +179 -18
- package/packages/datadog-shimmer/src/shimmer.js +6 -2
- package/packages/datadog-webpack/index.js +2 -2
- package/packages/dd-trace/src/aiguard/sdk.js +3 -0
- package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
- package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
- package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
- package/packages/dd-trace/src/appsec/handlers/http-request.js +215 -0
- package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
- package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
- package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +2 -2
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
- package/packages/dd-trace/src/appsec/index.js +48 -476
- package/packages/dd-trace/src/appsec/sdk/track_event.js +10 -1
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
- package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
- package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +29 -15
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +19 -1
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +25 -1
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
- package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +2 -2
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
- package/packages/dd-trace/src/config/defaults.js +14 -6
- package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -2
- package/packages/dd-trace/src/config/helper.js +2 -0
- package/packages/dd-trace/src/config/index.js +31 -5
- package/packages/dd-trace/src/config/parsers.js +19 -1
- package/packages/dd-trace/src/config/remote_config.js +1 -1
- package/packages/dd-trace/src/config/supported-configurations.json +13 -6
- package/packages/dd-trace/src/datastreams/encoding.js +3 -4
- package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
- package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
- package/packages/dd-trace/src/encode/tags-processors.js +2 -2
- package/packages/dd-trace/src/exporters/common/url.js +3 -5
- package/packages/dd-trace/src/exporters/electron/index.js +4 -1
- package/packages/dd-trace/src/external-logger/src/index.js +1 -2
- package/packages/dd-trace/src/flare/index.js +2 -2
- package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
- package/packages/dd-trace/src/guardrails/log.js +1 -10
- package/packages/dd-trace/src/id.js +1 -1
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
- package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
- package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
- package/packages/dd-trace/src/llmobs/experiments/index.js +66 -19
- package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
- package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
- package/packages/dd-trace/src/llmobs/experiments/util.js +220 -0
- package/packages/dd-trace/src/llmobs/noop.js +2 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +152 -78
- package/packages/dd-trace/src/llmobs/span_processor.js +9 -7
- package/packages/dd-trace/src/llmobs/tagger.js +1 -2
- package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
- package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
- package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
- package/packages/dd-trace/src/log/channels.js +1 -9
- package/packages/dd-trace/src/log/levels.js +12 -0
- package/packages/dd-trace/src/noop/proxy.js +2 -2
- package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
- package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +3 -2
- package/packages/dd-trace/src/opentelemetry/span.js +4 -0
- package/packages/dd-trace/src/opentracing/span.js +1 -1
- package/packages/dd-trace/src/opentracing/span_context.js +1 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +124 -23
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/plugin.js +1 -1
- package/packages/dd-trace/src/plugins/tracing.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +13 -6
- package/packages/dd-trace/src/plugins/util/git.js +7 -5
- package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +3 -3
- package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
- package/packages/dd-trace/src/plugins/util/llm.js +2 -2
- package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
- package/packages/dd-trace/src/plugins/util/tags.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +356 -86
- package/packages/dd-trace/src/plugins/util/url.js +5 -5
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
- package/packages/dd-trace/src/plugins/util/web.js +24 -2
- package/packages/dd-trace/src/priority_sampler.js +6 -3
- package/packages/dd-trace/src/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
- package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
- package/packages/dd-trace/src/proxy.js +1 -0
- package/packages/dd-trace/src/ritm.js +10 -20
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
- package/packages/dd-trace/src/sampling_rule.js +4 -2
- package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
- package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
- package/packages/dd-trace/src/startup-log.js +3 -0
- package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
- package/packages/dd-trace/src/util.js +16 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
- package/vendor/dist/pprof-format/index.js +1 -1
- package/vendor/dist/source-map/index.js +1 -1
- package/version.js +10 -8
- /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const path = require('node:path')
|
|
4
4
|
|
|
5
5
|
const { getArtifactId } = require('./artifact-id')
|
|
6
|
+
const { BLOCKER_CATEGORY_VALUES } = require('./blocker-category')
|
|
6
7
|
const {
|
|
7
8
|
MAX_GENERATED_FILES,
|
|
8
9
|
getGeneratedFileContentError,
|
|
@@ -111,6 +112,12 @@ function validateFrameworks (manifest, errors) {
|
|
|
111
112
|
requiredString(framework, 'id', prefix, errors)
|
|
112
113
|
requiredString(framework, 'framework', prefix, errors)
|
|
113
114
|
enumString(framework, 'status', STATUSES, prefix, errors)
|
|
115
|
+
if (framework.blockerCategory !== undefined &&
|
|
116
|
+
!BLOCKER_CATEGORY_VALUES.has(framework.blockerCategory)) {
|
|
117
|
+
errors.push(
|
|
118
|
+
`${prefix}.blockerCategory must be one of ${[...BLOCKER_CATEGORY_VALUES].join(', ')} when provided.`
|
|
119
|
+
)
|
|
120
|
+
}
|
|
114
121
|
requiredObject(framework, 'project', prefix, errors)
|
|
115
122
|
rejectExecutionFields(framework, prefix, errors)
|
|
116
123
|
|
|
@@ -132,6 +139,12 @@ function validateFrameworks (manifest, errors) {
|
|
|
132
139
|
validateCiWiring(manifest.repository?.root, framework.ciWiring, `${prefix}.ciWiring`, errors)
|
|
133
140
|
|
|
134
141
|
if (framework.status === 'runnable') {
|
|
142
|
+
if (typeof framework.allCandidatesRequireLocalSocket !== 'boolean') {
|
|
143
|
+
errors.push(`${prefix}.allCandidatesRequireLocalSocket must be a boolean.`)
|
|
144
|
+
}
|
|
145
|
+
if (typeof framework.buildArtifactRequired !== 'boolean') {
|
|
146
|
+
errors.push(`${prefix}.buildArtifactRequired must be a boolean.`)
|
|
147
|
+
}
|
|
135
148
|
validateRunnableFramework(manifest.repository?.root, framework, prefix, generatedPaths, errors)
|
|
136
149
|
} else {
|
|
137
150
|
for (const field of ['validation', 'preflight', 'generatedTestStrategy']) {
|
|
@@ -198,12 +211,13 @@ function validateRunnableFramework (repositoryRoot, framework, prefix, generated
|
|
|
198
211
|
}
|
|
199
212
|
containedPath(repositoryRoot, validation.runner, `${prefix}.validation.runner`, errors)
|
|
200
213
|
containedPath(repositoryRoot, validation.testFile, `${prefix}.validation.testFile`, errors)
|
|
214
|
+
validateFallbackTests(repositoryRoot, validation, `${prefix}.validation`, errors)
|
|
201
215
|
const runnerArgsError = getRunnerArgsError(framework.framework, validation.runnerArgs)
|
|
202
216
|
if (runnerArgsError) errors.push(`${prefix}.validation.runnerArgs ${runnerArgsError}.`)
|
|
203
217
|
if (validation.omittedRunnerOptions !== undefined) {
|
|
204
218
|
validateStringArray(validation.omittedRunnerOptions, `${prefix}.validation.omittedRunnerOptions`, errors)
|
|
205
219
|
for (const option of Array.isArray(validation.omittedRunnerOptions) ? validation.omittedRunnerOptions : []) {
|
|
206
|
-
if (!['--run', '--typecheck'].includes(option)) {
|
|
220
|
+
if (!['-R', '--reporter', '--run', '--typecheck'].includes(option)) {
|
|
207
221
|
errors.push(`${prefix}.validation.omittedRunnerOptions contains unsupported option ${option}.`)
|
|
208
222
|
}
|
|
209
223
|
}
|
|
@@ -225,21 +239,23 @@ function validateRunnableFramework (repositoryRoot, framework, prefix, generated
|
|
|
225
239
|
if (runnerInputError) errors.push(`${prefix}.validation runner configuration ${runnerInputError}.`)
|
|
226
240
|
}
|
|
227
241
|
validateStringArray(validation.requiredEnvVars, `${prefix}.validation.requiredEnvVars`, errors)
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
242
|
+
if (validation.requiredEnvVars) {
|
|
243
|
+
for (const name of validation.requiredEnvVars) {
|
|
244
|
+
if (!ENV_NAME_PATTERN.test(name)) {
|
|
245
|
+
errors.push(`${prefix}.validation.requiredEnvVars contains an invalid environment name.`)
|
|
246
|
+
}
|
|
247
|
+
if (/^(?:DD_|DATADOG_|OTEL_|NODE_OPTIONS$|TS_NODE_PROJECT$)/i.test(name)) {
|
|
248
|
+
errors.push(
|
|
249
|
+
`${prefix}.validation.requiredEnvVars must not inherit Datadog, OpenTelemetry, NODE_OPTIONS, or ` +
|
|
250
|
+
'TS_NODE_PROJECT.'
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
if (SECRET_ENV_PATTERN.test(name)) {
|
|
254
|
+
errors.push(`${prefix}.validation.requiredEnvVars must not inherit secret-like environment variables.`)
|
|
255
|
+
}
|
|
256
|
+
if (EXECUTION_ENV_PATTERN.test(name)) {
|
|
257
|
+
errors.push(`${prefix}.validation.requiredEnvVars must not inherit executable-loading environment variables.`)
|
|
258
|
+
}
|
|
243
259
|
}
|
|
244
260
|
}
|
|
245
261
|
if (!Number.isInteger(validation.timeoutMs) || validation.timeoutMs < 1 || validation.timeoutMs > MAX_TIMEOUT_MS) {
|
|
@@ -253,6 +269,51 @@ function validateRunnableFramework (repositoryRoot, framework, prefix, generated
|
|
|
253
269
|
validateGeneratedStrategy(repositoryRoot, framework, prefix, generatedPaths, errors)
|
|
254
270
|
}
|
|
255
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Validates bounded fallback test paths and their statically discovered prerequisites.
|
|
274
|
+
*
|
|
275
|
+
* @param {string} repositoryRoot repository root
|
|
276
|
+
* @param {object} validation direct-runner validation data
|
|
277
|
+
* @param {string} prefix error prefix
|
|
278
|
+
* @param {{push: function(string): void}} errors error collector
|
|
279
|
+
* @returns {void}
|
|
280
|
+
*/
|
|
281
|
+
function validateFallbackTests (repositoryRoot, validation, prefix, errors) {
|
|
282
|
+
if (validation.fallbackTests === undefined) return
|
|
283
|
+
if (!Array.isArray(validation.fallbackTests)) {
|
|
284
|
+
errors.push(`${prefix}.fallbackTests must be an array.`)
|
|
285
|
+
return
|
|
286
|
+
}
|
|
287
|
+
if (validation.fallbackTests.length > 2) {
|
|
288
|
+
errors.push(`${prefix}.fallbackTests must contain at most 2 entries.`)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const testFiles = new Set()
|
|
292
|
+
for (const [index, fallback] of validation.fallbackTests.slice(0, 2).entries()) {
|
|
293
|
+
const fallbackPrefix = `${prefix}.fallbackTests[${index}]`
|
|
294
|
+
if (!isObject(fallback)) {
|
|
295
|
+
errors.push(`${fallbackPrefix} must be an object.`)
|
|
296
|
+
continue
|
|
297
|
+
}
|
|
298
|
+
rejectExecutionFields(fallback, fallbackPrefix, errors)
|
|
299
|
+
requiredAbsolutePath(fallback, 'testFile', fallbackPrefix, errors)
|
|
300
|
+
containedPath(repositoryRoot, fallback.testFile, `${fallbackPrefix}.testFile`, errors)
|
|
301
|
+
if (typeof fallback.buildArtifactRequired !== 'boolean') {
|
|
302
|
+
errors.push(`${fallbackPrefix}.buildArtifactRequired must be a boolean.`)
|
|
303
|
+
}
|
|
304
|
+
if (typeof fallback.localSocketRequired !== 'boolean') {
|
|
305
|
+
errors.push(`${fallbackPrefix}.localSocketRequired must be a boolean.`)
|
|
306
|
+
}
|
|
307
|
+
if (fallback.testFile === validation.testFile) {
|
|
308
|
+
errors.push(`${fallbackPrefix}.testFile must differ from ${prefix}.testFile.`)
|
|
309
|
+
}
|
|
310
|
+
if (testFiles.has(fallback.testFile)) {
|
|
311
|
+
errors.push(`${prefix}.fallbackTests must contain unique testFile values.`)
|
|
312
|
+
}
|
|
313
|
+
testFiles.add(fallback.testFile)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
256
317
|
/**
|
|
257
318
|
* Validates canonical generated test data.
|
|
258
319
|
*
|
|
@@ -450,7 +511,9 @@ function validateAllStrings (value, prefix, errors) {
|
|
|
450
511
|
if (errors.full()) return
|
|
451
512
|
if (typeof value === 'string') {
|
|
452
513
|
if (Buffer.byteLength(value) > MAX_STRING_BYTES) errors.push(`${prefix} exceeds the string size limit.`)
|
|
453
|
-
|
|
514
|
+
const inertCiLabel = /\.ciWiring\.(?:job|step)$/.test(prefix)
|
|
515
|
+
const text = inertCiLabel ? value.replaceAll(/[\uFE0E\uFE0F]/g, '') : value
|
|
516
|
+
if (hasUnsafeInvisibleCharacter(text)) errors.push(`${prefix} contains an unsafe invisible character.`)
|
|
454
517
|
return
|
|
455
518
|
}
|
|
456
519
|
if (Array.isArray(value)) {
|
|
@@ -7,6 +7,7 @@ const path = require('node:path')
|
|
|
7
7
|
|
|
8
8
|
const { getArtifactId } = require('./artifact-id')
|
|
9
9
|
const { createFileSafely, ensureSafeDirectory } = require('./safe-files')
|
|
10
|
+
const { createValidationBlocker } = require('./validation-blocker')
|
|
10
11
|
|
|
11
12
|
const MAX_FIXTURE_FILE_BYTES = 1024 * 1024
|
|
12
13
|
const OFFLINE_FIXTURE_NONCE_PATTERN = /^[a-f0-9]{32}$/
|
|
@@ -71,9 +72,13 @@ function createOfflineFixture ({
|
|
|
71
72
|
}
|
|
72
73
|
ensurePrivateDirectory(base)
|
|
73
74
|
if (fs.existsSync(root)) {
|
|
74
|
-
throw
|
|
75
|
-
`Offline validation fixture already exists and will not be replaced or deleted: ${root}
|
|
76
|
-
|
|
75
|
+
throw createValidationBlocker(
|
|
76
|
+
`Offline validation fixture already exists and will not be replaced or deleted: ${root}.`,
|
|
77
|
+
{
|
|
78
|
+
kind: 'offline-fixture-exists',
|
|
79
|
+
recommendation: `Inspect ${root}. Remove only that validator fixture after confirming no validation process ` +
|
|
80
|
+
'is active, then render and approve a fresh plan.',
|
|
81
|
+
}
|
|
77
82
|
)
|
|
78
83
|
}
|
|
79
84
|
try {
|
|
@@ -244,7 +249,11 @@ function getFixtureSettingsOverrides (scenarioName) {
|
|
|
244
249
|
function cleanupOfflineFixture (fixtureRoot) {
|
|
245
250
|
const parent = path.dirname(path.dirname(fixtureRoot))
|
|
246
251
|
ensureSafeDirectory(parent, fixtureRoot, 'offline validation fixture cleanup')
|
|
247
|
-
|
|
252
|
+
try {
|
|
253
|
+
fs.rmSync(fixtureRoot, { recursive: true })
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (error.code !== 'ENOENT') throw error
|
|
256
|
+
}
|
|
248
257
|
removeEmptyParents(path.dirname(fixtureRoot), parent)
|
|
249
258
|
}
|
|
250
259
|
|
|
@@ -57,8 +57,8 @@ function readOfflineOutput (outputRoot) {
|
|
|
57
57
|
if (!exporterInitialized) return emptyOutput()
|
|
58
58
|
|
|
59
59
|
const state = {
|
|
60
|
-
bytes:
|
|
61
|
-
completionBytes:
|
|
60
|
+
bytes: 0n,
|
|
61
|
+
completionBytes: 0n,
|
|
62
62
|
decodedEntries: 0,
|
|
63
63
|
files: 0,
|
|
64
64
|
}
|
|
@@ -150,7 +150,7 @@ function readPayloadFiles (payloadsRoot, kind, state, consume) {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
function readRegularFile (filename, individualLimit, state, completion) {
|
|
153
|
-
const stat = fs.lstatSync(filename)
|
|
153
|
+
const stat = fs.lstatSync(filename, { bigint: true })
|
|
154
154
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink > 1) {
|
|
155
155
|
throw new Error('Offline validation artifact must be a regular, unlinked file.')
|
|
156
156
|
}
|
|
@@ -163,16 +163,16 @@ function readRegularFile (filename, individualLimit, state, completion) {
|
|
|
163
163
|
|
|
164
164
|
const file = fs.openSync(filename, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0))
|
|
165
165
|
try {
|
|
166
|
-
const opened = fs.fstatSync(file)
|
|
166
|
+
const opened = fs.fstatSync(file, { bigint: true })
|
|
167
167
|
if (!opened.isFile() || opened.nlink > 1 || opened.dev !== stat.dev || opened.ino !== stat.ino) {
|
|
168
168
|
throw new Error('Offline validation artifact changed while it was opened.')
|
|
169
169
|
}
|
|
170
170
|
const buffer = fs.readFileSync(file)
|
|
171
|
-
const completed = fs.fstatSync(file)
|
|
171
|
+
const completed = fs.fstatSync(file, { bigint: true })
|
|
172
172
|
if (completed.size !== opened.size || completed.mtimeMs !== opened.mtimeMs) {
|
|
173
173
|
throw new Error('Offline validation artifact changed while it was read.')
|
|
174
174
|
}
|
|
175
|
-
state[totalKey] += buffer.length
|
|
175
|
+
state[totalKey] += BigInt(buffer.length)
|
|
176
176
|
return buffer
|
|
177
177
|
} finally {
|
|
178
178
|
fs.closeSync(file)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require('node:child_process')
|
|
4
|
+
const path = require('node:path')
|
|
5
|
+
|
|
6
|
+
const { BLOCKER_CATEGORIES } = require('./blocker-category')
|
|
7
|
+
const { getBaseEnv } = require('./command-runner')
|
|
8
|
+
const { sanitizeString } = require('./redaction')
|
|
9
|
+
|
|
10
|
+
const DEFAULT_PACKAGE_ROOT = path.resolve(__dirname, '..', '..')
|
|
11
|
+
const LOAD_MARKER = 'dd-trace-test-optimization-package-check-ok'
|
|
12
|
+
const MAX_OUTPUT_BYTES = 64 * 1024
|
|
13
|
+
const TIMEOUT_MS = 10_000
|
|
14
|
+
|
|
15
|
+
// Loads the initialization entrypoint in an isolated child without initializing tracing.
|
|
16
|
+
function checkInstalledPackage ({ packageRoot = DEFAULT_PACKAGE_ROOT } = {}) {
|
|
17
|
+
const root = path.resolve(packageRoot)
|
|
18
|
+
const initPath = path.join(root, 'ci', 'init.js')
|
|
19
|
+
const script = [
|
|
20
|
+
`require(${JSON.stringify(initPath)})`,
|
|
21
|
+
`process.stdout.write(${JSON.stringify(LOAD_MARKER)})`,
|
|
22
|
+
'process.exit(0)',
|
|
23
|
+
].join(';')
|
|
24
|
+
const result = spawnSync(process.execPath, ['-e', script], {
|
|
25
|
+
cwd: root,
|
|
26
|
+
encoding: 'utf8',
|
|
27
|
+
env: {
|
|
28
|
+
...getBaseEnv('clean'),
|
|
29
|
+
DD_CIVISIBILITY_ENABLED: 'false',
|
|
30
|
+
DD_INSTRUMENTATION_TELEMETRY_ENABLED: 'false',
|
|
31
|
+
DD_PROFILING_ENABLED: 'false',
|
|
32
|
+
DD_REMOTE_CONFIGURATION_ENABLED: 'false',
|
|
33
|
+
DD_TRACE_ENABLED: 'false',
|
|
34
|
+
NODE_OPTIONS: '',
|
|
35
|
+
},
|
|
36
|
+
maxBuffer: MAX_OUTPUT_BYTES,
|
|
37
|
+
shell: false,
|
|
38
|
+
timeout: TIMEOUT_MS,
|
|
39
|
+
windowsHide: true,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
if (!result.error && result.status === 0 && result.stdout.includes(LOAD_MARKER)) {
|
|
43
|
+
return {
|
|
44
|
+
ok: true,
|
|
45
|
+
diagnosis: 'The installed dd-trace package loaded its Test Optimization initialization entrypoint.',
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const detail = getFailureDetail(result)
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
diagnosis: 'The installed dd-trace package could not load its Test Optimization initialization entrypoint' +
|
|
53
|
+
(detail ? `: ${detail}` : '.'),
|
|
54
|
+
recommendation: 'Reinstall dd-trace through the project\'s normal dependency workflow, then create a fresh ' +
|
|
55
|
+
'validation plan. If the load check still fails, report it as an installed-package problem without running ' +
|
|
56
|
+
'tests.',
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getInstalledPackageFailure (framework, packageCheck) {
|
|
61
|
+
return {
|
|
62
|
+
frameworkId: framework.id,
|
|
63
|
+
scenario: 'basic-reporting',
|
|
64
|
+
status: 'blocked',
|
|
65
|
+
diagnosis: `${packageCheck.diagnosis} No project test was run and no library-compatibility conclusion was reached.`,
|
|
66
|
+
evidence: {
|
|
67
|
+
blockerCategory: BLOCKER_CATEGORIES.PROJECT_SETUP_REQUIRED,
|
|
68
|
+
blockedByProjectSetup: true,
|
|
69
|
+
installedPackageIncomplete: true,
|
|
70
|
+
recommendation: packageCheck.recommendation,
|
|
71
|
+
validationIncomplete: true,
|
|
72
|
+
},
|
|
73
|
+
artifacts: [],
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getFailureDetail (result) {
|
|
78
|
+
if (result.error?.code === 'ETIMEDOUT') return `the isolated load check exceeded ${TIMEOUT_MS} ms.`
|
|
79
|
+
if (result.error) return `${sanitizeLine(result.error.message)}.`
|
|
80
|
+
|
|
81
|
+
const output = `${result.stderr || ''}\n${result.stdout || ''}`
|
|
82
|
+
const line = output.split(/\r?\n/).map(value => value.trim()).find(value => {
|
|
83
|
+
return /^(?:Error:|.*(?:MODULE_NOT_FOUND|Cannot find (?:module|package)|ERR_[A-Z_]+))/.test(value)
|
|
84
|
+
})
|
|
85
|
+
if (line) return `${sanitizeLine(line)}.`
|
|
86
|
+
if (result.signal) return `the isolated load check exited after signal ${sanitizeLine(result.signal)}.`
|
|
87
|
+
return `the isolated load check exited ${result.status ?? 'without a status'}.`
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function sanitizeLine (value) {
|
|
91
|
+
return sanitizeString(String(value)).replaceAll(/\p{Cc}+/gu, ' ').trim().slice(0, 1000).replace(/[.]+$/, '')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { checkInstalledPackage, getInstalledPackageFailure }
|
|
@@ -4,6 +4,7 @@ const crypto = require('node:crypto')
|
|
|
4
4
|
const path = require('node:path')
|
|
5
5
|
|
|
6
6
|
const { VERSION } = require('../../version')
|
|
7
|
+
const { getRequiredCapabilities } = require('./approval')
|
|
7
8
|
const { writeApprovalArtifacts } = require('./approval-artifacts')
|
|
8
9
|
const { serializeApprovalCommand } = require('./command-runner')
|
|
9
10
|
const { getUnavailableExecutable } = require('./executable')
|
|
@@ -41,6 +42,9 @@ function formatExecutionPlan (input) {
|
|
|
41
42
|
* @param {string[]} [input.selectedFrameworkIds] selected framework ids
|
|
42
43
|
* @param {string|null} [input.requestedScenario] selected scenario
|
|
43
44
|
* @param {boolean} [input.keepTempFiles] retain temporary files
|
|
45
|
+
* @param {object} [input.packageCheck] installed package-load result
|
|
46
|
+
* @param {Map<string, object>} [input.ciPreflightResults] static CI results by framework
|
|
47
|
+
* @param {Array<{path: string, sha256: string}>} [input.expectedProjectFiles] preflight project snapshot
|
|
44
48
|
* @param {boolean} [input.verbose] print progress
|
|
45
49
|
* @returns {{plan: string}} written plan
|
|
46
50
|
*/
|
|
@@ -50,6 +54,9 @@ function formatExecutionPlanArtifacts ({
|
|
|
50
54
|
selectedFrameworkIds = [],
|
|
51
55
|
requestedScenario = null,
|
|
52
56
|
keepTempFiles = false,
|
|
57
|
+
packageCheck,
|
|
58
|
+
ciPreflightResults = new Map(),
|
|
59
|
+
expectedProjectFiles,
|
|
53
60
|
verbose = false,
|
|
54
61
|
}) {
|
|
55
62
|
const offlineFixtureNonce = crypto.randomBytes(16).toString('hex')
|
|
@@ -61,6 +68,7 @@ function formatExecutionPlanArtifacts ({
|
|
|
61
68
|
offlineFixtureNonce,
|
|
62
69
|
keepTempFiles,
|
|
63
70
|
verbose,
|
|
71
|
+
expectedProjectFiles,
|
|
64
72
|
})
|
|
65
73
|
const validatorArgv = [
|
|
66
74
|
process.execPath,
|
|
@@ -72,8 +80,11 @@ function formatExecutionPlanArtifacts ({
|
|
|
72
80
|
]
|
|
73
81
|
const plan = formatApprovalPlan({
|
|
74
82
|
approvalArtifacts,
|
|
83
|
+
ciPreflightResults,
|
|
75
84
|
manifest,
|
|
76
85
|
out,
|
|
86
|
+
keepTempFiles,
|
|
87
|
+
packageCheck,
|
|
77
88
|
requestedScenario,
|
|
78
89
|
validatorArgv,
|
|
79
90
|
})
|
|
@@ -86,20 +97,67 @@ function formatExecutionPlanArtifacts ({
|
|
|
86
97
|
*
|
|
87
98
|
* @param {object} input formatting inputs
|
|
88
99
|
* @param {object} input.approvalArtifacts approval artifact paths and digest
|
|
100
|
+
* @param {Map<string, object>} input.ciPreflightResults static CI results by framework
|
|
89
101
|
* @param {object} input.manifest normalized manifest
|
|
90
102
|
* @param {string} input.out output directory
|
|
103
|
+
* @param {boolean} input.keepTempFiles retain temporary files
|
|
104
|
+
* @param {object|undefined} input.packageCheck installed package-load result
|
|
91
105
|
* @param {string|null} input.requestedScenario selected scenario
|
|
92
106
|
* @param {string[]} input.validatorArgv exact validator command
|
|
93
107
|
* @returns {string} Markdown plan
|
|
94
108
|
*/
|
|
95
|
-
function formatApprovalPlan ({
|
|
109
|
+
function formatApprovalPlan ({
|
|
110
|
+
approvalArtifacts,
|
|
111
|
+
ciPreflightResults,
|
|
112
|
+
manifest,
|
|
113
|
+
out,
|
|
114
|
+
keepTempFiles,
|
|
115
|
+
packageCheck,
|
|
116
|
+
requestedScenario,
|
|
117
|
+
validatorArgv,
|
|
118
|
+
}) {
|
|
96
119
|
const root = manifest.repository.root
|
|
120
|
+
const runnableFrameworks = manifest.frameworks.filter(framework => framework.status === 'runnable')
|
|
121
|
+
const requiredCapabilities = getRequiredCapabilities({
|
|
122
|
+
manifest,
|
|
123
|
+
requestedScenario,
|
|
124
|
+
})
|
|
125
|
+
const buildPrerequisites = runnableFrameworks.filter(framework => {
|
|
126
|
+
return framework.buildArtifactRequired === true ||
|
|
127
|
+
(framework.validation?.fallbackTests || []).some(fallback => fallback.buildArtifactRequired === true)
|
|
128
|
+
}).length
|
|
97
129
|
const lines = [
|
|
98
130
|
'# Test Optimization Validation Plan',
|
|
99
131
|
'',
|
|
100
132
|
`Repository: ${inline(root)}`,
|
|
101
133
|
`Validator: ${inline(`dd-trace ${VERSION}`)}`,
|
|
102
134
|
`Results: ${inline(relative(root, out))}`,
|
|
135
|
+
...(packageCheck?.ok
|
|
136
|
+
? [`Installed package check: ${plain(packageCheck.diagnosis)}`]
|
|
137
|
+
: []),
|
|
138
|
+
'',
|
|
139
|
+
'## Approval Summary',
|
|
140
|
+
'',
|
|
141
|
+
requestedScenario === 'ci-wiring'
|
|
142
|
+
? 'Local execution: none; this plan performs static CI analysis only.'
|
|
143
|
+
: `Local execution: ${runnableFrameworks.length} framework target${
|
|
144
|
+
runnableFrameworks.length === 1 ? '' : 's'
|
|
145
|
+
} eligible; ${manifest.frameworks.length - runnableFrameworks.length} setup or static-only.`,
|
|
146
|
+
`Required host capabilities: ${
|
|
147
|
+
requiredCapabilities.length > 0 ? requiredCapabilities.map(formatCapability).join(', ') : 'none declared'
|
|
148
|
+
}.`,
|
|
149
|
+
`Build prerequisites: ${
|
|
150
|
+
buildPrerequisites > 0
|
|
151
|
+
? `${buildPrerequisites} eligible framework target${
|
|
152
|
+
buildPrerequisites === 1 ? '' : 's'
|
|
153
|
+
} may require the project's normal build`
|
|
154
|
+
: 'none detected statically'
|
|
155
|
+
}.`,
|
|
156
|
+
`Mutable paths: ${inline(relative(root, out))} and the temporary files disclosed below; temporary files ${
|
|
157
|
+
keepTempFiles ? 'will be retained by request' : 'will be removed after validation'
|
|
158
|
+
}.`,
|
|
159
|
+
'CI replay: none. CI configuration is inspected statically and CI commands are never executed.',
|
|
160
|
+
'Approval: one checksum-bound validator command, shown once at the end of this plan.',
|
|
103
161
|
'',
|
|
104
162
|
'The validator will run only the displayed `node <repository-contained-runner> <one-test-file>` commands. The ' +
|
|
105
163
|
'runner may be an exact repository-owned Node test wrapper. The validator itself will not directly invoke ' +
|
|
@@ -112,8 +170,15 @@ function formatApprovalPlan ({ approvalArtifacts, manifest, out, requestedScenar
|
|
|
112
170
|
for (const framework of manifest.frameworks) {
|
|
113
171
|
lines.push(`### ${plain(getFrameworkLabel(framework, root))}`, '')
|
|
114
172
|
if (framework.status !== 'runnable') {
|
|
115
|
-
lines.push(
|
|
116
|
-
|
|
173
|
+
lines.push(
|
|
174
|
+
`Status: ${plain(formatStatus(framework.status))}.`,
|
|
175
|
+
...(framework.blockerCategory
|
|
176
|
+
? [`Blocker: ${plain(framework.blockerCategory.replaceAll('_', ' '))}.`]
|
|
177
|
+
: [])
|
|
178
|
+
)
|
|
179
|
+
if (framework.notes) {
|
|
180
|
+
for (const note of framework.notes) lines.push(`- ${plain(note)}`)
|
|
181
|
+
}
|
|
117
182
|
lines.push('')
|
|
118
183
|
continue
|
|
119
184
|
}
|
|
@@ -122,13 +187,25 @@ function formatApprovalPlan ({ approvalArtifacts, manifest, out, requestedScenar
|
|
|
122
187
|
lines.push('Status: static CI audit only; no project test command is selected.', '')
|
|
123
188
|
} else {
|
|
124
189
|
const basic = getBasicCommand(framework)
|
|
190
|
+
const fallbackTests = framework.validation.fallbackTests || []
|
|
125
191
|
const unavailable = getUnavailableExecutable(basic)
|
|
126
192
|
const status = unavailable
|
|
127
193
|
? `local validation will be incomplete because ${inline(unavailable)} is unavailable`
|
|
128
194
|
: 'eligible for approved clean preflight; runtime prerequisites are unverified'
|
|
129
195
|
lines.push(
|
|
130
196
|
`Status: ${status}.`,
|
|
131
|
-
`Representative test: ${inline(relative(root, framework.validation.testFile))}
|
|
197
|
+
`Representative test: ${inline(relative(root, framework.validation.testFile))}${formatCandidateRequirements({
|
|
198
|
+
buildArtifactRequired: framework.buildArtifactRequired,
|
|
199
|
+
localSocketRequired: framework.localSocketRequired,
|
|
200
|
+
})}`,
|
|
201
|
+
...(fallbackTests.length > 0
|
|
202
|
+
? [
|
|
203
|
+
'Fallback tests, tried in order only if the representative does not pass cleanly:',
|
|
204
|
+
...fallbackTests.map(fallback => {
|
|
205
|
+
return `- ${inline(relative(root, fallback.testFile))}${formatCandidateRequirements(fallback)}`
|
|
206
|
+
}),
|
|
207
|
+
]
|
|
208
|
+
: []),
|
|
132
209
|
`Working directory: ${inline(relative(root, basic.cwd))}`,
|
|
133
210
|
`Timeout: ${basic.timeoutMs} ms`,
|
|
134
211
|
...(Object.keys(basic.env || {}).length > 0
|
|
@@ -136,16 +213,71 @@ function formatApprovalPlan ({ approvalArtifacts, manifest, out, requestedScenar
|
|
|
136
213
|
: []),
|
|
137
214
|
...formatOmittedRunnerOptions(framework.validation.omittedRunnerOptions),
|
|
138
215
|
'',
|
|
216
|
+
...(framework.browserRequired
|
|
217
|
+
? [
|
|
218
|
+
'Browser permission: this approved validation command launches the project browser through its ' +
|
|
219
|
+
'selected test runner. If the agent platform blocks browser processes, request its narrow permission ' +
|
|
220
|
+
'for the exact checksum-bound validator command below; do not change or broaden the command. ' +
|
|
221
|
+
'Complete ' +
|
|
222
|
+
'the project\'s normal browser installation, application startup, and build first when the selected ' +
|
|
223
|
+
'test depends on them.',
|
|
224
|
+
'',
|
|
225
|
+
]
|
|
226
|
+
: []),
|
|
227
|
+
...(framework.localSocketRequired
|
|
228
|
+
? [
|
|
229
|
+
`Localhost prerequisite: ${
|
|
230
|
+
framework.allCandidatesRequireLocalSocket
|
|
231
|
+
? 'every approved candidate appears'
|
|
232
|
+
: 'the selected test appears'
|
|
233
|
+
} to open or contact a local listener. A restricted execution environment may require narrow ` +
|
|
234
|
+
'permission for the exact validator command.',
|
|
235
|
+
'',
|
|
236
|
+
]
|
|
237
|
+
: []),
|
|
238
|
+
...(framework.buildArtifactRequired
|
|
239
|
+
? [
|
|
240
|
+
'Build prerequisite: the selected test appears to load a build or dist artifact. Complete the ' +
|
|
241
|
+
'project\'s normal build before validation if that artifact is not already present.',
|
|
242
|
+
'',
|
|
243
|
+
]
|
|
244
|
+
: []),
|
|
139
245
|
'Basic Reporting command:',
|
|
140
246
|
'',
|
|
141
247
|
codeBlock(serializeApprovalCommand(basic)),
|
|
142
248
|
'',
|
|
143
249
|
'The command runs once without Datadog and once with validator-owned offline initialization. A debug rerun ' +
|
|
144
250
|
'and one clean confirmation may run only when needed to diagnose a mismatch.',
|
|
251
|
+
...(fallbackTests.length > 0
|
|
252
|
+
? [
|
|
253
|
+
'If this test does not pass cleanly, the validator may try the disclosed fallback tests in order. It ' +
|
|
254
|
+
'will initialize only the first candidate that passes, and will stop early when the failure is a ' +
|
|
255
|
+
'confirmed shared runtime prerequisite rather than a candidate-specific test failure.',
|
|
256
|
+
]
|
|
257
|
+
: []),
|
|
145
258
|
''
|
|
146
259
|
)
|
|
147
260
|
|
|
261
|
+
for (const [index, fallback] of fallbackTests.entries()) {
|
|
262
|
+
const fallbackCommand = getBasicCommand(framework, fallback.testFile)
|
|
263
|
+
lines.push(
|
|
264
|
+
`Fallback Basic Reporting command ${index + 1}:`,
|
|
265
|
+
'',
|
|
266
|
+
codeBlock(serializeApprovalCommand(fallbackCommand)),
|
|
267
|
+
''
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
|
|
148
271
|
const selectedScenarios = getSelectedGeneratedScenarios(framework, requestedScenario)
|
|
272
|
+
if (selectedScenarios.length > 0) {
|
|
273
|
+
lines.push(
|
|
274
|
+
`Advanced checks use working directory: ${inline(relative(
|
|
275
|
+
root,
|
|
276
|
+
getGeneratedCommand(framework, selectedScenarios[0]).cwd
|
|
277
|
+
))}`,
|
|
278
|
+
''
|
|
279
|
+
)
|
|
280
|
+
}
|
|
149
281
|
for (const scenario of selectedScenarios) {
|
|
150
282
|
const command = getGeneratedCommand(framework, scenario)
|
|
151
283
|
const source = getGeneratedSource(framework, scenario)
|
|
@@ -157,9 +289,6 @@ function formatApprovalPlan ({ approvalArtifacts, manifest, out, requestedScenar
|
|
|
157
289
|
`Temporary file: ${inline(relative(root, scenario.testIdentities[0].file))}`,
|
|
158
290
|
'',
|
|
159
291
|
codeBlock(source),
|
|
160
|
-
'',
|
|
161
|
-
'Execution count: one clean generated-test verification, one instrumented identity-discovery run, and ' +
|
|
162
|
-
'one feature-validation run. One debug rerun may run only after a failure.',
|
|
163
292
|
''
|
|
164
293
|
)
|
|
165
294
|
}
|
|
@@ -173,12 +302,23 @@ function formatApprovalPlan ({ approvalArtifacts, manifest, out, requestedScenar
|
|
|
173
302
|
''
|
|
174
303
|
)
|
|
175
304
|
}
|
|
305
|
+
if (selectedScenarios.length > 0) {
|
|
306
|
+
lines.push(
|
|
307
|
+
'Advanced execution policy: each generated scenario has one clean verification, one instrumented ' +
|
|
308
|
+
'identity-discovery run, and one feature-validation run. One debug rerun may run only after a failure.',
|
|
309
|
+
''
|
|
310
|
+
)
|
|
311
|
+
}
|
|
176
312
|
}
|
|
177
313
|
|
|
178
314
|
const ci = framework.ciWiring
|
|
315
|
+
const ciPreflight = ciPreflightResults.get(framework.id)
|
|
179
316
|
lines.push(
|
|
180
317
|
'CI audit: static only; no CI or package command will execute.',
|
|
181
318
|
`CI review: ${plain(ci?.reviewComplete ? 'complete' : 'incomplete')}.`,
|
|
319
|
+
...(ciPreflight
|
|
320
|
+
? [`CI pre-approval result: ${plain(formatCiPreflightResult(ciPreflight))}`]
|
|
321
|
+
: []),
|
|
182
322
|
...(ci?.configFile ? [`CI file: ${inline(relative(root, ci.configFile))}`] : []),
|
|
183
323
|
''
|
|
184
324
|
)
|
|
@@ -188,6 +328,8 @@ function formatApprovalPlan ({ approvalArtifacts, manifest, out, requestedScenar
|
|
|
188
328
|
'## Writes and Cleanup',
|
|
189
329
|
'',
|
|
190
330
|
`- Validation artifacts: ${inline(relative(root, out))}`,
|
|
331
|
+
'- Single-flight lock: the approved validator creates one fixed lock in the result directory while it runs. An ' +
|
|
332
|
+
'existing lock blocks execution and is never reclaimed automatically.',
|
|
191
333
|
...(requestedScenario === 'ci-wiring'
|
|
192
334
|
? []
|
|
193
335
|
: [
|
|
@@ -233,10 +375,41 @@ function formatApprovalPlan ({ approvalArtifacts, manifest, out, requestedScenar
|
|
|
233
375
|
return lines.join('\n')
|
|
234
376
|
}
|
|
235
377
|
|
|
378
|
+
function formatCapability (capability) {
|
|
379
|
+
const labels = {
|
|
380
|
+
browser_process: 'browser process',
|
|
381
|
+
localhost_socket: 'localhost socket',
|
|
382
|
+
}
|
|
383
|
+
return labels[capability] || capability
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function formatCandidateRequirements (candidate) {
|
|
387
|
+
const requirements = [
|
|
388
|
+
...(candidate.localSocketRequired ? ['localhost'] : []),
|
|
389
|
+
...(candidate.buildArtifactRequired ? ['build output'] : []),
|
|
390
|
+
]
|
|
391
|
+
return requirements.length > 0 ? ` (${requirements.join(', ')} required)` : ''
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function formatCiPreflightResult (result) {
|
|
395
|
+
const status = result.status === 'fail'
|
|
396
|
+
? 'confirmed actionable problem'
|
|
397
|
+
: result.status === 'pass'
|
|
398
|
+
? 'no confirmed problem'
|
|
399
|
+
: 'incomplete'
|
|
400
|
+
return `${status}: ${result.diagnosis}`
|
|
401
|
+
}
|
|
402
|
+
|
|
236
403
|
function formatOmittedRunnerOptions (options = []) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
404
|
+
const descriptions = {
|
|
405
|
+
'-R': 'Omitted runner option: `-R` selects only Mocha report presentation; the validator uses its bounded ' +
|
|
406
|
+
'reporter.',
|
|
407
|
+
'--reporter': 'Omitted runner option: `--reporter` selects only Mocha report presentation; the validator uses ' +
|
|
408
|
+
'its bounded reporter.',
|
|
409
|
+
'--run': 'Normalized runner option: `--run` is omitted because the validator supplies Vitest `run` itself.',
|
|
410
|
+
'--typecheck': 'Omitted runner option: `--typecheck` is excluded because validation executes runtime tests only.',
|
|
411
|
+
}
|
|
412
|
+
return options.map(option => descriptions[option])
|
|
240
413
|
}
|
|
241
414
|
|
|
242
415
|
/**
|