@go-to-k/cdkd 0.291.8 → 0.291.10

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.
@@ -1,8 +1,8 @@
1
- import { $ as yellow, $a as retryClassificationText, $i as expectedOwnerParam, $n as repairMalformedResourcePropertiesForReadOnly, $r as rewriteTemplateAssetReferences, $t as requireConfigObject, A as createPreDeleteFinalSnapshot, Aa as LocalInvokeBuildError$1, Ai as getDefaultStateBucketName, An as malformedOutputsRefusalMessage, Ar as buildForceUnlockCommand, At as carriesDynamicReference, B as isStatefulRecreateTargetSync, Ba as StackTerminationProtectionError, Bi as warnDeprecatedNoPrefixCliFlag, Bn as refuseMalformedOrphansForDestroy, Br as hasUnverifiableParameterRefusal, Bt as isSettledInstanceState, C as namesAnyDeclaredParameter, Ca as CdkdError, Ci as runDockerStreaming, Cn as isReadableResourceEntry, Cr as recoverMaskedOutput, Ct as slowCcOperationTimeoutMs, D as PRE_DELETE_SNAPSHOT_TYPES, Da as DeployCancelledError, Di as getDockerImageBySourceHash, Dn as malformedOrphanRecordsWarning, Dr as S3StateBackend, Dt as disableInstanceApiTermination, E as ATOMIC_FINAL_SNAPSHOT_TYPES, Ei as AssetManifestLoader, En as malformedLocalOutputsWarning, Er as LockManager, Et as deleteSkipReason, F as extractDeploymentEventError, Fa as PartialFailureError, Fi as resolveSkipPrefix, Fn as malformedResourcesWarning, Fr as CUSTOM_RESOURCE_RESPONSE_PREFIX, Ft as isImpossibleEmptyStoredAttribute, G as toCfnInteger, Ga as normalizeAwsError, Gi as findLargeInlineResources, Gn as refuseMalformedResourceEntries, Gr as orphansCarriedFrom, Gt as coerceCfnBoolean, H as WARM_THROUGHPUT_MEMBERS, Ha as SynthesisError, Hi as CFN_TEMPLATE_BODY_LIMIT, Hn as refuseMalformedOutputs, Hr as importableOutputs, Ht as normalizeAwsTagsToCfn, I as makeSiblingClaimReader, Ia as ProvisioningError, Ii as resolveStateBucketWithDefault, In as malformedStateRefusalMessage, Ir as DEFAULT_STATE_PREFIX, It as isNamelessDynamicReferenceError, J as bold, Ja as isRetryableTransientError, Ji as injectiveKey, Jn as refuseMalformedResourcesForDestroy, Jr as stringifyValue, Jt as configIntegerRefusal, K as toFiniteNumber, Ka as withErrorHandling, Ki as uploadCfnTemplate, Kn as refuseMalformedResourceEntriesForOrphan, Kr as shouldRetainResource, Kt as coerceCfnInteger, L as planOrphanAdoption, La as ResourceTimeoutError, Li as resolveStateBucketWithDefaultAndSource, Ln as refuseDivergentRecordRegionForDestroy, Lr as exportNamesCarriedFrom, Lt as isUnboundTemplateParameter, M as refusesFinalSnapshot, Mi as resolveApp, Mn as malformedRenderedContainersWarning, Mr as forceQuitRecoveryClause, Mt as cfnRefValueFromPhysicalId, N as unsupportedFinalSnapshotError, Na as MalformedProducerRecordRefusalError, Ni as resolveAutoAssetStorage, Nn as malformedResourceEntriesWarning, Nr as formatLockExpiry, Nt as coerceParameterTypedValue, O as buildFinalSnapshotIdentifier, Oa as DynamicReferenceRegionAmbiguousError, Oi as Synthesizer, On as malformedOrphansRefusalMessage, Or as rebuildClientForBucketRegion, Ot as isTerminationProtectionPropagationError, P as makeCanonicalizePropertiesFn, Pa as NestedStackChildDirectDestroyError, Pi as resolveCaptureObservedState, Pn as malformedResourcePropertiesWarning, Pr as shellQuote, Pt as getAccountInfo, Q as red, Qa as markRedactedCause, Qi as decodeListingKey, Qn as repairMalformedResourceEntriesForReadOnly, Qr as loadPublishableAssetManifest, Qt as requireConfigArray, R as MULTI_REGION_RECREATE_BLOCKED_TYPES, Ra as ResourceUpdateNotSupportedError, Ri as resolveUseCdkBootstrapAssets, Rn as refuseMalformedNestedChildOutputs, Rr as hasReadableExportSet, Rt as parameterTypeMayLoseSecretIdentity, S as computeParameterTaint, Si as runDockerForeground, Sn as hasReadableResources, Sr as recordMaskOnlyValue, St as isWaitAbandonedError, T as resourcesNamingDeclaredParameter, Ta as CrossAccountSecretRefusalError, Ti as stripControlChars, Tn as malformedExportSourceWarning, Tr as scrubResourceRecord, Tt as deleteIndeterminateGuards, U as coerceWarmThroughput$1, Ui as CFN_TEMPLATE_URL_LIMIT, Un as refuseMalformedOutputsForDestroy, Ur as isReadableBag, Ut as resolveExplicitPhysicalId, V as renderStatefulReason, Vi as ssmResolvedValueType, Vn as refuseMalformedOrphansForOrphan, Vr as importableOutputKeys, Vt as WAFv2WebACLProvider, W as isWarmThroughputDecrease, Wi as MIGRATE_TMP_PREFIX, Wn as refuseMalformedResourceAttributesForOrphan, Wr as orphansAfterRollback, Wt as assertRegionMatch, X as gray, Xa as isTransientServerError, Xi as producerCoordinateKey, Xn as repairMalformedOrphansForReadOnly, Xr as buildAssetRedirectMap, Xt as readConfigString, Y as cyan, Ya as isThrottlingError, Yi as injectiveKeyPrefix, Yn as refuseMalformedState, Yr as WorkGraph, Yt as configStringRefusal, Z as green, Za as markNonRetryable, Zi as producerRecordKey, Zn as repairMalformedOutputsForReadOnly, Zr as createAssetRedirectResolver, Zt as replayWarn, _ as bagHoldsSecretExpression, _a as AwsClients, _i as dockerSpawnEnvWithSensitive, _n as UNREADABLE_ORPHANS_CONTAINER_ROW, _r as identityKeyFor, _t as endCommandInterruptScope, a as DeploymentEventsStore, aa as globMatches, ai as isCrossRegionRedirect, an as s3BucketDomainName, ar as describeTypeWithThrottleRetry, at as secretBearingStateKeyWarning, b as mergeNoChangeOutputs, ba as runWithStackAwsClients, bi as partitionSensitiveEnv, bn as hasReadableOrphans, br as maskSecretsInError, bt as startInterruptWatch, c as replayFailedOperations, ca as hasOwnKey, ci as validateAssetBucketName, cn as s3BucketWebsiteUrl, cr as TemplateParser, ct as IAMRoleProvider, d as updatePartialMessage, da as ownValue, di as buildDockerImage, dn as applyCallerIdentityCredentials, dr as STATE_SOURCED_READBACK_RULES, dt as ProviderRegistry, ea as PARTITION_TABLE, ei as AssetModeResolver, en as requireConfigString, eo as describeAwsFailure, er as repairMalformedResourcesForReadOnly, et as collectDeclaredOutputNames, f as updatePartialReason, fa as absoluteAssemblyPathEscape, fi as resolveDockerContextDirectory, fn as callerEnvCredentials, fr as TEMPLATE_SOURCED_RULES, ft as wouldReturnToSdkProvider, g as computeImplicitDeleteEdges, ga as resolveBucketRegion, gi as describeDockerFailure, gn as STATE_RESOURCES_MALFORMED, gr as errorCauseChain, gt as beginCommandInterruptScope, h as IMPLICIT_DELETE_DEPENDENCIES, hi as describeDockerExecFailure, hn as INTRINSIC_KEYS, hr as dynamicReferenceTokens, ht as maskerOrIdentity, i as DeploymentEventsReader, ia as failedStageNote, ii as getBootstrapMarkerKey, in as s3BucketArn, ir as findSilentDropProperties, it as secretBearing, j as isFinalSnapshotError, ja as LocalStartServiceError, ji as getLegacyStateBucketName, jn as malformedOutputsWarning, jr as buildLockContentionMessage, jt as carriesFetchableDynamicReference, k as ccRoutedFinalSnapshotError, ka as IntrinsicResolutionRefusalError, ki as synthesisStatusMessage, kn as malformedOrphansWarning, kr as UNREPRODUCIBLE_LOCK_CLAUSE, kt as IntrinsicFunctionResolver, l as replayRollback, la as hasPlainPrototype, li as validateContainerRepoName, ln as applyRoleArnIfSet, lr as STATE_SOURCED_BASELINE_RULES, lt as collectInlinePolicyNamesManagedBySiblings, m as maskingRetryLogger, ma as resolveAssemblyPath, mi as describeDockerCapturedOutput, mn as DiffCalculator, mr as createSecretMasker, mt as maskDeep, n as DEFAULT_RESOURCE_WARN_AFTER_MS, na as derivePartitionAndUrlSuffix, ni as assertAssetBucketRegion, nn as producerRegionsFromState, nr as safeStackName, nt as exportAliasCollisionScrubWarning, o as planFailedOps, oa as processStackMessages, oi as parseBootstrapMarker, on as s3BucketDualStackDomainName, or as withRetry, ot as secretSafeKeyDisplay, p as withResourceDeadline, pa as renderAssemblyPathEscape, pi as warnManifestExecutable, pn as envCredentialsAreAssumedRole, pr as carriesSecretMask, pt as createMaskedRetryLogger, q as formatResourceLine, qa as isMarkedNonRetryable, qi as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, qn as refuseMalformedResourcePropertiesForOrphan, qr as AssetPublisher, qt as configBooleanRefusal, r as DeployEngine, ra as AssemblyReader, ri as ensureAssetStorage, rn as withSharedDrainBudget, rr as findActionableSilentDrops, rt as isExportAliasCollision, s as planRollback, sa as defineOwnKey, si as readBootstrapMarkerBody, sn as s3BucketRegionalDomainName, sr as DagBuilder, st as getCurrentResourceSecrets, t as DEFAULT_RESOURCE_TIMEOUT_MS, ta as canonicalizeRegion, ti as BOOTSTRAP_MARKER_PREFIX, tn as classifyReplaySecretRegion, to as safeStringify, tr as safeRegion, tt as collectPublishedOutputNames, u as resolveReplacementOldType, ua as nullPrototypeRecord, ui as buildDenyExternalAccessPolicy, un as AWS_CREDENTIAL_ENV_KEYS, ur as STATE_SOURCED_CROSS_GENERATION_RULES, ut as clearOnUpdateRemoval, v as isSecretBearingReferenceString, va as getAwsClients, vi as formatDockerLoginError, vn as UNREADABLE_RESOURCES_MAP_ROW, vr as isSingleDynamicReferenceToken, vt as interruptWatchListenerCount, w as reachableDivergentParameters, wi as escapeRegExp, wn as malformedExportNamesWarning, wr as redactSecretsForState, wt as UNSPECIFIED_SKIP_REASON, x as bindingSkippedOutputs, xa as setAwsClients, xi as redactDockerArgvValues, xn as hasReadableOutputs, xr as maskSecretsInText, xt as CloudControlProvider, y as keptWholeReasonText, ya as resetAwsClients, yi as getDockerCmd, yn as displayLogicalId, yr as markSameGenerationBag, yt as isInterruptedWaitError, z as isStatefulRecreateTargetForReplace, za as StackHasActiveImportsError, zi as stateBucketExistenceConfirmed, zn as refuseMalformedOrphans, zr as hasReasonlessBaselineRefusal, zt as refStateLookupFromResource } from "./deploy-engine-eLdrQQ2R.js";
1
+ import { $ as yellow, $a as markNonRetryable, $i as expectedOwnerParam, $n as repairMalformedResourcePropertiesForReadOnly, $r as AssetModeResolver, $t as requireConfigObject, A as createPreDeleteFinalSnapshot, Aa as LocalInvokeBuildError$1, Ai as getDefaultStateBucketName, An as malformedOutputsRefusalMessage, Ar as buildForceUnlockCommand, At as carriesDynamicReference, B as isStatefulRecreateTargetSync, Ba as StackTerminationProtectionError, Bi as warnDeprecatedNoPrefixCliFlag, Bn as refuseMalformedOrphansForDestroy, Br as importableOutputKeys, Bt as isSettledInstanceState, C as namesAnyDeclaredParameter, Ca as CdkdError, Ci as runDockerStreaming, Cn as isReadableResourceEntry, Cr as recoverMaskedOutput, Ct as UNSPECIFIED_SKIP_REASON, D as PRE_DELETE_SNAPSHOT_TYPES, Da as DeployCancelledError, Di as getDockerImageBySourceHash, Dn as malformedOrphanRecordsWarning, Dr as S3StateBackend, Dt as disableInstanceApiTermination, E as ATOMIC_FINAL_SNAPSHOT_TYPES, Ei as AssetManifestLoader, En as malformedLocalOutputsWarning, Er as LockManager, Et as isWaitAbandonedError, F as extractDeploymentEventError, Fa as PartialFailureError, Fi as resolveSkipPrefix, Fn as malformedResourcesWarning, Fr as DEFAULT_STATE_PREFIX, Ft as isImpossibleEmptyStoredAttribute, G as toCfnInteger, Ga as normalizeAwsError, Gi as findLargeInlineResources, Gn as refuseMalformedResourceEntries, Gr as shouldRetainResource, Gt as coerceCfnBoolean, H as WARM_THROUGHPUT_MEMBERS, Ha as SynthesisError, Hi as CFN_TEMPLATE_BODY_LIMIT, Hn as refuseMalformedOutputs, Hr as isReadableBag, Ht as normalizeAwsTagsToCfn, I as makeSiblingClaimReader, Ia as ProvisioningError, Ii as resolveStateBucketWithDefault, In as malformedStateRefusalMessage, Ir as exportNamesCarriedFrom, It as isNamelessDynamicReferenceError, J as bold, Ja as shellQuote, Ji as injectiveKey, Jn as refuseMalformedResourcesForDestroy, Jr as WorkGraph, Jt as configIntegerRefusal, K as toFiniteNumber, Ka as withErrorHandling, Ki as uploadCfnTemplate, Kn as refuseMalformedResourceEntriesForOrphan, Kr as AssetPublisher, Kt as coerceCfnInteger, L as planOrphanAdoption, La as ResourceTimeoutError, Li as resolveStateBucketWithDefaultAndSource, Ln as refuseDivergentRecordRegionForDestroy, Lr as hasReadableExportSet, Lt as isUnboundTemplateParameter, M as refusesFinalSnapshot, Mi as resolveApp, Mn as malformedRenderedContainersWarning, Mr as forceQuitRecoveryClause, Mt as cfnRefValueFromPhysicalId, N as unsupportedFinalSnapshotError, Na as MalformedProducerRecordRefusalError, Ni as resolveAutoAssetStorage, Nn as malformedResourceEntriesWarning, Nr as formatLockExpiry, Nt as coerceParameterTypedValue, O as buildFinalSnapshotIdentifier, Oa as DynamicReferenceRegionAmbiguousError, Oi as Synthesizer, On as malformedOrphansRefusalMessage, Or as rebuildClientForBucketRegion, Ot as isTerminationProtectionPropagationError, P as makeCanonicalizePropertiesFn, Pa as NestedStackChildDirectDestroyError, Pi as resolveCaptureObservedState, Pn as malformedResourcePropertiesWarning, Pr as CUSTOM_RESOURCE_RESPONSE_PREFIX, Pt as getAccountInfo, Q as red, Qa as isTransientServerError, Qi as decodeListingKey, Qn as repairMalformedResourceEntriesForReadOnly, Qr as rewriteTemplateAssetReferences, Qt as requireConfigArray, R as MULTI_REGION_RECREATE_BLOCKED_TYPES, Ra as ResourceUpdateNotSupportedError, Ri as resolveUseCdkBootstrapAssets, Rn as refuseMalformedNestedChildOutputs, Rr as hasReasonlessBaselineRefusal, Rt as parameterTypeMayLoseSecretIdentity, S as computeParameterTaint, Si as runDockerForeground, Sn as hasReadableResources, Sr as recordMaskOnlyValue, St as slowCcOperationTimeoutMs, T as resourcesNamingDeclaredParameter, Ta as CrossAccountSecretRefusalError, Ti as stripControlChars, Tn as malformedExportSourceWarning, Tr as scrubResourceRecord, Tt as deleteSkipReason, U as coerceWarmThroughput$1, Ui as CFN_TEMPLATE_URL_LIMIT, Un as refuseMalformedOutputsForDestroy, Ur as orphansAfterRollback, Ut as resolveExplicitPhysicalId, V as renderStatefulReason, Vi as ssmResolvedValueType, Vn as refuseMalformedOrphansForOrphan, Vr as importableOutputs, Vt as WAFv2WebACLProvider, W as isWarmThroughputDecrease, Wi as MIGRATE_TMP_PREFIX, Wn as refuseMalformedResourceAttributesForOrphan, Wr as orphansCarriedFrom, Wt as assertRegionMatch, X as gray, Xa as isRetryableTransientError, Xi as producerCoordinateKey, Xn as repairMalformedOrphansForReadOnly, Xr as createAssetRedirectResolver, Xt as readConfigString, Y as cyan, Ya as isMarkedNonRetryable, Yi as injectiveKeyPrefix, Yn as refuseMalformedState, Yr as buildAssetRedirectMap, Yt as configStringRefusal, Z as green, Za as isThrottlingError, Zi as producerRecordKey, Zn as repairMalformedOutputsForReadOnly, Zr as loadPublishableAssetManifest, Zt as replayWarn, _ as bagHoldsSecretExpression, _a as AwsClients, _i as dockerSpawnEnvWithSensitive, _n as UNREADABLE_ORPHANS_CONTAINER_ROW, _r as identityKeyFor, _t as endCommandInterruptScope, a as DeploymentEventsStore, aa as globMatches, ai as parseBootstrapMarker, an as s3BucketDomainName, ar as describeTypeWithThrottleRetry, at as secretBearingStateKeyWarning, b as mergeNoChangeOutputs, ba as runWithStackAwsClients, bi as partitionSensitiveEnv, bn as hasReadableOrphans, br as maskSecretsInError, bt as startInterruptWatch, c as replayFailedOperations, ca as hasOwnKey, ci as validateContainerRepoName, cn as s3BucketWebsiteUrl, cr as TemplateParser, ct as IAMRoleProvider, d as updatePartialMessage, da as ownValue, di as buildDockerImage, dn as applyCallerIdentityCredentials, dr as STATE_SOURCED_READBACK_RULES, dt as ProviderRegistry, ea as PARTITION_TABLE, ei as BOOTSTRAP_MARKER_PREFIX, en as requireConfigString, eo as markRedactedCause, er as repairMalformedResourcesForReadOnly, et as collectDeclaredOutputNames, f as updatePartialReason, fa as absoluteAssemblyPathEscape, fi as resolveDockerContextDirectory, fn as callerEnvCredentials, fr as TEMPLATE_SOURCED_RULES, ft as wouldReturnToSdkProvider, g as computeImplicitDeleteEdges, ga as resolveBucketRegion, gi as describeDockerFailure, gn as STATE_RESOURCES_MALFORMED, gr as errorCauseChain, gt as beginCommandInterruptScope, h as IMPLICIT_DELETE_DEPENDENCIES, hi as describeDockerExecFailure, hn as INTRINSIC_KEYS, hr as dynamicReferenceTokens, ht as maskerOrIdentity, i as DeploymentEventsReader, ia as failedStageNote, ii as isCrossRegionRedirect, in as s3BucketArn, ir as findSilentDropProperties, it as secretBearing, j as isFinalSnapshotError, ja as LocalStartServiceError, ji as getLegacyStateBucketName, jn as malformedOutputsWarning, jr as buildLockContentionMessage, jt as carriesFetchableDynamicReference, k as ccRoutedFinalSnapshotError, ka as IntrinsicResolutionRefusalError, ki as synthesisStatusMessage, kn as malformedOrphansWarning, kr as UNREPRODUCIBLE_LOCK_CLAUSE, kt as IntrinsicFunctionResolver, l as replayRollback, la as hasPlainPrototype, li as buildDenyExternalAccessPolicy, ln as applyRoleArnIfSet, lr as STATE_SOURCED_BASELINE_RULES, lt as collectInlinePolicyNamesManagedBySiblings, m as maskingRetryLogger, ma as resolveAssemblyPath, mi as describeDockerCapturedOutput, mn as DiffCalculator, mr as createSecretMasker, mt as maskDeep, n as DEFAULT_RESOURCE_WARN_AFTER_MS, na as derivePartitionAndUrlSuffix, ni as ensureAssetStorage, nn as producerRegionsFromState, no as describeAwsFailure, nr as safeStackName, nt as exportAliasCollisionScrubWarning, o as planFailedOps, oa as processStackMessages, oi as readBootstrapMarkerBody, on as s3BucketDualStackDomainName, or as withRetry, ot as secretSafeKeyDisplay, p as withResourceDeadline, pa as renderAssemblyPathEscape, pi as warnManifestExecutable, pn as envCredentialsAreAssumedRole, pr as carriesSecretMask, pt as createMaskedRetryLogger, q as formatResourceLine, qa as pasteableCommand, qi as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, qn as refuseMalformedResourcePropertiesForOrphan, qr as stringifyValue, qt as configBooleanRefusal, r as DeployEngine, ra as AssemblyReader, ri as getBootstrapMarkerKey, rn as withSharedDrainBudget, ro as safeStringify, rr as findActionableSilentDrops, rt as isExportAliasCollision, s as planRollback, sa as defineOwnKey, si as validateAssetBucketName, sn as s3BucketRegionalDomainName, sr as DagBuilder, st as getCurrentResourceSecrets, t as DEFAULT_RESOURCE_TIMEOUT_MS, ta as canonicalizeRegion, ti as assertAssetBucketRegion, tn as classifyReplaySecretRegion, to as retryClassificationText, tr as safeRegion, tt as collectPublishedOutputNames, u as resolveReplacementOldType, ua as nullPrototypeRecord, ui as assertCdkLocalDockerContextContained, un as AWS_CREDENTIAL_ENV_KEYS, ur as STATE_SOURCED_CROSS_GENERATION_RULES, ut as clearOnUpdateRemoval, v as isSecretBearingReferenceString, va as getAwsClients, vi as formatDockerLoginError, vn as UNREADABLE_RESOURCES_MAP_ROW, vr as isSingleDynamicReferenceToken, vt as interruptWatchListenerCount, w as reachableDivergentParameters, wi as escapeRegExp, wn as malformedExportNamesWarning, wr as redactSecretsForState, wt as deleteIndeterminateGuards, x as bindingSkippedOutputs, xa as setAwsClients, xi as redactDockerArgvValues, xn as hasReadableOutputs, xr as maskSecretsInText, xt as CloudControlProvider, y as keptWholeReasonText, ya as resetAwsClients, yi as getDockerCmd, yn as displayLogicalId, yr as markSameGenerationBag, yt as isInterruptedWaitError, z as isStatefulRecreateTargetForReplace, za as StackHasActiveImportsError, zi as stateBucketExistenceConfirmed, zn as refuseMalformedOrphans, zr as hasUnverifiableParameterRefusal, zt as refStateLookupFromResource } from "./deploy-engine-BomFOZF4.js";
2
2
  import { C as displayAwsMessage, D as truncateCodePoints, E as isPasteableIdent, S as UNRENDERABLE, T as displaySafe, _ as withSkipPrefix, b as SECRET_REF_MAX_CODE_POINTS, 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, w as displayIdent, x as STACK_REF_MAX_CODE_POINTS } from "./logger-GASr9bAF.js";
3
3
  import { n as awsClientDefaults, s as ambientRegion } from "./aws-client-defaults-jISU4PHo.js";
4
- import { t as getCdkdVersion } from "./version-D_XcHZoy.js";
5
- import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-B3RJYZnx.js";
4
+ import { t as getCdkdVersion } from "./version-5OUy_OlQ.js";
5
+ import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-BY-tj21g.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";
@@ -1759,10 +1759,18 @@ async function bootstrapDestroyCommand(options) {
1759
1759
  if (markerBody === null) {
1760
1760
  const probed = rawMarkerKey === markerKey ? markerKey : `${markerKey}, ${rawMarkerKey}`;
1761
1761
  logger.info(`No bootstrap marker for region '${region}' (${probed}) — asset storage is not opted in for this region (or was already destroyed); nothing to delete.`);
1762
- if (markerSiblings.sameRegionKeys.length > 0) for (const siblingKey of markerSiblings.sameRegionKeys) logger.warn(`A bootstrap marker for this region DOES exist at '${siblingKey}', under a different spelling of the region name — its asset bucket and ECR repository are still alive. Re-run 'cdkd bootstrap --destroy --region ${markerRegionOfKey(siblingKey)}' to tear it down.`);
1762
+ if (markerSiblings.sameRegionKeys.length > 0) for (const siblingKey of markerSiblings.sameRegionKeys) logger.warn(`A bootstrap marker for this region DOES exist at '${siblingKey}', under a different spelling of the region name — its asset bucket and ECR repository are still alive.\nTear it down with: ${pasteableCommand("cdkd bootstrap", [{ literal: "--destroy" }, {
1763
+ flag: "--region",
1764
+ value: markerRegionOfKey(siblingKey),
1765
+ hole: "region"
1766
+ }]).command}`);
1763
1767
  if (!options.includeStateBucket) {
1764
1768
  if (!markerListingSucceeded) logger.warn(`The bootstrap-marker listing could not be made, so it is NOT known whether this region has a marker under another spelling of its name. Check 's3://${bucketName}/${BOOTSTRAP_MARKER_PREFIX}' before re-bootstrapping — re-running 'cdkd bootstrap' while one exists writes a SECOND marker with DEFAULT names, and the next destroy would then tear down that storage while the storage the existing marker names survives.`);
1765
- else if (markerSiblings.sameRegionKeys.length === 0) logger.info(`If the asset bucket / ECR repo still exist without a marker, re-run 'cdkd bootstrap --region ${region}' to recreate the marker, then destroy again.`);
1769
+ else if (markerSiblings.sameRegionKeys.length === 0) logger.info(`If the asset bucket / ECR repo still exist without a marker, recreate the marker and destroy again.\nRecreate it with: ${pasteableCommand("cdkd bootstrap", [{
1770
+ flag: "--region",
1771
+ value: region,
1772
+ hole: "region"
1773
+ }]).command}`);
1766
1774
  return;
1767
1775
  }
1768
1776
  } else marker = parseBootstrapMarker(markerBody, resolvedMarkerKey);
@@ -1780,9 +1788,14 @@ async function bootstrapDestroyCommand(options) {
1780
1788
  if (markerSiblings.otherRegions.length > 0) throw new CdkdError(`Refusing to delete state bucket '${bucketName}': region(s) ${markerSiblings.otherRegions.map((r) => displayIdent(r)).join(", ")} are still opted in to cdkd asset storage (their bootstrap markers live in this bucket). Run 'cdkd bootstrap --destroy --region <r>' for each first.`, "STATE_BUCKET_HOLDS_MARKERS");
1781
1789
  if (markerSiblings.sameRegionKeys.length > 0) {
1782
1790
  const canonicalStillPresent = markerBody !== null && resolvedMarkerKey === markerKey;
1783
- const listing = markerSiblings.sameRegionKeys.map((k) => canonicalStillPresent ? ` - ${k}` : ` - ${k} (cdkd bootstrap --destroy --region ${markerRegionOfKey(k)})`).join("\n");
1784
- const remedy = canonicalStillPresent ? `Run 'cdkd bootstrap --destroy --region ${region}' WITHOUT --include-state-bucket ONCE: it tears down '${resolvedMarkerKey}' and then prints the exact --region spelling for each marker still standing. Follow those, then re-run with --include-state-bucket. (No per-marker spelling is given here because '${resolvedMarkerKey}' still exists and would be resolved first — and simply repeating this same command would not clear the rest, since once it is gone this region's canonical key no longer resolves anything.)` : "Run the command shown against each marker above, then re-run with --include-state-bucket.";
1785
- throw new CdkdError(`Refusing to delete state bucket '${bucketName}': region '${region}' has ${markerSiblings.sameRegionKeys.length} further bootstrap marker(s) under a different spelling of its name, which this teardown does not delete:\n${listing}\n${remedy}\nDestroy each one first — deleting the state bucket now would remove those markers while the asset storage they name survives, with no record of its names.`, "STATE_BUCKET_HOLDS_SIBLING_MARKERS");
1791
+ const listing = markerSiblings.sameRegionKeys.map((k, i) => ` ${i + 1}. ${k}`).join("\n");
1792
+ const teardownCommands = canonicalStillPresent ? "" : markerSiblings.sameRegionKeys.map((k, i) => `\n${i + 1}. Tear it down with: ${pasteableCommand("cdkd bootstrap", [{ literal: "--destroy" }, {
1793
+ flag: "--region",
1794
+ value: markerRegionOfKey(k),
1795
+ hole: "region"
1796
+ }]).command}`).join("");
1797
+ const remedy = canonicalStillPresent ? `Re-run the destroy for this region WITHOUT --include-state-bucket ONCE: it tears down '${resolvedMarkerKey}' and then prints the exact --region spelling for each marker still standing. Follow those, then re-run with --include-state-bucket. (No per-marker spelling is given here because '${resolvedMarkerKey}' still exists and would be resolved first — and simply repeating this same command would not clear the rest, since once it is gone this region's canonical key no longer resolves anything.)` : `Run each command below, then re-run with --include-state-bucket.`;
1798
+ throw new CdkdError(`Refusing to delete state bucket '${bucketName}': region '${region}' has ${markerSiblings.sameRegionKeys.length} further bootstrap marker(s) under a different spelling of its name, which this teardown does not delete:\n${listing}\n${remedy}\nDestroy each one first — deleting the state bucket now would remove those markers while the asset storage they name survives, with no record of its names.` + teardownCommands, "STATE_BUCKET_HOLDS_SIBLING_MARKERS");
1786
1799
  }
1787
1800
  }
1788
1801
  const planLines = [];
@@ -1808,7 +1821,11 @@ async function bootstrapDestroyCommand(options) {
1808
1821
  await stateBackend.purgeNoncurrentVersions([resolvedMarkerKey], { objectDescription: "the bootstrap marker, which names this region's asset bucket and container-asset ECR repository" });
1809
1822
  }
1810
1823
  logger.info(`✓ Deleted bootstrap marker (${resolvedMarkerKey})`);
1811
- for (const siblingKey of markerSiblings.sameRegionKeys) logger.warn(`A further bootstrap marker for this region remains at '${siblingKey}' (this region was bootstrapped under more than one spelling of its name). It names asset storage that was NOT destroyed by this run — re-run 'cdkd bootstrap --destroy --region ${markerRegionOfKey(siblingKey)}' to tear that one down too.`);
1824
+ for (const siblingKey of markerSiblings.sameRegionKeys) logger.warn(`A further bootstrap marker for this region remains at '${siblingKey}' (this region was bootstrapped under more than one spelling of its name). It names asset storage that was NOT destroyed by this run.\nTear it down with: ${pasteableCommand("cdkd bootstrap", [{ literal: "--destroy" }, {
1825
+ flag: "--region",
1826
+ value: markerRegionOfKey(siblingKey),
1827
+ hole: "region"
1828
+ }]).command}`);
1812
1829
  if (!markerListingSucceeded) logger.warn(`The bootstrap-marker listing could not be made, so a marker under another spelling of this region may still be live. Check 's3://${bucketName}/${BOOTSTRAP_MARKER_PREFIX}'.`);
1813
1830
  logger.info(`\ncdkd asset storage is now OFF for region ${region}: future deploys in this region fall back to legacy mode (CDK bootstrap destinations) unless the region is bootstrapped again.`);
1814
1831
  }
@@ -7346,86 +7363,41 @@ function isPlainObject$2(value) {
7346
7363
  return typeof value === "object" && value !== null && !Array.isArray(value);
7347
7364
  }
7348
7365
  /**
7349
- * Is this `Destination` block written in the CFn FLATTENED form
7350
- * (`{ BucketArn, Format, ... }`) rather than the SDK NESTED one
7351
- * (`{ S3BucketDestination: { ... } }`)?
7352
- *
7353
- * ONE predicate, called from both `resolveS3BucketDestination` (which picks the
7354
- * bag) and `s3BucketDestinationPath` (which names it). They were written out
7355
- * twice and a reviewer rightly pointed out that two copies of a branch
7356
- * condition must be edited together or a refusal starts naming the wrong key.
7357
- *
7358
- * `Bucket` is probed alongside `BucketArn` because the readers accept it
7359
- * (`s3Dest['BucketArn'] ?? s3Dest['Bucket']`); omitting it made a
7360
- * `{ Bucket }`-only block take the nested branch, find nothing, and drop — the
7361
- * same silent drop one shape over.
7362
- */
7363
- function isFlattenedDestination(dest) {
7364
- return Boolean(dest["BucketArn"] || dest["Bucket"] || dest["BucketAccountId"] || dest["Format"]);
7365
- }
7366
- /**
7367
- * The CFn path of the destination bag `resolveS3BucketDestination` will pick —
7368
- * issue #1493 item 3, which is a MESSAGE concern, not a value one.
7369
- *
7370
- * Kept separate from the resolver rather than returned alongside the bag:
7371
- * `gen-nested-key-coverage`'s write-evidence walk follows a plain
7372
- * `const x = this.helper(...)` binding to the members written beneath it, and
7373
- * a DESTRUCTURED `const { bag, path } = ...` is a shape it cannot follow — so
7374
- * pairing the two silently withdrew the write evidence for
7375
- * `Destination.BucketArn` / `.BucketAccountId` and turned an opted-in target
7376
- * red with no behavior change at all. Measured against the pre-change provider
7377
- * via the critic's `--providers-dir=` seam.
7378
- */
7379
- function s3BucketDestinationPath(dest, destinationPath) {
7380
- return isPlainObject$2(dest) && isFlattenedDestination(dest) ? destinationPath : `${destinationPath}.S3BucketDestination`;
7381
- }
7382
- /**
7383
- * The FLATTENED CFn `Destination` block cdkd actually SENDS for `declared`
7384
- * (issue #1707) — the shared fold behind BOTH halves of the #1633 pair: the
7385
- * per-item `effectiveProperties` record and
7366
+ * The CFn `Destination` block cdkd actually SENDS for `declared` (issue #1707) —
7367
+ * the shared fold behind BOTH halves of the #1633 pair: the per-item
7368
+ * `effectiveProperties` record and
7386
7369
  * {@link S3BucketProvider.canonicalizeDesiredProperties}.
7387
7370
  *
7388
- * The desired side accepts TWO spellings (`resolveS3BucketDestination` picks
7389
- * either the flattened CFn block or the nested SDK `S3BucketDestination` one)
7390
- * plus a `Bucket` / `BucketArn` alias inside either, while the readback
7391
- * (`inventorySdkToCfn` / `analyticsSdkToCfn`) emits ONLY the flattened form with
7392
- * `BucketArn`. So a record written in any other spelling can never match what
7393
- * `readCurrentState` returns: `cdkd drift` re-reports it forever and `--revert`
7394
- * re-issues the same Put — the never-emitted-KEY class of issue #1686, reached
7395
- * through the SHAPE rather than through a value. The live CFn registry schema
7396
- * agrees the flattened form is the only CFn spelling (`Destination` is
7397
- * `{BucketArn, BucketAccountId, Format, Prefix}` with `additionalProperties:
7398
- * false`), so `S3BucketDestination` and `Bucket` are cdkd-only tolerances.
7399
- *
7400
- * This NORMALIZES rather than retracting the tolerance, per the #1686 rule:
7401
- * refusing the SDK spelling would break templates that deploy today. It also
7402
- * supersedes #1670's write-back-at-the-DECLARED-branch decision, whose whole
7403
- * reason was that writing at a hardcoded branch would leave the malformed value
7404
- * alive at the other key and add a stray one — there is no other key left once
7405
- * the block is normalized wholesale.
7371
+ * The readback (`inventorySdkToCfn` / `analyticsSdkToCfn`) emits only
7372
+ * `{BucketArn, BucketAccountId, Format, Prefix}`, the members the live CFn
7373
+ * registry schema declares (`additionalProperties: false`). The block is rebuilt
7374
+ * from exactly those members so a record never carries a key the readback cannot
7375
+ * report.
7376
+ *
7377
+ * The SDK spellings this once also folded (the nested
7378
+ * `{ S3BucketDestination: { ... } }` shape and a `Bucket` alias for
7379
+ * `BucketArn`) are gone: `BucketArn` and `Format` are schema-required here and
7380
+ * `AWS::S3::Bucket` is in `CFN_ENFORCED_TYPES`, so a template using either is
7381
+ * refused pre-flight by `nested-required.ts` (issue #3602).
7406
7382
  *
7407
7383
  * `Format` is the one member a caller can override: the applier passes the
7408
7384
  * value it SENT, which differs from the declared one when the warn-and-
7409
- * SUBSTITUTE read replaced it (#1670). Absent on BOTH sides it defaults to the
7410
- * `'CSV'` the appliers send, so the record carries what AWS will report back
7411
- * rather than one key fewer.
7385
+ * SUBSTITUTE read replaced a malformed value (#1670). Absent on BOTH sides it
7386
+ * defaults to the `'CSV'` the appliers send, so the record carries what AWS
7387
+ * will report back rather than one key fewer.
7412
7388
  *
7413
- * @returns the flattened block, or `declared` BY IDENTITY when it already is
7414
- * one (so an ordinary template records byte-for-byte what it declared) or is
7415
- * not a shape this can fold at all (absent / malformed — left for the
7416
- * resolver's own refusal to report).
7389
+ * @returns the rebuilt block, or `declared` BY IDENTITY when it already is one
7390
+ * (so an ordinary template records byte-for-byte what it declared) or is not
7391
+ * a shape this can fold at all (absent / malformed / no `BucketArn` — left for
7392
+ * the resolver's own refusal to report).
7417
7393
  */
7418
7394
  function effectiveS3BucketDestination(declared, sentFormat) {
7419
7395
  if (!isPlainObject$2(declared)) return declared;
7420
- const bag = isFlattenedDestination(declared) ? declared : declared["S3BucketDestination"];
7421
- if (!isPlainObject$2(bag)) return declared;
7422
- if (!bag["BucketArn"] && !bag["Bucket"]) return declared;
7423
- const out = {};
7424
- const bucketArn = bag["BucketArn"] ?? bag["Bucket"];
7425
- if (bucketArn !== void 0) out["BucketArn"] = bucketArn;
7426
- if (bag["BucketAccountId"] !== void 0) out["BucketAccountId"] = bag["BucketAccountId"];
7427
- out["Format"] = sentFormat ?? declaredOrDefault(bag, "Format", INVENTORY_DESTINATION_DEFAULT_FORMAT);
7428
- if (bag["Prefix"] !== void 0) out["Prefix"] = bag["Prefix"];
7396
+ if (!declared["BucketArn"]) return declared;
7397
+ const out = { BucketArn: declared["BucketArn"] };
7398
+ if (declared["BucketAccountId"] !== void 0) out["BucketAccountId"] = declared["BucketAccountId"];
7399
+ out["Format"] = sentFormat ?? declaredOrDefault(declared, "Format", INVENTORY_DESTINATION_DEFAULT_FORMAT);
7400
+ if (declared["Prefix"] !== void 0) out["Prefix"] = declared["Prefix"];
7429
7401
  return sameScalarRecord(out, declared) ? declared : out;
7430
7402
  }
7431
7403
  /**
@@ -7433,14 +7405,6 @@ function effectiveS3BucketDestination(declared, sentFormat) {
7433
7405
  * NEVER `??`, and that distinction is the whole guard against issue #1670's
7434
7406
  * finding 3.
7435
7407
  *
7436
- * The ONE licensed `??` in these folds is the ALIAS read
7437
- * `bag['BucketArn'] ?? bag['Bucket']`, and it is not an exception to this rule —
7438
- * it supplies no DEFAULT. It picks between two spellings of the same declared
7439
- * value, exactly as the applier's own read does, so a nullish `BucketArn`
7440
- * correctly falls through to the alias the wire would have used. When neither is
7441
- * declared the key is simply absent from the fold's output, which is what makes
7442
- * a bucket-less block identity-return rather than gain a phantom key.
7443
- *
7444
7408
  * The default belongs to a key the template OMITTED: there the provider sends
7445
7409
  * it, the readback reports it, and folding both sides is what makes them agree.
7446
7410
  * A key DECLARED with a malformed value is a different case entirely — the
@@ -7662,10 +7626,14 @@ function foldToleratedAliases(bag, aliases) {
7662
7626
  }
7663
7627
  /** The transition date member, held in a CONST so the fold writes it COMPUTED. */
7664
7628
  const TRANSITION_DATE_KEY = "TransitionDate";
7665
- /** The lifecycle `Transitions[]` aliases the applier accepts (issue #1748). */
7629
+ /**
7630
+ * The lifecycle `Transitions[]` aliases the applier accepts (issue #1748).
7631
+ *
7632
+ * `NoncurrentVersionTransitions[]` has none: its `TransitionInDays` is a
7633
+ * schema-required member, so a template spelling it `NoncurrentDays` is refused
7634
+ * pre-flight by `nested-required.ts` and the alias was dropped (issue #3585).
7635
+ */
7666
7636
  const TRANSITION_ALIASES = [["TransitionInDays", "Days"], ["TransitionDate", "Date"]];
7667
- /** The lifecycle `NoncurrentVersionTransitions[]` alias (issue #1748). */
7668
- const NONCURRENT_VERSION_TRANSITION_ALIASES = [["TransitionInDays", "NoncurrentDays"]];
7669
7637
  /** The refusal path for the notification EventBridge boolean (issue #1759). */
7670
7638
  const EVENTBRIDGE_CONFIG_PATH = "AWS::S3::Bucket NotificationConfiguration.EventBridgeConfiguration";
7671
7639
  /**
@@ -7736,27 +7704,25 @@ function dropEmptyNotificationFamilies(config) {
7736
7704
  }
7737
7705
  return out ?? config;
7738
7706
  }
7739
- /** The `NotificationConfiguration` ARN aliases, per family (issue #1748). */
7740
- const NOTIFICATION_ARN_ALIASES = [
7741
- ["TopicConfigurations", [["Topic", "TopicArn"]]],
7742
- ["QueueConfigurations", [["Queue", "QueueArn"]]],
7743
- ["LambdaConfigurations", [["Function", "LambdaFunctionArn"]]]
7744
- ];
7745
7707
  /**
7746
- * The `Event` / `Events` half of the same item (issue #1748).
7708
+ * The `Event` / `Events` spelling on a notification item (issue #1748).
7709
+ *
7710
+ * `Event` is the member the CFn registry schema declares (`TopicConfiguration`
7711
+ * is `{Event, Filter, Topic}` — there is no `Events`) and therefore the one
7712
+ * EVERY template carries. The applier accepts both
7713
+ * (`t['Event'] !== undefined ? [t['Event']] : t['Events']`), because the
7714
+ * readback emits `Events` for a multi-event live configuration and
7715
+ * `drift --revert` sends a readback-shaped bag.
7747
7716
  *
7748
- * The ARN alias is not the only never-emitted spelling on a notification item,
7749
- * and this one is the WIDER of the two: `TopicArn` is a cdkd-only tolerance
7750
- * almost no template uses, while `Event` is the member the CFn registry schema
7751
- * declares (`TopicConfiguration` is `{Event, Filter, Topic}` — there is no
7752
- * `Events`) and therefore the one EVERY template carries. The applier accepts
7753
- * both (`t['Event'] !== undefined ? [t['Event']] : t['Events']`).
7717
+ * This is not a pure rename — the SDK member is a LIST — so it folds by ARITY:
7718
+ * a single-element list is the CFn `Event`, and a longer one has no CFn
7719
+ * spelling at all and is left as `Events`, which is what the readback emits for
7720
+ * it.
7754
7721
  *
7755
- * Unlike the ARN pair this is not a pure rename — the SDK member is a LIST — so
7756
- * it folds by ARITY: a single-element list is the CFn `Event`, and a longer one
7757
- * has no CFn spelling at all and is left as `Events`, which is what the readback
7758
- * emits for it. That asymmetry is why it is a separate step rather than another
7759
- * {@link foldToleratedAliases} entry.
7722
+ * The destination ARN has no alias: `Topic` / `Queue` / `Function` are
7723
+ * schema-required members, so the SDK spellings (`TopicArn`, …) are refused
7724
+ * pre-flight by `nested-required.ts` and the applier no longer reads them
7725
+ * (issue #3585).
7760
7726
  *
7761
7727
  * `readNotification` was emitting `Events` unconditionally, so the record and
7762
7728
  * the readback disagreed on every notification-configured bucket; it now
@@ -7781,20 +7747,12 @@ function foldNotificationEvents(item) {
7781
7747
  * `readNotification` emits (issue #1748) — the never-emitted-KEY class of issue
7782
7748
  * #1686 reached through the SHAPE rather than through a value.
7783
7749
  *
7784
- * `applyNotificationConfiguration` accepts the SDK spelling of each destination
7785
- * ARN alongside the CFn one (`t['Topic'] ?? t['TopicArn']`, and the two
7786
- * siblings), while `readNotification` emits only `Topic` / `Queue` /
7787
- * `Function` — the spellings the live CFn registry schema declares
7788
- * (`TopicConfiguration` is `{Event, Filter, Topic}`, with no `TopicArn`
7789
- * member at all, so the SDK spellings are cdkd-only tolerances). A record
7790
- * written in the tolerated spelling therefore can never match the readback:
7791
- * `cdkd drift` re-reports it forever and `--revert` re-issues the same Put,
7792
- * with NO warning anywhere, because nothing is malformed and nothing is
7793
- * substituted.
7794
- *
7795
- * This NORMALIZES rather than retracting the tolerance, per #1686's third
7796
- * generalization: refusing the SDK spelling would break templates that deploy
7797
- * today, and buys nothing the normalization does not.
7750
+ * `applyNotificationConfiguration` accepts a single-element `Events` list
7751
+ * alongside the CFn scalar `Event` ({@link foldNotificationEvents}), while
7752
+ * `readNotification` emits `Event` for it. A record written in the list
7753
+ * spelling therefore can never match the readback: `cdkd drift` re-reports it
7754
+ * forever and `--revert` re-issues the same Put, with NO warning anywhere,
7755
+ * because nothing is malformed and nothing is substituted.
7798
7756
  *
7799
7757
  * The Put is a FULL REPLACE with no skip arm, so there is no
7800
7758
  * previous-value-retained case to distinguish — whenever it succeeds, this is
@@ -7807,7 +7765,7 @@ function foldNotificationEvents(item) {
7807
7765
  function effectiveNotificationConfiguration(config) {
7808
7766
  if (!isPlainObject$2(config)) return config;
7809
7767
  let out = config;
7810
- for (const [listKey, aliases] of NOTIFICATION_ARN_ALIASES) out = canonicalizeItemList(out, listKey, (item) => foldNotificationEvents(foldToleratedAliases(item, aliases)));
7768
+ for (const listKey of NOTIFICATION_LIST_KEYS) out = canonicalizeItemList(out, listKey, foldNotificationEvents);
7811
7769
  out = dropEmptyNotificationFamilies(out);
7812
7770
  return foldEventBridgeConfiguration(out);
7813
7771
  }
@@ -8248,18 +8206,13 @@ function foldLifecycleScope(rule, useFilterForm) {
8248
8206
  * `readLifecycle` emits (issue #1748) — the lifecycle half of the same class.
8249
8207
  *
8250
8208
  * `applyLifecycleConfiguration` accepts the SDK day/date spellings alongside the
8251
- * CFn ones on both transition families (`t['TransitionInDays'] ?? t['Days']`,
8252
- * `t['TransitionDate'] ?? t['Date']`, `nvt['TransitionInDays'] ??
8253
- * nvt['NoncurrentDays']`) while `readLifecycle` emits only `TransitionInDays` /
8254
- * `TransitionDate` — again the only spellings the registry schema declares
8255
- * (`Transition` is `{StorageClass, TransitionDate, TransitionInDays}`,
8256
- * `NoncurrentVersionTransition` is `{StorageClass, TransitionInDays,
8257
- * NewerNoncurrentVersions}`).
8258
- *
8259
- * The issue named the `Date` alias; the mechanical audit it asks for (match the
8260
- * `(a['X'] ?? a['Y'])` ALIAS form, not a plain bracket read) found all THREE in
8261
- * the same two item shapes, so fixing only the reported one would have left its
8262
- * siblings broken — the "diff the WHOLE blob, not the reported key" rule.
8209
+ * CFn ones on `Transitions` (`t['TransitionInDays'] ?? t['Days']`,
8210
+ * `t['TransitionDate'] ?? t['Date']`) while `readLifecycle` emits only
8211
+ * `TransitionInDays` / `TransitionDate` — again the only spellings the registry
8212
+ * schema declares (`Transition` is `{StorageClass, TransitionDate,
8213
+ * TransitionInDays}`). `NoncurrentVersionTransitions` has no alias to fold: its
8214
+ * `TransitionInDays` is schema-required, so the `NoncurrentDays` spelling is
8215
+ * refused pre-flight and the applier no longer reads it (issue #3585).
8263
8216
  *
8264
8217
  * Issues #1754 / #1755 finished the block, so the "still differs on those keys"
8265
8218
  * caveat this doc carried is gone: the legacy SINGULAR `Transition` /
@@ -8309,7 +8262,6 @@ function effectiveLifecycleConfiguration(config) {
8309
8262
  function effectiveLifecycleRule(rule, useFilterForm) {
8310
8263
  let out = foldLegacySingularTransitions(rule);
8311
8264
  out = canonicalizeItemList(out, "Transitions", (t) => foldTransitionDate(foldToleratedAliases(t, TRANSITION_ALIASES)));
8312
- out = canonicalizeItemList(out, "NoncurrentVersionTransitions", (nvt) => foldToleratedAliases(nvt, NONCURRENT_VERSION_TRANSITION_ALIASES));
8313
8265
  out = dropEmptyTransitionLists(out);
8314
8266
  out = foldLifecycleExpiration(out);
8315
8267
  out = foldNoncurrentVersionExpiration(out);
@@ -8376,8 +8328,8 @@ function canonicalizeItemList(properties, key, fold) {
8376
8328
  /**
8377
8329
  * Name a malformed value in a refusal message, the way `config-shape.ts`'s
8378
8330
  * private `describe` does for the string-reading guards. Kept local rather than
8379
- * exported from there because `resolveS3BucketDestination` is S3-specific
8380
- * branch selection, not a shared shape guard — the two only share the wording.
8331
+ * exported from there because `resolveS3BucketDestination` is an S3-specific
8332
+ * refusal, not a shared shape guard — the two only share the wording.
8381
8333
  */
8382
8334
  function describeValue$1(value, maskSecrets = (text) => text) {
8383
8335
  if (value === null) return "null";
@@ -8915,7 +8867,7 @@ var S3BucketProvider = class S3BucketProvider {
8915
8867
  };
8916
8868
  else if (coerceCfnNumber(legacyNveDays) !== void 0) sdkRule.NoncurrentVersionExpiration = { NoncurrentDays: coerceCfnNumber(legacyNveDays) };
8917
8869
  const toSdkNvt = (nvt) => ({
8918
- NoncurrentDays: nvt["TransitionInDays"] ?? nvt["NoncurrentDays"],
8870
+ NoncurrentDays: nvt["TransitionInDays"],
8919
8871
  StorageClass: nvt["StorageClass"],
8920
8872
  NewerNoncurrentVersions: nvt["NewerNoncurrentVersions"]
8921
8873
  });
@@ -9126,21 +9078,21 @@ var S3BucketProvider = class S3BucketProvider {
9126
9078
  const topics = notifConfig["TopicConfigurations"];
9127
9079
  if (topics && Array.isArray(topics) && topics.length > 0) cfg.TopicConfigurations = topics.map((t) => ({
9128
9080
  Id: t["Id"],
9129
- TopicArn: t["Topic"] ?? t["TopicArn"],
9081
+ TopicArn: t["Topic"],
9130
9082
  Events: t["Event"] !== void 0 ? [t["Event"]] : t["Events"],
9131
9083
  Filter: this.cfnNotifFilterToSdk(t["Filter"])
9132
9084
  }));
9133
9085
  const queues = notifConfig["QueueConfigurations"];
9134
9086
  if (queues && Array.isArray(queues) && queues.length > 0) cfg.QueueConfigurations = queues.map((q) => ({
9135
9087
  Id: q["Id"],
9136
- QueueArn: q["Queue"] ?? q["QueueArn"],
9088
+ QueueArn: q["Queue"],
9137
9089
  Events: q["Event"] !== void 0 ? [q["Event"]] : q["Events"],
9138
9090
  Filter: this.cfnNotifFilterToSdk(q["Filter"])
9139
9091
  }));
9140
9092
  const lambdas = notifConfig["LambdaConfigurations"];
9141
9093
  if (lambdas && Array.isArray(lambdas) && lambdas.length > 0) cfg.LambdaFunctionConfigurations = lambdas.map((l) => ({
9142
9094
  Id: l["Id"],
9143
- LambdaFunctionArn: l["Function"] ?? l["LambdaFunctionArn"],
9095
+ LambdaFunctionArn: l["Function"],
9144
9096
  Events: l["Event"] !== void 0 ? [l["Event"]] : l["Events"],
9145
9097
  Filter: this.cfnNotifFilterToSdk(l["Filter"])
9146
9098
  }));
@@ -9225,39 +9177,32 @@ var S3BucketProvider = class S3BucketProvider {
9225
9177
  };
9226
9178
  }
9227
9179
  /**
9228
- * Pick the `S3BucketDestination` bag out of an analytics / inventory
9229
- * `Destination` block, refusing a shape that would otherwise be dropped.
9230
- *
9231
- * Two shapes are legitimate and both must keep working: the CFn FLATTENED
9232
- * form (`Destination: { BucketArn, Format, ... }`, what the schema declares)
9233
- * and the SDK NESTED form (`Destination: { S3BucketDestination: { ... } }`,
9234
- * accepted because state records and hand-written templates carry it).
9235
- *
9236
- * Issue #1493 item 2: the branch was picked by probing member presence
9237
- * (`dest?.['BucketAccountId'] || dest?.['BucketArn'] || dest?.['Format']`),
9238
- * so a `Destination` that is a STRING / array / unresolved intrinsic indexed
9239
- * every probe to `undefined`, fell through to an equally-`undefined`
9240
- * `S3BucketDestination`, and the caller's `s3Dest ? … : undefined` omitted
9241
- * the whole block from the Put — a configuration silently deployed without
9242
- * the destination it declared. Unlike the sibling defaulting class this is a
9243
- * DROP, not a substituted default, so `readConfigString` does not cover it:
9244
- * it needs its own decision, and per issue #1513's precedent that decision is
9245
- * REFUSE on the template-borne create path, WARN on the replay-reachable
9246
- * update path (`onUnusable`), where the desired bag can be a historical cdkd
9247
- * state record with no template-side remedy.
9248
- *
9249
- * Also returns the CFn path of the bag it picked (issue #1493 item 3): the
9250
- * callers used to hardcode `…Destination.S3BucketDestination` in the
9251
- * `containerPath` they pass to `readConfigString`, so a refusal on the
9252
- * FLATTENED branch — where the bag IS `dest` itself — named a key the user's
9253
- * template does not contain.
9254
- *
9255
- * @returns the picked bag, or `undefined` when the block was ABSENT or was
9256
- * refused on a warn-only path. The caller distinguishes the two by testing
9257
- * its own `Destination` value — see the `continue` at each call site.
9180
+ * Validate an analytics / inventory `Destination` block, refusing a shape
9181
+ * that would otherwise be dropped.
9182
+ *
9183
+ * The block is the CFn form the schema declares (`Destination: { BucketArn,
9184
+ * Format, ... }`). The SDK NESTED form (`{ S3BucketDestination: { ... } }`)
9185
+ * and a `Bucket` alias for `BucketArn` are no longer read: `BucketArn` and
9186
+ * `Format` are schema-required, so a template using either is refused
9187
+ * pre-flight by `nested-required.ts` (issue #3602). What still reaches this
9188
+ * guard with such a shape (an unresolved intrinsic on the path, or a replayed
9189
+ * pre-#1707 state record) is refused here like any other bucket-less block.
9190
+ *
9191
+ * Issue #1493 item 2: a `Destination` that is a STRING / array / unresolved
9192
+ * intrinsic used to index every probe to `undefined`, and the caller's
9193
+ * `s3Dest ? … : undefined` omitted the whole block from the Put — a
9194
+ * configuration silently deployed without the destination it declared. Unlike
9195
+ * the sibling defaulting class this is a DROP, not a substituted default, so
9196
+ * `readConfigString` does not cover it: per issue #1513's precedent the
9197
+ * decision is REFUSE on the template-borne create path, WARN on the
9198
+ * replay-reachable update path (`onUnusable`), where the desired bag can be a
9199
+ * historical cdkd state record with no template-side remedy.
9200
+ *
9201
+ * @returns the block, or `undefined` when it was ABSENT or was refused on a
9202
+ * warn-only path. The caller distinguishes the two by testing its own
9203
+ * `Destination` value — see the `continue` at each call site.
9258
9204
  */
9259
9205
  resolveS3BucketDestination(dest, destinationPath, onUnusable, maskSecrets = (text) => text) {
9260
- const nestedPath = `${destinationPath}.S3BucketDestination`;
9261
9206
  const drop = (message) => {
9262
9207
  if (onUnusable) {
9263
9208
  onUnusable(`${message}. Leaving this configuration unchanged; the same value is REFUSED on a template-path create`);
@@ -9267,11 +9212,8 @@ var S3BucketProvider = class S3BucketProvider {
9267
9212
  };
9268
9213
  if (dest === void 0 || dest === null) return;
9269
9214
  if (!isPlainObject$2(dest)) return drop(`${destinationPath} must be an object (got ${describeValue$1(dest, maskSecrets)}) — check for an unresolved intrinsic or a mis-nested template value; the destination would otherwise be dropped from the request with no error`);
9270
- const bag = isFlattenedDestination(dest) ? dest : dest["S3BucketDestination"];
9271
- if (bag === void 0 || bag === null) return drop(`${destinationPath} carries neither a bucket (BucketArn / Bucket) nor a nested S3BucketDestination object (got ${describeValue$1(dest, maskSecrets)}) — the destination would otherwise be dropped from the request with no error`);
9272
- if (!isPlainObject$2(bag)) return drop(`${nestedPath} must be an object (got ${describeValue$1(bag, maskSecrets)}) — check for an unresolved intrinsic or a mis-nested template value; the destination would otherwise be dropped from the request with no error`);
9273
- if (!bag["BucketArn"] && !bag["Bucket"]) return drop(`${bag === dest ? destinationPath : nestedPath} has no destination bucket (BucketArn / Bucket) (got ${describeValue$1(bag, maskSecrets)}) — the request would be rejected by S3, or silently carry no destination`);
9274
- return bag;
9215
+ if (!dest["BucketArn"]) return drop(`${destinationPath} has no destination bucket (BucketArn) (got ${describeValue$1(dest, maskSecrets)}) — the request would be rejected by S3, or silently carry no destination`);
9216
+ return dest;
9275
9217
  }
9276
9218
  /**
9277
9219
  * Apply analytics configurations
@@ -9332,13 +9274,12 @@ var S3BucketProvider = class S3BucketProvider {
9332
9274
  skipped.push(index);
9333
9275
  continue;
9334
9276
  }
9335
- const destPath = s3BucketDestinationPath(rawDest, analyticsDestPath);
9336
9277
  analyticsConfig.StorageClassAnalysis = { DataExport: {
9337
9278
  OutputSchemaVersion: sentOutputSchemaVersion = this.readSubstitutedConfigString(dataExport, "OutputSchemaVersion", ANALYTICS_DEFAULT_OUTPUT_SCHEMA_VERSION, "AWS::S3::Bucket AnalyticsConfigurations[].StorageClassAnalysis.DataExport", { ...onUnusable ? { onUnusable } : {} }),
9338
9279
  Destination: s3Dest ? { S3BucketDestination: {
9339
- Bucket: s3Dest["BucketArn"] ?? s3Dest["Bucket"],
9280
+ Bucket: s3Dest["BucketArn"],
9340
9281
  BucketAccountId: s3Dest["BucketAccountId"],
9341
- Format: sentFormat = this.readSubstitutedConfigString(s3Dest, "Format", INVENTORY_DESTINATION_DEFAULT_FORMAT, destPath, { ...onUnusable ? { onUnusable } : {} }),
9282
+ Format: sentFormat = this.readSubstitutedConfigString(s3Dest, "Format", INVENTORY_DESTINATION_DEFAULT_FORMAT, analyticsDestPath, { ...onUnusable ? { onUnusable } : {} }),
9342
9283
  Prefix: s3Dest["Prefix"]
9343
9284
  } } : void 0
9344
9285
  } };
@@ -9462,7 +9403,6 @@ var S3BucketProvider = class S3BucketProvider {
9462
9403
  skipped.push(index);
9463
9404
  continue;
9464
9405
  }
9465
- const destPath = s3BucketDestinationPath(rawDest, inventoryDestPath);
9466
9406
  let sentFormat;
9467
9407
  const scheduleFrequencyRefusal = onUnusable ? configStringRefusal(config, "ScheduleFrequency", "Weekly", "AWS::S3::Bucket InventoryConfigurations[]") : void 0;
9468
9408
  if (scheduleFrequencyRefusal !== void 0 && config["Schedule"] == null) {
@@ -9483,9 +9423,9 @@ var S3BucketProvider = class S3BucketProvider {
9483
9423
  IncludedObjectVersions: readConfigString(config, "IncludedObjectVersions", INVENTORY_DEFAULT_INCLUDED_OBJECT_VERSIONS, "AWS::S3::Bucket InventoryConfigurations[]"),
9484
9424
  Schedule: { Frequency: frequency },
9485
9425
  Destination: { S3BucketDestination: s3Dest ? {
9486
- Bucket: s3Dest["BucketArn"] ?? s3Dest["Bucket"],
9426
+ Bucket: s3Dest["BucketArn"],
9487
9427
  AccountId: s3Dest["BucketAccountId"],
9488
- Format: sentFormat = this.readSubstitutedConfigString(s3Dest, "Format", INVENTORY_DESTINATION_DEFAULT_FORMAT, destPath, { ...onUnusable ? { onUnusable } : {} }),
9428
+ Format: sentFormat = this.readSubstitutedConfigString(s3Dest, "Format", INVENTORY_DESTINATION_DEFAULT_FORMAT, inventoryDestPath, { ...onUnusable ? { onUnusable } : {} }),
9489
9429
  Prefix: s3Dest["Prefix"]
9490
9430
  } : void 0 },
9491
9431
  OptionalFields: config["OptionalFields"],
@@ -64681,8 +64621,25 @@ async function runDestroyForStack(stackName, state, ctx) {
64681
64621
  const stateTargetFor = (logicalId, resourceType) => resourceType === NESTED_STACK_TYPE ? `${stackName}~${logicalId}` : stackName;
64682
64622
  const skippedStateTargets = /* @__PURE__ */ new Set();
64683
64623
  const failedStateTargets = /* @__PURE__ */ new Set();
64684
- /** `'cmd A' / 'cmd B'` — the quoted, slash-joined hint both summary arms print. */
64685
- const hintFor = (command, targets) => targets.map((t) => `'${command} ${t}'`).join(" / ");
64624
+ /**
64625
+ * One labelled line per target — the hint both summary arms print.
64626
+ *
64627
+ * The PROSE quotes are gone, every target goes through the shared gate, and
64628
+ * each command gets its OWN line (go-to-k/cdkd#3436). The old
64629
+ * `'${command} ${t}'` wrapper is the shape whose paste ran an interpolated
64630
+ * value, and this builder was the issue's example of a command the repo-wide
64631
+ * grep cannot see, since neither `'cdkd ` nor the value is on the same line
64632
+ * as the other. The ` / ` join that first replaced it was its own defect:
64633
+ * pasted, it handed the second command to the first as arguments.
64634
+ */
64635
+ const hintFor = (command, targets, label) => [...new Set(targets.map((t) => `\n${label}: ${pasteableCommand(command, [{
64636
+ value: t,
64637
+ hole: "stack"
64638
+ }, {
64639
+ flag: "--stack-region",
64640
+ value: regionForState,
64641
+ hole: "region"
64642
+ }]).command}`))].join("");
64686
64643
  const buildDestroySnapshot = () => {
64687
64644
  const { imports: _imports, outputReads: _outputReads, ...rest } = state;
64688
64645
  return {
@@ -64970,21 +64927,26 @@ async function runDestroyForStack(stackName, state, ctx) {
64970
64927
  const skippedSuffix = result.skippedCount > 0 ? `, ${yellow(result.skippedCount)} skipped` : "";
64971
64928
  const guardSuffix = result.guardIndeterminateCount > 0 ? `, ${yellow(result.guardIndeterminateCount)} unverified` : "";
64972
64929
  if (result.guardIndeterminateCount > 0) {
64973
- const durablePointer = ctx.eventRecorder === void 0 ? "This summary is the only record: this run wrote no deployment events, either because it is a 'cdkd state destroy' (go-to-k/cdkd#2423) or because it is a nested-stack child, neither of which threads an event recorder." : `Run 'cdkd events ${stackName}' for the RESOURCE_GUARD_INDETERMINATE entries, which name the check and the reason and survive the run.`;
64930
+ const durablePointer = ctx.eventRecorder === void 0 ? "This summary is the only record: this run wrote no deployment events, either because it is a 'cdkd state destroy' (go-to-k/cdkd#2423) or because it is a nested-stack child, neither of which threads an event recorder." : `The RESOURCE_GUARD_INDETERMINATE entries name the check and the reason and survive the run.\nRead them with: ${pasteableCommand("cdkd events", [{
64931
+ value: stackName,
64932
+ hole: "stack"
64933
+ }]).command}`;
64974
64934
  logger.warn(`\n${yellow("⚠")} ${result.guardIndeterminateCount} pre-flight safety check(s) could NOT be completed during this destroy and cdkd proceeded anyway: ${[...guardIndeterminateTargets].join(", ")}. A check can be suppressed by DENYING the permission it needs, so treat this as unconfirmed rather than benign. ${durablePointer}`);
64975
64935
  }
64976
64936
  if (!preserveState) logger.info(`\n${green("✓")} ${bold(`Stack ${stackName} destroyed`)} (${green(result.deletedCount)} deleted${retainedSuffix}${guardSuffix}, ${result.errorCount} errors)`);
64977
64937
  else if (result.interrupted && result.errorCount === 0) logger.warn(`\n${yellow("⚠")} ${bold(`Stack ${stackName} destroy interrupted`)} (${green(result.deletedCount)} deleted${retainedSuffix}${skippedSuffix}${guardSuffix}, ${result.errorCount} errors). State preserved — re-run 'cdkd destroy' / 'cdkd state destroy' to finish.`);
64978
64938
  else if (result.errorCount === 0) {
64979
64939
  const targets = [...skippedStateTargets];
64980
- const showHint = hintFor("cdkd state show", targets);
64981
- const orphanHint = hintFor("cdkd state orphan", targets);
64982
- logger.warn(`\n${yellow("⚠")} ${bold(`Stack ${stackName} partially destroyed`)} (${green(result.deletedCount)} deleted${retainedSuffix}${skippedSuffix}${guardSuffix}, ${result.errorCount} errors). cdkd could not address the skipped resource(s), so they may still exist in AWS. Fix the physicalId in state.json (${showHint}) and re-run, or delete them by hand and drop the records with ${orphanHint}.`);
64940
+ const showHint = hintFor("cdkd state show", targets, "Inspect it with");
64941
+ const orphanHint = hintFor("cdkd state orphan", targets, "Drop the record with");
64942
+ logger.warn(`\n${yellow("⚠")} ${bold(`Stack ${stackName} partially destroyed`)} (${green(result.deletedCount)} deleted${retainedSuffix}${skippedSuffix}${guardSuffix}, ${result.errorCount} errors). cdkd could not address the skipped resource(s), so they may still exist in AWS. Fix the physicalId in state.json and re-run, or delete them by hand and drop the records.` + showHint + orphanHint);
64983
64943
  } else {
64984
- const orphanHint = hintFor("cdkd state orphan", failedStateTargets.size > 0 ? [...failedStateTargets] : [stackName]);
64944
+ const orphanHint = hintFor("cdkd state orphan", failedStateTargets.size > 0 ? [...failedStateTargets] : [stackName], "Drop the record with");
64985
64945
  const skippedTargets = [...skippedStateTargets];
64986
- const skippedClause = skippedTargets.length > 0 ? ` Separately, ${result.skippedCount} resource(s) were SKIPPED — cdkd could not address them, so no delete was issued and they may still exist in AWS. Fix the physicalId in state.json (${hintFor("cdkd state show", skippedTargets)}) and re-run, or delete them by hand and drop the records with ${hintFor("cdkd state orphan", skippedTargets)}.` : "";
64987
- logger.warn(`\n${yellow("⚠")} ${bold(`Stack ${stackName} partially destroyed`)} (${green(result.deletedCount)} deleted${retainedSuffix}${skippedSuffix}${guardSuffix}, ${red(result.errorCount)} errors). State preserved — re-run 'cdkd destroy' / 'cdkd state destroy' to clean up. If the same resource keeps failing, ${orphanHint} is the last resort: it removes the state record without deleting AWS resources.` + skippedClause);
64946
+ const skippedClause = skippedTargets.length > 0 ? ` Separately, ${result.skippedCount} resource(s) were SKIPPED — cdkd could not address them, so no delete was issued and they may still exist in AWS. Fix the physicalId in state.json and re-run, or delete them by hand and drop their records.` : "";
64947
+ const skippedCommands = hintFor("cdkd state show", skippedTargets, "Inspect it with") + hintFor("cdkd state orphan", skippedTargets, "Drop the record with");
64948
+ const dedupedCommands = (lines) => [...new Set(lines.split("\n").filter((l) => l !== ""))].map((l) => `\n${l}`).join("");
64949
+ logger.warn(`\n${yellow("⚠")} ${bold(`Stack ${stackName} partially destroyed`)} (${green(result.deletedCount)} deleted${retainedSuffix}${skippedSuffix}${guardSuffix}, ${red(result.errorCount)} errors). State preserved — re-run 'cdkd destroy' / 'cdkd state destroy' to clean up. If the same resource keeps failing, dropping the state record is the last resort: it removes the record without deleting AWS resources.` + skippedClause + dedupedCommands(orphanHint + skippedCommands));
64988
64950
  }
64989
64951
  } finally {
64990
64952
  try {
@@ -65104,11 +65066,11 @@ function getCurrentNestedStackContext() {
65104
65066
  * @param alsoSkippedCount resources the child SKIPPED (no AWS call issued)
65105
65067
  * @param alsoInterrupted whether the child's destroy was also interrupted
65106
65068
  */
65107
- function nestedStackChildFailureMessage(childStackName, errorCount, alsoSkippedCount, alsoInterrupted) {
65069
+ function nestedStackChildFailureMessage(childStackName, errorCount, alsoSkippedCount, alsoInterrupted, inspectCommand) {
65108
65070
  const extra = [];
65109
65071
  if (alsoSkippedCount > 0) extra.push(`${alsoSkippedCount} resource(s) were also skipped`);
65110
65072
  if (alsoInterrupted) extra.push("the child destroy was also interrupted");
65111
- return `Nested stack ${childStackName} failed to destroy: ${errorCount} resource(s) failed to delete` + (extra.length > 0 ? ` (${extra.join("; ")})` : "") + `. The child's state is PRESERVED and still lists them — inspect it with 'cdkd state show ${childStackName}', resolve the failure, and re-run the destroy. The parent's record of this nested stack is kept so the child stays reachable.`;
65073
+ return `Nested stack ${childStackName} failed to destroy: ${errorCount} resource(s) failed to delete` + (extra.length > 0 ? ` (${extra.join("; ")})` : "") + `. The child's state is PRESERVED and still lists them — inspect it, resolve the failure, and re-run the destroy. The parent's record of this nested stack is kept so the child stays reachable.\nInspect it with: ${inspectCommand}`;
65112
65074
  }
65113
65075
 
65114
65076
  //#endregion
@@ -65285,7 +65247,10 @@ var NestedStackProvider = class {
65285
65247
  ...ctx.destroyOptions?.resourceTimeoutByType && { resourceTimeoutByType: ctx.destroyOptions.resourceTimeoutByType }
65286
65248
  }));
65287
65249
  if (childResult.errorCount > 0) {
65288
- const failure = new Error(nestedStackChildFailureMessage(childStackName, childResult.errorCount, childResult.skippedCount, childResult.interrupted));
65250
+ const failure = new Error(nestedStackChildFailureMessage(childStackName, childResult.errorCount, childResult.skippedCount, childResult.interrupted, pasteableCommand("cdkd state show", [{
65251
+ value: childStackName,
65252
+ hole: "stack"
65253
+ }]).command));
65289
65254
  throw childResult.interrupted ? markNonRetryable(failure) : failure;
65290
65255
  }
65291
65256
  if (childResult.skippedCount > 0 || childResult.interrupted) {
@@ -68325,6 +68290,36 @@ function conditionsDependOnTokenParameters(template, tokenParameterNames) {
68325
68290
  }
68326
68291
  return false;
68327
68292
  }
68293
+ /**
68294
+ * The reason a repaired `properties` map puts on the node's `blocking`
68295
+ * (go-to-k/cdkd#3335).
68296
+ *
68297
+ * `cdkd diff` REPAIRS this container and previews the record; `cdkd deploy`
68298
+ * REFUSES it inside `DiffCalculator.calculateDiff`, which both deploy paths
68299
+ * reach. So the preview is honest about the rows and wrong about what happens
68300
+ * next, and this is the sentence that says so.
68301
+ *
68302
+ * The ids are NOT re-rendered here: they have already been through
68303
+ * `malformedResourcePropertiesWarning`'s sanitizer in the warning printed
68304
+ * beside this, and the renderer strips control characters from every blocking
68305
+ * reason on the way out. The count is what this line adds — and the line does
68306
+ * not send the reader to that warning for the names, because the warning caps
68307
+ * at five before `and N more`, and under `--json` this reason ships in the
68308
+ * payload, where "above" has no referent.
68309
+ */
68310
+ function deployRefusesPropertiesReason(logicalIds) {
68311
+ return `${logicalIds.length} resource record(s) hold a 'properties' map that cannot be read. This preview repaired them to empty; 'cdkd deploy' refuses the record instead, so the deploy this previews will not start.`;
68312
+ }
68313
+ /**
68314
+ * The `outputs` twin of {@link deployRefusesPropertiesReason}, a constant
68315
+ * because the bag is refused as a whole and there is nothing to count.
68316
+ *
68317
+ * `refuseMalformedOutputs` in `DeployEngine` is what refuses it. This arm is
68318
+ * the one the issue could not drive end to end — a record whose template
68319
+ * declares no Outputs produces no delta at all, so before this the node had
68320
+ * nothing to report and `--fail` exited 0.
68321
+ */
68322
+ const DEPLOY_REFUSES_OUTPUTS_REASON = "The 'outputs' bag cannot be read. This preview repaired it to empty; 'cdkd deploy' refuses the record instead, so the deploy this previews will not start.";
68328
68323
  /** How many unreadable row names the human preview lists before summarizing. */
68329
68324
  const UNREADABLE_PREVIEW_NAMES = 10;
68330
68325
  /** Empty template used to diff a removed nested child's state → all DELETE. */
@@ -68392,6 +68387,7 @@ async function loadStateOrEmpty(stackName, region, stateBackend) {
68392
68387
  const result = await stateBackend.getState(stackName, region);
68393
68388
  if (result) {
68394
68389
  const unreadable = [];
68390
+ const deployRefusals = [];
68395
68391
  if (repairMalformedResourcesForReadOnly(result.state)) {
68396
68392
  logger.warn(malformedResourcesWarning(stackName, region));
68397
68393
  unreadable.push(UNREADABLE_RESOURCES_MAP_ROW);
@@ -68402,8 +68398,14 @@ async function loadStateOrEmpty(stackName, region, stateBackend) {
68402
68398
  unreadable.push(...dropped);
68403
68399
  }
68404
68400
  const unreadableProps = repairMalformedResourcePropertiesForReadOnly(result.state);
68405
- if (unreadableProps.length > 0) logger.warn(malformedResourcePropertiesWarning(stackName, region, unreadableProps));
68406
- if (repairMalformedOutputsForReadOnly(result.state)) logger.warn(malformedOutputsWarning(stackName, region));
68401
+ if (unreadableProps.length > 0) {
68402
+ logger.warn(malformedResourcePropertiesWarning(stackName, region, unreadableProps));
68403
+ deployRefusals.push(deployRefusesPropertiesReason(unreadableProps));
68404
+ }
68405
+ if (repairMalformedOutputsForReadOnly(result.state)) {
68406
+ logger.warn(malformedOutputsWarning(stackName, region));
68407
+ deployRefusals.push(DEPLOY_REFUSES_OUTPUTS_REASON);
68408
+ }
68407
68409
  if (repairMalformedOrphansForReadOnly(result.state)) {
68408
68410
  logger.warn(malformedOrphansWarning(stackName, region));
68409
68411
  unreadable.push(UNREADABLE_ORPHANS_CONTAINER_ROW);
@@ -68411,7 +68413,8 @@ async function loadStateOrEmpty(stackName, region, stateBackend) {
68411
68413
  if (isReadableBag(result.state.outputs) && !hasReadableExportSet(result.state)) logger.warn(malformedExportNamesWarning(stackName, region));
68412
68414
  return {
68413
68415
  state: result.state,
68414
- unreadable
68416
+ unreadable,
68417
+ deployRefusals
68415
68418
  };
68416
68419
  }
68417
68420
  return {
@@ -68423,7 +68426,8 @@ async function loadStateOrEmpty(stackName, region, stateBackend) {
68423
68426
  version: 10,
68424
68427
  lastModified: Date.now()
68425
68428
  },
68426
- unreadable: []
68429
+ unreadable: [],
68430
+ deployRefusals: []
68427
68431
  };
68428
68432
  }
68429
68433
  /**
@@ -68572,6 +68576,7 @@ async function computeStackDiff(currentState, template, region, stackName, state
68572
68576
  let adoptedOrphans = [];
68573
68577
  let adoptedRecords = {};
68574
68578
  let blocking = [];
68579
+ const deployRefusals = [];
68575
68580
  let stateForDiff = currentState;
68576
68581
  const unreadableOrphans = [];
68577
68582
  if (currentState.orphans?.length && options.previewOrphanAdoption) {
@@ -68598,7 +68603,10 @@ async function computeStackDiff(currentState, template, region, stackName, state
68598
68603
  }
68599
68604
  };
68600
68605
  const tornAdopted = repairMalformedResourcePropertiesForReadOnly(stateForDiff);
68601
- if (tornAdopted.length > 0) logger.warn(malformedResourcePropertiesWarning(stackName, region, tornAdopted));
68606
+ if (tornAdopted.length > 0) {
68607
+ logger.warn(malformedResourcePropertiesWarning(stackName, region, tornAdopted));
68608
+ deployRefusals.push(deployRefusesPropertiesReason(tornAdopted));
68609
+ }
68602
68610
  }
68603
68611
  }
68604
68612
  const changes = await diffCalculator.calculateDiff(stateForDiff, effectiveTemplate, resolveFn, canonicalizeProperties);
@@ -68641,7 +68649,8 @@ async function computeStackDiff(currentState, template, region, stackName, state
68641
68649
  adoptedOrphans,
68642
68650
  adoptedRecords,
68643
68651
  blocking,
68644
- unreadableOrphans
68652
+ unreadableOrphans,
68653
+ deployRefusals
68645
68654
  };
68646
68655
  }
68647
68656
  /**
@@ -68719,10 +68728,10 @@ async function resolveChildStackParameters(parentStackRow, parentTemplate, paren
68719
68728
  * fail at deploy time.
68720
68729
  */
68721
68730
  async function buildDiffTree(args) {
68722
- const { stackName, displayName, region, template, nestedTemplates, recursive, stateBackend, diffCalculator, parentHasSecretReference, parameters, canonicalizeProperties, assetRedirect, cfnFallback, previewOrphanAdoption, ancestorTemplatePaths } = args;
68723
- const { state, unreadable } = await loadStateOrEmpty(stackName, region, stateBackend);
68731
+ const { stackName, displayName, region, template, nestedTemplates, recursive, stateBackend, diffCalculator, parentHasSecretReference, parameters, canonicalizeProperties, assetRedirect, cfnFallback, previewOrphanAdoption, ancestorTemplatePaths, isNestedChild } = args;
68732
+ const { state, unreadable, deployRefusals } = await loadStateOrEmpty(stackName, region, stateBackend);
68724
68733
  const secretBearingAbove = parentHasSecretReference === true || templateHasSecretDynamicReference(template);
68725
- const { changes, outputChanges, adoptedOrphans, adoptedRecords, blocking, unreadableOrphans } = await computeStackDiff(state, template, region, stackName, stateBackend, diffCalculator, {
68734
+ const { changes, outputChanges, adoptedOrphans, adoptedRecords, blocking, unreadableOrphans, deployRefusals: adoptedDeployRefusals } = await computeStackDiff(state, template, region, stackName, stateBackend, diffCalculator, {
68726
68735
  ...parameters && { parameters },
68727
68736
  ...canonicalizeProperties && { canonicalizeProperties },
68728
68737
  ...cfnFallback !== void 0 && { cfnFallback },
@@ -68744,7 +68753,11 @@ async function buildDiffTree(args) {
68744
68753
  ccApiRoutes: collectCcApiRoutes(template, stateAfterAdoption),
68745
68754
  outputChanges,
68746
68755
  adoptedOrphans,
68747
- blocking,
68756
+ blocking: isNestedChild ? blocking : [
68757
+ ...deployRefusals,
68758
+ ...adoptedDeployRefusals,
68759
+ ...blocking
68760
+ ],
68748
68761
  unreadable: [...unreadable, ...unreadableOrphans],
68749
68762
  children: []
68750
68763
  };
@@ -68778,6 +68791,7 @@ async function buildDiffTree(args) {
68778
68791
  ...cfnFallback !== void 0 && { cfnFallback },
68779
68792
  ...previewOrphanAdoption && { previewOrphanAdoption },
68780
68793
  ancestorTemplatePaths: childAncestorTemplatePaths,
68794
+ isNestedChild: true,
68781
68795
  parentHasSecretReference: secretBearingAbove
68782
68796
  }));
68783
68797
  }
@@ -69214,9 +69228,18 @@ var DiffDetectedError = class DiffDetectedError extends CdkdError {
69214
69228
  }
69215
69229
  };
69216
69230
  /**
69217
- * Signals that the preview completed and `cdkd deploy` would REFUSE to start
69218
- * (issue go-to-k/cdkd#2943) — today, a rollback-orphan record whose physical
69219
- * id another cdkd stack already manages.
69231
+ * Signals that the preview completed and `cdkd deploy` would REFUSE to start.
69232
+ * Two conditions reach it, and the message must fit BOTH:
69233
+ *
69234
+ * - a rollback-orphan record whose physical id another cdkd stack already
69235
+ * manages (issue go-to-k/cdkd#2943), where the remedy is resolving the
69236
+ * ownership conflict between two stacks;
69237
+ * - a container this preview REPAIRED and the deploy refuses — a resource's
69238
+ * unreadable `properties` map or an unreadable `outputs` bag (issue
69239
+ * go-to-k/cdkd#3335), where there is no second stack at all and the remedy
69240
+ * is repairing the one record.
69241
+ *
69242
+ * So the message names no remedy at all and closes on the count.
69220
69243
  *
69221
69244
  * Exit **3**, and neither 1 nor 2, for reasons that are about what a caller
69222
69245
  * can conclude:
@@ -69226,18 +69249,19 @@ var DiffDetectedError = class DiffDetectedError extends CdkdError {
69226
69249
  * code for "there is work to do" and "the work cannot begin".
69227
69250
  * - **2** is this CLI's partial-failure family, documented as "work
69228
69251
  * completed, re-running typically resolves it" (`docs/cli-reference.md`).
69229
- * A refusal is the opposite: re-running changes nothing until a human
69230
- * resolves the ownership conflict.
69252
+ * A refusal is the opposite: re-running changes nothing until a human acts
69253
+ * — repairing the record, or resolving the ownership conflict, depending on
69254
+ * which condition fired.
69231
69255
  *
69232
69256
  * It is NOT silent: unlike `--fail`, this is not a flag the user opted into,
69233
69257
  * so the reason has to be visible even when the diff report scrolled away.
69234
- * The `Blocking` section already printed the per-record detail; this adds the
69235
- * one line that says the run cannot proceed.
69258
+ * The `Blocking` section already printed the per-condition detail; this adds
69259
+ * the one line that says the run cannot proceed.
69236
69260
  */
69237
69261
  var DeployRefusalPreviewError = class DeployRefusalPreviewError extends CdkdError {
69238
69262
  exitCode = 3;
69239
69263
  constructor(count) {
69240
- super(`cdkd deploy would refuse to start: ${count} blocking condition(s) reported above. Resolve them, or run \`cdkd state\` to inspect the conflicting stack.`, "DEPLOY_REFUSAL_PREVIEW");
69264
+ super(`cdkd deploy would refuse to start: ${count} blocking condition(s) reported above.`, "DEPLOY_REFUSAL_PREVIEW");
69241
69265
  this.name = "DeployRefusalPreviewError";
69242
69266
  Object.setPrototypeOf(this, DeployRefusalPreviewError.prototype);
69243
69267
  }
@@ -69362,7 +69386,8 @@ async function diffCommand(stacks, options) {
69362
69386
  canonicalizeProperties,
69363
69387
  ...assetRedirect && { assetRedirect },
69364
69388
  ...options.cfnFallback === false && { cfnFallback: false },
69365
- previewOrphanAdoption
69389
+ previewOrphanAdoption,
69390
+ isNestedChild: false
69366
69391
  }));
69367
69392
  }
69368
69393
  if (options.json) process.stdout.write(`${JSON.stringify(trees.map(diffTreeToJson), null, 2)}\n`);
@@ -70735,9 +70760,15 @@ function scrubRefusalWording(verdict, loggedExportKey, loggedProducerStack, logg
70735
70760
  const through = chain.length > 1 ? ` (through ${chain.slice(0, -1).map((s) => `'${s}'`).join(", ")})` : "";
70736
70761
  const templateClaim = verdict.kind === "declared" ? `declares '${loggedExportKey}' from a {{resolve:...}} expression` : verdict.kind === "chained" ? chainRoot !== void 0 ? `publishes '${loggedExportKey}' by RE-EXPORTING a value that '${chainRoot}' declares from a {{resolve:...}} expression${through}` : `publishes '${loggedExportKey}' by RE-EXPORTING a value another stack of this app declares from a {{resolve:...}} expression` : chainRoot !== void 0 ? `publishes at least one output that RE-EXPORTS a value '${chainRoot}' declares from a {{resolve:...}} expression${through} (this run could not match '${loggedExportKey}' to a declared output, so it cannot say which)` : `publishes at least one output from a {{resolve:...}} expression (this run could not match '${loggedExportKey}' to a declared output, so it cannot say which)`;
70737
70762
  const scrubOrder = dedupePreservingOrder([...chain].reverse().concat(loggedProducerStack), "last");
70763
+ const remedyCommands = scrubOrder.map((name) => `Scrub with: ${pasteableCommand("cdkd scrub", [{
70764
+ value: name,
70765
+ hole: "stack",
70766
+ opts: { patternMatched: true }
70767
+ }]).command}`);
70738
70768
  return {
70739
70769
  templateClaim,
70740
- remedy: scrubOrder.length > 1 ? `Scrub the producers first, from the head of the chain (${scrubOrder.map((s) => `'cdkd scrub ${s}'`).join(", then ")})` : `Scrub the producer first ('cdkd scrub ${loggedProducerStack}')`
70770
+ remedy: scrubOrder.length > 1 ? `Scrub the producers first, from the head of the chain, in the order below` : `Scrub the producer first`,
70771
+ remedyCommands
70741
70772
  };
70742
70773
  }
70743
70774
  /**
@@ -70799,8 +70830,12 @@ function dedupePreservingOrder(values, keep) {
70799
70830
  function plaintextProducerCrossStackReadError(origin, stackName, intrinsic, path, producerStack, exportKey, verdict, secrets) {
70800
70831
  const loggedExportKey = maskSecretsInText(exportKey, secrets);
70801
70832
  const loggedProducerStack = maskSecretsInText(producerStack, secrets);
70802
- const { templateClaim, remedy } = scrubRefusalWording(verdict, loggedExportKey, loggedProducerStack, verdict.via.map((s) => maskSecretsInText(s, secrets)));
70803
- return new ScrubRefusalError(`Scrub of ${stackName} resolved the ${intrinsic} in ${origin}${path ? ` at ${path}` : ""} to a PLAINTEXT value: the producer stack '${loggedProducerStack}' ${templateClaim}, but its own state still stores the resolved plaintext rather than that expression. scrub has no expression to write in this stack's place, so it cannot redact the imported secret and must not report this stack clean. ${remedy}, then re-run 'cdkd scrub ${stackName}' — the read will then return the expression and this stack is scrubbed normally.`, "SCRUB_CROSS_STACK_PRODUCER_PLAINTEXT");
70833
+ const { templateClaim, remedy, remedyCommands } = scrubRefusalWording(verdict, loggedExportKey, loggedProducerStack, verdict.via.map((s) => maskSecretsInText(s, secrets)));
70834
+ return new ScrubRefusalError(`Scrub of ${stackName} resolved the ${intrinsic} in ${origin}${path ? ` at ${path}` : ""} to a PLAINTEXT value: the producer stack '${loggedProducerStack}' ${templateClaim}, but its own state still stores the resolved plaintext rather than that expression. scrub has no expression to write in this stack's place, so it cannot redact the imported secret and must not report this stack clean. ${remedy}, then re-run scrub for this stack — the read will then return the expression and this stack is scrubbed normally.\n${remedyCommands.join("\n")}\nThen re-run: ${pasteableCommand("cdkd scrub", [{
70835
+ value: stackName,
70836
+ hole: "stack",
70837
+ opts: { patternMatched: true }
70838
+ }]).command}`, "SCRUB_CROSS_STACK_PRODUCER_PLAINTEXT");
70804
70839
  }
70805
70840
  /** The `no` verdict, shared so the walk allocates nothing for the common case. */
70806
70841
  const NO_SECRET_EXPRESSION = {
@@ -72421,10 +72456,9 @@ function createScrubCommand() {
72421
72456
  * (phantom drift against an ISO-string baseline, and `--accept` persisted the
72422
72457
  * `{}`). The identity return is what makes the second visible to the
72423
72458
  * comparator; the null-prototype target is what keeps the first an own key.
72424
- * One layer LATER the same flattening survives in `secret-redaction.ts`'s
72425
- * value walk, which `--accept` runs over a resource that RECORDS a secret
72426
- * (issue #2427) -- so the ISO string reaches the baseline only where that
72427
- * walk does not run.
72459
+ * One layer LATER `secret-redaction.ts`'s value walk, which `--accept` runs
72460
+ * over a resource that RECORDS a secret, keeps a `Date` by identity too (issue
72461
+ * #2427), so the ISO string reaches the baseline on both kinds of resource.
72428
72462
  */
72429
72463
  function canonicalizeTagListsDeep(v) {
72430
72464
  if (Array.isArray(v)) {
@@ -76854,7 +76888,13 @@ async function destroyCommand(stackArgs, options) {
76854
76888
  } else if (synthRegion && refs.some((r) => r.region === synthRegion)) stackTargetRegion = synthRegion;
76855
76889
  else {
76856
76890
  const regions = refs.map((r) => r.region ?? "(legacy)").join(", ");
76857
- throw new Error(`Stack '${stackName}' has state in multiple regions: ${regions}. Use 'cdkd state orphan ${stackName} --stack-region <region>' to remove cdkd's record for one region, or run destroy from a CDK app whose env.region matches one of them.`);
76891
+ throw new Error(`Stack '${stackName}' has state in multiple regions: ${regions}. Remove cdkd's record for ONE region with the command below (fill in the region), or run destroy from a CDK app whose env.region matches one of them.\nRemove one record with: ${pasteableCommand("cdkd state orphan", [{
76892
+ value: stackName,
76893
+ hole: "stack"
76894
+ }, {
76895
+ flag: "--stack-region",
76896
+ hole: "region"
76897
+ }]).command}`);
76858
76898
  }
76859
76899
  const stateResult = await stateBackend.getState(stackName, stackTargetRegion);
76860
76900
  if (!stateResult) {
@@ -78744,7 +78784,10 @@ async function exportCommand(stackArg, options) {
78744
78784
  const { state, etag, migrationPending } = stateData;
78745
78785
  const exportJournal = await stateBackend.loadRollbackJournal(resolvedStackName, targetRegion);
78746
78786
  if (exportJournal && exportJournal.segments.length > 0) {
78747
- logger.warn(`Stack '${resolvedStackName}' has a rollback journal — a previous deploy failed or was interrupted and has not been reverted. Exporting this half-deployed state to CloudFormation is likely unintended; run 'cdkd rollback ${resolvedStackName}' to revert (or 'cdkd deploy' to fix forward) first.`);
78787
+ logger.warn(`Stack '${resolvedStackName}' has a rollback journal — a previous deploy failed or was interrupted and has not been reverted. Exporting this half-deployed state to CloudFormation is likely unintended; revert it first (or 'cdkd deploy' to fix forward).\nRevert it with: ${pasteableCommand("cdkd rollback", [{
78788
+ value: resolvedStackName,
78789
+ hole: "stack"
78790
+ }]).command}`);
78748
78791
  if (!options.yes && !options.dryRun) {
78749
78792
  if (!await confirmPrompt$3("Export anyway?")) {
78750
78793
  logger.info("Export cancelled");
@@ -79094,7 +79137,7 @@ function safeDetail(value) {
79094
79137
  */
79095
79138
  function orphanCommandFor(stackName, region) {
79096
79139
  const shownStack = safeSegment(stackName);
79097
- if (!(typeof stackName === "string" && shownStack === stackName && typeof region === "string" && safeSegment(region) === region)) return "cdkd state orphan <stack> --stack-region <region> — spelled out because this record's name or region does NOT render exactly, so another record may render identically; list them as stored with 'cdkd state list --long' and act on the one whose key matches";
79140
+ if (!(typeof stackName === "string" && shownStack === stackName && !stackName.startsWith("-") && typeof region === "string" && safeSegment(region) === region && !region.startsWith("-"))) return "cdkd state orphan <stack> --stack-region <region> — spelled out because this record's name or region does NOT render exactly, so another record may render identically; list them as stored with 'cdkd state list --long' and act on the one whose key matches";
79098
79141
  return `cdkd state orphan ${shellQuote(shownStack)} --stack-region ${shellQuote(safeSegment(region))}`;
79099
79142
  }
79100
79143
  /**
@@ -80975,7 +81018,10 @@ async function runPerStackImportLoop(args) {
80975
81018
  } catch (err) {
80976
81019
  const importedSummary = importedStacks.length > 0 ? importedStacks.map((s) => `${safeSegment(s.cdkdStackName)} → ${safeSegment(s.cfnStackName)}`).join(", ") : "(none)";
80977
81020
  const remainingSummary = perStackPlans.slice(i).map((p) => safeSegment(p.cdkdName)).join(", ");
80978
- throw new Error(`Phase 1A IMPORT changeset failed for cdkd stack '${safeSegment(plan.cdkdName)}' (CFn name '${plan.cfnName}'). Stacks imported successfully so far: ${importedSummary}. Stacks not yet imported (cdkd state preserved): ${remainingSummary}. After resolving the underlying cause, re-run 'cdkd export ${rootStackName}' — already-imported children will be adopted as nested references on retry. Cause: ${err instanceof Error ? err.message : String(err)}`, { cause: err instanceof Error ? err : void 0 });
81021
+ throw new Error(`Phase 1A IMPORT changeset failed for cdkd stack '${safeSegment(plan.cdkdName)}' (CFn name '${plan.cfnName}'). Stacks imported successfully so far: ${importedSummary}. Stacks not yet imported (cdkd state preserved): ${remainingSummary}. After resolving the underlying cause, re-run the export — already-imported children will be adopted as nested references on retry. Cause: ${err instanceof Error ? err.message : String(err)}\nRe-run with: ${pasteableCommand("cdkd export", [{
81022
+ value: rootStackName,
81023
+ hole: "stack"
81024
+ }]).command}`, { cause: err instanceof Error ? err : void 0 });
80979
81025
  }
80980
81026
  const cfnArn = (await deps.cfnClient.send(new DescribeStacksCommand({ StackName: plan.cfnName }))).Stacks?.[0]?.StackId;
80981
81027
  if (!cfnArn) throw new Error(`runPerStackImportLoop: DescribeStacks returned no StackId for '${plan.cfnName}' immediately after Phase 1A IMPORT — AWS may be in an unexpected state.`);
@@ -81757,7 +81803,11 @@ async function stateResourcesCommand(stackName, options) {
81757
81803
  const setup = await setupStateBackend(options);
81758
81804
  try {
81759
81805
  const ref = resolveSingleRegion(stackName, await setup.stateBackend.listStacks(), options.stackRegion);
81760
- if (!ref.region) throw new Error(`Stack '${safe$1(stackName)}' has only a legacy state record without a region. Run 'cdkd deploy ${safe$1(stackName)}' (or any cdkd write) to migrate it to the region-scoped layout, then re-run this command.`);
81806
+ if (!ref.region) throw new Error(`Stack '${safe$1(stackName)}' has only a legacy state record without a region. A cdkd write migrates it to the region-scoped layout; re-run this command after it.\nMigrate with: ${pasteableCommand("cdkd deploy", [{
81807
+ value: stackName,
81808
+ hole: "stack",
81809
+ opts: { patternMatched: true }
81810
+ }]).command}`);
81761
81811
  const stateResult = await setup.stateBackend.getState(stackName, ref.region);
81762
81812
  if (!stateResult) throw new Error(`No state found for stack '${safe$1(stackName)}' (${safe$1(ref.region)}) in s3://${setup.bucket}/${setup.prefix}/. Run 'cdkd state list' to see available stacks.`);
81763
81813
  if (repairMalformedResourcesForReadOnly(stateResult.state)) logger.warn(malformedResourcesWarning(stackName, ref.region));
@@ -81964,7 +82014,11 @@ async function stateShowCommand(stackName, options) {
81964
82014
  const setup = await setupStateBackend(options);
81965
82015
  try {
81966
82016
  const ref = resolveSingleRegion(stackName, await setup.stateBackend.listStacks(), options.stackRegion);
81967
- if (!ref.region) throw new Error(`Stack '${safe$1(stackName)}' has only a legacy state record without a region. Run 'cdkd deploy ${safe$1(stackName)}' (or any cdkd write) to migrate it to the region-scoped layout, then re-run this command.`);
82017
+ if (!ref.region) throw new Error(`Stack '${safe$1(stackName)}' has only a legacy state record without a region. A cdkd write migrates it to the region-scoped layout; re-run this command after it.\nMigrate with: ${pasteableCommand("cdkd deploy", [{
82018
+ value: stackName,
82019
+ hole: "stack",
82020
+ opts: { patternMatched: true }
82021
+ }]).command}`);
81968
82022
  const [stateResult, lockInfo] = await Promise.all([setup.stateBackend.getState(stackName, ref.region), setup.lockManager.getLockInfo(stackName, ref.region)]);
81969
82023
  if (!stateResult) throw new Error(`No state found for stack '${safe$1(stackName)}' (${safe$1(ref.region)}) in s3://${setup.bucket}/${setup.prefix}/. Run 'cdkd state list' to see available stacks.`);
81970
82024
  if (options.showNested) {
@@ -82432,7 +82486,11 @@ async function stateOrphanCommand(stackArgs, options) {
82432
82486
  }
82433
82487
  if (!options.yes && !options.force) {
82434
82488
  const targetList = targets.map((t) => formatStackRefSafe(t)).join(", ");
82435
- process.stdout.write(`\nWARNING: This removes cdkd's state record for [${targetList}] only. AWS resources will NOT be deleted.\nUse 'cdkd destroy ${displaySafe(stackName, { asciiOnly: true }) || "<unrenderable>"}' if you want to delete the actual resources.\n\n`);
82489
+ process.stdout.write(`\nWARNING: This removes cdkd's state record for [${targetList}] only. AWS resources will NOT be deleted.\nDelete the actual resources instead with the command below.\nDestroy with: ${pasteableCommand("cdkd destroy", [{
82490
+ value: stackName,
82491
+ hole: "stack",
82492
+ opts: { patternMatched: true }
82493
+ }]).command}\n\n`);
82436
82494
  if (!await confirmStateOrphanRemoval(`Remove state for ${targetList} from s3://${setup.bucket}/${setup.prefix}/?`)) {
82437
82495
  logger.info(`Cancelled removal of state for stack: ${stackName}`);
82438
82496
  continue;
@@ -82964,6 +83022,68 @@ function createStateInfoCommand() {
82964
83022
  return cmd;
82965
83023
  }
82966
83024
  /**
83025
+ * The hole the legacy refusal's `cdkd deploy` prints for a name it cannot
83026
+ * name, and the key {@link withheldNameClause} reads the reason back by. ONE
83027
+ * spelling so the two cannot drift apart (m22 of the go-to-k/cdkd#3499 review).
83028
+ */
83029
+ const STACK_HOLE = "stack";
83030
+ /**
83031
+ * The sentence for a name `pasteableCommand` would not print, rendered from the
83032
+ * REASON it gave rather than from a predicate of this site's own — M11 of the
83033
+ * go-to-k/cdkd#3499 review. Keying it on a second predicate got both directions
83034
+ * wrong at once: `Old;Stack` renders exactly, so a rendering-based clause called
83035
+ * a command that names it "not exact", and `--all` also renders exactly, so the
83036
+ * same clause said nothing above a hole the operator was then invited to fill
83037
+ * with the name printed beside it.
83038
+ *
83039
+ * THREE of the five reasons are reachable from this call site, and the other
83040
+ * two are bounded out by where the name comes from — an S3 key segment:
83041
+ *
83042
+ * - `altered`, `option-shaped` and `pattern-shaped` are all reachable: a
83043
+ * planted key can spell a name any of those ways in a handful of bytes.
83044
+ * - `empty` is not. `listStacks` drops a key whose stack segment is empty
83045
+ * (`s3-state-backend.ts`'s `if (!stackName) continue`), so `target.stackName`
83046
+ * is non-empty by the time the refusal is built.
83047
+ * - `too-long` is not either, and the bound is not obvious: the cap is
83048
+ * `STACK_REF_MAX_CODE_POINTS` (1152), while S3 caps the WHOLE key at 1024
83049
+ * bytes, leaving at most 1008 for the name inside `cdkd/<name>/state.json`.
83050
+ * A multi-byte character only lowers the code-point count further, so a name
83051
+ * arriving through `listStacks` can never reach the cap.
83052
+ *
83053
+ * Both unreachable arms stay, and are covered, because the REASON is the
83054
+ * gate's and not this site's: `pasteableCommand` can return either to a caller
83055
+ * whose value comes from somewhere else. A missing arm is no longer a silent
83056
+ * fall-through — the `switch` below is exhaustive over `WithholdReason`, so
83057
+ * dropping one is a compile error (M13). What the cases buy on top of that is
83058
+ * the SENTENCE: the type checker knows an arm exists, not that it says the
83059
+ * right thing, and substituting one arm's text for another's is exactly the
83060
+ * disagreement M11 closed.
83061
+ */
83062
+ function withheldNameClause(built) {
83063
+ const reason = built.withheld.find((w) => w.hole === STACK_HOLE)?.reason;
83064
+ if (reason === void 0) return "";
83065
+ let why;
83066
+ switch (reason) {
83067
+ case "altered":
83068
+ why = `does NOT render exactly, so another record may render identically`;
83069
+ break;
83070
+ case "empty":
83071
+ why = `is empty`;
83072
+ break;
83073
+ case "too-long":
83074
+ why = `is too long to print`;
83075
+ break;
83076
+ case "option-shaped":
83077
+ why = "begins with a '-', so it is not safe to print as an argument to 'cdkd deploy' — a name like '--all' is parsed as the FLAG and targets every stack";
83078
+ break;
83079
+ case "pattern-shaped":
83080
+ why = `would be read as a PATTERN by 'cdkd deploy', which can match other stacks`;
83081
+ break;
83082
+ default: throw new Error(`withheldNameClause: unhandled WithholdReason ${String(reason)}`);
83083
+ }
83084
+ return ` This record's name ${why} — so it is not named in the command below; list the records as stored with 'cdkd state list --long' and act on the one whose key matches.`;
83085
+ }
83086
+ /**
82967
83087
  * `cdkd state refresh-observed <stack>` command implementation.
82968
83088
  *
82969
83089
  * Walks every resource in the given stack(s) and refreshes its
@@ -83053,9 +83173,13 @@ async function stateRefreshObservedCommand(stackArgs, options) {
83053
83173
  }
83054
83174
  }
83055
83175
  for (const target of targets) if (!target.region) {
83056
- const stack = shellQuote(safeStackName(target.stackName));
83057
- const example = safeStackName(target.stackName) === target.stackName && !/^-|[*/]/.test(target.stackName) ? ` For example: cdkd deploy ${stack}` : "";
83058
- throw new Error(`Stack ${stack} has only a legacy state record without a region. Migrate it to the region-scoped layout with any cdkd write, then re-run refresh-observed.` + example);
83176
+ const stack = displayIdent(target.stackName, { maxCodePoints: STACK_REF_MAX_CODE_POINTS });
83177
+ const migrate = pasteableCommand("cdkd deploy", [{
83178
+ value: target.stackName,
83179
+ hole: STACK_HOLE,
83180
+ opts: { patternMatched: true }
83181
+ }]);
83182
+ throw new Error(`Stack ${stack} has only a legacy state record without a region. Migrate it to the region-scoped layout with any cdkd write, then re-run refresh-observed.` + withheldNameClause(migrate) + `\nMigrate with: ${migrate.command}`);
83059
83183
  }
83060
83184
  let totalRefreshed = 0;
83061
83185
  let totalUnsupported = 0;
@@ -83644,7 +83768,10 @@ async function rollbackCommand(stackArg, options) {
83644
83768
  const fresh = await setup.stateBackend.getState(stackName, region);
83645
83769
  currentEtag = await setup.stateBackend.saveState(stackName, region, next(), { ...fresh?.etag !== void 0 && { expectedEtag: fresh.etag } });
83646
83770
  } catch (retryError) {
83647
- logger.warn(`Failed to persist state after a rollback operation: ${displaySafe(retryError instanceof Error ? retryError.message : String(retryError))}. The resource was reverted in AWS; re-run 'cdkd rollback ${safeStack(stackName)}' to reconcile state.`);
83771
+ logger.warn(`Failed to persist state after a rollback operation: ${displaySafe(retryError instanceof Error ? retryError.message : String(retryError))}. The resource was reverted in AWS; re-run the rollback to reconcile state.\nRe-run with: ${pasteableCommand("cdkd rollback", [{
83772
+ value: stackName,
83773
+ hole: "stack"
83774
+ }]).command}`);
83648
83775
  }
83649
83776
  }
83650
83777
  };
@@ -83725,8 +83852,14 @@ async function rollbackCommand(stackArg, options) {
83725
83852
  await setup.stateBackend.deleteState(stackName, region);
83726
83853
  logger.info(`State for '${safeStack(stackName)}' (${safe(region)}) removed (stack fully rolled back).`);
83727
83854
  }
83728
- if (interrupted) throw new PartialFailureError(`Rollback interrupted. Journal preserved — re-run 'cdkd rollback ${safeStack(stackName)}' to finish.`);
83729
- if (totalFailures > 0) throw new PartialFailureError(`Rollback completed with ${totalFailures} failed operation(s). Journal preserved — re-run 'cdkd rollback ${safeStack(stackName)}' to retry.`);
83855
+ if (interrupted) throw new PartialFailureError(`Rollback interrupted. Journal preserved — re-run the rollback to finish.\nRe-run with: ${pasteableCommand("cdkd rollback", [{
83856
+ value: stackName,
83857
+ hole: "stack"
83858
+ }]).command}`);
83859
+ if (totalFailures > 0) throw new PartialFailureError(`Rollback completed with ${totalFailures} failed operation(s). Journal preserved — re-run the rollback to retry.\nRe-run with: ${pasteableCommand("cdkd rollback", [{
83860
+ value: stackName,
83861
+ hole: "stack"
83862
+ }]).command}`);
83730
83863
  if (totalWarnings > 0) throw new PartialFailureError(`Rollback completed with ${totalWarnings} skipped/unrecoverable operation(s) (see warnings above).`);
83731
83864
  logger.info(`\nRollback of '${safeStack(stackName)}' (${safe(region)}) complete.`);
83732
83865
  } finally {
@@ -84073,7 +84206,13 @@ function printRunList(stackName, region, runs) {
84073
84206
  const resultColored = result === "SUCCEEDED" ? green(result) : result === "UNKNOWN" || result === "<unrenderable>" ? gray(result) : red(result);
84074
84207
  logger.info(` ${cyan(safeId(run.runId) || "<unrenderable>")} ${safeId(run.command) || "<unrenderable>"} ${resultColored} ${gray(safeId(run.startedAt) || "?")} -> ${gray(safeId(run.finishedAt) || "?")} ${gray(`cdkd ${safeId(run.cdkdVersion) || "<unrenderable>"}`)} ${gray(`${safeCount(run.eventCount)} events`)}`);
84075
84208
  }
84076
- logger.info(gray(`\nUse 'cdkd events ${safeStack} --run <runId>' to read one run's events.`));
84209
+ logger.info(gray(`\nRead one run's events with: ${pasteableCommand("cdkd events", [{
84210
+ value: stackName,
84211
+ hole: "stack"
84212
+ }, {
84213
+ flag: "--run",
84214
+ hole: "runId"
84215
+ }]).command}`));
84077
84216
  }
84078
84217
  /** Human-readable single-run event stream (in recorded order). */
84079
84218
  /**
@@ -89366,7 +89505,9 @@ function resolveAssetCodeDirectory(opts) {
89366
89505
  * `process.cwd()` survives only for a `StackInfo` carrying NEITHER field, where
89367
89506
  * base and bound coincide again.
89368
89507
  *
89369
- * Exported for `local-start-api.ts`'s copy of the caller, and for unit testing.
89508
+ * Exported for `local-start-api.ts`'s copy of the caller, for the Docker
89509
+ * context check the ECS emulator commands run over cdk-local's `StackInfo`
89510
+ * (which is why it takes only the two fields it reads), and for unit testing.
89370
89511
  */
89371
89512
  function assetPathDirs(stack) {
89372
89513
  const manifestDir = stack.assetManifestPath ? dirname(stack.assetManifestPath) : stack.assetOutdir ?? process.cwd();
@@ -90870,7 +91011,7 @@ async function runDetached(opts) {
90870
91011
  args.push(opts.image, ...entryPointTail, ...opts.cmd);
90871
91012
  const logger = getLogger().child("docker");
90872
91013
  logger.debug(`${getDockerCmd()} ${redactDockerArgvValues(args).join(" ")}`);
90873
- if (collisions.length > 0) logger.warn(`Env var(s) ${collisions.map((k) => JSON.stringify(k)).join(", ")} share a name with a docker-client environment variable or have a malformed name (empty, or containing '=' / NUL), and were NOT passed to the container at all (a colliding name would hijack the docker client; a malformed name cannot form a valid environment variable). Rename the env var if the container needs it.`);
91014
+ if (collisions.length > 0) logger.warn(`Env var(s) ${collisions.map((k) => JSON.stringify(k)).join(", ")} share a name with a variable the container client reads (docker, or the CDK_DOCKER binary) or have a malformed name (empty, or containing '=' / NUL), and were NOT passed to the container at all (a colliding name would hijack that client; a malformed name cannot form a valid environment variable). Rename the env var if the container needs it.`);
90874
91015
  try {
90875
91016
  const { stdout } = await execFileAsync$3(getDockerCmd(), args, {
90876
91017
  maxBuffer: 10485760,
@@ -91002,13 +91143,25 @@ const SENSITIVE_ENV_KEYS = /* @__PURE__ */ new Set([
91002
91143
  * wrapper translates cdk-local's thrown error back to cdkd's class at the shim
91003
91144
  * boundary, so a local-invoke build failure still surfaces with cdkd's exit code
91004
91145
  * / branding. `architectureToPlatform` is a pure helper (re-exported directly).
91146
+ *
91147
+ * It is also where the build context is CONTAINED: cdk-local joins
91148
+ * `source.directory` onto `cdkOutDir` with no check, so the shim refuses an
91149
+ * escaping value before delegating, against a REQUIRED `assetOutdir` bound
91150
+ * (issue [#3503](https://github.com/go-to-k/cdkd/issues/3503)).
91005
91151
  * See cdk-local's `src/local/docker-image-builder.ts`.
91006
91152
  */
91007
- async function buildContainerImage$1(...args) {
91008
- const executable = args[0]?.source?.executable;
91153
+ async function buildContainerImage$1(asset, cdkOutDir, options) {
91154
+ const executable = asset?.source?.executable;
91009
91155
  if (executable && executable.length > 0) warnManifestExecutable(executable);
91156
+ const { assetOutdir, ...implOptions } = options;
91157
+ assertCdkLocalDockerContextContained({
91158
+ manifestDir: cdkOutDir,
91159
+ source: asset.source,
91160
+ assetOutdir,
91161
+ wrapError: (message) => new LocalInvokeBuildError$1(`Refusing to build the container image: ${message}`)
91162
+ });
91010
91163
  try {
91011
- return await buildContainerImage(...args);
91164
+ return await buildContainerImage(asset, cdkOutDir, implOptions);
91012
91165
  } catch (e) {
91013
91166
  if (e instanceof LocalInvokeBuildError) throw new LocalInvokeBuildError$1(e.message);
91014
91167
  throw e;
@@ -93240,7 +93393,10 @@ async function buildContainerSpec(args) {
93240
93393
  async function resolveContainerImageForStartApi(lambda, skipPull) {
93241
93394
  const logger = getLogger();
93242
93395
  const localBuild = await resolveLocalBuildPlan$1(lambda);
93243
- if (localBuild) return { imageRef: await buildContainerImage$1(localBuild.asset, localBuild.cdkOutDir, { architecture: lambda.architecture }) };
93396
+ if (localBuild) return { imageRef: await buildContainerImage$1(localBuild.asset, localBuild.cdkOutDir, {
93397
+ assetOutdir: localBuild.assetOutdir,
93398
+ architecture: lambda.architecture
93399
+ }) };
93244
93400
  if (!parseEcrUri(lambda.imageUri)) throw new Error(`Container Lambda '${lambda.logicalId}' has no matching asset in cdk.out, and Code.ImageUri '${lambda.imageUri}' is not an ECR URI cdkd can authenticate against. Re-synthesize the CDK app (so cdk.out includes the build context) or deploy the image to ECR first.`);
93245
93401
  logger.info(`No matching cdk.out asset for ${lambda.imageUri}; falling back to ECR pull (uses your own credentials — this command has no --ecr-role-arn, so a cross-account image needs cross-account ECR permissions)...`);
93246
93402
  return { imageRef: await pullEcrImage(lambda.imageUri, { skipPull }) };
@@ -93254,16 +93410,16 @@ async function resolveContainerImageForStartApi(lambda, skipPull) {
93254
93410
  * the two commands evolve their asset-lookup heuristics independently.
93255
93411
  */
93256
93412
  async function resolveLocalBuildPlan$1(lambda) {
93257
- const manifestPath = lambda.stack.assetManifestPath;
93258
- if (!manifestPath) return void 0;
93259
- const cdkOutDir = path.dirname(manifestPath);
93413
+ if (!lambda.stack.assetManifestPath) return void 0;
93414
+ const { manifestDir: cdkOutDir, assetOutdir } = assetPathDirs(lambda.stack);
93260
93415
  const manifest = await new AssetManifestLoader().loadManifest(cdkOutDir, lambda.stack.stackName);
93261
93416
  if (!manifest) return void 0;
93262
93417
  const entry = getDockerImageBySourceHash(manifest, lambda.imageUri);
93263
93418
  if (!entry) return void 0;
93264
93419
  return {
93265
93420
  asset: entry.asset,
93266
- cdkOutDir
93421
+ cdkOutDir,
93422
+ assetOutdir
93267
93423
  };
93268
93424
  }
93269
93425
  /**
@@ -94123,7 +94279,7 @@ async function createNetworkAndSidecar(args) {
94123
94279
  if (cluster) sidecarEnv["CLUSTER"] = cluster;
94124
94280
  const { flags: sidecarFlags, sensitiveEnv: sidecarSensitiveEnv, collisions: sidecarCollisions } = partitionSensitiveEnv(sidecarEnv, SENSITIVE_ENV_KEYS);
94125
94281
  sidecarArgs.push(...sidecarFlags);
94126
- if (sidecarCollisions.length > 0) logger.warn(`Sidecar env var(s) ${sidecarCollisions.map((k) => JSON.stringify(k)).join(", ")} share a name with a docker-client environment variable and were NOT passed to the sidecar at all (they would hijack the docker client).`);
94282
+ if (sidecarCollisions.length > 0) logger.warn(`Sidecar env var(s) ${sidecarCollisions.map((k) => JSON.stringify(k)).join(", ")} share a name with a variable the container client reads (docker, or the CDK_DOCKER binary) and were NOT passed to the sidecar at all (they would hijack that client).`);
94127
94283
  sidecarArgs.push(METADATA_ENDPOINT_IMAGE);
94128
94284
  logger.info(`Starting ECS local-container-endpoints sidecar at ${sidecarIp}...`);
94129
94285
  try {
@@ -94489,7 +94645,7 @@ async function runEcsTask(task, options, state) {
94489
94645
  });
94490
94646
  dockerCmds.set(container.name, built.args);
94491
94647
  dockerEnvs.set(container.name, built.sensitiveEnv);
94492
- if (built.collisions.length > 0) logger.warn(`Container '${container.name}': secret(s) ${built.collisions.map((k) => JSON.stringify(k)).join(", ")} share a name with a docker-client environment variable or have a malformed name (empty, or containing '=' / NUL), and were NOT passed to the container at all (a colliding name would hijack the docker client; a malformed name cannot form a valid environment variable). Rename the secret if the container needs it.`);
94648
+ if (built.collisions.length > 0) logger.warn(`Container '${container.name}': secret(s) ${built.collisions.map((k) => JSON.stringify(k)).join(", ")} share a name with a variable the container client reads (docker, or the CDK_DOCKER binary) or have a malformed name (empty, or containing '=' / NUL), and were NOT passed to the container at all (a colliding name would hijack that client; a malformed name cannot form a valid environment variable). Rename the secret if the container needs it.`);
94493
94649
  }
94494
94650
  const startedByName = /* @__PURE__ */ new Map();
94495
94651
  for (const containerName of startOrder) {
@@ -95388,6 +95544,130 @@ function createLocalRunTaskCommand() {
95388
95544
  return cmd;
95389
95545
  }
95390
95546
 
95547
+ //#endregion
95548
+ //#region src/cli/commands/emulator-docker-context.ts
95549
+ /**
95550
+ * Decorate an `EmulatorStrategy` so every Docker image asset the bundled
95551
+ * engine could build for this run is containment-checked BEFORE it builds one
95552
+ * (issue [#3503](https://github.com/go-to-k/cdkd/issues/3503)).
95553
+ *
95554
+ * `cdkd local start-service` / `start-alb` hand the whole run to cdk-local's
95555
+ * `runEcsServiceEmulator`, which reads each stack's asset manifest itself and
95556
+ * joins `source.directory` onto the manifest directory raw — for an ECS
95557
+ * container image and for an ALB Lambda target's container image alike. No
95558
+ * argument reaches that join, so cdkd judges the manifests here instead.
95559
+ *
95560
+ * `resolveBoots` is the seam because the engine calls it after every synth —
95561
+ * at boot, and again on every `--watch` reload — and before any image is
95562
+ * built on either path. A refusal at boot fails the command; on a reload the
95563
+ * engine logs it and keeps the previous replicas serving, which is the
95564
+ * fail-closed outcome.
95565
+ *
95566
+ * Every stack with a manifest is checked, not only the booted targets' stacks:
95567
+ * which stack a target resolves to is decided inside the engine, and the ALB
95568
+ * front door builds Lambda images from stacks no ECS boot names. An escaping
95569
+ * `source.directory` is never something a real `cdk synth` writes, so
95570
+ * refusing one anywhere in the assembly costs a legitimate run nothing, and
95571
+ * `cdkd deploy` refuses the same assembly.
95572
+ */
95573
+ function containEmulatorDockerContexts(strategy) {
95574
+ return {
95575
+ ...strategy,
95576
+ resolveBoots: (stacks, chosenTargets) => {
95577
+ assertEmulatorDockerContextsContained(stacks);
95578
+ return strategy.resolveBoots(stacks, chosenTargets);
95579
+ }
95580
+ };
95581
+ }
95582
+ /**
95583
+ * The check itself, over the manifests the engine will read. Exported for
95584
+ * unit testing.
95585
+ *
95586
+ * It reads the SAME file the engine does — `<manifest dir>/<stackName>.assets.json`,
95587
+ * not `assetManifestPath` verbatim — and refuses a `stackName` that would
95588
+ * carry that filename out of the manifest directory, as cdkd's own
95589
+ * `AssetManifestLoader.loadManifest` does.
95590
+ *
95591
+ * A manifest that is absent, unreadable or not JSON is skipped: the engine
95592
+ * reads it again and reports that failure itself, before building anything.
95593
+ */
95594
+ function assertEmulatorDockerContextsContained(stacks) {
95595
+ for (const stack of stacks) {
95596
+ if (!stack.assetManifestPath) continue;
95597
+ const dirs = assetPathDirs(stack);
95598
+ const { manifestDir } = dirs;
95599
+ const assetOutdir = stack.assetOutdir === void 0 || stack.assetOutdir === "" ? dirs.assetOutdir : engineAssemblyRoot(stack.assetOutdir);
95600
+ const manifestFile = resolveAssemblyPath(manifestDir, `${stack.stackName}.assets.json`);
95601
+ if (!manifestFile.contained) throw new LocalInvokeBuildError$1(`Refusing to build container images: the asset manifest for stack '${displaySafe(stack.stackName)}' ${renderAssemblyPathEscape(manifestFile, manifestDir)}`);
95602
+ const dockerImages = readDockerImages(manifestFile.path);
95603
+ for (const [assetId, asset] of Object.entries(dockerImages)) {
95604
+ const source = asset?.source;
95605
+ if (source === null || typeof source !== "object") continue;
95606
+ const { directory } = source;
95607
+ if (typeof directory !== "string") continue;
95608
+ assertCdkLocalDockerContextContained({
95609
+ manifestDir,
95610
+ source,
95611
+ assetOutdir,
95612
+ wrapError: (message) => new LocalInvokeBuildError$1(`Refusing to build container image asset '${displaySafe(assetId)}' of stack '${displaySafe(stack.stackName)}': ${message}`)
95613
+ });
95614
+ }
95615
+ }
95616
+ }
95617
+ /**
95618
+ * The containment bound the ENGINE itself uses for a stack it synthesized: its
95619
+ * `assetOutdir`, climbed to the parent assembly when `--app` names a
95620
+ * `cdk.Stage` sub-assembly. cdk-local enumerates no Stage stacks from an app
95621
+ * root, so `--app cdk.out/assembly-<Stage>` is the only way these commands run
95622
+ * a Stage, and its assets are staged one level up as `../asset.<hash>`.
95623
+ *
95624
+ * A copy of cdk-local's `assemblyRootOf` (not exported by the package): climb
95625
+ * only while the directory is named `assembly-*` AND its parent's
95626
+ * `manifest.json` declares it as a `cdk:cloud-assembly` artifact, and WARN
95627
+ * whenever a climb happens, since the predicate is read from the tree being
95628
+ * judged. Replace with the upstream export once one exists (go-to-k/cdkd#3597).
95629
+ */
95630
+ function engineAssemblyRoot(outdir) {
95631
+ let dir = resolve(outdir);
95632
+ let climbed = false;
95633
+ for (;;) {
95634
+ if (!basename(dir).startsWith("assembly-")) break;
95635
+ const parent = dirname(dir);
95636
+ if (parent === dir || !parentDeclaresNestedAssembly(parent, basename(dir))) break;
95637
+ dir = parent;
95638
+ climbed = true;
95639
+ }
95640
+ if (!climbed) return outdir;
95641
+ if (!warnedDerivedRoots.has(dir)) {
95642
+ warnedDerivedRoots.add(dir);
95643
+ getLogger().warn(`'${displaySafe(outdir)}' is a cdk.Stage sub-assembly, so cdkd is treating its parent '${displaySafe(dir)}' as the assembly root when it checks Docker build contexts — that is where cdk synth stages a Stage's assets. Everything under that parent is now inside the containment bound, including siblings of the directory you named.`);
95644
+ }
95645
+ return dir;
95646
+ }
95647
+ const warnedDerivedRoots = /* @__PURE__ */ new Set();
95648
+ function parentDeclaresNestedAssembly(parent, child) {
95649
+ try {
95650
+ const artifacts = JSON.parse(readFileSync(join(parent, "manifest.json"), "utf-8")).artifacts;
95651
+ if (artifacts === null || typeof artifacts !== "object") return false;
95652
+ return Object.values(artifacts).some((a) => {
95653
+ const art = a;
95654
+ return art?.type === "cdk:cloud-assembly" && art.properties?.directoryName === child;
95655
+ });
95656
+ } catch {
95657
+ return false;
95658
+ }
95659
+ }
95660
+ function readDockerImages(manifestPath) {
95661
+ let parsed;
95662
+ try {
95663
+ parsed = JSON.parse(readFileSync(manifestPath, "utf-8"));
95664
+ } catch {
95665
+ return {};
95666
+ }
95667
+ const dockerImages = parsed?.dockerImages;
95668
+ return dockerImages !== null && typeof dockerImages === "object" ? dockerImages : {};
95669
+ }
95670
+
95391
95671
  //#endregion
95392
95672
  //#region src/cli/commands/local-start-service.ts
95393
95673
  /**
@@ -95415,6 +95695,17 @@ function serviceStrategy() {
95415
95695
  };
95416
95696
  }
95417
95697
  /**
95698
+ * The `EmulatorStrategy` `cdkd local start-service` runs with:
95699
+ * {@link serviceStrategy}, decorated by `containEmulatorDockerContexts` so the
95700
+ * engine's own Docker builds are containment-checked (issue
95701
+ * [#3503](https://github.com/go-to-k/cdkd/issues/3503)). A NAMED seam for the
95702
+ * same reason `buildAlbEmulatorStrategy` is one: inline in the action, the
95703
+ * decorator was deletable at its only call site.
95704
+ */
95705
+ function buildServiceEmulatorStrategy() {
95706
+ return containEmulatorDockerContexts(serviceStrategy());
95707
+ }
95708
+ /**
95418
95709
  * `cdkl start-service <Stack/Service>...` — run one or more `AWS::ECS::Service`
95419
95710
  * resources locally as a long-running emulator. Spins up DesiredCount task
95420
95711
  * replicas per service (clamped by --max-tasks) using the same per-task
@@ -95427,7 +95718,7 @@ function serviceStrategy() {
95427
95718
  */
95428
95719
  function createLocalStartServiceCommand() {
95429
95720
  const cmd = new Command("start-service").description("Run one or more AWS::ECS::Service resources locally as a long-running emulator. Spins up DesiredCount task replicas per service (clamped by --max-tasks) using the same per-task docker network + metadata sidecar pattern as `cdkd local run-task`, then keeps each replica running and restarts it on exit per --restart-policy. ^C tears every replica + sidecar + network down. Each <target> accepts a CDK display path (MyStack/MyService) or stack-qualified logical ID (MyStack:MyServiceXYZ); single-stack apps may omit the stack prefix. When two or more <target>s are supplied, every service is booted into a shared Cloud Map / Service Connect registry so peer services discover each other via docker --add-host overlay (Issue #460). Omit <targets> in an interactive terminal to multi-select the ECS services from a list.").argument("[targets...]", "One or more CDK display paths or stack-qualified logical IDs of the AWS::ECS::Service resources to run (omit to multi-select interactively in a TTY)").addOption(new Option("--from-state", "Read cdkd's S3 state for the target stack and substitute Ref / Fn::GetAtt / Fn::Sub / Fn::ImportValue / Fn::GetStackOutput intrinsics in container images, environment variables, secrets, role ARNs, and volumes. Mutually exclusive with --from-cfn-stack.").default(false)).addOption(new Option("--state-bucket <bucket>", "S3 bucket for --from-state. Falls back to CDKD_STATE_BUCKET env or cdk.json context.cdkd.stateBucket.")).addOption(new Option("--state-prefix <prefix>", "S3 key prefix for --from-state state files.").default("cdkd")).action(withErrorHandling(async (targets, options) => {
95430
- await runEcsServiceEmulator(targets, options, serviceStrategy(), cdkdExtraStateProviders);
95721
+ await runEcsServiceEmulator(targets, options, buildServiceEmulatorStrategy(), cdkdExtraStateProviders);
95431
95722
  }));
95432
95723
  addStartServiceSpecificOptions(cmd);
95433
95724
  return adoptDeprecatedRegionFlag(addCommonEcsServiceOptions(cmd));
@@ -96323,6 +96614,7 @@ async function resolveAgentCoreImage(resolved, options, assemblyDir, loaded) {
96323
96614
  if (manifest) {
96324
96615
  const entry = getDockerImageBySourceHash(manifest, containerUri);
96325
96616
  if (entry) return buildContainerImage$1(entry.asset, cdkOutDir, {
96617
+ assetOutdir: assemblyDir,
96326
96618
  architecture,
96327
96619
  noBuild: options.build === false
96328
96620
  });
@@ -97115,7 +97407,8 @@ function warnUnresolvedLambdaTargetEnv(strategy, options) {
97115
97407
  }
97116
97408
  /**
97117
97409
  * The `EmulatorStrategy` `cdkd local start-alb` runs with: cdk-local's ALB
97118
- * strategy, decorated by {@link warnUnresolvedLambdaTargetEnv}.
97410
+ * strategy, decorated by {@link warnUnresolvedLambdaTargetEnv} and then by
97411
+ * `containEmulatorDockerContexts` (issue go-to-k/cdkd#3503).
97119
97412
  *
97120
97413
  * Extracted from the command's action for ONE reason, and it is the finding
97121
97414
  * that produced it: with the composition written inline in the `.action(...)`
@@ -97135,7 +97428,7 @@ function warnUnresolvedLambdaTargetEnv(strategy, options) {
97135
97428
  * can satisfy reads as coverage while providing none.
97136
97429
  */
97137
97430
  function buildAlbEmulatorStrategy(options) {
97138
- return warnUnresolvedLambdaTargetEnv(albStrategy(options), options);
97431
+ return containEmulatorDockerContexts(warnUnresolvedLambdaTargetEnv(albStrategy(options), options));
97139
97432
  }
97140
97433
  /**
97141
97434
  * `cdkl start-alb <Stack/Alb>` — Issue #86 v1. Names an
@@ -97693,6 +97986,7 @@ async function resolveContainerImagePlan(lambda, options) {
97693
97986
  const localBuild = await resolveLocalBuildPlan(lambda);
97694
97987
  let imageRef;
97695
97988
  if (localBuild) imageRef = await buildContainerImage$1(localBuild.asset, localBuild.cdkOutDir, {
97989
+ assetOutdir: localBuild.assetOutdir,
97696
97990
  architecture: lambda.architecture,
97697
97991
  noBuild: options.build === false
97698
97992
  });
@@ -97724,16 +98018,16 @@ async function resolveContainerImagePlan(lambda, options) {
97724
98018
  * apply either) — the caller falls back to the ECR-pull path.
97725
98019
  */
97726
98020
  async function resolveLocalBuildPlan(lambda) {
97727
- const manifestPath = lambda.stack.assetManifestPath;
97728
- if (!manifestPath) return void 0;
97729
- const cdkOutDir = dirname(manifestPath);
98021
+ if (!lambda.stack.assetManifestPath) return void 0;
98022
+ const { manifestDir: cdkOutDir, assetOutdir } = assetPathDirs(lambda.stack);
97730
98023
  const manifest = await new AssetManifestLoader().loadManifest(cdkOutDir, lambda.stack.stackName);
97731
98024
  if (!manifest) return void 0;
97732
98025
  const entry = getDockerImageBySourceHash(manifest, lambda.imageUri);
97733
98026
  if (!entry) return void 0;
97734
98027
  return {
97735
98028
  asset: entry.asset,
97736
- cdkOutDir
98029
+ cdkOutDir,
98030
+ assetOutdir
97737
98031
  };
97738
98032
  }
97739
98033
  /**
@@ -98272,4 +98566,4 @@ function buildProgram() {
98272
98566
 
98273
98567
  //#endregion
98274
98568
  export { buildProgram };
98275
- //# sourceMappingURL=program-Fgtmg1PN.js.map
98569
+ //# sourceMappingURL=program-BfCsjuyU.js.map