diff --git a/.github/workflows/codescanning-config-cli.yml b/.github/workflows/codescanning-config-cli.yml index 0c4829339f..38b5b6ecce 100644 --- a/.github/workflows/codescanning-config-cli.yml +++ b/.github/workflows/codescanning-config-cli.yml @@ -6,13 +6,6 @@ env: # Diff informed queries add an additional query filter which is not yet # taken into account by these tests. CODEQL_ACTION_DIFF_INFORMED_QUERIES: false - # Specify overlay enablement manually to ensure stability around the exclude-from-incremental - # query filter. Here we only enable for the default code scanning suite. - CODEQL_ACTION_OVERLAY_ANALYSIS: true - CODEQL_ACTION_OVERLAY_ANALYSIS_JAVASCRIPT: false - CODEQL_ACTION_OVERLAY_ANALYSIS_CODE_SCANNING_JAVASCRIPT: true - CODEQL_ACTION_OVERLAY_ANALYSIS_STATUS_CHECK: false - CODEQL_ACTION_OVERLAY_ANALYSIS_SKIP_RESOURCE_CHECKS: true on: push: @@ -79,33 +72,13 @@ jobs: with: version: ${{ matrix.version }} - # On PRs, overlay analysis may change the config that is passed to the CLI. - # Therefore, we have two variants of the following test, one for PRs and one for other events. - - name: Empty file (non-PR) - if: github.event_name != 'pull_request' + - name: Empty file uses: ./../action/.github/actions/check-codescanning-config with: expected-config-file-contents: "{}" languages: javascript tools: ${{ steps.prepare-test.outputs.tools-url }} - - name: Empty file (PR) - if: github.event_name == 'pull_request' - uses: ./../action/.github/actions/check-codescanning-config - with: - expected-config-file-contents: | - { - "query-filters": [ - { - "exclude": { - "tags": "exclude-from-incremental" - } - } - ] - } - languages: javascript - tools: ${{ steps.prepare-test.outputs.tools-url }} - - name: Packs from input if: success() || failure() uses: ./../action/.github/actions/check-codescanning-config diff --git a/lib/init-action.js b/lib/init-action.js index 6acd2a5670..c707def1fd 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -106359,9 +106359,6 @@ function initFeatures(gitHubVersion, repositoryNwo, tempDir, logger) { } // src/diff-informed-analysis-utils.ts -async function shouldPerformDiffInformedAnalysis(codeql, features, logger) { - return await getDiffInformedAnalysisBranches(codeql, features, logger) !== void 0; -} async function getDiffInformedAnalysisBranches(codeql, features, logger) { if (!await features.getValue("diff_informed_queries" /* DiffInformedQueries */, codeql)) { return void 0; @@ -106378,6 +106375,30 @@ async function getDiffInformedAnalysisBranches(codeql, features, logger) { } return branches; } +async function prepareDiffInformedAnalysis(codeql, features, logger) { + let branches; + try { + branches = await getDiffInformedAnalysisBranches(codeql, features, logger); + } catch (e) { + logger.warning( + `Failed to determine branch information for diff-informed analysis: ${getErrorMessage(e)}` + ); + return false; + } + if (!branches) { + return false; + } + return await withGroupAsync("Computing PR diff ranges", async () => { + try { + return await computeAndPersistDiffRanges(branches, logger); + } catch (e) { + logger.warning( + `Failed to compute diff-informed analysis ranges: ${getErrorMessage(e)}` + ); + return false; + } + }); +} function writeDiffRangesJsonFile(logger, ranges) { const jsonContents = JSON.stringify(ranges, null, 2); const jsonFilePath = getDiffRangesJsonFilePath(); @@ -106408,6 +106429,18 @@ async function getPullRequestEditedDiffRanges(branches, logger) { } return results; } +async function computeAndPersistDiffRanges(branches, logger) { + const ranges = await getPullRequestEditedDiffRanges(branches, logger); + if (ranges === void 0) { + return false; + } + writeDiffRangesJsonFile(logger, ranges); + const distinctFiles = new Set(ranges.map((r) => r.path)).size; + logger.info( + `Persisted ${ranges.length} diff range(s) across ${distinctFiles} file(s).` + ); + return true; +} async function getFileDiffsWithBasehead(branches, logger) { const repositoryNwo = getRepositoryNwoFromEnv( "CODE_SCANNING_REPOSITORY", @@ -107251,6 +107284,25 @@ function userConfigFromActionPath(tempDir) { function hasQueryCustomisation(userConfig) { return isDefined2(userConfig["disable-default-queries"]) || isDefined2(userConfig.queries) || isDefined2(userConfig["query-filters"]); } +async function applyIncrementalAnalysisSettings(config, hasDiffRanges, codeql, logger) { + if (config.overlayDatabaseMode === "overlay" /* Overlay */ && !hasDiffRanges) { + logger.info( + `Reverting overlay database mode to ${"none" /* None */} because the PR diff ranges could not be computed.` + ); + config.overlayDatabaseMode = "none" /* None */; + config.useOverlayDatabaseCaching = false; + await addOverlayDisablementDiagnostics( + config, + codeql, + "pr-diff-ranges-not-computed" /* PrDiffRangesNotComputed */ + ); + } + if (hasDiffRanges) { + config.extraQueryExclusions.push({ + exclude: { tags: "exclude-from-incremental" } + }); + } +} async function initConfig(features, inputs) { const { logger, tempDir } = inputs; if (inputs.configInput) { @@ -107360,15 +107412,17 @@ async function initConfig(features, inputs) { overlayDisabledReason ); } - if (config.overlayDatabaseMode === "overlay" /* Overlay */ || await shouldPerformDiffInformedAnalysis( + const hasDiffRanges = await prepareDiffInformedAnalysis( inputs.codeql, inputs.features, logger - )) { - config.extraQueryExclusions.push({ - exclude: { tags: "exclude-from-incremental" } - }); - } + ); + await applyIncrementalAnalysisSettings( + config, + hasDiffRanges, + inputs.codeql, + logger + ); if (await isTrapCachingEnabled(features, config.overlayDatabaseMode)) { const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime( inputs.codeql, @@ -110636,7 +110690,6 @@ async function run(startedAt) { logFileCoverageOnPrsDeprecationWarning(logger); } await checkInstallPython311(config.languages, codeql); - await computeAndPersistDiffRanges(codeql, features, logger); } catch (unwrappedError) { const error3 = wrapError(unwrappedError); core15.setFailed(error3.message); @@ -110909,33 +110962,6 @@ async function loadRepositoryProperties(repositoryNwo, logger) { return new Failure(error3); } } -async function computeAndPersistDiffRanges(codeql, features, logger) { - await withGroupAsync("Computing PR diff ranges", async () => { - try { - const branches = await getDiffInformedAnalysisBranches( - codeql, - features, - logger - ); - if (!branches) { - return; - } - const ranges = await getPullRequestEditedDiffRanges(branches, logger); - if (ranges === void 0) { - return; - } - writeDiffRangesJsonFile(logger, ranges); - const distinctFiles = new Set(ranges.map((r) => r.path)).size; - logger.info( - `Persisted ${ranges.length} diff range(s) across ${distinctFiles} file(s).` - ); - } catch (e) { - logger.warning( - `Failed to compute and persist PR diff ranges: ${getErrorMessage(e)}` - ); - } - }); -} async function recordZstdAvailability(config, zstdAvailability) { addNoLanguageDiagnostic( config, diff --git a/src/config-utils.test.ts b/src/config-utils.test.ts index 25aa414334..417b61c753 100644 --- a/src/config-utils.test.ts +++ b/src/config-utils.test.ts @@ -2200,3 +2200,79 @@ test.serial( }); }, ); + +test("applyIncrementalAnalysisSettings: no-op when mode is not Overlay and diff ranges are unavailable", async (t) => { + const config = createTestConfig({}); + config.overlayDatabaseMode = OverlayDatabaseMode.None; + const codeql = createStubCodeQL({}); + const logger = getRunnerLogger(true); + + await configUtils.applyIncrementalAnalysisSettings( + config, + false, + codeql, + logger, + ); + + t.is(config.overlayDatabaseMode, OverlayDatabaseMode.None); + t.deepEqual(config.extraQueryExclusions, []); +}); + +test("applyIncrementalAnalysisSettings: keeps overlay mode and adds exclusions when diff ranges are available", async (t) => { + const config = createTestConfig({ + overlayDatabaseMode: OverlayDatabaseMode.Overlay, + }); + const codeql = createStubCodeQL({}); + const logger = getRunnerLogger(true); + + await configUtils.applyIncrementalAnalysisSettings( + config, + true, + codeql, + logger, + ); + + t.is(config.overlayDatabaseMode, OverlayDatabaseMode.Overlay); + t.deepEqual(config.extraQueryExclusions, [ + { exclude: { tags: "exclude-from-incremental" } }, + ]); +}); + +test("applyIncrementalAnalysisSettings: disables overlay analysis when diff ranges are unavailable", async (t) => { + const config = createTestConfig({ + overlayDatabaseMode: OverlayDatabaseMode.Overlay, + }); + config.useOverlayDatabaseCaching = true; + const codeql = createStubCodeQL({}); + const logger = getRunnerLogger(true); + + await configUtils.applyIncrementalAnalysisSettings( + config, + false, + codeql, + logger, + ); + + t.is(config.overlayDatabaseMode, OverlayDatabaseMode.None); + t.is(config.useOverlayDatabaseCaching, false); + t.deepEqual(config.extraQueryExclusions, []); +}); + +test("applyIncrementalAnalysisSettings: adds exclusions for diff-informed-only runs", async (t) => { + const config = createTestConfig({}); + config.overlayDatabaseMode = OverlayDatabaseMode.None; + const codeql = createStubCodeQL({}); + const logger = getRunnerLogger(true); + + await configUtils.applyIncrementalAnalysisSettings( + config, + true, + codeql, + logger, + ); + + t.is(config.overlayDatabaseMode, OverlayDatabaseMode.None); + t.deepEqual(config.extraQueryExclusions, [ + { exclude: { tags: "exclude-from-incremental" } }, + ]); +}); diff --git a/src/config-utils.ts b/src/config-utils.ts index 0b07010070..563cd31c42 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -31,7 +31,7 @@ import { addNoLanguageDiagnostic, makeTelemetryDiagnostic, } from "./diagnostics"; -import { shouldPerformDiffInformedAnalysis } from "./diff-informed-analysis-utils"; +import { prepareDiffInformedAnalysis } from "./diff-informed-analysis-utils"; import { EnvVar } from "./environment"; import * as errorMessages from "./error-messages"; import { Feature, FeatureEnablement } from "./feature-flags"; @@ -1076,6 +1076,48 @@ function hasQueryCustomisation(userConfig: UserConfig): boolean { ); } +/** + * Finalize the incremental-analysis configuration for this run. + * + * Overlay analysis has only been validated in combination with diff-informed + * analysis, so if `Overlay` mode was selected for a pull request but the diff + * ranges could not be computed, fall back to a full non-overlay analysis. + * + * Query exclusions for incremental-only queries are then applied whenever the + * diff ranges are available — which, after the fallback above, is exactly the + * set of runs where any kind of incremental analysis (overlay or + * diff-informed) is in effect. + */ +export async function applyIncrementalAnalysisSettings( + config: Config, + hasDiffRanges: boolean, + codeql: CodeQL, + logger: Logger, +): Promise { + if ( + config.overlayDatabaseMode === OverlayDatabaseMode.Overlay && + !hasDiffRanges + ) { + logger.info( + `Reverting overlay database mode to ${OverlayDatabaseMode.None} ` + + "because the PR diff ranges could not be computed.", + ); + config.overlayDatabaseMode = OverlayDatabaseMode.None; + config.useOverlayDatabaseCaching = false; + await addOverlayDisablementDiagnostics( + config, + codeql, + OverlayDisabledReason.PrDiffRangesNotComputed, + ); + } + + if (hasDiffRanges) { + config.extraQueryExclusions.push({ + exclude: { tags: "exclude-from-incremental" }, + }); + } +} + /** * Load and return the config. * @@ -1230,18 +1272,18 @@ export async function initConfig( ); } - if ( - config.overlayDatabaseMode === OverlayDatabaseMode.Overlay || - (await shouldPerformDiffInformedAnalysis( - inputs.codeql, - inputs.features, - logger, - )) - ) { - config.extraQueryExclusions.push({ - exclude: { tags: "exclude-from-incremental" }, - }); - } + const hasDiffRanges = await prepareDiffInformedAnalysis( + inputs.codeql, + inputs.features, + logger, + ); + + await applyIncrementalAnalysisSettings( + config, + hasDiffRanges, + inputs.codeql, + logger, + ); if (await isTrapCachingEnabled(features, config.overlayDatabaseMode)) { const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime( diff --git a/src/diff-informed-analysis-utils.test.ts b/src/diff-informed-analysis-utils.test.ts index bec0c2c0ab..96fce23eba 100644 --- a/src/diff-informed-analysis-utils.test.ts +++ b/src/diff-informed-analysis-utils.test.ts @@ -5,14 +5,16 @@ import * as actionsUtil from "./actions-util"; import type { PullRequestBranches } from "./actions-util"; import * as apiClient from "./api-client"; import { - shouldPerformDiffInformedAnalysis, + getDiffInformedAnalysisBranches, + prepareDiffInformedAnalysis, exportedForTesting, } from "./diff-informed-analysis-utils"; -import { Feature, initFeatures } from "./feature-flags"; +import { Feature, FeatureEnablement, initFeatures } from "./feature-flags"; import { getRunnerLogger } from "./logging"; import { parseRepositoryNwo } from "./repository"; import { setupTests, + createFeatures, mockCodeQLVersion, mockFeatureFlagApiEndpoint, setupActionsVars, @@ -80,13 +82,13 @@ const testShouldPerformDiffInformedAnalysis = test.macro({ .stub(actionsUtil, "getPullRequestBranches") .returns(testCase.pullRequestBranches); - const result = await shouldPerformDiffInformedAnalysis( + const branches = await getDiffInformedAnalysisBranches( codeql, features, logger, ); - t.is(result, expectedResult); + t.is(branches !== undefined, expectedResult); delete process.env.CODEQL_ACTION_DIFF_INFORMED_QUERIES; @@ -94,7 +96,7 @@ const testShouldPerformDiffInformedAnalysis = test.macro({ getPullRequestBranchesStub.restore(); }); }, - title: (_, title) => `shouldPerformDiffInformedAnalysis: ${title}`, + title: (_, title) => `getDiffInformedAnalysisBranches: ${title}`, }); test.serial( @@ -187,6 +189,135 @@ test.serial( false, ); +test.serial( + "prepareDiffInformedAnalysis: returns false when not a pull request", + async (t) => { + await withTmpDir(async (tmpDir) => { + setupActionsVars(tmpDir, tmpDir); + const logger = getRunnerLogger(true); + const codeql = mockCodeQLVersion("2.21.0"); + const features = createFeatures([Feature.DiffInformedQueries]); + + sinon.stub(actionsUtil, "getPullRequestBranches").returns(undefined); + sinon + .stub(apiClient, "getGitHubVersion") + .resolves({ type: GitHubVariant.DOTCOM }); + + const result = await prepareDiffInformedAnalysis( + codeql, + features, + logger, + ); + + t.false(result); + }); + }, +); + +test.serial( + "prepareDiffInformedAnalysis: returns false when applicability check throws", + async (t) => { + await withTmpDir(async (tmpDir) => { + setupActionsVars(tmpDir, tmpDir); + const logger = getRunnerLogger(true); + const codeql = mockCodeQLVersion("2.21.0"); + // A features implementation whose getValue rejects, simulating an + // unexpected failure when determining whether diff-informed analysis + // should run. + const features: FeatureEnablement = { + getDefaultCliVersion: async () => { + throw new Error("not implemented"); + }, + getValue: async () => { + throw new Error("feature flag lookup failed"); + }, + }; + + const result = await prepareDiffInformedAnalysis( + codeql, + features, + logger, + ); + + t.false(result); + }); + }, +); + +test.serial( + "prepareDiffInformedAnalysis: returns true when the diff is fetched successfully", + async (t) => { + await withTmpDir(async (tmpDir) => { + setupActionsVars(tmpDir, tmpDir); + const logger = getRunnerLogger(true); + const codeql = mockCodeQLVersion("2.21.0"); + const features = createFeatures([Feature.DiffInformedQueries]); + + sinon + .stub(actionsUtil, "getPullRequestBranches") + .returns({ base: "main", head: "feature" }); + sinon + .stub(apiClient, "getGitHubVersion") + .resolves({ type: GitHubVariant.DOTCOM }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + sinon.stub(apiClient, "getApiClient").returns({ + rest: { + repos: { + compareCommitsWithBasehead: sinon + .stub() + .resolves({ data: { files: [] } }), + }, + }, + } as any); + + const result = await prepareDiffInformedAnalysis( + codeql, + features, + logger, + ); + + t.true(result); + }); + }, +); + +test.serial( + "prepareDiffInformedAnalysis: returns false when the diff API call fails", + async (t) => { + await withTmpDir(async (tmpDir) => { + setupActionsVars(tmpDir, tmpDir); + const logger = getRunnerLogger(true); + const codeql = mockCodeQLVersion("2.21.0"); + const features = createFeatures([Feature.DiffInformedQueries]); + + sinon + .stub(actionsUtil, "getPullRequestBranches") + .returns({ base: "main", head: "feature" }); + sinon + .stub(apiClient, "getGitHubVersion") + .resolves({ type: GitHubVariant.DOTCOM }); + const notFoundError: any = new Error("Not Found"); + notFoundError.status = 404; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + sinon.stub(apiClient, "getApiClient").returns({ + rest: { + repos: { + compareCommitsWithBasehead: sinon.stub().rejects(notFoundError), + }, + }, + } as any); + + const result = await prepareDiffInformedAnalysis( + codeql, + features, + logger, + ); + + t.false(result); + }); + }, +); + function runGetDiffRanges(changes: number, patch: string[] | undefined): any { return exportedForTesting.getDiffRanges( { diff --git a/src/diff-informed-analysis-utils.ts b/src/diff-informed-analysis-utils.ts index 1c98d4caca..a48c6dcfdf 100644 --- a/src/diff-informed-analysis-utils.ts +++ b/src/diff-informed-analysis-utils.ts @@ -5,9 +5,9 @@ import type { PullRequestBranches } from "./actions-util"; import { getApiClient, getGitHubVersion } from "./api-client"; import type { CodeQL } from "./codeql"; import { Feature, FeatureEnablement } from "./feature-flags"; -import { Logger } from "./logging"; +import { Logger, withGroupAsync } from "./logging"; import { getRepositoryNwoFromEnv } from "./repository"; -import { GitHubVariant, satisfiesGHESVersion } from "./util"; +import { getErrorMessage, GitHubVariant, satisfiesGHESVersion } from "./util"; /** * This interface is an abbreviated version of the file diff object returned by @@ -21,20 +21,6 @@ interface FileDiff { patch?: string | undefined; } -/** - * Check if the action should perform diff-informed analysis. - */ -export async function shouldPerformDiffInformedAnalysis( - codeql: CodeQL, - features: FeatureEnablement, - logger: Logger, -): Promise { - return ( - (await getDiffInformedAnalysisBranches(codeql, features, logger)) !== - undefined - ); -} - /** * Get the branches to use for diff-informed analysis. * @@ -69,6 +55,46 @@ export async function getDiffInformedAnalysisBranches( return branches; } +/** + * Prepares the diff ranges needed for diff-informed analysis for the current + * run. + * + * @returns `true` if the diff ranges were successfully computed and persisted + * and are therefore available for use, `false` otherwise. + */ +export async function prepareDiffInformedAnalysis( + codeql: CodeQL, + features: FeatureEnablement, + logger: Logger, +): Promise { + let branches: PullRequestBranches | undefined; + try { + branches = await getDiffInformedAnalysisBranches(codeql, features, logger); + } catch (e) { + // If we cannot determine whether diff-informed analysis applies (for + // example, because a feature-flag lookup failed), treat it as not + // applicable rather than triggering the overlay fallback. + logger.warning( + `Failed to determine branch information for diff-informed analysis: ${getErrorMessage(e)}`, + ); + return false; + } + if (!branches) { + return false; + } + + return await withGroupAsync("Computing PR diff ranges", async () => { + try { + return await computeAndPersistDiffRanges(branches, logger); + } catch (e) { + logger.warning( + `Failed to compute diff-informed analysis ranges: ${getErrorMessage(e)}`, + ); + return false; + } + }); +} + export interface DiffThunkRange { /** Relative path from the repository root, using forward slashes as separators. */ path: string; @@ -151,6 +177,33 @@ export async function getPullRequestEditedDiffRanges( return results; } +/** + * Compute and persist the diff ranges for a pull request. This fetches the + * diff from the GitHub API and writes it to the diff ranges JSON file so that + * CodeQL can use it for diff-informed analysis. + * + * @param branches The base and head branches of the pull request, as returned + * by `getDiffInformedAnalysisBranches`. + * @param logger + * @returns `true` if the diff ranges were successfully computed and persisted, + * otherwise `false`. + */ +export async function computeAndPersistDiffRanges( + branches: PullRequestBranches, + logger: Logger, +): Promise { + const ranges = await getPullRequestEditedDiffRanges(branches, logger); + if (ranges === undefined) { + return false; + } + writeDiffRangesJsonFile(logger, ranges); + const distinctFiles = new Set(ranges.map((r) => r.path)).size; + logger.info( + `Persisted ${ranges.length} diff range(s) across ${distinctFiles} file(s).`, + ); + return true; +} + async function getFileDiffsWithBasehead( branches: PullRequestBranches, logger: Logger, diff --git a/src/init-action.ts b/src/init-action.ts index 859dcefa2c..50a66f9b01 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -37,11 +37,6 @@ import { makeDiagnostic, makeTelemetryDiagnostic, } from "./diagnostics"; -import { - getDiffInformedAnalysisBranches, - getPullRequestEditedDiffRanges, - writeDiffRangesJsonFile, -} from "./diff-informed-analysis-utils"; import { EnvVar } from "./environment"; import { Feature, FeatureEnablement, initFeatures } from "./feature-flags"; import { @@ -427,7 +422,6 @@ async function run(startedAt: Date) { } await checkInstallPython311(config.languages, codeql); - await computeAndPersistDiffRanges(codeql, features, logger); } catch (unwrappedError) { const error = wrapError(unwrappedError); core.setFailed(error.message); @@ -823,42 +817,6 @@ async function loadRepositoryProperties( } } -/** - * Compute and persist diff ranges when diff-informed analysis is enabled - * (feature flag + PR context). This writes the standard pr-diff-range.json - * file for later reuse in the analyze step. Failures are logged but non-fatal. - */ -async function computeAndPersistDiffRanges( - codeql: CodeQL, - features: FeatureEnablement, - logger: Logger, -): Promise { - await withGroupAsync("Computing PR diff ranges", async () => { - try { - const branches = await getDiffInformedAnalysisBranches( - codeql, - features, - logger, - ); - if (!branches) { - return; - } - const ranges = await getPullRequestEditedDiffRanges(branches, logger); - if (ranges === undefined) { - return; - } - writeDiffRangesJsonFile(logger, ranges); - const distinctFiles = new Set(ranges.map((r) => r.path)).size; - logger.info( - `Persisted ${ranges.length} diff range(s) across ${distinctFiles} file(s).`, - ); - } catch (e) { - logger.warning( - `Failed to compute and persist PR diff ranges: ${getErrorMessage(e)}`, - ); - } - }); -} async function recordZstdAvailability( config: configUtils.Config, zstdAvailability: ZstdAvailability, diff --git a/src/overlay/diagnostics.ts b/src/overlay/diagnostics.ts index 6bc11a73ff..d350e38f3e 100644 --- a/src/overlay/diagnostics.ts +++ b/src/overlay/diagnostics.ts @@ -39,6 +39,13 @@ export enum OverlayDisabledReason { NotPullRequestOrDefaultBranch = "not-pull-request-or-default-branch", /** The top-level overlay analysis feature flag is not enabled. */ OverallFeatureNotEnabled = "overall-feature-not-enabled", + /** + * Overlay analysis was selected for a pull request, but the PR diff ranges + * needed for diff-informed analysis could not be computed. Overlay analysis + * has only been validated in combination with diff-informed analysis, so we + * fall back to a non-overlay analysis in this case. + */ + PrDiffRangesNotComputed = "pr-diff-ranges-not-computed", /** Overlay analysis was skipped because it previously failed with similar hardware resources. */ SkippedDueToCachedStatus = "skipped-due-to-cached-status", /** Disk usage could not be determined during the overlay status check. */