@go-to-k/cdkd 0.288.4 → 0.288.5

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 ProviderRegistry, $n as partitionSensitiveEnv, $r as ResourceTimeoutError, $t as STATE_SOURCED_CROSS_GENERATION_RULES, A as renderStatefulReason, An as buildAssetRedirectMap, Ar as derivePartitionAndUrlSuffix, At as configStringRefusal, B as red, Bn as parseBootstrapMarker, Br as CdkdError, Bt as s3BucketDomainName, C as refusesFinalSnapshot, Cn as exportNamesCarriedFrom, Cr as findLargeInlineResources, Ct as refStateLookupFromResource, D as MULTI_REGION_RECREATE_BLOCKED_TYPES, Dn as AssetPublisher, Dr as expectedOwnerParam, Dt as assertRegionMatch, E as extractDeploymentEventError, En as shouldRetainResource, Er as displaySafe, Et as resolveExplicitPhysicalId, F as formatResourceLine, Fn as BOOTSTRAP_MARKER_PREFIX, Fr as AwsClients, Ft as requireConfigString, G as isExportAliasCollision, Gn as describeAwsFailure, Gr as DynamicReferenceRegionAmbiguousError, Gt as DiffCalculator, H as collectDeclaredOutputNames, Hn as validateAssetBucketName, Hr as CrossAccountSecretRefusalError, Ht as s3BucketRegionalDomainName, I as bold, In as assertAssetBucketRegion, Ir as getAwsClients, It as classifyReplaySecretRegion, J as secretSafeKeyDisplay, Jn as describeDockerExecFailure, Jr as LocalStartServiceError, Jt as findSilentDropProperties, K as secretBearing, Kn as buildDockerImage, Kr as IntrinsicResolutionRefusalError, Kt as INTRINSIC_KEYS, L as cyan, Ln as ensureAssetStorage, Lr as resetAwsClients, Lt as producerRegionsFromState, M as coerceWarmThroughput$1, Mn as loadPublishableAssetManifest, Mr as processStackMessages, Mt as replayWarn, N as isWarmThroughputDecrease, Nn as rewriteTemplateAssetReferences, Nt as requireConfigArray, O as isStatefulRecreateTargetForReplace, On as stringifyValue, Or as PARTITION_TABLE, Ot as coerceCfnBoolean, P as toFiniteNumber, Pn as AssetModeResolver, Pr as resolveBucketRegion, Pt as requireConfigObject, Q as clearOnUpdateRemoval, Qn as getDockerCmd, Qr as ProvisioningError, Qt as TemplateParser, R as gray, Rn as getBootstrapMarkerKey, Rr as setAwsClients, Rt as withSharedDrainBudget, S as isFinalSnapshotError, Sn as DEFAULT_STATE_PREFIX, Sr as MIGRATE_TMP_PREFIX, St as parameterTypeMayLoseSecretIdentity, T as makeCanonicalizePropertiesFn, Tn as importableOutputs, Tr as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, Tt as normalizeAwsTagsToCfn, U as collectPublishedOutputNames, Un as validateContainerRepoName, Ut as s3BucketWebsiteUrl, V as yellow, Vn as readBootstrapMarkerBody, Vt as s3BucketDualStackDomainName, W as exportAliasCollisionScrubWarning, Wn as buildDenyExternalAccessPolicy, Wr as DeployCancelledError, Wt as applyRoleArnIfSet, X as IAMRoleProvider, Xn as dockerSpawnEnvWithSensitive, Xr as NestedStackChildDirectDestroyError, Xt as withRetry, Y as getCurrentResourceSecrets, Yn as describeDockerFailure, Yt as describeTypeWithThrottleRetry, Z as collectInlinePolicyNamesManagedBySiblings, Zn as formatDockerLoginError, Zr as PartialFailureError, Zt as DagBuilder, _ as ATOMIC_FINAL_SNAPSHOT_TYPES, _n as buildForceUnlockCommand, _r as resolveUseCdkBootstrapAssets, _t as carriesDynamicReference, a as DeploymentEventsStore, an as errorCauseChain, ar as AssetManifestLoader, at as endCommandInterruptScope, b as ccRoutedFinalSnapshotError, bn as shellQuote, br as CFN_TEMPLATE_BODY_LIMIT, bt as getAccountInfo, c as replayFailedOperations, ci as withErrorHandling, cn as maskSecretsInText, cr as synthesisStatusMessage, ct as startInterruptWatch, d as updatePartialReason, di as isThrottlingError, dn as redactSecretsForState, dr as resolveApp, dt as UNSPECIFIED_SKIP_REASON, ei as ResourceUpdateNotSupportedError, en as STATE_SOURCED_READBACK_RULES, er as redactDockerArgvValues, et as wouldReturnToSdkProvider, f as withResourceDeadline, fi as isTransientServerError, fn as scrubResourceRecord, fr as resolveAutoAssetStorage, ft as deleteIndeterminateGuards, g as bindingSkippedOutputs, gn as UNRENDERABLE, gr as resolveStateBucketWithDefaultAndSource, gt as IntrinsicFunctionResolver, h as computeImplicitDeleteEdges, hi as retryClassificationText, hn as rebuildClientForBucketRegion, hr as resolveStateBucketWithDefault, ht as isTerminationProtectionPropagationError, i as DeploymentEventsReader, in as dynamicReferenceTokens, ir as stripControlChars, it as beginCommandInterruptScope, j as WARM_THROUGHPUT_MEMBERS, jn as createAssetRedirectResolver, jr as AssemblyReader, jt as readConfigString, k as isStatefulRecreateTargetSync, kn as WorkGraph, kr as canonicalizeRegion, kt as configBooleanRefusal, l as replayRollback, li as isMarkedNonRetryable, ln as recordMaskOnlyValue, lr as getDefaultStateBucketName, lt as CloudControlProvider, m as IMPLICIT_DELETE_DEPENDENCIES, mi as markRedactedCause, mn as S3StateBackend, mr as resolveSkipPrefix, mt as disableInstanceApiTermination, n as DEFAULT_RESOURCE_WARN_AFTER_MS, ni as StackTerminationProtectionError, nn as carriesSecretMask, nr as runDockerStreaming, nt as maskDeep, o as planFailedOps, on as isSingleDynamicReferenceToken, or as getDockerImageBySourceHash, ot as interruptWatchListenerCount, p as maskingRetryLogger, pi as markNonRetryable, pn as LockManager, pr as resolveCaptureObservedState, pt as deleteSkipReason, q as secretBearingStateKeyWarning, qn as describeDockerCapturedOutput, qr as LocalInvokeBuildError$1, qt as findActionableSilentDrops, r as DeployEngine, rn as createSecretMasker, rr as escapeRegExp, rt as maskerOrIdentity, s as planRollback, si as normalizeAwsError, sn as maskSecretsInError, sr as Synthesizer, st as isInterruptedWaitError, t as DEFAULT_RESOURCE_TIMEOUT_MS, ti as StackHasActiveImportsError, tn as TEMPLATE_SOURCED_RULES, tr as runDockerForeground, tt as createMaskedRetryLogger, u as updatePartialMessage, ui as isRetryableTransientError, un as recoverMaskedOutput, ur as getLegacyStateBucketName, ut as slowCcOperationTimeoutMs, v as PRE_DELETE_SNAPSHOT_TYPES, vn as buildLockContentionMessage, vr as stateBucketExistenceConfirmed, vt as cfnRefValueFromPhysicalId, w as unsupportedFinalSnapshotError, wn as importableOutputKeys, wr as uploadCfnTemplate, wt as WAFv2WebACLProvider, x as createPreDeleteFinalSnapshot, xn as CUSTOM_RESOURCE_RESPONSE_PREFIX, xr as CFN_TEMPLATE_URL_LIMIT, xt as isUnboundTemplateParameter, y as buildFinalSnapshotIdentifier, yn as forceQuitRecoveryClause, yr as warnDeprecatedNoPrefixCliFlag, yt as coerceParameterTypedValue, z as green, zn as isCrossRegionRedirect, zt as s3BucketArn } from "./deploy-engine-BiPxTgKT.js";
1
+ import { $ as ProviderRegistry, $n as formatDockerLoginError, $r as PartialFailureError, $t as STATE_SOURCED_BASELINE_RULES, A as renderStatefulReason, An as stringifyValue, Ar as PARTITION_TABLE, At as configStringRefusal, B as red, Bn as getBootstrapMarkerKey, Br as setAwsClients, Bt as s3BucketDomainName, C as refusesFinalSnapshot, Cn as CUSTOM_RESOURCE_RESPONSE_PREFIX, Cr as CFN_TEMPLATE_URL_LIMIT, Ct as refStateLookupFromResource, D as MULTI_REGION_RECREATE_BLOCKED_TYPES, Dn as importableOutputs, Dr as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, Dt as assertRegionMatch, E as extractDeploymentEventError, En as importableOutputKeys, Er as uploadCfnTemplate, Et as resolveExplicitPhysicalId, F as formatResourceLine, Fn as rewriteTemplateAssetReferences, Ft as requireConfigString, G as isExportAliasCollision, Gn as validateContainerRepoName, Gt as DiffCalculator, H as collectDeclaredOutputNames, Hn as parseBootstrapMarker, Hr as CdkdError, Ht as s3BucketRegionalDomainName, I as bold, In as AssetModeResolver, Ir as resolveBucketRegion, It as classifyReplaySecretRegion, J as secretSafeKeyDisplay, Jn as buildDockerImage, Jr as IntrinsicResolutionRefusalError, Jt as findSilentDropProperties, K as secretBearing, Kn as buildDenyExternalAccessPolicy, Kr as DeployCancelledError, Kt as INTRINSIC_KEYS, L as cyan, Ln as BOOTSTRAP_MARKER_PREFIX, Lr as AwsClients, Lt as producerRegionsFromState, M as coerceWarmThroughput$1, Mn as buildAssetRedirectMap, Mr as derivePartitionAndUrlSuffix, Mt as replayWarn, N as isWarmThroughputDecrease, Nn as createAssetRedirectResolver, Nr as AssemblyReader, Nt as requireConfigArray, O as isStatefulRecreateTargetForReplace, On as shouldRetainResource, Or as displaySafe, Ot as coerceCfnBoolean, P as toFiniteNumber, Pn as loadPublishableAssetManifest, Pr as processStackMessages, Pt as requireConfigObject, Q as clearOnUpdateRemoval, Qn as dockerSpawnEnvWithSensitive, Qr as NestedStackChildDirectDestroyError, Qt as TemplateParser, R as gray, Rn as assertAssetBucketRegion, Rr as getAwsClients, Rt as withSharedDrainBudget, S as isFinalSnapshotError, Sn as shellQuote, Sr as CFN_TEMPLATE_BODY_LIMIT, St as parameterTypeMayLoseSecretIdentity, T as makeCanonicalizePropertiesFn, Tn as exportNamesCarriedFrom, Tr as findLargeInlineResources, Tt as normalizeAwsTagsToCfn, U as collectPublishedOutputNames, Un as readBootstrapMarkerBody, Ut as s3BucketWebsiteUrl, V as yellow, Vn as isCrossRegionRedirect, Vt as s3BucketDualStackDomainName, W as exportAliasCollisionScrubWarning, Wn as validateAssetBucketName, Wr as CrossAccountSecretRefusalError, Wt as applyRoleArnIfSet, X as IAMRoleProvider, Xn as describeDockerExecFailure, Xr as LocalStartServiceError, Xt as withRetry, Y as getCurrentResourceSecrets, Yn as describeDockerCapturedOutput, Yr as LocalInvokeBuildError$1, Yt as describeTypeWithThrottleRetry, Z as collectInlinePolicyNamesManagedBySiblings, Zn as describeDockerFailure, Zt as DagBuilder, _ as ATOMIC_FINAL_SNAPSHOT_TYPES, _i as retryClassificationText, _n as rebuildClientForBucketRegion, _r as resolveStateBucketWithDefault, _t as carriesDynamicReference, a as DeploymentEventsStore, an as dynamicReferenceTokens, ar as escapeRegExp, at as endCommandInterruptScope, b as ccRoutedFinalSnapshotError, bn as buildLockContentionMessage, br as stateBucketExistenceConfirmed, bt as getAccountInfo, c as replayFailedOperations, cn as isSingleDynamicReferenceToken, cr as getDockerImageBySourceHash, ct as startInterruptWatch, d as updatePartialReason, di as isMarkedNonRetryable, dn as recordMaskOnlyValue, dr as getDefaultStateBucketName, dt as UNSPECIFIED_SKIP_REASON, ei as ProvisioningError, en as STATE_SOURCED_CROSS_GENERATION_RULES, er as getDockerCmd, et as wouldReturnToSdkProvider, f as withResourceDeadline, fi as isRetryableTransientError, fn as recoverMaskedOutput, fr as getLegacyStateBucketName, ft as deleteIndeterminateGuards, g as bindingSkippedOutputs, gi as markRedactedCause, gn as S3StateBackend, gr as resolveSkipPrefix, gt as IntrinsicFunctionResolver, h as computeImplicitDeleteEdges, hi as markNonRetryable, hn as LockManager, hr as resolveCaptureObservedState, ht as isTerminationProtectionPropagationError, i as DeploymentEventsReader, ii as StackTerminationProtectionError, in as createSecretMasker, ir as runDockerStreaming, it as beginCommandInterruptScope, j as WARM_THROUGHPUT_MEMBERS, jn as WorkGraph, jr as canonicalizeRegion, jt as readConfigString, k as isStatefulRecreateTargetSync, kn as AssetPublisher, kr as expectedOwnerParam, kt as configBooleanRefusal, l as replayRollback, li as normalizeAwsError, ln as maskSecretsInError, lr as Synthesizer, lt as CloudControlProvider, m as IMPLICIT_DELETE_DEPENDENCIES, mi as isTransientServerError, mn as scrubResourceRecord, mr as resolveAutoAssetStorage, mt as disableInstanceApiTermination, n as DEFAULT_RESOURCE_WARN_AFTER_MS, ni as ResourceUpdateNotSupportedError, nn as TEMPLATE_SOURCED_RULES, nr as redactDockerArgvValues, nt as maskDeep, o as planFailedOps, on as errorCauseChain, or as stripControlChars, ot as interruptWatchListenerCount, p as maskingRetryLogger, pi as isThrottlingError, pn as redactSecretsForState, pr as resolveApp, pt as deleteSkipReason, q as secretBearingStateKeyWarning, qn as describeAwsFailure, qr as DynamicReferenceRegionAmbiguousError, qt as findActionableSilentDrops, r as DeployEngine, ri as StackHasActiveImportsError, rn as carriesSecretMask, rr as runDockerForeground, rt as maskerOrIdentity, s as planRollback, sn as identityKeyFor, sr as AssetManifestLoader, st as isInterruptedWaitError, t as DEFAULT_RESOURCE_TIMEOUT_MS, ti as ResourceTimeoutError, tn as STATE_SOURCED_READBACK_RULES, tr as partitionSensitiveEnv, tt as createMaskedRetryLogger, u as updatePartialMessage, ui as withErrorHandling, un as maskSecretsInText, ur as synthesisStatusMessage, ut as slowCcOperationTimeoutMs, v as PRE_DELETE_SNAPSHOT_TYPES, vn as UNRENDERABLE, vr as resolveStateBucketWithDefaultAndSource, vt as cfnRefValueFromPhysicalId, w as unsupportedFinalSnapshotError, wn as DEFAULT_STATE_PREFIX, wr as MIGRATE_TMP_PREFIX, wt as WAFv2WebACLProvider, x as createPreDeleteFinalSnapshot, xn as forceQuitRecoveryClause, xr as warnDeprecatedNoPrefixCliFlag, xt as isUnboundTemplateParameter, y as buildFinalSnapshotIdentifier, yn as buildForceUnlockCommand, yr as resolveUseCdkBootstrapAssets, yt as coerceParameterTypedValue, z as green, zn as ensureAssetStorage, zr as resetAwsClients, zt as s3BucketArn } from "./deploy-engine-Dsf9bTqH.js";
2
2
  import { _ as withStackName, c as PATTERN_B_NAME_OPTIONS, f as generateResourceName, g as withSkipPrefix, i as reserveStdoutForPayload, l as PATTERN_B_NAME_PROPERTIES, m as getCurrentStackName, n as getLogger, o as runStackBuffered, p as generateResourceNameWithFallback, s as getLiveRenderer, u as PATTERN_B_RESOURCE_TYPES } from "./logger-Dw-3y48G.js";
3
3
  import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
4
- import { t as getCdkdVersion } from "./version-BmM4QJ9t.js";
5
- import { r as protectedReplacementAdvice, t as ASGProvider } from "./asg-provider-TexsJufw.js";
4
+ import { t as getCdkdVersion } from "./version-BFmq8S23.js";
5
+ import { r as protectedReplacementAdvice, t as ASGProvider } from "./asg-provider-C_AHAmgD.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";
@@ -44136,7 +44136,11 @@ var CognitoUserPoolProvider = class {
44136
44136
  * Retry a Cognito control-plane call on transient "settling" errors. A
44137
44137
  * SetUserPoolMfaConfig issued immediately after CreateUserPool (or another
44138
44138
  * control-plane write) can briefly hit `ConcurrentModificationException` /
44139
- * "please retry". Backoff 1s -> 2s -> 4s, default 3 attempts.
44139
+ * "please retry". Backoff is capped at 4s, but the DEFAULT 3 attempts means
44140
+ * only TWO sleeps -- 1s then 2s, 3s total -- so the 4s step is unreachable
44141
+ * unless a caller raises `maxAttempts`. (`retryable-errors.ts`'s Cognito
44142
+ * SMS-role entry depends on that arithmetic; an earlier revision of it
44143
+ * copied a 7s figure out of this comment's previous wording.)
44140
44144
  */
44141
44145
  async retryOnTransientControlPlane(fn, label, maxAttempts = 3) {
44142
44146
  for (let attempt = 1;; attempt++) try {
@@ -70046,7 +70050,7 @@ async function runDriftForStack(stackName, region, stateBackend, providerRegistr
70046
70050
  logger.warn(`${logicalId} (${resource.resourceType}): ` + (refused ? "refused to resolve a dynamic reference this resource's state records, so its secret-bearing properties are NOT compared — " : "could not resolve the dynamic reference(s) this resource's state records, so its secret-bearing properties are NOT compared — ") + `${maskSecretsInText(err instanceof Error ? err.message : String(err), secrets)}`);
70047
70051
  secrets.clear();
70048
70052
  }
70049
- if (unresolvedTokens.size > 0) logger.warn(`${logicalId} (${resource.resourceType}): cdkd cannot resolve ${maskSecretsInText([...unresolvedTokens].join(", "), secrets)} — those properties are NOT compared. A revert leaves a property whose WHOLE value is one of these tokens untouched; where a token is EMBEDDED in a longer string, the revert writes that string with the token literal, exactly as 'cdkd deploy' does — so a resolved value AWS holds there WOULD be overwritten. cdkd resolves 'secretsmanager', 'ssm' and 'ssm-secure' references; anything else is left as written.`);
70053
+ if (unresolvedTokens.size > 0) logger.warn(`${logicalId} (${resource.resourceType}): cdkd cannot resolve ${maskSecretsInText([...unresolvedTokens].join(", "), secrets)} — those properties are NOT compared. A revert leaves a property whose WHOLE value is one of these tokens untouched where its position can be matched against AWS's report (a list element pairs by an identity field, or by the list's own unchanged literal values); where it cannot be matched, and where a token is EMBEDDED in a longer string, the revert writes the token literal, exactly as 'cdkd deploy' does — so a resolved value AWS holds there WOULD be overwritten. cdkd resolves 'secretsmanager', 'ssm' and 'ssm-secure' references; anything else is left as written.`);
70050
70054
  const observedIgnorePaths = useObserved ? undeclaredEmptyObservedKeys(resource.observedProperties, resource.properties ?? {}) : [];
70051
70055
  const normalized = await canonicalizePrincipalUniqueIds(comparisonBaseline, aws, resolvePrincipalUniqueId);
70052
70056
  const protocolNormalized = canonicalizeIpProtocols(normalized.baseline, normalized.aws, resource.resourceType);
@@ -70523,6 +70527,22 @@ function isPlainRecord(value) {
70523
70527
  return typeof value === "object" && value !== null && !Array.isArray(value);
70524
70528
  }
70525
70529
  /**
70530
+ * Does this value carry an ORDINARY object prototype?
70531
+ *
70532
+ * {@link isPlainRecord} admits `Date` / `Map` / `Set` / class instances, whose
70533
+ * own enumerable keys are `[]` — so a key-walk over one reports "no
70534
+ * contradiction" between two values that actually differ. The redaction walk's
70535
+ * `deepEqualJsonValue` carries the same guard for the same reason
70536
+ * (`secret-redaction.ts`, issue #2869): a `Date` anchor corroborates NOTHING.
70537
+ * Kept file-local rather than imported because `secret-redaction.js` is
70538
+ * `vi.mock`ed by several drift suites, and a value import from it reds them
70539
+ * with a missing-export failure.
70540
+ */
70541
+ function hasPlainPrototype(value) {
70542
+ const proto = Object.getPrototypeOf(value);
70543
+ return proto === Object.prototype || proto === null;
70544
+ }
70545
+ /**
70526
70546
  * Merge the AWS-current value with the revert baseline, KEEPING every path the
70527
70547
  * baseline does not declare (issue
70528
70548
  * [#1626](https://github.com/go-to-k/cdkd/issues/1626) items 2 + 3).
@@ -70606,7 +70626,8 @@ function mergeUntemplatedValue(awsValue, desiredValue) {
70606
70626
  return merged;
70607
70627
  }
70608
70628
  if (!isPlainRecord(awsValue) || !isPlainRecord(desiredValue)) return desiredValue;
70609
- const merged = {};
70629
+ if (!hasPlainPrototype(awsValue) || !hasPlainPrototype(desiredValue)) return desiredValue;
70630
+ const merged = Object.create(null);
70610
70631
  for (const [key, value] of Object.entries(awsValue)) merged[key] = key in desiredValue ? mergeUntemplatedValue(value, desiredValue[key]) : value;
70611
70632
  for (const [key, value] of Object.entries(desiredValue)) if (!(key in awsValue)) merged[key] = value;
70612
70633
  return merged;
@@ -70640,9 +70661,26 @@ function mergeUntemplatedValue(awsValue, desiredValue) {
70640
70661
  *
70641
70662
  * Falls back to KEEPING the token wherever AWS has nothing at that position —
70642
70663
  * that is what `cdkd deploy` sends, so it is the safe residual rather than
70643
- * dropping a property the resource may require. Array descent is positional
70644
- * and bails to that same residual on any length mismatch, since a reordered
70645
- * readback cannot be positioned against.
70664
+ * dropping a property the resource may require. Array descent pairs live
70665
+ * elements through {@link pairedLiveItems} an identity field first, else the
70666
+ * array's own literal frame corroborated per LEAF — never by blind index
70667
+ * (issue [#2893](https://github.com/go-to-k/cdkd/issues/2893)): until that
70668
+ * issue the descent was positional behind a length test, so a readback AWS
70669
+ * REORDERED copied element *i*'s live value into element *i*'s token position
70670
+ * and `provider.update` shipped a value belonging to a DIFFERENT element. The
70671
+ * wildcard handed to the pairing is "whole token OR the redaction mask": a
70672
+ * token leaf cannot corroborate an order (its live counterpart is the resolved
70673
+ * value, so comparing would refuse every array this pass exists to serve), and
70674
+ * a mask leaf belongs to the SIBLING mask walk but must abstain here rather
70675
+ * than contradict. Where the pairing refuses, the token is KEPT — the same
70676
+ * residual as everywhere else in this pass, and deliberately NOT the mask
70677
+ * walk's drop-the-resource refusal: that one would abandon every other drifted
70678
+ * property over an alignment this pass never needed, while the kept token is
70679
+ * what `cdkd deploy` sends. The trade this accepts: an array whose OTHER
70680
+ * leaves genuinely drifted no longer takes a positional copy (the frame cannot
70681
+ * corroborate an order it no longer matches), so the token ships there —
70682
+ * a no-op for a record cdkd deployed, and for any other record a preserved
70683
+ * pre-existing breakage rather than another element's value written live.
70646
70684
  */
70647
70685
  function preserveLiveValuesAtUnresolvedTokens(send, awsProperties) {
70648
70686
  const walk = (value, live) => {
@@ -70652,12 +70690,13 @@ function preserveLiveValuesAtUnresolvedTokens(send, awsProperties) {
70652
70690
  return live;
70653
70691
  }
70654
70692
  if (Array.isArray(value)) {
70655
- const liveItems = Array.isArray(live) && live.length === value.length ? live : void 0;
70693
+ const liveItems = pairedLiveItems(value, live, isTokenOrMaskLeaf, carriesTokenOrMask, true);
70656
70694
  return value.map((item, i) => walk(item, liveItems?.[i]));
70657
70695
  }
70658
70696
  if (value !== null && typeof value === "object") {
70697
+ if (!hasPlainPrototype(value)) return value;
70659
70698
  const liveObject = isPlainRecord(live) ? live : void 0;
70660
- const out = {};
70699
+ const out = Object.create(null);
70661
70700
  for (const [k, v] of Object.entries(value)) out[k] = walk(v, liveObject?.[k]);
70662
70701
  return out;
70663
70702
  }
@@ -70666,6 +70705,335 @@ function preserveLiveValuesAtUnresolvedTokens(send, awsProperties) {
70666
70705
  return walk(send, awsProperties);
70667
70706
  }
70668
70707
  /**
70708
+ * Paths in the bag `--revert` is about to send whose value is an unresolved
70709
+ * CloudFormation intrinsic OBJECT — `{ Ref: ... }` or a single-key
70710
+ * `{ 'Fn::*': ... }` (issue
70711
+ * [#2855](https://github.com/go-to-k/cdkd/issues/2855)).
70712
+ *
70713
+ * WHY THE SHAPE IS REACHABLE. `cdkd import`'s warn path persists raw intrinsic
70714
+ * objects into a record's `properties`, and issue #2842's baseline refusal is
70715
+ * what routes such a record here: it leaves `observedProperties` undefined, so
70716
+ * `runRevert`'s `revertBaseline` falls back to the raw bag.
70717
+ * `resolveStateSecretExpressions` re-resolves only `{{resolve:...}}` STRINGS,
70718
+ * so the OBJECT walks through it untouched, and the drift comparator's
70719
+ * `{{resolve:` skip is a string test too — the object simply differs from the
70720
+ * readback, drifts, and `buildRevertNewProperties` overlays it into the send
70721
+ * bag.
70722
+ *
70723
+ * WHY IT MUST BE REFUSED rather than shipped or live-preserved. Measured
70724
+ * (issue #2855's open question) with mocked SDK clients on both routes: NO
70725
+ * route fails loudly on cdkd's side. `SSMParameterProvider.update` puts the
70726
+ * raw object straight into `PutParameterCommand.input.Value`;
70727
+ * `CloudControlProvider.update` emits it verbatim as a JSON-patch value, and
70728
+ * for a `JSON_STRING_PROPERTIES` type (`AWS::Events::Rule.EventPattern`) it is
70729
+ * `JSON.stringify`ed into a schema-VALID string — a payload AWS cannot even
70730
+ * reject on type, i.e. guaranteed silent corruption. Live-preserving (the
70731
+ * token pass's answer) is no better: the intrinsic's real value depends on
70732
+ * template context a synth-free command does not have, and copying whatever
70733
+ * AWS holds would silently bless an out-of-band edit as the baseline.
70734
+ *
70735
+ * The predicate mirrors the resolver's own rule (a CloudFormation intrinsic is
70736
+ * ALWAYS a single-key object; `detectUnknownIntrinsicKey` in
70737
+ * `intrinsic-function-resolver.ts` states why single-key is what keeps a real
70738
+ * property literally named `Ref` from false-positiving). A non-plain object
70739
+ * (`Date`, `Uint8Array`) cannot be an intrinsic and is not descended. The scan
70740
+ * is scoped to `topLevelKeys` — the DRIFTED keys, the only ones
70741
+ * `buildRevertNewProperties` sources from the baseline — so a value that
70742
+ * merely looks intrinsic under a non-drifted key cannot refuse a revert it
70743
+ * never participates in. The caller hands it the BASELINE
70744
+ * (`desiredProperties`), never the post-overlay send bag: on the very
70745
+ * population this refusal exists for, `mergeUntemplatedValue`'s key-merge can
70746
+ * FUSE a single-key intrinsic with a plain-record live value into a multi-key
70747
+ * object the predicate no longer matches, while every overlay arm re-emits
70748
+ * every baseline path into the send bag in some shape — so the baseline scan
70749
+ * is complete where the merged-bag scan was dilutable, and an AWS-echoed
70750
+ * lookalike (which lives only in the overlay) stays exempt by construction.
70751
+ * The caller additionally gates the whole scan on
70752
+ * `observedProperties === undefined` — intrinsic objects reach a revert
70753
+ * baseline only through the raw-`properties` fallback, while an
70754
+ * `observedProperties` baseline is READBACK-derived, where a single-key map
70755
+ * named like an intrinsic is a real AWS value and a refusal would be
70756
+ * permanent (the call site's comment carries the full derivation).
70757
+ *
70758
+ * The whole flagged leaf is reported once; nothing beneath it is separately
70759
+ * scanned, since the caller refuses the resource on the first path anyway.
70760
+ */
70761
+ function collectUnresolvedIntrinsicObjectPaths(bag, topLevelKeys) {
70762
+ const found = [];
70763
+ const visit = (value, path) => {
70764
+ if (Array.isArray(value)) {
70765
+ value.forEach((item, i) => visit(item, `${path}[${i}]`));
70766
+ return;
70767
+ }
70768
+ if (value !== null && typeof value === "object") {
70769
+ if (!hasPlainPrototype(value)) return;
70770
+ const record = value;
70771
+ const keys = Object.keys(record);
70772
+ const key = keys[0];
70773
+ if (keys.length === 1 && key !== void 0 && (key === "Ref" || key.startsWith("Fn::"))) {
70774
+ found.push(path);
70775
+ return;
70776
+ }
70777
+ for (const [k, v] of Object.entries(record)) visit(v, `${path}.${k}`);
70778
+ }
70779
+ };
70780
+ for (const key of topLevelKeys) if (Object.hasOwn(bag, key)) visit(bag[key], key);
70781
+ return found.sort();
70782
+ }
70783
+ /**
70784
+ * The live array to read a preserved leaf's replacement from, aligned to
70785
+ * `send`'s indices — or `undefined` when the two lists cannot be aligned at
70786
+ * all, which routes every mask beneath them to `unpreservablePaths` in the
70787
+ * mask walk and keeps the token in the token walk (see the wildcard
70788
+ * paragraph below for the split).
70789
+ *
70790
+ * WHY THIS IS NOT INDEX ALIGNMENT ANY MORE (issue
70791
+ * [#2884](https://github.com/go-to-k/cdkd/issues/2884)). Until issue
70792
+ * [#2852](https://github.com/go-to-k/cdkd/issues/2852) the only mask in a
70793
+ * persisted bag came from a `NoEcho` custom resource's `Data` (issue #2274),
70794
+ * which sits at a scalar leaf of a property the template names. #2852 added a
70795
+ * second source, and it lands in exactly the population index alignment is
70796
+ * WRONG on: a leaf is masked BECAUSE `refuseUncertifiedReadbackPositions` could
70797
+ * not pair the two lists — an unkeyed array AWS reordered, or a keyed element
70798
+ * whose identity AWS normalised. A resource that produced a mask is by
70799
+ * construction one whose list does not come back in a stable order.
70800
+ *
70801
+ * Traced consequence, which is the issue #1498 / #1501 data-loss class this
70802
+ * module's own doc calls "strictly worse than the disclosure": for
70803
+ * `Environment: [{Name:'DB_PASS', Value:<ref>}, {Name:'REGION', Value:'...'}]`
70804
+ * echoed back case-normalised and reordered, the baseline becomes
70805
+ * `[{Name:'REGION',...}, {Name:'***', Value:'***'}]`, and an index-aligned
70806
+ * revert copies `live[1]` into the masked slot — shipping a duplicated `REGION`
70807
+ * and DELETING the password variable from the live task definition.
70808
+ *
70809
+ * So the alignment must be one AWS's own reordering cannot break:
70810
+ * {@link identityKeyFor} — the same rule the redaction walk pairs by, so the
70811
+ * two can never disagree about which elements correspond. It is asked FIRST and
70812
+ * with NO length test, which is a change from the first cut of this function: a
70813
+ * length test ahead of it refuses a list AWS legitimately ADDED an element to,
70814
+ * while the keyed lookup needs no such partner — a `send` element whose identity
70815
+ * AWS does not report simply yields no live value, which is already the
70816
+ * refusing arm.
70817
+ *
70818
+ * WITHOUT AN IDENTITY FIELD the array's own literal FRAME has to vouch for the
70819
+ * order — the same corroboration `unkeyedArrayPairsByAnchors` requires on the
70820
+ * redaction side — and the test is PER-LEAF. Three rules, and the first cut of
70821
+ * this function had none of them right:
70822
+ *
70823
+ * - **the lengths must match**, positional alignment being all that is left;
70824
+ * - **every leaf of `send` that is not a mask must equal `live`'s leaf at the
70825
+ * same position** ({@link corroboratedLeafCount}), masked leaves wildcarded.
70826
+ * A whole-ELEMENT exemption is what shipped first, and it corroborated
70827
+ * NOTHING: {@link carriesSecretMask} is a DEEP, whole-subtree test, so an
70828
+ * element carrying one mask anywhere skipped the comparison entirely.
70829
+ * Measured on `[{Pw:'***',Role:'reader'},{Pw:'***',Role:'admin'}]` against a
70830
+ * live list AWS returned in the other order — the admin password was written
70831
+ * onto the reader entry and `provider.update` shipped it;
70832
+ * - **at most ONE element may carry a mask, and at least one non-mask leaf must
70833
+ * actually have been COMPARED.** Two masked slots leave the surviving literal
70834
+ * frame unable to say which mask goes where (two secret environment variables
70835
+ * in one container definition is the ordinary shape), and zero compared
70836
+ * leaves is the all-masked array {@link SECRET_MASK} produces when AWS
70837
+ * normalised the identity field too — there the old `every` was vacuously
70838
+ * true and the alignment rested on no evidence at all. With ONE masked slot
70839
+ * and every other position literally equal, the single live element left over
70840
+ * is the only value that can belong there.
70841
+ *
70842
+ * The `compared > 0` floor DELIBERATELY also refuses a single-element scalar
70843
+ * array — `[SECRET_MASK]` against one live value — even though one masked slot
70844
+ * against one live slot looks unambiguous. It is unambiguous only IF the live
70845
+ * list corresponds to this one, and with zero corroborated leaves the only
70846
+ * evidence for that is the length-1 match, which is no evidence: this is the
70847
+ * all-masked reading applied to its smallest shape, and the cost is a refusal
70848
+ * (the safe residual), not a wrong write. THAT COST SENTENCE IS THE MASK
70849
+ * WALK'S ALONE — for the token walk a refusal WRITES the token, so the same
70850
+ * floor inverts there and the singleton is accepted instead; see
70851
+ * `pairedLiveItems`' `acceptForcedSingleton` for the derivation.
70852
+ *
70853
+ * THE EVIDENCE MUST NOT BE MANUFACTURED BY A SIBLING PASS. `send` here must be
70854
+ * values the caller did not itself copy from `live` — see
70855
+ * {@link preserveLiveValuesAtMaskedLeaves}'s `corroborationSource` parameter
70856
+ * for the round-4 #2884 defect where the token pass's index-copied live values
70857
+ * corroborated their own indices.
70858
+ *
70859
+ * Refusing is the correct outcome for the MASK caller — it drops the whole
70860
+ * resource rather than send a guess. The TOKEN caller's refusal residual is
70861
+ * KEEPING the token (see below); both are the refusal `undefined` spells here,
70862
+ * and what it costs is the CALLER's contract, not this function's.
70863
+ *
70864
+ * PARAMETERISED WILDCARDS (issue #2893). This pairing now serves BOTH preserve
70865
+ * walks, because the two questions are identical — "which live element belongs
70866
+ * to this send element?" — and a second hand-rolled copy of the rule is
70867
+ * exactly how the `maskDeep` class of divergence starts. What differs per
70868
+ * caller is only WHICH leaf is the one being paired FOR (and so must be
70869
+ * wildcarded rather than compared) and what a refusal costs:
70870
+ *
70871
+ * - the MASK walk wildcards {@link SECRET_MASK} alone; a refusal drops the
70872
+ * resource, so over-refusal is a safe posture there;
70873
+ * - the TOKEN walk wildcards a WHOLE `{{resolve:...}}` token (its live
70874
+ * counterpart is the resolved value, so comparing would contradict on every
70875
+ * array that pass exists to serve) AND the mask (owned by the LATER mask
70876
+ * walk — comparing `***` against live would contradict spuriously, and
70877
+ * treating it as evidence would be wrong in the other direction). Its
70878
+ * refusal residual is keeping the token, which `provider.update` ships —
70879
+ * a no-op for a record cdkd deployed, a preserved pre-existing breakage
70880
+ * otherwise, and in both cases better than another element's live value
70881
+ * copied into the wrong position.
70882
+ *
70883
+ * `carriesWildcard` must be the DEEP form of `wildcardLeaf` (an element
70884
+ * carrying a wildcard anywhere is a slot whose alignment part-rests on
70885
+ * abstention), and the one-slot bound applies to it: with two such elements
70886
+ * the surviving literal frame cannot say which wildcard goes where.
70887
+ *
70888
+ * `acceptForcedSingleton` (PR #2912 review, blocker 1) is where the two
70889
+ * callers' refusal COSTS meet the evidence rules, and it exists because a
70890
+ * rule that is sound for one arm BECAUSE of what its refusal costs cannot be
70891
+ * shared without re-deriving that cost. At `send.length === 1` against
70892
+ * `live.length === 1` no evidence question remains OPEN: the position is
70893
+ * FORCED (there is no other candidate to mis-pair with), and a positional
70894
+ * copy writes each live value back to its own position, so no
70895
+ * cross-POSITION donation — the credential-swap class every bound above
70896
+ * exists to stop — is constructible. Cross-IDENTITY donation is: the
70897
+ * bypass sits above the identity arm and its wildcard-identity guard, so
70898
+ * `[{Name:'DB', Value:<token>}]` against a live `[{Name:'OTHER', Value:'X'}]`
70899
+ * ships `{Name:'DB', Value:'X'}` — a value from a differently-identified
70900
+ * element. That is position-neutral (X lands at the exact position AWS
70901
+ * holds it), it is what the pre-#2893 positional descent did for every such
70902
+ * array, and the alternative writes the token over X; the trade is stated
70903
+ * here rather than claimed away. What the evidence rules still doubt at
70904
+ * 1-vs-1 is only "does this live list correspond to this send list at
70905
+ * all", and the two callers answer that doubt oppositely because their
70906
+ * refusal residuals invert:
70907
+ *
70908
+ * - the MASK walk refuses (`false`, the default): its residual is dropping
70909
+ * the resource, so doubt costs a refusal — the safe direction, and the
70910
+ * `[SECRET_MASK]`-vs-one-live-value reading its own doc defends;
70911
+ * - the TOKEN walk accepts (`true`): its residual WRITES the literal token
70912
+ * over whatever AWS holds (`provider.update` ships it — the #1914
70913
+ * corruption), so refusing on a forced position DESTROYS the live value a
70914
+ * pairing would have preserved, while accepting is write-neutral even when
70915
+ * the correspondence doubt is real (the copied value lands at the exact
70916
+ * position AWS already holds it). Refusal buys nothing and costs a
70917
+ * destructive write, so the token walk takes the pairing — which is also
70918
+ * exactly the pre-#2893 behaviour for every 1-vs-1 array.
70919
+ *
70920
+ * The bypass sits ABOVE the identity arm deliberately: at 1-vs-1 an identity
70921
+ * mismatch (AWS normalised the one element's Name) changes which EVIDENCE
70922
+ * rule fails, not the forced position, and routing it through the identity
70923
+ * arm would re-open the same inverted cost through a different door. Every
70924
+ * bound below is untouched for `length >= 2`, where mis-pairing is real.
70925
+ */
70926
+ function pairedLiveItems(send, live, wildcardLeaf = isMaskLeaf, carriesWildcard = carriesSecretMask, acceptForcedSingleton = false) {
70927
+ if (!Array.isArray(live)) return void 0;
70928
+ if (acceptForcedSingleton && send.length === 1 && live.length === 1) return live;
70929
+ const key = identityKeyFor(send, live);
70930
+ if (key !== void 0) {
70931
+ const byIdentity = /* @__PURE__ */ new Map();
70932
+ for (const item of live) byIdentity.set(item[key], item);
70933
+ return send.map((item) => {
70934
+ const identity = item[key];
70935
+ return wildcardLeaf(identity) ? void 0 : byIdentity.get(identity);
70936
+ });
70937
+ }
70938
+ if (live.length !== send.length) return void 0;
70939
+ let wildcardSlots = 0;
70940
+ let compared = 0;
70941
+ for (let i = 0; i < send.length; i++) {
70942
+ if (carriesWildcard(send[i])) wildcardSlots++;
70943
+ const corroborated = corroboratedLeafCount(send[i], live[i], wildcardLeaf);
70944
+ if (corroborated === void 0) return void 0;
70945
+ compared += corroborated;
70946
+ }
70947
+ if (wildcardSlots > 1 || compared === 0) return void 0;
70948
+ return live;
70949
+ }
70950
+ /** The MASK walk's wildcard: the redaction mask alone. */
70951
+ function isMaskLeaf(leaf) {
70952
+ return leaf === "***";
70953
+ }
70954
+ /**
70955
+ * The TOKEN walk's wildcard: a leaf whose WHOLE value is a `{{resolve:...}}`
70956
+ * token, plus the mask (which that walk never touches but must abstain on —
70957
+ * see {@link pairedLiveItems}). A MIXED leaf (a token embedded in a longer
70958
+ * string) is deliberately NOT a wildcard: the token walk does not preserve it
70959
+ * either, and its live counterpart genuinely differs, so letting it abstain
70960
+ * would make every partially-resolved string an unbounded free pass.
70961
+ */
70962
+ function isTokenOrMaskLeaf(leaf) {
70963
+ if (leaf === "***") return true;
70964
+ return typeof leaf === "string" && leaf.includes("{{resolve:") && isSingleDynamicReferenceToken(leaf);
70965
+ }
70966
+ /**
70967
+ * Deep form of {@link isTokenOrMaskLeaf}, for the one-slot bound.
70968
+ *
70969
+ * Cycle-guarded like its sibling `carriesSecretMask`, via the same inner
70970
+ * closure so the visited set is not a callable parameter (PR #2912 review):
70971
+ * the value walked here is an element of `buildRevertNewProperties`' output,
70972
+ * which carries RAW `readCurrentState` returns — provider-authored, not
70973
+ * JSON-round-tripped — so a self-referential object must answer `false`
70974
+ * rather than throw `RangeError` out of the pairing. The guard bounds the
70975
+ * PAIRING frame only: a cyclic PLAIN element still overflows the enclosing
70976
+ * preserve walk one frame later (pre-existing, caught by `runRevert`'s
70977
+ * per-resource payload-build catch); what this guard keeps alive is the
70978
+ * NON-PLAIN cyclic element, which that walk returns by identity and only
70979
+ * this slot count ever descends.
70980
+ */
70981
+ function carriesTokenOrMask(value) {
70982
+ const seen = /* @__PURE__ */ new Set();
70983
+ const walkNode = (node) => {
70984
+ if (isTokenOrMaskLeaf(node)) return true;
70985
+ if (node === null || typeof node !== "object") return false;
70986
+ if (seen.has(node)) return false;
70987
+ seen.add(node);
70988
+ if (Array.isArray(node)) return node.some(walkNode);
70989
+ return Object.values(node).some(walkNode);
70990
+ };
70991
+ return walkNode(value);
70992
+ }
70993
+ /**
70994
+ * How many of `send`'s leaves the live side CORROBORATES, or `undefined` the
70995
+ * moment one of them CONTRADICTS it.
70996
+ *
70997
+ * A WILDCARD leaf (the caller's `wildcardLeaf` — the mask for the mask walk,
70998
+ * a whole token or the mask for the token walk) contributes 0 and is never
70999
+ * compared: it is the value being paired FOR, so requiring it to match would
71000
+ * refuse every array {@link pairedLiveItems} exists to align. Everything else
71001
+ * must agree — including the container SHAPE around it, since a subtree of a
71002
+ * different shape is a contradiction rather than an absent comparison.
71003
+ *
71004
+ * A COUNT rather than a boolean, because "nothing contradicted me" is exactly
71005
+ * the answer an all-wildcard array gives and it is not evidence. The caller
71006
+ * requires a non-zero count before it reads the order as corroborated.
71007
+ */
71008
+ function corroboratedLeafCount(send, live, wildcardLeaf = isMaskLeaf) {
71009
+ if (wildcardLeaf(send)) return 0;
71010
+ if (Array.isArray(send)) {
71011
+ if (!Array.isArray(live) || live.length !== send.length) return void 0;
71012
+ let total = 0;
71013
+ for (let i = 0; i < send.length; i++) {
71014
+ const corroborated = corroboratedLeafCount(send[i], live[i], wildcardLeaf);
71015
+ if (corroborated === void 0) return void 0;
71016
+ total += corroborated;
71017
+ }
71018
+ return total;
71019
+ }
71020
+ if (isPlainRecord(send)) {
71021
+ if (!hasPlainPrototype(send)) return void 0;
71022
+ if (!isPlainRecord(live) || !hasPlainPrototype(live)) return void 0;
71023
+ const keys = Object.keys(send);
71024
+ if (keys.length !== Object.keys(live).length) return void 0;
71025
+ let total = 0;
71026
+ for (const k of keys) {
71027
+ if (!Object.prototype.hasOwnProperty.call(live, k)) return void 0;
71028
+ const corroborated = corroboratedLeafCount(send[k], live[k], wildcardLeaf);
71029
+ if (corroborated === void 0) return void 0;
71030
+ total += corroborated;
71031
+ }
71032
+ return total;
71033
+ }
71034
+ return deepEqualUnordered(send, live) ? 1 : void 0;
71035
+ }
71036
+ /**
70669
71037
  * Keep `cdkd drift --revert` from pushing a REDACTION MASK to AWS (issue
70670
71038
  * [#2274](https://github.com/go-to-k/cdkd/issues/2274)).
70671
71039
  *
@@ -70689,9 +71057,16 @@ function preserveLiveValuesAtUnresolvedTokens(send, awsProperties) {
70689
71057
  * revision's reason for not doing so was wrong: it said "the live value is one
70690
71058
  * cdkd never resolved and cannot recognise, so there is no needle to record".
70691
71059
  * The needle is `liveValue -> {@link SECRET_MASK}`, i.e. a MASK-ONLY entry, and
70692
- * the evidence for it is the position — THE MASKED POSITION IS THE PROOF THE
70693
- * VALUE IS SECRET, since nothing but this module's own redaction puts a mask at
70694
- * a leaf. Without the registration this function copies live plaintext into the
71060
+ * the evidence for it is the position — the masked position proves the value
71061
+ * is ONE CDKD CHOSE TO MASK, since nothing but this module's own redaction
71062
+ * puts a mask at a leaf. That is deliberately weaker than "proof the value is
71063
+ * secret" (issue #2881): a #2852 fail-closed mask deliberately over-masks a
71064
+ * value the walk merely could not certify — a normalised `US-EAST-1` as
71065
+ * readily as a credential — so the live value registered here may be an
71066
+ * ordinary literal. The consequence is bounded to over-masking log output,
71067
+ * the safe direction, and registering it is still right: when the position
71068
+ * DOES hold a secret, the registration is what keeps the moved plaintext out
71069
+ * of every reader below. Without it this function copies live plaintext into the
70695
71070
  * send bag and then `collectNarrowedTopLevelKeys` persists that delta into
70696
71071
  * `observedProperties` against a `secrets` map holding no entry for it — the
70697
71072
  * same disclosure the mask exists to prevent, arriving through the mechanism
@@ -70718,17 +71093,58 @@ function preserveLiveValuesAtUnresolvedTokens(send, awsProperties) {
70718
71093
  * would be the corruption; dropping the key would delete a property the
70719
71094
  * resource may require.
70720
71095
  *
70721
- * Array descent is POSITIONAL and bails to "no live value" on any length
70722
- * mismatch, matching the sibling: a reordered readback cannot be positioned
70723
- * against, and guessing would put one element's live value at another's index.
70724
- *
70725
- * Returns the input bag BY IDENTITY when it holds no mask, so the ordinary
70726
- * revert is byte-identical and pays one walk.
70727
- */
70728
- function preserveLiveValuesAtMaskedLeaves(send, awsProperties, secrets) {
71096
+ * Array descent is NOT positional and does NOT bail on a length mismatch
71097
+ * {@link pairedLiveItems} owns that question, for BOTH walks since issue
71098
+ * #2893 (the sibling's earlier answer, index alignment behind a length test,
71099
+ * was wrong by construction on the population that produces these masks, and
71100
+ * wrong under any reordered readback for the sibling's own tokens). An
71101
+ * earlier revision of this paragraph still claimed the sibling's rule after
71102
+ * the pairing had changed underneath it.
71103
+ *
71104
+ * `corroborationSource` is the bag the PAIRING EVIDENCE is read from, and it
71105
+ * exists because of a round-4 #2884 defect: `runRevert` runs
71106
+ * {@link preserveLiveValuesAtUnresolvedTokens} FIRST, and that pass copies
71107
+ * `live` values into whole-token leaves — BY INDEX when the defect was found;
71108
+ * by certified pairing since issue #2893, which does not retire this
71109
+ * parameter, because a copied leaf is trivially deep-equal to `live` at its
71110
+ * own position HOWEVER it was chosen, so corroborating `send` (the post-token
71111
+ * bag) against that same `live` still counts evidence a sibling pass
71112
+ * manufactured rather than evidence AWS's readback supplied. Under the
71113
+ * original index copies, an unkeyed array
71114
+ * whose only non-mask leaves were tokens satisfied both the
71115
+ * `compared > 0` floor and the one-mask bound on evidence the sibling pass
71116
+ * manufactured, and a reordered `live` wrote another element's secret into the
71117
+ * masked slot — the credential-swap class the guards were added for. The same
71118
+ * fabrication reached the IDENTITY arm: an identity field that was itself a
71119
+ * token took its live value by index, and {@link pairedLiveItems}' keyed
71120
+ * lookup then "paired" it right back to that index. So the caller passes the
71121
+ * PRE-token bag here; both the identity lookup and the per-leaf corroboration
71122
+ * run over values no sibling pass copied from `live`, a token leaf compares
71123
+ * against the resolved live value and CONTRADICTS (its live counterpart is
71124
+ * unknowable, and an abstention would make token leaves unbounded wildcards
71125
+ * beside the deliberately bounded one-mask rule), and the array refuses. The
71126
+ * masked VALUES the walk writes still come from `send` — only the evidence
71127
+ * source changes. Defaults to `send` (self-corroboration, the pre-round-4
71128
+ * behaviour) for a caller with no sibling pass; a structural mismatch between
71129
+ * the two bags — which the token pass CAN produce, by returning the live
71130
+ * CONTAINER AWS holds over a whole-token string leaf — refuses the masks
71131
+ * beneath it rather than falling back to the fabricable bag. That refusal is
71132
+ * a deliberate fail-closed cost, not a reachable loss: the mismatching
71133
+ * subtree is live-derived and carries no mask, so nothing preservable is
71134
+ * refused today.
71135
+ *
71136
+ * Returns the input bag BY IDENTITY when it holds no mask — checked FIRST via
71137
+ * {@link carriesSecretMask}, so a mask-free revert (the ordinary case) pays
71138
+ * one boolean walk instead of a full rebuild plus per-array pairing.
71139
+ */
71140
+ function preserveLiveValuesAtMaskedLeaves(send, awsProperties, secrets, corroborationSource = send) {
71141
+ if (!carriesSecretMask(send)) return {
71142
+ properties: send,
71143
+ unpreservablePaths: []
71144
+ };
70729
71145
  const unpreservablePaths = [];
70730
71146
  let changed = false;
70731
- const walk = (value, live, path) => {
71147
+ const walk = (value, corr, live, path) => {
70732
71148
  if (value === "***") {
70733
71149
  if (live === void 0) {
70734
71150
  unpreservablePaths.push(path);
@@ -70739,18 +71155,24 @@ function preserveLiveValuesAtMaskedLeaves(send, awsProperties, secrets) {
70739
71155
  return live;
70740
71156
  }
70741
71157
  if (Array.isArray(value)) {
70742
- const liveItems = Array.isArray(live) && live.length === value.length ? live : void 0;
70743
- return value.map((item, i) => walk(item, liveItems?.[i], `${path}[${i}]`));
71158
+ const corrItems = Array.isArray(corr) && corr.length === value.length ? corr : void 0;
71159
+ const liveItems = corrItems === void 0 ? void 0 : pairedLiveItems(corrItems, live);
71160
+ return value.map((item, i) => walk(item, corrItems?.[i], liveItems?.[i], `${path}[${i}]`));
70744
71161
  }
70745
71162
  if (value !== null && typeof value === "object") {
71163
+ if (!hasPlainPrototype(value)) {
71164
+ if (carriesSecretMask(value)) unpreservablePaths.push(path);
71165
+ return value;
71166
+ }
70746
71167
  const liveObject = isPlainRecord(live) ? live : void 0;
70747
- const out = {};
70748
- for (const [k, v] of Object.entries(value)) out[k] = walk(v, liveObject?.[k], path === "" ? k : `${path}.${k}`);
71168
+ const corrObject = isPlainRecord(corr) ? corr : void 0;
71169
+ const out = Object.create(null);
71170
+ for (const [k, v] of Object.entries(value)) out[k] = walk(v, corrObject?.[k], liveObject?.[k], path === "" ? k : `${path}.${k}`);
70749
71171
  return out;
70750
71172
  }
70751
71173
  return value;
70752
71174
  };
70753
- const properties = walk(send, awsProperties, "");
71175
+ const properties = walk(send, corroborationSource, awsProperties, "");
70754
71176
  if (!changed && unpreservablePaths.length === 0) return {
70755
71177
  properties: send,
70756
71178
  unpreservablePaths
@@ -70997,15 +71419,28 @@ async function runRevert(reports, providerRegistry, stateBackend, stateConfig, a
70997
71419
  return;
70998
71420
  }
70999
71421
  const unrevertablePaths = outcome.changes.map((c) => c.path).filter((path) => (path.split(".", 1)[0] ?? "").includes("***"));
71000
- if (unrevertablePaths.length > 0) logger.warn(` ! ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): cannot revert ${unrevertablePaths.join(", ")} — cdkd cannot name the property, so it is left as AWS has it. ROTATE the secret that leaked into the property name.`);
71001
- if (unresolvedTokens.size > 0) logger.warn(` ! [revert] ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): cdkd cannot resolve ${maskSecretsInText([...unresolvedTokens].join(", "), secrets)} — a property whose WHOLE value is one of these tokens is left UNCHANGED by this revert. Where a token is EMBEDDED in a longer string, that string is written with the token literal, exactly as 'cdkd deploy' does, so a resolved value AWS holds there is overwritten.`);
71422
+ if (stateResource.observedProperties === void 0) {
71423
+ const driftedTopLevelKeys = /* @__PURE__ */ new Set();
71424
+ for (const change of outcome.changes) {
71425
+ const topLevelKey = change.path.split(".", 1)[0];
71426
+ if (topLevelKey) driftedTopLevelKeys.add(topLevelKey);
71427
+ }
71428
+ const intrinsicObjectPaths = collectUnresolvedIntrinsicObjectPaths(desiredProperties, driftedTopLevelKeys);
71429
+ if (intrinsicObjectPaths.length > 0) {
71430
+ totalUnresolvable++;
71431
+ logger.error(` ✗ ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): refused to revert ${maskSecretsInText(intrinsicObjectPaths.join(", "), secrets)} — the recorded baseline holds an unresolved CloudFormation intrinsic there (e.g. Fn::Join, Ref), which cdkd cannot resolve outside a deploy; writing it would set the live property to the raw intrinsic object instead of its value. Run 'cdkd deploy' for this stack — the deploy resolves the template and records a resolvable baseline — then re-run the revert if drift remains.`);
71432
+ return;
71433
+ }
71434
+ }
71435
+ if (unrevertablePaths.length > 0) logger.warn(` ! ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): cannot revert ${maskSecretsInText(unrevertablePaths.join(", "), secrets)} — cdkd cannot name the property, so it is left as AWS has it. ROTATE the secret that leaked into the property name.`);
71436
+ if (unresolvedTokens.size > 0) logger.warn(` ! [revert] ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): cdkd cannot resolve ${maskSecretsInText([...unresolvedTokens].join(", "), secrets)} — a property whose WHOLE value is one of these tokens is left UNCHANGED by this revert when cdkd can pair its position against AWS's report (a list element by an identity field, or by its list's own unchanged literal values). Where it cannot pair, and where a token is EMBEDDED in a longer string, the token is written literally, exactly as 'cdkd deploy' does, so a resolved value AWS holds there is overwritten.`);
71002
71437
  let newProperties;
71003
71438
  try {
71004
71439
  const overlaid = buildRevertNewProperties(outcome.changes, desiredProperties, outcome.awsProperties, { preserveUntemplated: stateResource.observedProperties === void 0 });
71005
- const maskPreserved = preserveLiveValuesAtMaskedLeaves(unresolvedTokens.size > 0 ? preserveLiveValuesAtUnresolvedTokens(overlaid, outcome.awsProperties) : overlaid, outcome.awsProperties, secrets);
71440
+ const maskPreserved = preserveLiveValuesAtMaskedLeaves(unresolvedTokens.size > 0 ? preserveLiveValuesAtUnresolvedTokens(overlaid, outcome.awsProperties) : overlaid, outcome.awsProperties, secrets, overlaid);
71006
71441
  if (maskPreserved.unpreservablePaths.length > 0) {
71007
71442
  totalUnresolvable++;
71008
- logger.error(` ✗ ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): refused to revert ${maskPreserved.unpreservablePaths.join(", ")} — the recorded baseline holds only the redaction mask there (a NoEcho custom-resource value), and AWS reports nothing to preserve, so cdkd has no value it may write. Force that custom resource to update (change one of its properties, e.g. a nonce) and re-deploy, so its handler runs again and supplies the value an ordinary re-deploy leaves it unchanged and the mask stays.`);
71443
+ logger.error(` ✗ ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): refused to revert ${maskSecretsInText(maskPreserved.unpreservablePaths.join(", "), secrets)} — the recorded baseline holds only the redaction mask there, and AWS reports nothing to preserve, so cdkd has no value it may write. Two causes leave such a mask, and the record does not say which: a NoEcho custom-resource value (force that custom resource to update change one of its properties, e.g. a nonce and re-deploy, so its handler runs again and supplies the value), or a readback position cdkd could not certify when the baseline was captured (deploy a change to this resource, so the baseline is recaptured against the template). An ordinary no-change re-deploy clears neither.`);
71009
71444
  return;
71010
71445
  }
71011
71446
  newProperties = maskPreserved.properties;
@@ -71102,7 +71537,7 @@ async function runRevert(reports, providerRegistry, stateBackend, stateConfig, a
71102
71537
  if (totalFailed > 0) summaryParts.push(`${totalFailed} failed`);
71103
71538
  logger.info(`\nRevert summary: ${summaryParts.join(", ")}.`);
71104
71539
  if (totalUnsupported > 0) logger.warn(`${totalUnsupported} resource(s) cannot be reverted in place — re-deploy the stack with cdkd deploy --replace, or destroy + redeploy to push the cdkd-state values back into AWS.`);
71105
- if (totalFailed > 0 || totalUnsupported > 0 || totalUnresolvable > 0) throw new PartialFailureError(`Revert completed with ${totalFailed + totalUnsupported + totalUnresolvable} resource error(s) (${totalFailed} AWS update failure(s), ${totalUnsupported} update-not-supported, ${totalUnresolvable} whose dynamic reference(s) could not be resolved — those never reached provider.update; grant the caller secretsmanager:GetSecretValue / ssm:GetParameter, or fix the reference). Re-run 'cdkd drift <stack>' to see the remaining drift, then 'cdkd drift <stack> --revert' to retry.`);
71540
+ if (totalFailed > 0 || totalUnsupported > 0 || totalUnresolvable > 0) throw new PartialFailureError(`Revert completed with ${totalFailed + totalUnsupported + totalUnresolvable} resource error(s) (${totalFailed} AWS update failure(s), ${totalUnsupported} update-not-supported, ${totalUnresolvable} refused or unresolvable — those never reached provider.update; each per-resource message above names its cause and remedy, e.g. missing secretsmanager:GetSecretValue / ssm:GetParameter grants for an unresolvable reference). Re-run 'cdkd drift <stack>' to see the remaining drift, then 'cdkd drift <stack> --revert' to retry.`);
71106
71541
  }
71107
71542
  function humanTextSink(json) {
71108
71543
  return json ? {
@@ -76980,7 +77415,7 @@ async function refreshObservedForStack(stackName, region, stateBackend, lockMana
76980
77415
  unsupported++;
76981
77416
  return;
76982
77417
  }
76983
- resource.observedProperties = redactSecretsForState(observed, NO_RECORDED_SECRETS$1, resource.properties ?? {}, STATE_SOURCED_READBACK_RULES);
77418
+ resource.observedProperties = redactSecretsForState(observed, NO_RECORDED_SECRETS$1, resource.properties ?? {}, STATE_SOURCED_BASELINE_RULES);
76984
77419
  refreshed++;
76985
77420
  } catch (err) {
76986
77421
  failed++;
@@ -90417,4 +90852,4 @@ function buildProgram() {
90417
90852
 
90418
90853
  //#endregion
90419
90854
  export { buildProgram };
90420
- //# sourceMappingURL=program-Ci4Y5UO9.js.map
90855
+ //# sourceMappingURL=program-CMhpftan.js.map