@go-to-k/cdkd 0.290.8 → 0.290.9

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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $r as setAwsClients, Cr as Synthesizer, Dn as LockManager, Jr as clearBucketRegionCache, Kr as AssemblyReader, On as S3StateBackend, Qr as resetAwsClients, Si as normalizeAwsError, Un as AssetPublisher, Xr as AwsClients, Yr as resolveBucketRegion, Zr as getAwsClients, bi as formatError, cn as DagBuilder, ei as AssetError, gt as CloudControlProvider, ii as DependencyError, ln as TemplateParser, ni as ConfigError, nn as DiffCalculator, ot as ProviderRegistry, pi as ProvisioningError, r as DeployEngine, rt as IAMRoleProvider, ti as CdkdError, ui as LockError, vi as StateError, xi as isCdkdError, yi as SynthesisError } from "./deploy-engine-Bq0TJ9iN.js";
1
+ import { $r as setAwsClients, Cr as Synthesizer, Dn as LockManager, Jr as clearBucketRegionCache, Kr as AssemblyReader, On as S3StateBackend, Qr as resetAwsClients, Si as normalizeAwsError, Un as AssetPublisher, Xr as AwsClients, Yr as resolveBucketRegion, Zr as getAwsClients, bi as formatError, cn as DagBuilder, ei as AssetError, gt as CloudControlProvider, ii as DependencyError, ln as TemplateParser, ni as ConfigError, nn as DiffCalculator, ot as ProviderRegistry, pi as ProvisioningError, r as DeployEngine, rt as IAMRoleProvider, ti as CdkdError, ui as LockError, vi as StateError, xi as isCdkdError, yi as SynthesisError } from "./deploy-engine-B3yP0pHa.js";
2
2
  import { a as setLogger, n as getLogger, t as ConsoleLogger } from "./logger-H8n5Qjhq.js";
3
3
 
4
4
  export { AssemblyReader, AssetError, AssetPublisher, AwsClients, CdkdError, CloudControlProvider, ConfigError, ConsoleLogger, DagBuilder, DependencyError, DeployEngine, DiffCalculator, IAMRoleProvider, LockError, LockManager, ProviderRegistry, ProvisioningError, S3StateBackend, StateError, SynthesisError, Synthesizer, TemplateParser, clearBucketRegionCache, formatError, getAwsClients, getLogger, isCdkdError, normalizeAwsError, resetAwsClients, resolveBucketRegion, setAwsClients, setLogger };
@@ -1,8 +1,8 @@
1
- import { $ as secretBearing, $n as ensureAssetStorage, $r as setAwsClients, $t as s3BucketRegionalDomainName, A as extractDeploymentEventError, Ai as retryClassificationText, An as buildForceUnlockCommand, Ar as resolveSkipPrefix, At as parameterTypeMayLoseSecretIdentity, B as toCfnInteger, Bn as orphansAfterRollback, Br as uploadCfnTemplate, Bt as configIntegerRefusal, C as buildFinalSnapshotIdentifier, Ci as withErrorHandling, Cn as recordMaskOnlyValue, Cr as Synthesizer, Ct as IntrinsicFunctionResolver, D as refusesFinalSnapshot, Di as isTransientServerError, Dn as LockManager, Dr as resolveApp, Dt as getAccountInfo, E as isFinalSnapshotError, Ei as isThrottlingError, En as scrubResourceRecord, Er as getLegacyStateBucketName, Et as coerceParameterTypedValue, F as isStatefulRecreateTargetSync, Fn as CUSTOM_RESOURCE_RESPONSE_PREFIX, Fr as warnDeprecatedNoPrefixCliFlag, Ft as resolveExplicitPhysicalId, G as gray, Gn as WorkGraph, Gr as derivePartitionAndUrlSuffix, Gt as requireConfigObject, H as formatResourceLine, Hn as shouldRetainResource, Hr as expectedOwnerParam, Ht as readConfigString, I as renderStatefulReason, In as DEFAULT_STATE_PREFIX, Ir as CFN_TEMPLATE_BODY_LIMIT, It as assertRegionMatch, J as yellow, Jn as loadPublishableAssetManifest, Jt as producerRegionsFromState, K as green, Kn as buildAssetRedirectMap, Kr as AssemblyReader, Kt as requireConfigString, L as WARM_THROUGHPUT_MEMBERS, Ln as exportNamesCarriedFrom, Lr as CFN_TEMPLATE_URL_LIMIT, Lt as coerceCfnBoolean, M as planOrphanAdoption, Mn as forceQuitRecoveryClause, Mr as resolveStateBucketWithDefaultAndSource, Mt as isSettledInstanceState, N as MULTI_REGION_RECREATE_BLOCKED_TYPES, Nn as formatLockExpiry, Nr as resolveUseCdkBootstrapAssets, Nt as WAFv2WebACLProvider, O as unsupportedFinalSnapshotError, Oi as markNonRetryable, On as S3StateBackend, Or as resolveAutoAssetStorage, Ot as isImpossibleEmptyStoredAttribute, P as isStatefulRecreateTargetForReplace, Pn as shellQuote, Pr as stateBucketExistenceConfirmed, Pt as normalizeAwsTagsToCfn, Q as isExportAliasCollision, Qn as assertAssetBucketRegion, Qr as resetAwsClients, Qt as s3BucketDualStackDomainName, R as coerceWarmThroughput$1, Rn as importableOutputKeys, Rr as MIGRATE_TMP_PREFIX, Rt as coerceCfnInteger, S as PRE_DELETE_SNAPSHOT_TYPES, Si as normalizeAwsError, Sn as maskSecretsInText, Sr as getDockerImageBySourceHash, St as isTerminationProtectionPropagationError, T as createPreDeleteFinalSnapshot, Ti as isRetryableTransientError, Tn as redactSecretsForState, Tr as getDefaultStateBucketName, Tt as cfnRefValueFromPhysicalId, U as bold, Un as AssetPublisher, Ur as PARTITION_TABLE, Ut as replayWarn, V as toFiniteNumber, Vn as orphansCarriedFrom, Vr as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, Vt as configStringRefusal, W as cyan, Wn as stringifyValue, Wr as canonicalizeRegion, Wt as requireConfigArray, X as collectPublishedOutputNames, Xn as AssetModeResolver, Xr as AwsClients, Xt as s3BucketArn, Y as collectDeclaredOutputNames, Yn as rewriteTemplateAssetReferences, Yr as resolveBucketRegion, Yt as withSharedDrainBudget, Z as exportAliasCollisionScrubWarning, Zn as BOOTSTRAP_MARKER_PREFIX, Zr as getAwsClients, Zt as s3BucketDomainName, _ as isSecretBearingReferenceString, _i as StackTerminationProtectionError, _n as errorCauseChain, _r as runDockerForeground, _t as slowCcOperationTimeoutMs, a as DeploymentEventsStore, ai as DeployCancelledError, an as findSilentDropProperties, ar as validateContainerRepoName, at as clearOnUpdateRemoval, b as bindingSkippedOutputs, bn as markSameGenerationBag, br as stripControlChars, bt as deleteSkipReason, c as replayFailedOperations, ci as LocalInvokeBuildError$1, cn as DagBuilder, cr as buildDockerImage, ct as createMaskedRetryLogger, d as updatePartialReason, di as NestedStackChildDirectDestroyError, dn as STATE_SOURCED_CROSS_GENERATION_RULES, dr as describeDockerFailure, dt as beginCommandInterruptScope, en as s3BucketWebsiteUrl, er as getBootstrapMarkerKey, et as secretBearingStateKeyWarning, f as withResourceDeadline, fi as PartialFailureError, fn as STATE_SOURCED_READBACK_RULES, fr as dockerSpawnEnvWithSensitive, ft as endCommandInterruptScope, g as bagHoldsSecretExpression, gi as StackHasActiveImportsError, gn as dynamicReferenceTokens, gr as redactDockerArgvValues, gt as CloudControlProvider, h as computeImplicitDeleteEdges, hi as ResourceUpdateNotSupportedError, hn as createSecretMasker, hr as partitionSensitiveEnv, ht as startInterruptWatch, i as DeploymentEventsReader, in as findActionableSilentDrops, ir as validateAssetBucketName, it as collectInlinePolicyNamesManagedBySiblings, j as makeSiblingClaimReader, jn as buildLockContentionMessage, jr as resolveStateBucketWithDefault, jt as refStateLookupFromResource, k as makeCanonicalizePropertiesFn, ki as markRedactedCause, kn as rebuildClientForBucketRegion, kr as resolveCaptureObservedState, kt as isUnboundTemplateParameter, l as replayRollback, li as LocalStartServiceError, ln as TemplateParser, lr as describeDockerCapturedOutput, lt as maskDeep, m as IMPLICIT_DELETE_DEPENDENCIES, mi as ResourceTimeoutError, mn as carriesSecretMask, mr as getDockerCmd, mt as isInterruptedWaitError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as DiffCalculator, nr as parseBootstrapMarker, nt as getCurrentResourceSecrets, o as planFailedOps, oi as DynamicReferenceRegionAmbiguousError, on as describeTypeWithThrottleRetry, or as buildDenyExternalAccessPolicy, ot as ProviderRegistry, p as maskingRetryLogger, pi as ProvisioningError, pn as TEMPLATE_SOURCED_RULES, pr as formatDockerLoginError, pt as interruptWatchListenerCount, q as red, qn as createAssetRedirectResolver, qr as processStackMessages, qt as classifyReplaySecretRegion, r as DeployEngine, ri as CrossAccountSecretRefusalError, rn as INTRINSIC_KEYS, rr as readBootstrapMarkerBody, rt as IAMRoleProvider, s as planRollback, si as IntrinsicResolutionRefusalError, sn as withRetry, sr as describeAwsFailure, st as wouldReturnToSdkProvider, t as DEFAULT_RESOURCE_TIMEOUT_MS, ti as CdkdError, tn as applyRoleArnIfSet, tr as isCrossRegionRedirect, tt as secretSafeKeyDisplay, u as updatePartialMessage, un as STATE_SOURCED_BASELINE_RULES, ur as describeDockerExecFailure, ut as maskerOrIdentity, v as keptWholeReasonText, vn as identityKeyFor, vr as runDockerStreaming, vt as UNSPECIFIED_SKIP_REASON, w as ccRoutedFinalSnapshotError, wi as isMarkedNonRetryable, wn as recoverMaskedOutput, wr as synthesisStatusMessage, wt as carriesDynamicReference, x as ATOMIC_FINAL_SNAPSHOT_TYPES, xn as maskSecretsInError, xr as AssetManifestLoader, xt as disableInstanceApiTermination, y as mergeNoChangeOutputs, yn as isSingleDynamicReferenceToken, yr as escapeRegExp, yt as deleteIndeterminateGuards, z as isWarmThroughputDecrease, zn as importableOutputs, zr as findLargeInlineResources, zt as configBooleanRefusal } from "./deploy-engine-Bq0TJ9iN.js";
1
+ import { $ as secretBearing, $n as ensureAssetStorage, $r as setAwsClients, $t as s3BucketRegionalDomainName, A as extractDeploymentEventError, Ai as retryClassificationText, An as buildForceUnlockCommand, Ar as resolveSkipPrefix, At as parameterTypeMayLoseSecretIdentity, B as toCfnInteger, Bn as orphansAfterRollback, Br as uploadCfnTemplate, Bt as configIntegerRefusal, C as buildFinalSnapshotIdentifier, Ci as withErrorHandling, Cn as recordMaskOnlyValue, Cr as Synthesizer, Ct as IntrinsicFunctionResolver, D as refusesFinalSnapshot, Di as isTransientServerError, Dn as LockManager, Dr as resolveApp, Dt as getAccountInfo, E as isFinalSnapshotError, Ei as isThrottlingError, En as scrubResourceRecord, Er as getLegacyStateBucketName, Et as coerceParameterTypedValue, F as isStatefulRecreateTargetSync, Fn as CUSTOM_RESOURCE_RESPONSE_PREFIX, Fr as warnDeprecatedNoPrefixCliFlag, Ft as resolveExplicitPhysicalId, G as gray, Gn as WorkGraph, Gr as derivePartitionAndUrlSuffix, Gt as requireConfigObject, H as formatResourceLine, Hn as shouldRetainResource, Hr as expectedOwnerParam, Ht as readConfigString, I as renderStatefulReason, In as DEFAULT_STATE_PREFIX, Ir as CFN_TEMPLATE_BODY_LIMIT, It as assertRegionMatch, J as yellow, Jn as loadPublishableAssetManifest, Jt as producerRegionsFromState, K as green, Kn as buildAssetRedirectMap, Kr as AssemblyReader, Kt as requireConfigString, L as WARM_THROUGHPUT_MEMBERS, Ln as exportNamesCarriedFrom, Lr as CFN_TEMPLATE_URL_LIMIT, Lt as coerceCfnBoolean, M as planOrphanAdoption, Mn as forceQuitRecoveryClause, Mr as resolveStateBucketWithDefaultAndSource, Mt as isSettledInstanceState, N as MULTI_REGION_RECREATE_BLOCKED_TYPES, Nn as formatLockExpiry, Nr as resolveUseCdkBootstrapAssets, Nt as WAFv2WebACLProvider, O as unsupportedFinalSnapshotError, Oi as markNonRetryable, On as S3StateBackend, Or as resolveAutoAssetStorage, Ot as isImpossibleEmptyStoredAttribute, P as isStatefulRecreateTargetForReplace, Pn as shellQuote, Pr as stateBucketExistenceConfirmed, Pt as normalizeAwsTagsToCfn, Q as isExportAliasCollision, Qn as assertAssetBucketRegion, Qr as resetAwsClients, Qt as s3BucketDualStackDomainName, R as coerceWarmThroughput$1, Rn as importableOutputKeys, Rr as MIGRATE_TMP_PREFIX, Rt as coerceCfnInteger, S as PRE_DELETE_SNAPSHOT_TYPES, Si as normalizeAwsError, Sn as maskSecretsInText, Sr as getDockerImageBySourceHash, St as isTerminationProtectionPropagationError, T as createPreDeleteFinalSnapshot, Ti as isRetryableTransientError, Tn as redactSecretsForState, Tr as getDefaultStateBucketName, Tt as cfnRefValueFromPhysicalId, U as bold, Un as AssetPublisher, Ur as PARTITION_TABLE, Ut as replayWarn, V as toFiniteNumber, Vn as orphansCarriedFrom, Vr as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, Vt as configStringRefusal, W as cyan, Wn as stringifyValue, Wr as canonicalizeRegion, Wt as requireConfigArray, X as collectPublishedOutputNames, Xn as AssetModeResolver, Xr as AwsClients, Xt as s3BucketArn, Y as collectDeclaredOutputNames, Yn as rewriteTemplateAssetReferences, Yr as resolveBucketRegion, Yt as withSharedDrainBudget, Z as exportAliasCollisionScrubWarning, Zn as BOOTSTRAP_MARKER_PREFIX, Zr as getAwsClients, Zt as s3BucketDomainName, _ as isSecretBearingReferenceString, _i as StackTerminationProtectionError, _n as errorCauseChain, _r as runDockerForeground, _t as slowCcOperationTimeoutMs, a as DeploymentEventsStore, ai as DeployCancelledError, an as findSilentDropProperties, ar as validateContainerRepoName, at as clearOnUpdateRemoval, b as bindingSkippedOutputs, bn as markSameGenerationBag, br as stripControlChars, bt as deleteSkipReason, c as replayFailedOperations, ci as LocalInvokeBuildError$1, cn as DagBuilder, cr as buildDockerImage, ct as createMaskedRetryLogger, d as updatePartialReason, di as NestedStackChildDirectDestroyError, dn as STATE_SOURCED_CROSS_GENERATION_RULES, dr as describeDockerFailure, dt as beginCommandInterruptScope, en as s3BucketWebsiteUrl, er as getBootstrapMarkerKey, et as secretBearingStateKeyWarning, f as withResourceDeadline, fi as PartialFailureError, fn as STATE_SOURCED_READBACK_RULES, fr as dockerSpawnEnvWithSensitive, ft as endCommandInterruptScope, g as bagHoldsSecretExpression, gi as StackHasActiveImportsError, gn as dynamicReferenceTokens, gr as redactDockerArgvValues, gt as CloudControlProvider, h as computeImplicitDeleteEdges, hi as ResourceUpdateNotSupportedError, hn as createSecretMasker, hr as partitionSensitiveEnv, ht as startInterruptWatch, i as DeploymentEventsReader, in as findActionableSilentDrops, ir as validateAssetBucketName, it as collectInlinePolicyNamesManagedBySiblings, j as makeSiblingClaimReader, jn as buildLockContentionMessage, jr as resolveStateBucketWithDefault, jt as refStateLookupFromResource, k as makeCanonicalizePropertiesFn, ki as markRedactedCause, kn as rebuildClientForBucketRegion, kr as resolveCaptureObservedState, kt as isUnboundTemplateParameter, l as replayRollback, li as LocalStartServiceError, ln as TemplateParser, lr as describeDockerCapturedOutput, lt as maskDeep, m as IMPLICIT_DELETE_DEPENDENCIES, mi as ResourceTimeoutError, mn as carriesSecretMask, mr as getDockerCmd, mt as isInterruptedWaitError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as DiffCalculator, nr as parseBootstrapMarker, nt as getCurrentResourceSecrets, o as planFailedOps, oi as DynamicReferenceRegionAmbiguousError, on as describeTypeWithThrottleRetry, or as buildDenyExternalAccessPolicy, ot as ProviderRegistry, p as maskingRetryLogger, pi as ProvisioningError, pn as TEMPLATE_SOURCED_RULES, pr as formatDockerLoginError, pt as interruptWatchListenerCount, q as red, qn as createAssetRedirectResolver, qr as processStackMessages, qt as classifyReplaySecretRegion, r as DeployEngine, ri as CrossAccountSecretRefusalError, rn as INTRINSIC_KEYS, rr as readBootstrapMarkerBody, rt as IAMRoleProvider, s as planRollback, si as IntrinsicResolutionRefusalError, sn as withRetry, sr as describeAwsFailure, st as wouldReturnToSdkProvider, t as DEFAULT_RESOURCE_TIMEOUT_MS, ti as CdkdError, tn as applyRoleArnIfSet, tr as isCrossRegionRedirect, tt as secretSafeKeyDisplay, u as updatePartialMessage, un as STATE_SOURCED_BASELINE_RULES, ur as describeDockerExecFailure, ut as maskerOrIdentity, v as keptWholeReasonText, vn as identityKeyFor, vr as runDockerStreaming, vt as UNSPECIFIED_SKIP_REASON, w as ccRoutedFinalSnapshotError, wi as isMarkedNonRetryable, wn as recoverMaskedOutput, wr as synthesisStatusMessage, wt as carriesDynamicReference, x as ATOMIC_FINAL_SNAPSHOT_TYPES, xn as maskSecretsInError, xr as AssetManifestLoader, xt as disableInstanceApiTermination, y as mergeNoChangeOutputs, yn as isSingleDynamicReferenceToken, yr as escapeRegExp, yt as deleteIndeterminateGuards, z as isWarmThroughputDecrease, zn as importableOutputs, zr as findLargeInlineResources, zt as configBooleanRefusal } from "./deploy-engine-B3yP0pHa.js";
2
2
  import { C as truncateCodePoints, S as displaySafe, _ as withSkipPrefix, b as UNRENDERABLE, c as PATTERN_B_NAME_OPTIONS, f as explicitNamePropertyFor, h as getCurrentStackName, i as reserveStdoutForPayload, l as PATTERN_B_NAME_PROPERTIES, m as generateResourceNameWithFallback, n as getLogger, o as runStackBuffered, p as generateResourceName, s as getLiveRenderer, u as PATTERN_B_RESOURCE_TYPES, v as withStackName, x as displayIdent, y as STACK_REF_MAX_CODE_POINTS } from "./logger-H8n5Qjhq.js";
3
3
  import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
4
- import { t as getCdkdVersion } from "./version-CFdjI1Tq.js";
5
- import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-B4m3magB.js";
4
+ import { t as getCdkdVersion } from "./version-wXpog0Ci.js";
5
+ import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-BTsQjH_E.js";
6
6
  import { AsyncLocalStorage } from "node:async_hooks";
7
7
  import { createHash, randomBytes, randomUUID } from "node:crypto";
8
8
  import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketEncryptionCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketOwnershipControlsCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, NotFound, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
@@ -67048,6 +67048,7 @@ async function scrubCommand(stacks, options) {
67048
67048
  let totalStacksScrubbed = 0;
67049
67049
  let totalStacksWithUnscrubbableKeys = 0;
67050
67050
  let totalStacksWithUnverifiableReads = 0;
67051
+ let totalStacksWithUnverifiableLeaves = 0;
67051
67052
  /**
67052
67053
  * Stacks this `--dry-run` proceeded over with an UNREADABLE resources map
67053
67054
  * (issue go-to-k/cdkd#3018). Tracked exactly like `indexUnreadable`: an
@@ -67129,8 +67130,12 @@ async function scrubCommand(stacks, options) {
67129
67130
  if (scrubbed.recordsChanged > 0) {
67130
67131
  totalStacksScrubbed++;
67131
67132
  logger.info(`${options.dryRun ? "Would scrub" : "Scrubbed"} ${scrubbed.recordsChanged} resource record(s) in ${stack.stackName}`);
67132
- } else if (scrubbed.secretBearingKeys === 0 && scrubbed.unverifiableReads === 0 && indexConverged === 0) logger.info(`No plaintext secrets found in ${stack.stackName}`);
67133
+ } else if (scrubbed.secretBearingKeys === 0 && scrubbed.unverifiableReads === 0 && scrubbed.unverifiableLeaves === 0 && indexConverged === 0) logger.info(`No plaintext secrets found in ${stack.stackName}`);
67133
67134
  if (scrubbed.malformedResources) malformedRecords.push(stack.stackName);
67135
+ if (scrubbed.unverifiableLeaves > 0) {
67136
+ totalStacksWithUnverifiableLeaves++;
67137
+ logger.warn(`${scrubbed.unverifiableLeaves} scan(s) in ${stack.stackName} were ABANDONED mid-value because a {{resolve:...}} reference could not be resolved (see the warnings above). The resolver stops at the first failing token, so a real secret after it in the same value recorded no needle — this stack is not reported clean. The count is per abandoned SCAN, not per secret: one record can contribute more than once (an output whose Export.Name and Value both abandon), and one abandoned scan can hide several unexamined leaves.`);
67138
+ }
67134
67139
  if (scrubbed.unverifiableReads > 0) {
67135
67140
  totalStacksWithUnverifiableReads++;
67136
67141
  logger.warn(`${scrubbed.unverifiableReads} cross-stack read(s) in ${stack.stackName} could NOT be verified — cdkd declines them by design (see the warnings above), so this stack is not reported clean.`);
@@ -67161,7 +67166,7 @@ async function scrubCommand(stacks, options) {
67161
67166
  totalIndexEntriesUnexamined += unexamined.length;
67162
67167
  logger.info(`Exports index (${indexRegion}): ${unexamined.length} entr${unexamined.length === 1 ? "y is" : "ies are"} published by a producer this run did not scrub, so nothing here read ${unexamined.length === 1 ? "its" : "their"} value. Coverage composes across the apps sharing this bucket and region — run each app's own cdkd scrub to reach its entries.`);
67163
67168
  }
67164
- if (totalStacksScrubbed === 0 && totalStacksWithUnscrubbableKeys === 0 && totalStacksWithUnverifiableReads === 0 && totalIndexEntriesConverged === 0 && indexUnwritten.length === 0 && indexUnreadable.length === 0 && malformedRecords.length === 0) {
67169
+ if (totalStacksScrubbed === 0 && totalStacksWithUnscrubbableKeys === 0 && totalStacksWithUnverifiableReads === 0 && totalStacksWithUnverifiableLeaves === 0 && totalIndexEntriesConverged === 0 && indexUnwritten.length === 0 && indexUnreadable.length === 0 && malformedRecords.length === 0) {
67165
67170
  if (failures.length === 0) {
67166
67171
  logger.info("\nNo plaintext secrets found in any target stack state. Nothing to scrub.");
67167
67172
  return;
@@ -67171,6 +67176,7 @@ async function scrubCommand(stacks, options) {
67171
67176
  }
67172
67177
  const failureNote = failures.length > 0 ? ` ${failures.length} stack(s) could NOT be scrubbed at all and are NOT covered by this summary — see the errors above.` : "";
67173
67178
  const keyNote = totalStacksWithUnscrubbableKeys > 0 ? ` ${totalStacksWithUnscrubbableKeys} stack(s) hold plaintext in an output KEY, which cdkd scrub cannot rewrite — rename that output's Export.Name and redeploy.` : "";
67179
+ const leafNote = totalStacksWithUnverifiableLeaves > 0 ? ` ${totalStacksWithUnverifiableLeaves} stack(s) had a {{resolve:...}} scan ABANDONED mid-value, so part of their state was never examined (see the warnings above).` : "";
67174
67180
  const unverifiableNote = totalStacksWithUnverifiableReads > 0 ? ` ${totalStacksWithUnverifiableReads} stack(s) carry a cross-stack read cdkd declines to perform, so their imported values could NOT be checked — export a non-secret value (e.g. the secret's ARN) from the producer, or reference it from within its own account.` : "";
67175
67181
  const indexNote = (() => {
67176
67182
  const parts = [];
@@ -67180,20 +67186,20 @@ async function scrubCommand(stacks, options) {
67180
67186
  return parts.join("");
67181
67187
  })();
67182
67188
  if (options.dryRun) {
67183
- if (totalStacksScrubbed > 0) logger.info(`\nPlan: ${totalStacksScrubbed} stack(s) hold plaintext secrets and would be scrubbed (--dry-run, no state written).${keyNote}${unverifiableNote}${indexNote}${failureNote} ROTATE any exposed secret in Secrets Manager.`);
67184
- else logger.info(`\nPlan: no state record can be rewritten.${keyNote}${unverifiableNote}${indexNote}${failureNote} ROTATE any exposed secret.`);
67189
+ if (totalStacksScrubbed > 0) logger.info(`\nPlan: ${totalStacksScrubbed} stack(s) hold plaintext secrets and would be scrubbed (--dry-run, no state written).${keyNote}${unverifiableNote}${leafNote}${indexNote}${failureNote} ROTATE any exposed secret in Secrets Manager.`);
67190
+ else logger.info(`\nPlan: no state record can be rewritten.${keyNote}${unverifiableNote}${leafNote}${indexNote}${failureNote} ROTATE any exposed secret.`);
67185
67191
  if (failures.length > 0) throw scrubStacksFailedError(failures);
67186
67192
  if (indexUnwritten.length > 0 || indexUnreadable.length > 0) throw exportIndexIncompleteError(indexUnwritten, indexUnreadable, true);
67187
67193
  if (malformedRecords.length > 0) throw malformedRecordsAuditedError(malformedRecords);
67188
67194
  if (options.fail) throw new ScrubNeededError();
67189
67195
  return;
67190
67196
  }
67191
- if (totalStacksScrubbed > 0) logger.info(`\nDone: scrubbed ${totalStacksScrubbed} stack(s). The CURRENT state.json no longer holds the plaintext. Where the state bucket is VERSIONED — which cdkd bootstrap enables — the pre-scrub body survives as a noncurrent version, stays readable with GetObject and a VersionId, and scrub does not purge it. So a value that was ever persisted must be treated as compromised — ROTATE it in Secrets Manager (scrub matches the current value, so scrub BEFORE rotating); rotation is what makes any surviving version harmless.${keyNote}${unverifiableNote}${indexNote}${failureNote}`);
67192
- else logger.info(`\nNo state record was rewritten.${keyNote}${unverifiableNote}${indexNote}${failureNote} ROTATE any exposed secret.`);
67197
+ if (totalStacksScrubbed > 0) logger.info(`\nDone: scrubbed ${totalStacksScrubbed} stack(s). The CURRENT state.json no longer holds the plaintext. Where the state bucket is VERSIONED — which cdkd bootstrap enables — the pre-scrub body survives as a noncurrent version, stays readable with GetObject and a VersionId, and scrub does not purge it. So a value that was ever persisted must be treated as compromised — ROTATE it in Secrets Manager (scrub matches the current value, so scrub BEFORE rotating); rotation is what makes any surviving version harmless.${keyNote}${unverifiableNote}${leafNote}${indexNote}${failureNote}`);
67198
+ else logger.info(`\nNo state record was rewritten.${keyNote}${unverifiableNote}${leafNote}${indexNote}${failureNote} ROTATE any exposed secret.`);
67193
67199
  if (failures.length > 0) throw scrubStacksFailedError(failures);
67194
67200
  if (indexUnwritten.length > 0 || indexUnreadable.length > 0) throw exportIndexIncompleteError(indexUnwritten, indexUnreadable, false);
67195
67201
  if (malformedRecords.length > 0) throw malformedRecordsAuditedError(malformedRecords);
67196
- if (options.fail && (totalStacksWithUnscrubbableKeys > 0 || totalStacksWithUnverifiableReads > 0)) throw new ScrubNeededError();
67202
+ if (options.fail && (totalStacksWithUnscrubbableKeys > 0 || totalStacksWithUnverifiableReads > 0 || totalStacksWithUnverifiableLeaves > 0)) throw new ScrubNeededError();
67197
67203
  }
67198
67204
  /**
67199
67205
  * Every secret plaintext this run recorded, from ANY position in the template
@@ -68477,6 +68483,205 @@ function isNamelessDynamicReferenceFailure(err) {
68477
68483
  return err instanceof Error && errorCauseChain(err).some((link) => link instanceof Error && link.message.includes(DYNAMIC_REFERENCE_PREFIX) && NAMELESS_DYNAMIC_REFERENCE_MARKERS.some((marker) => link.message.includes(marker)));
68478
68484
  }
68479
68485
  /**
68486
+ * The per-record line each counting site prints. Deliberately ONE SHORT
68487
+ * SENTENCE, and that is a fix rather than a style choice: the first cut emitted
68488
+ * a ~440-character paragraph PER RECORD at default verbosity, and the arm it
68489
+ * fires on is one a single `Default`-less parameter triggers for every resource
68490
+ * in the stack at once. Dozens of identical paragraphs bury the record names,
68491
+ * which are the only part a reader cannot reconstruct.
68492
+ *
68493
+ * The explanation is emitted ONCE PER STACK instead, by
68494
+ * {@link abandonedScanStackNote}.
68495
+ *
68496
+ * The CAUSE is absent on both arms: the sibling log at each site already prints
68497
+ * it through `maskSecretsInText`, and a resolver error echoes what it was
68498
+ * handed — which, after `pinCrossRegionSecrets`, can be a foreign plaintext.
68499
+ *
68500
+ * RESIDUAL on the `subject` the call sites pass. They compose
68501
+ * `maskSecretsInText(... displaySafe(id) ...)` — sanitise, then mask — and
68502
+ * `displaySafe` REPLACES a control character with a space rather than deleting
68503
+ * it, so it cannot JOIN a split needle (the go-to-k/cdkd#2874 direction) but it
68504
+ * can BREAK one, and it leaves `U+200B`-`U+200D` / `U+FEFF` untouched. A secret
68505
+ * would have to sit inside a template logical id or Outputs key to be reachable,
68506
+ * which is why `secretSafeKeyDisplay` is not used here — that one is for RESOLVED
68507
+ * state-bag keys. Recorded rather than closed: go-to-k/cdkd#3178 security review.
68508
+ */
68509
+ function abandonedScanWarning(subject, verdict) {
68510
+ return verdict === "count" ? `The {{resolve:...}} scan of ${subject} was ABANDONED — this record is NOT certified clean.` : `The {{resolve:...}} scan of ${subject} was cut short by a TEMPLATE problem — this record is NOT certified clean.`;
68511
+ }
68512
+ /**
68513
+ * The one-per-stack explanation behind {@link abandonedScanWarning}'s lines.
68514
+ *
68515
+ * Two arms, because they need opposite things said. The `count` arm is a
68516
+ * FINDING and gates; the reader needs the cause, which is verbose-only. The
68517
+ * `warn` arm does NOT gate, and a reader seeing "NOT certified clean" beside a
68518
+ * green exit has no way to tell a passing run from a broken one unless it says
68519
+ * so.
68520
+ *
68521
+ * The `warn` arm's remedy is deliberately two-sided. The dominant population is
68522
+ * a parameter with no `Default` — legal CloudFormation, no defect to fix — so
68523
+ * telling the reader only to "fix the template reference" describes a repair
68524
+ * that does not exist for them. It names what they CAN do and then says plainly
68525
+ * that scrub may simply not be able to certify the record.
68526
+ */
68527
+ function abandonedScanStackNote(verdict, records) {
68528
+ const subject = `${records} record(s) above`;
68529
+ return verdict === "count" ? `${subject}: the resolver stops at the first {{resolve:...}} token it cannot fetch, so any secret after it in the same value recorded no needle and could not be rewritten. Re-run with --verbose for the cause of each.` : `${subject}: the scan stopped on something cdkd scrub cannot resolve with template defaults alone — an unresolvable Ref or Fn::GetAtt, a parameter with no Default, or a reference whose own argument still holds an unsubstituted \${...}. These do NOT fail --fail, because scrub takes no --parameters and a gate failure here could not be cleared. Give the parameter a Default, or resolve the reference, to let scrub certify these records; otherwise accept that it cannot.`;
68530
+ }
68531
+ /**
68532
+ * The verdict a counting site acts on: `count` (a finding — warn AND raise the
68533
+ * exit code), `warn` (say it, but do not gate) or `silent`.
68534
+ *
68535
+ * The middle arm exists because the EXCLUSION has a cost in
68536
+ * go-to-k/cdkd#3160's OWN harm direction, and two round-4 reviewers found it
68537
+ * independently. `resolver.resolve` walks the whole properties bag and ONE
68538
+ * throw aborts the rest of it — so a bag holding `{A: {Ref: NoSuchThing},
68539
+ * B: '{{resolve:secretsmanager:prod/db:...}}'}` has `B` abandoned by a failure
68540
+ * that is nothing to do with `B`. Excluding it means `B` records no needle, its
68541
+ * legacy plaintext is never rewritten, and the stack can still print
68542
+ * `No plaintext secrets found` at exit 0. Reachable accidentally (one
68543
+ * `Default`-less parameter empties the bag, so every `{Ref: <param>}` throws)
68544
+ * and, on a repo whose CI runs `--dry-run --fail`, defeatable on purpose by
68545
+ * adding one dangling `Ref` ahead of the secret. That loss is REAL and is not
68546
+ * repaired by making it visible — go-to-k/cdkd#3196 tracks scoping the resolve
68547
+ * per property so the sibling reference is still scanned.
68548
+ *
68549
+ * Counting it is not the answer — that is the round-2 blocker, an unclearable
68550
+ * red gate on a healthy stack. What the two failure modes do NOT share is
68551
+ * VISIBILITY: an operator can act on a warning naming the record, and a warning
68552
+ * gates nothing. So the excluded arm still speaks, and only the exit code is
68553
+ * withheld.
68554
+ *
68555
+ * **The ORDER of the three tests is load-bearing, and getting it wrong is how
68556
+ * round 5 nearly shipped a silent regression.** An earlier cut asked
68557
+ * `carriesFetchableDynamicReference` FIRST and returned `silent` on a miss,
68558
+ * which made a gate-downgrade test into a universal silencer. Because
68559
+ * `DYNAMIC_REFERENCE_INNER_CHAR` is `[^}]`, a token with a placeholder
68560
+ * ANYWHERE in it fails that test — including the dominant CDK spelling, where
68561
+ * the reference is assembled by an `Fn::Sub` over parameters that DO have
68562
+ * defaults:
68563
+ *
68564
+ * ```json
68565
+ * {"A": {"Fn::Sub": "{{resolve:ssm-secure:/deleted/${Env}/p}}"},
68566
+ * "B": {"Fn::Sub": "{{resolve:secretsmanager:${Db}:SecretString:password}}"}}
68567
+ * ```
68568
+ *
68569
+ * Those assemble fine, `A` raises a RAW `ParameterNotFound`, `B` is abandoned
68570
+ * and records no needle — and the earlier order printed nothing at all. That is
68571
+ * go-to-k/cdkd#3160's own headline repro, silenced by the fix for a different
68572
+ * false positive.
68573
+ *
68574
+ * So the tests are separated by what each one is EVIDENCE of:
68575
+ *
68576
+ * - `carriesDynamicReference` — did this bag hold a reference at all? If not,
68577
+ * nothing was lost and the pre-existing `logger.debug` is the right level.
68578
+ * This, and only this, decides SILENCE.
68579
+ * - `isTemplateShapeResolutionFailure` / `carriesFetchableDynamicReference` —
68580
+ * is the abandonment one an operator could act on? If not, it still gets said;
68581
+ * it just does not gate. These decide the EXIT CODE, never the visibility.
68582
+ */
68583
+ function abandonedScanVerdict(source, err) {
68584
+ if (!carriesDynamicReference(source)) return "silent";
68585
+ if (isTemplateShapeResolutionFailure(err) || !carriesFetchableDynamicReference(source)) return "warn";
68586
+ return "count";
68587
+ }
68588
+ /**
68589
+ * `source` carries a `{{resolve:...}}` token cdkd could actually have FETCHED —
68590
+ * i.e. one whose argument holds no surviving `${...}` placeholder.
68591
+ *
68592
+ * `carriesDynamicReference` alone is too generous here, and the gap is not
68593
+ * theoretical: scrub resolves with `bestEffort`, under which
68594
+ * `rethrowStructuralSubFailure` returns EARLY, so an `Fn::Sub` over an unbound
68595
+ * placeholder does not throw — it warn-and-KEEPS the literal `${Field}`. The
68596
+ * assembled token is then a request for a JSON key literally named `${Field}`,
68597
+ * which fails as `Dynamic reference: key '${Field}' not found in secret '<id>'`.
68598
+ * That is not a fetch that failed; it is a token that was never fetchable,
68599
+ * because scrub has only template DEFAULTS and accepts no `--parameters`. It is
68600
+ * ALSO not excludable by message, since `key ... not found` is a class
68601
+ * go-to-k/cdkd#3160 asks to COUNT when the key was real. So the discriminator
68602
+ * has to be the TOKEN, not the error.
68603
+ *
68604
+ * `some`, not `every`: a bag holding one placeholder-bearing token and one
68605
+ * fully substituted token still counts, because the second one genuinely was
68606
+ * fetchable and its scan genuinely stopped.
68607
+ *
68608
+ * WHAT THIS DOES AND DOES NOT BUY, since round 5 shipped a cut that confused
68609
+ * the two. A `false` here downgrades the GATE only — the record is still named
68610
+ * at default verbosity by the `warn` arm. It must never decide SILENCE: the
68611
+ * inner class is `[^}]`, so a placeholder ANYWHERE in a token fails this test,
68612
+ * and the reference assembled by an `Fn::Sub` over DEFAULTED parameters is the
68613
+ * dominant CDK spelling rather than an edge case.
68614
+ *
68615
+ * STATED RESIDUALS, both UNDER-counts against the gate, neither a silence:
68616
+ *
68617
+ * - A two-argument `Fn::Sub` whose placeholder IS bound
68618
+ * (`{'Fn::Sub': ['{{resolve:ssm:${N}}}', {N: 'x'}]}`) substitutes fine and
68619
+ * yields a fetchable token, but the raw bag still spells `${`, so a real
68620
+ * fetch failure there warns instead of gating.
68621
+ * - The same for a single-argument `Fn::Sub` over a defaulted parameter, and
68622
+ * for a placeholder mid-token, which yields no token at all.
68623
+ *
68624
+ * And one OVER-count, recorded for symmetry: this predicate is BAG-scoped while
68625
+ * the failure is TOKEN-scoped, so a bag holding a placeholder-bearing token
68626
+ * BESIDE a genuinely fetchable one returns `true`, and a `key '${Field}' not
68627
+ * found` raised for the first is gated as if it came from the second. Safety
68628
+ * is in the right direction (a gate that fires, not a plaintext under `clean`),
68629
+ * and same-bag co-location bounds it.
68630
+ *
68631
+ * go-to-k/cdkd#3181 is the fix that removes the need for any of these proxies.
68632
+ */
68633
+ function carriesFetchableDynamicReference(source) {
68634
+ if (typeof source === "string") return dynamicReferenceTokens(source).some((token) => !token.includes("${"));
68635
+ if (Array.isArray(source)) return source.some(carriesFetchableDynamicReference);
68636
+ if (source !== null && typeof source === "object") return Object.values(source).some(carriesFetchableDynamicReference);
68637
+ return false;
68638
+ }
68639
+ /**
68640
+ * `err` is cdkd's OWN refusal to resolve a template SHAPE — a `Ref`, an
68641
+ * `Fn::GetAtt`, a parameter binding — rather than a failure to fetch a
68642
+ * `{{resolve:...}}` reference.
68643
+ *
68644
+ * Matched on cdkd-authored prose, which is sound here for the reason the
68645
+ * inverse is not: every string below is built by `intrinsic-function-resolver.ts`
68646
+ * itself, so it changes only when this repo changes it, and the fence in
68647
+ * `tests/unit/cli/scrub-abandoned-scan-origin.test.ts` reds when a throw site
68648
+ * there stops matching. An AWS-authored message could never carry that
68649
+ * guarantee.
68650
+ */
68651
+ function isTemplateShapeResolutionFailure(err) {
68652
+ if (!(err instanceof Error)) return false;
68653
+ return TEMPLATE_SHAPE_FAILURE_PATTERNS.some((pattern) => pattern.test(err.message));
68654
+ }
68655
+ /**
68656
+ * The cdkd-authored messages {@link isTemplateShapeResolutionFailure} excludes,
68657
+ * ANCHORED. A substring test cannot be used here and the reason is concrete:
68658
+ * `'Parameter '` alone also matches the SDK's own `Parameter /deleted not
68659
+ * found.` — `ParameterNotFound`, this issue's HEADLINE repro — so the loose
68660
+ * spelling would have excluded the very case the positional half exists to
68661
+ * catch. Each pattern must stay tight enough that no AWS-authored message can
68662
+ * satisfy it.
68663
+ *
68664
+ * Deliberately just these THREE, not every shape failure the resolver can
68665
+ * raise. They are the ones that fire EN MASSE on a healthy stack:
68666
+ * `resolveParameters` is caught wholesale by `scrubStack`, so one
68667
+ * `Default`-less parameter empties the whole bag and every `{Ref: <param>}`
68668
+ * throws; and `resolveGetAtt` refuses on the same condition as `resolveRef`,
68669
+ * which a branch adding a not-yet-deployed resource hits for every
68670
+ * `Fn::GetAtt` to it. A rarer shape failure (an `Fn::Select` over a
68671
+ * non-array, say) is a genuine template defect, and counting a leaf whose
68672
+ * scan it abandoned is not wrong — the scan really did stop. Over-counting
68673
+ * THERE costs a warn on a broken template; over-counting on the three below
68674
+ * would cost a red CI gate on a working one.
68675
+ *
68676
+ * Per-pattern throw sites, and the fence that keeps them true, are in
68677
+ * `tests/unit/cli/scrub-abandoned-scan-origin.test.ts`.
68678
+ */
68679
+ const TEMPLATE_SHAPE_FAILURE_PATTERNS = [
68680
+ /^Ref \S+ not found$/,
68681
+ /^Resource \S+ not found for Fn::GetAtt$/,
68682
+ /^Parameter \S+ is required but no value was provided/
68683
+ ];
68684
+ /**
68480
68685
  * A secrets map with its OWN identity whose entries ARE `target`'s (issue
68481
68686
  * #2531): every read and write goes to `target`; nothing is stored here.
68482
68687
  *
@@ -68907,6 +69112,33 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
68907
69112
  */
68908
69113
  const orphanSecrets = /* @__PURE__ */ new Map();
68909
69114
  const prePassFindings = { unverifiable: [] };
69115
+ /**
69116
+ * Leaves whose dynamic-reference scan was ABANDONED mid-token (issue
69117
+ * go-to-k/cdkd#3160). A counted finding, not a refusal — see
69118
+ * `abandonedScanVerdict` for why these cannot refuse.
69119
+ */
69120
+ let unverifiableLeaves = 0;
69121
+ let ungateableAbandonedScans = 0;
69122
+ /**
69123
+ * ONE note per stack per arm. The per-record lines are deliberately short
69124
+ * (see `abandonedScanWarning`), so this is where the explanation lives, and
69125
+ * emitting it after the loops rather than at the first occurrence is what
69126
+ * lets it say HOW MANY records it is explaining.
69127
+ *
69128
+ * A CLOSURE called at BOTH returns, not a block at the late one. The early
69129
+ * `totalSecrets === 0` return is the DOMINANT path for an abandoned scan --
69130
+ * an abandoned scan records no needle, which is the whole defect -- so a
69131
+ * single late emission left the note unreachable for exactly the case it
69132
+ * exists to explain. `emitted` because the two call sites are exclusive
69133
+ * today and a third would not be.
69134
+ */
69135
+ let notesEmitted = false;
69136
+ const emitAbandonedScanNotes = () => {
69137
+ if (notesEmitted) return;
69138
+ notesEmitted = true;
69139
+ if (unverifiableLeaves > 0) logger.warn(abandonedScanStackNote("count", unverifiableLeaves));
69140
+ if (ungateableAbandonedScans > 0) logger.warn(abandonedScanStackNote("warn", ungateableAbandonedScans));
69141
+ };
68910
69142
  try {
68911
69143
  const loaded = await stateBackend.getState(stack.stackName, region);
68912
69144
  if (!loaded) {
@@ -68916,6 +69148,7 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
68916
69148
  secretsFound: 0,
68917
69149
  secretBearingKeys: 0,
68918
69150
  unverifiableReads: 0,
69151
+ unverifiableLeaves: 0,
68919
69152
  exportNameDisplay: (name) => secretSafeKeyDisplay(name, outputSecrets)
68920
69153
  };
68921
69154
  }
@@ -68989,6 +69222,12 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
68989
69222
  await resolver.resolve(resolveInput, resourceContext);
68990
69223
  } catch (err) {
68991
69224
  if (isRegionAmbiguousRefusal(err) || isNamelessDynamicReferenceFailure(err)) throw err;
69225
+ const leafVerdict = abandonedScanVerdict(resolveInput, err);
69226
+ if (leafVerdict !== "silent") {
69227
+ if (leafVerdict === "count") unverifiableLeaves++;
69228
+ else ungateableAbandonedScans++;
69229
+ logger.warn(maskSecretsInText(abandonedScanWarning(`resource '${displaySafe(logicalId)}'`, leafVerdict), recordedSecretValues));
69230
+ }
68992
69231
  logger.debug(`Resolution of ${logicalId} during scrub was partial: ${maskSecretsInText(err instanceof Error ? err.message : String(err), recordedSecretValues)}`);
68993
69232
  }
68994
69233
  perResourceTemplateProps.set(logicalId, templateResource.Properties);
@@ -69011,6 +69250,12 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
69011
69250
  await resolver.resolve(resolveInput, resolverContext(recordedSecretValues));
69012
69251
  } catch (err) {
69013
69252
  if (isRegionAmbiguousRefusal(err) || isNamelessDynamicReferenceFailure(err)) throw err;
69253
+ const leafVerdict = abandonedScanVerdict(resolveInput, err);
69254
+ if (leafVerdict !== "silent") {
69255
+ if (leafVerdict === "count") unverifiableLeaves++;
69256
+ else ungateableAbandonedScans++;
69257
+ logger.warn(maskSecretsInText(abandonedScanWarning(`orphan record '${displaySafe(record.logicalId)}'`, leafVerdict), recordedSecretValues));
69258
+ }
69014
69259
  logger.debug(`Resolution of orphan record ${record.logicalId} during scrub was partial: ${maskSecretsInText(err instanceof Error ? err.message : String(err), recordedSecretValues)}`);
69015
69260
  }
69016
69261
  }
@@ -69034,6 +69279,12 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
69034
69279
  exportName = await resolver.resolve(nameSource, nameContext);
69035
69280
  } catch (err) {
69036
69281
  if (isRegionAmbiguousRefusal(err) || isNamelessDynamicReferenceFailure(err)) throw err;
69282
+ const leafVerdict = abandonedScanVerdict(nameSource, err);
69283
+ if (leafVerdict !== "silent") {
69284
+ if (leafVerdict === "count") unverifiableLeaves++;
69285
+ else ungateableAbandonedScans++;
69286
+ logger.warn(maskSecretsInText(abandonedScanWarning(`the Export.Name of output '${displaySafe(name)}'`, leafVerdict), outputSecrets));
69287
+ }
69037
69288
  nameFailed = true;
69038
69289
  nameError = err;
69039
69290
  }
@@ -69072,6 +69323,12 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
69072
69323
  await resolver.resolve(valueSource, valueContext);
69073
69324
  } catch (err) {
69074
69325
  if (isRegionAmbiguousRefusal(err) || isNamelessDynamicReferenceFailure(err)) throw err;
69326
+ const leafVerdict = abandonedScanVerdict(valueSource, err);
69327
+ if (leafVerdict !== "silent") {
69328
+ if (leafVerdict === "count") unverifiableLeaves++;
69329
+ else ungateableAbandonedScans++;
69330
+ logger.warn(maskSecretsInText(abandonedScanWarning(`output '${displaySafe(name)}'`, leafVerdict), outputSecrets));
69331
+ }
69075
69332
  logger.debug(`Resolution of output ${name} during scrub was partial: ${maskSecretsInText(err instanceof Error ? err.message : String(err), outputSecrets)}`);
69076
69333
  }
69077
69334
  }
@@ -69085,15 +69342,19 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
69085
69342
  logger.warn(secretBearingStateKeyWarning(stack.stackName, display));
69086
69343
  }
69087
69344
  const totalSecrets = outputSecrets.size + [...perResourceSecrets.values()].reduce((n, m) => n + m.size, 0) + [...orphanSecrets.values()].reduce((n, m) => n + m.size, 0);
69088
- if (totalSecrets === 0) return {
69089
- recordsChanged: 0,
69090
- secretsFound: 0,
69091
- secretBearingKeys: secretBearingKeys.length,
69092
- unverifiableReads: prePassFindings.unverifiable.length,
69093
- ...malformedResources ? { malformedResources } : {},
69094
- outputs: state.outputs,
69095
- exportNameDisplay: (name) => secretSafeKeyDisplay(name, outputSecrets)
69096
- };
69345
+ if (totalSecrets === 0) {
69346
+ emitAbandonedScanNotes();
69347
+ return {
69348
+ recordsChanged: 0,
69349
+ secretsFound: 0,
69350
+ secretBearingKeys: secretBearingKeys.length,
69351
+ unverifiableReads: prePassFindings.unverifiable.length,
69352
+ unverifiableLeaves,
69353
+ ...malformedResources ? { malformedResources } : {},
69354
+ outputs: state.outputs,
69355
+ exportNameDisplay: (name) => secretSafeKeyDisplay(name, outputSecrets)
69356
+ };
69357
+ }
69097
69358
  let recordsChanged = 0;
69098
69359
  const newResources = {};
69099
69360
  for (const [logicalId, record] of Object.entries(state.resources ?? {})) {
@@ -69122,6 +69383,7 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
69122
69383
  };
69123
69384
  });
69124
69385
  const newOutputs = redactUnaccountedOutputs(outputSecrets.size > 0 ? redactSecretsForState(state.outputs, outputSecrets, outputsSourceUntrusted ? void 0 : outputsTemplateSource, TEMPLATE_SOURCED_RULES) : state.outputs, state.outputs, accountedOutputKeys, allRecordedSecrets(outputSecrets, perResourceSecrets, orphanSecrets));
69386
+ emitAbandonedScanNotes();
69125
69387
  if (JSON.stringify(newOutputs) !== JSON.stringify(state.outputs)) recordsChanged++;
69126
69388
  if (recordsChanged > 0 && !opts.dryRun) {
69127
69389
  const { skippedOutputs: _droppedByScrub, ...carriedState } = state;
@@ -69139,6 +69401,7 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
69139
69401
  secretsFound: totalSecrets,
69140
69402
  secretBearingKeys: secretBearingKeys.length,
69141
69403
  unverifiableReads: prePassFindings.unverifiable.length,
69404
+ unverifiableLeaves,
69142
69405
  ...malformedResources ? { malformedResources } : {},
69143
69406
  outputs: newOutputs,
69144
69407
  exportNameDisplay: (name) => secretSafeKeyDisplay(name, outputSecrets)
@@ -93473,4 +93736,4 @@ function buildProgram() {
93473
93736
 
93474
93737
  //#endregion
93475
93738
  export { buildProgram };
93476
- //# sourceMappingURL=program-C7UAuWDp.js.map
93739
+ //# sourceMappingURL=program-BslZXdJR.js.map