@go-to-k/cdkd 0.286.1 → 0.286.3

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,9 +1,9 @@
1
1
  import { _ as withStackName, d as applyDefaultNameForFallback, f as generateResourceName, h as looksLikeCdkdGeneratedName, m as getCurrentStackName, n as getLogger, p as generateResourceNameWithFallback, r as isStdoutReservedForPayload, s as getLiveRenderer } from "./logger-Dw-3y48G.js";
2
2
  import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
3
- import { t as getCdkdVersion } from "./version-CnQK6Jeh.js";
3
+ import { t as getCdkdVersion } from "./version-D3md0Lde.js";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  import { randomUUID } from "node:crypto";
6
- import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
6
+ import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetBucketReplicationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
7
7
  import { CloudControlClient, CreateResourceCommand, DeleteResourceCommand, GetResourceCommand, GetResourceRequestStatusCommand, ListResourcesCommand, UpdateResourceCommand } from "@aws-sdk/client-cloudcontrol";
8
8
  import { AttachRolePolicyCommand, CreateRoleCommand, DeleteRoleCommand, DeleteRolePermissionsBoundaryCommand, DeleteRolePolicyCommand, DetachRolePolicyCommand, GetRoleCommand, GetRolePolicyCommand, IAMClient, ListAttachedRolePoliciesCommand, ListInstanceProfilesForRoleCommand, ListRolePoliciesCommand, ListRoleTagsCommand, NoSuchEntityException, PutRolePermissionsBoundaryCommand, PutRolePolicyCommand, RemoveRoleFromInstanceProfileCommand, TagRoleCommand, UntagRoleCommand, UpdateAssumeRolePolicyCommand, UpdateRoleCommand } from "@aws-sdk/client-iam";
9
9
  import { SQSClient } from "@aws-sdk/client-sqs";
@@ -35,6 +35,7 @@ import { KMSClient, ListAliasesCommand } from "@aws-sdk/client-kms";
35
35
  import { readFile } from "fs/promises";
36
36
  import { join as join$1 } from "path";
37
37
  import { CreateRepositoryCommand, DescribeImagesCommand as DescribeImagesCommand$1, DescribeRepositoriesCommand, ECRClient, GetAuthorizationTokenCommand, PutImageTagMutabilityCommand } from "@aws-sdk/client-ecr";
38
+ import { inspect } from "node:util";
38
39
  import { hostname } from "os";
39
40
  import graphlib from "graphlib";
40
41
  import { DescribeDBClustersCommand, DescribeDBInstancesCommand, RDSClient } from "@aws-sdk/client-rds";
@@ -836,6 +837,116 @@ function isNameCooldownError(message) {
836
837
  function isRecreateRetryableError(message) {
837
838
  return isNameCollisionError(message) || isNameCooldownError(message);
838
839
  }
840
+ /**
841
+ * The Cloud Control exception NAME for "this resource type ships no handler
842
+ * for the action you asked for" (issue
843
+ * [#2520](https://github.com/go-to-k/cdkd/issues/2520)).
844
+ *
845
+ * A NAME, not a message fragment: AWS SDK v3 sets `error.name` to the service
846
+ * exception's own identifier, which is a wire-level contract, while the prose
847
+ * beside it is text AWS is free to reword. The sibling READ classifier in
848
+ * `src/cli/commands/drift.ts` (`NO_READ_HANDLER_NAMES`) already keys on this
849
+ * same name for the GET direction.
850
+ */
851
+ const CC_UNSUPPORTED_ACTION_ERROR_NAME = "UnsupportedActionException";
852
+ /**
853
+ * The AWS prose the update-not-supported classifier accepted before the
854
+ * structured signal existed. Kept as a TOP-LEVEL-only fallback — see
855
+ * {@link isUpdateUnsupportedError} for why it is not walked down the chain.
856
+ */
857
+ const CC_UPDATE_UNSUPPORTED_MESSAGE_FALLBACK = "does not support UPDATE";
858
+ /**
859
+ * True when a failed `provider.update()` for `logicalId` was rejected because
860
+ * the resource type has no UPDATE handler at all — the signal the deploy
861
+ * engine's update-failure fallback fires on, turning the update into a
862
+ * replacement.
863
+ *
864
+ * ## Why the chain is walked
865
+ *
866
+ * `CloudControlProvider.handleError` WRAPS the raw AWS rejection in a
867
+ * `ProvisioningError` and interpolates `err.message` only; the exception name
868
+ * is never copied into the wrapper's text. Measured 2026-09-04: `aws
869
+ * cloudcontrol update-resource --type-name AWS::DocDB::DBCluster` answers
870
+ * `UnsupportedActionException` with the message `Resource type
871
+ * AWS::DocDB::DBCluster does not support UPDATE action`, which does not repeat
872
+ * the name. That is why the predicate's pre-#2520
873
+ * `message.includes('UnsupportedActionException')` half matched nothing cdkd
874
+ * produces, and why the structured read has to look one link down.
875
+ *
876
+ * Two structured signals, both read off a link rather than out of prose:
877
+ *
878
+ * - `name` — the synchronous `UpdateResource` rejection, one cause link below
879
+ * the provider's wrapper.
880
+ * - `ccErrorCode` PLUS `ccOperation === 'UPDATE'` — the two fields
881
+ * `CloudControlOperationFailedError` carries for an asynchronous
882
+ * progress-event failure, read structurally exactly as
883
+ * `cloud-control-provider.ts` reads the code for `AlreadyExists` /
884
+ * `NotFound`. No async occurrence has been MEASURED
885
+ * (`UnsupportedActionException` is raised synchronously by `UpdateResource`
886
+ * today); the arm exists so the async shape cannot silently fall through to
887
+ * prose, and it is pinned by unit cases built from the real error class.
888
+ * `ccOperation` is required rather than decorative precisely BECAUSE the
889
+ * arm is unmeasured: a CREATE or DELETE sub-operation reporting the same
890
+ * code says nothing about whether the type has an UPDATE handler, and
891
+ * reading the code alone would let it trigger a DELETE + CREATE. The
892
+ * narrowing is not absolute and the gap is stated rather than papered over:
893
+ * such a failure arriving at the TOP level still classifies if its own
894
+ * MESSAGE quotes AWS's prose. Unreachable today —
895
+ * `CloudControlOperationFailedError`'s message is built as
896
+ * `${operation} failed for <id>: <StatusMessage>`, so a CREATE's text
897
+ * cannot contain the UPDATE phrase unless AWS puts it there — and closing
898
+ * it would mean anchoring the prose read on the operation too, which would
899
+ * narrow the retained pre-#2520 reach rather than preserve it.
900
+ *
901
+ * ## Why the walk stops at another resource's error
902
+ *
903
+ * `logicalId` is not decoration — it is the fence that keeps a chain walk from
904
+ * being WIDER than the message read it replaces. `NestedStackProvider.update`
905
+ * runs a whole child deploy inside the PARENT's `provider.update()` call, so a
906
+ * child resource's Cloud Control rejection propagates into the parent's update
907
+ * catch, several cause links down. An unanchored walk would classify that as
908
+ * "the nested stack cannot be updated in place" and DELETE + CREATE the entire
909
+ * child stack. The pre-#2520 message read was immune by accident — the child
910
+ * engine's wrapper is `Failed to update resource <child>`, which quotes no AWS
911
+ * text — and this anchor makes the immunity deliberate: `ProvisioningError`
912
+ * and `ResourceUpdateNotSupportedError` both carry `logicalId`, so the walk
913
+ * stops dead at the first link that names a resource other than the one being
914
+ * updated.
915
+ *
916
+ * The prose fallback is read at the TOP LEVEL ONLY, exactly as the pre-#2520
917
+ * predicate did, and for the same asymmetry: missing the signal fails the
918
+ * deploy (safe), matching it too broadly replaces a resource nobody asked to
919
+ * replace (unsafe). It is read INSIDE the walk, after the anchor, so the
920
+ * anchor governs every route into a `true` — ordered ahead of it, the
921
+ * nested-stack immunity would rest on the child engine's wrapper happening to
922
+ * quote no AWS text, which is a property of another file.
923
+ *
924
+ * ## Codes deliberately NOT matched
925
+ *
926
+ * The Cloud Control handler error code `NotUpdatable` reports "this particular
927
+ * patch is not applicable" (a create-only property, an invalid document)
928
+ * rather than "the type has no UPDATE handler", and cdkd already routes the
929
+ * create-only case through its own property-driven replacement — accepting it
930
+ * here would convert ordinary update rejections into replacements.
931
+ * `TypeNotFoundException`, which `handleError` wraps into the SAME sentence as
932
+ * the unsupported-action case, is not matched either: an unregistered type has
933
+ * no replacement story, so it must keep failing the deploy rather than
934
+ * deleting the resource.
935
+ */
936
+ function isUpdateUnsupportedError(error, logicalId) {
937
+ let current = error;
938
+ for (let depth = 0; current !== null && current !== void 0 && depth < MAX_CAUSE_CHAIN_DEPTH; depth++) {
939
+ const link = current;
940
+ if (typeof link.logicalId === "string" && link.logicalId !== logicalId) return false;
941
+ if (link.name === "UnsupportedActionException") return true;
942
+ if (link.ccErrorCode === "UnsupportedActionException" && link.ccOperation === "UPDATE") return true;
943
+ if (depth === 0) {
944
+ if ((current instanceof Error ? current.message : String(current)).includes("does not support UPDATE")) return true;
945
+ }
946
+ current = link.cause;
947
+ }
948
+ return false;
949
+ }
839
950
 
840
951
  //#endregion
841
952
  //#region src/utils/error-handler.ts
@@ -3917,16 +4028,402 @@ function displaySafe(value, opts) {
3917
4028
  }
3918
4029
 
3919
4030
  //#endregion
3920
- //#region src/state/s3-noncurrent-version-purge.ts
4031
+ //#region src/state/s3-replication-purge-gap.ts
4032
+ /**
4033
+ * Tell the user when S3 REPLICATION has silently kept the bodies that
4034
+ * {@link ../state/s3-noncurrent-version-purge.js | the noncurrent-version
4035
+ * purge} just removed (issue
4036
+ * [#2447](https://github.com/go-to-k/cdkd/issues/2447)).
4037
+ *
4038
+ * ## The hole this closes
4039
+ *
4040
+ * `purgeNoncurrentKeyVersions` exists so that "cdkd deleted the object" means
4041
+ * the body is no longer retrievable. It reaches that by issuing
4042
+ * `DeleteObjects` entries that name a specific `VersionId`.
4043
+ *
4044
+ * **S3 never replicates a version-id delete.** Replication propagates PUTs
4045
+ * and — only when `DeleteMarkerReplication` is enabled — delete markers; a
4046
+ * delete naming a version id is deliberately not replicated, so that a delete
4047
+ * on the source cannot destroy data on the destination. On a state bucket
4048
+ * with Cross-Region (CRR) or Same-Region (SRR) replication the purge is
4049
+ * therefore SOURCE-ONLY: the replica keeps its own copy of every version,
4050
+ * `GetObject` with a `VersionId` still returns the secret, and cdkd reports
4051
+ * the purge as complete because on the only bucket it knows about, it was.
4052
+ *
4053
+ * That is exactly the shape the purge was built to remove — an operation that
4054
+ * reports success while the value stays readable — reproduced one bucket over
4055
+ * and invisible from every signal cdkd emitted. The user is not merely
4056
+ * unprotected, they are REASSURED, which is the direction this repo treats as
4057
+ * a defect rather than a gap.
4058
+ *
4059
+ * ## Why a probe and not only a doc line
4060
+ *
4061
+ * Documentation reaches the reader who goes looking. This reaches the one who
4062
+ * does not, at the moment the false belief would be formed. The whole cost is
4063
+ * one bucket-metadata call per process (see the cache below) and one IAM
4064
+ * action the caller may well not hold.
4065
+ *
4066
+ * ## Degrading is the DEFAULT, not the failure
4067
+ *
4068
+ * The purge's contract is that a missing permission produces a warning and
4069
+ * never an abort, and a detector bolted onto it must not be able to break
4070
+ * that. So this module NEVER THROWS, and treats every non-answer as "we do
4071
+ * not know" rather than as either polarity:
4072
+ *
4073
+ * - `ReplicationConfigurationNotFoundError` — the answer "no replication
4074
+ * configuration exists". S3 reports the ABSENCE of a configuration as an
4075
+ * ERROR, not as an empty response body, so this arm is the ordinary case
4076
+ * for almost every bucket and must not read as a failure.
4077
+ * `ReplicationConfiguration` ABSENT from a 200 response is treated the same
4078
+ * way: the SDK models the field as optional, and a modelled field is not a
4079
+ * promise that the API populates it.
4080
+ * - `AccessDenied` and friends — the caller lacks
4081
+ * `s3:GetReplicationConfiguration`. Logged at DEBUG and never at `warn`:
4082
+ * the overwhelming majority of buckets are not replicated, so warning here
4083
+ * would tell almost every user to grant a permission in order to be told
4084
+ * nothing. The doc names the action for the user who wants the check.
4085
+ * - anything else (throttle, 5xx, network) — DEBUG, and deliberately NOT
4086
+ * cached, so the next purge in the same process retries.
4087
+ *
4088
+ * ## What is cached, what is deduped, and why they are different keys
4089
+ *
4090
+ * The PROBE is cached per (bucket, asserted owner) for the process lifetime, as
4091
+ * the sibling `src/provisioning/create-only-properties.ts` caches its
4092
+ * `DescribeType` lookups and for the same reason: the answer cannot change
4093
+ * mid-run, the value stored is the in-flight PROMISE so concurrent purges share
4094
+ * one call, and only non-transient outcomes are kept. Without it a stack with
4095
+ * thirty custom resources would issue thirty identical `GetBucketReplication`
4096
+ * calls.
4097
+ *
4098
+ * The WARNING is deduped too, but on a DIFFERENT and deliberately wider key —
4099
+ * (bucket, object description, destinations) — with the repeats sent to
4100
+ * `debug`. The two keys answer two questions and must not be merged:
4101
+ *
4102
+ * - the bucket alone is not enough, because `lock-manager.ts` routes this
4103
+ * module's `warn` sink to `debug` on the ordinary release path (it purges a
4104
+ * heartbeat record on every command and must not warn about it). Keyed on the
4105
+ * bucket, that demoted line would consume the one warning the rollback
4106
+ * journal or the response sidecar needed;
4107
+ * - adding the description is still not enough, because two rules covering
4108
+ * different prefixes can name DIFFERENT destinations. Purging a `cdkd/dev/…`
4109
+ * key can match only the first rule; a later `cdkd/prod/…` purge under the
4110
+ * same description matches both, and without the destinations in the key the
4111
+ * user would clean the first replica and never learn the second one holds the
4112
+ * production journal.
4113
+ *
4114
+ * Deduping at all is a departure from the sibling purge module, which
4115
+ * deliberately repeats its own failure warning. The difference is that a
4116
+ * failure RECURS — each one is another object cdkd could not remove — while
4117
+ * this is a fact about a BUCKET, identical every time it is re-derived.
4118
+ * Repeating it thirty times in a deploy adds nothing and erodes the failure
4119
+ * warning standing next to it.
4120
+ */
3921
4121
  /**
3922
4122
  * Parenthetical used when a caller names nothing.
3923
4123
  *
3924
- * True of ANY object this function is pointed at, which is the bar for a
3925
- * default here: a caller that forgets to describe its object must still emit a
3926
- * warning that is correct, just less specific. It deliberately does not guess
3927
- * at content.
4124
+ * True of ANY object the purge is pointed at, which is the bar for a default
4125
+ * here: a caller that forgets to describe its object must still emit a warning
4126
+ * that is correct, just less specific. It deliberately does not guess at
4127
+ * content.
4128
+ *
4129
+ * Lives HERE rather than in `s3-noncurrent-version-purge.ts` only to keep the
4130
+ * import one-directional — the purge imports this module, so a shared constant
4131
+ * defined there and read here would be a cycle. It is the same sentence both
4132
+ * warnings fall back to, and one definition is what stops the two drifting.
4133
+ */
4134
+ const DEFAULT_PURGED_OBJECT_DESCRIPTION = "the body of an object cdkd has just reported as removed";
4135
+ /**
4136
+ * Cache of the replication probe, holding the in-flight promise.
4137
+ *
4138
+ * Keyed on the bucket AND the `ExpectedBucketOwner` the caller asserts. The
4139
+ * SUCCESS answer does not depend on the owner assertion — two callers
4140
+ * differing only in whether they assert it read the same configuration — but
4141
+ * the FAILURE does: a mismatched-owner 403 is an answer about that caller's
4142
+ * request, and caching it under the bare bucket name silenced the detector for
4143
+ * every correctly-scoped caller in the process. cdkd has two shapes today (the
4144
+ * provider probes without an owner, the state backend and lock manager with
4145
+ * one), so the two are distinguishable in practice.
4146
+ */
4147
+ const replicationProbeCache = /* @__PURE__ */ new Map();
4148
+ /**
4149
+ * (bucket, object description, destinations) triples already warned about.
4150
+ *
4151
+ * The one thing the warning says that VARIES is `objectDescription`; the rest
4152
+ * is a fact about the bucket, identical every time. So a stack with thirty
4153
+ * custom resources repeated a ~600-character warning thirty times while adding
4154
+ * no information after the first — and warning fatigue erodes the purge's own
4155
+ * failure warning standing next to it.
4156
+ *
4157
+ * Keyed on all three precisely so this cannot become a silence hole; the
4158
+ * module doc above gives the two failures a narrower key produces. Entries are
4159
+ * added only after the warning is actually emitted, so a throwing sink does
4160
+ * not burn a slot.
4161
+ *
4162
+ * RESIDUAL, bounded and recorded: cdkd cannot see whether the caller's sink
4163
+ * DEMOTED the line, so a `lock-manager.ts` release (which routes `warn` to
4164
+ * `debug`) claims the lock's slot and a later reap of a stale lock — which
4165
+ * would have warned — is suppressed. It is bounded to the LOCK description,
4166
+ * whose object the docs describe as carrying no secret, and to one process.
4167
+ */
4168
+ const warnedWarnings = /* @__PURE__ */ new Set();
4169
+ /** How many destinations the warning names before it truncates. */
4170
+ const MAX_NAMED_DESTINATIONS = 3;
4171
+ /** Shown when a rule carries no `Destination.Bucket` for us to name. */
4172
+ const UNNAMED_DESTINATION = "<destination not named by the replication rule>";
4173
+ /**
4174
+ * Error codes meaning "this principal will never get an answer here".
4175
+ *
4176
+ * Cached like a real answer, because unlike a throttle they do not clear
4177
+ * within the process: retrying on every purge would spend one denied API call
4178
+ * per custom resource for a result that cannot change.
4179
+ */
4180
+ const PERMANENT_DENIALS = /* @__PURE__ */ new Set([
4181
+ "AccessDenied",
4182
+ "AccessDeniedException",
4183
+ "AllAccessDisabled",
4184
+ "MethodNotAllowed",
4185
+ "NotImplemented"
4186
+ ]);
4187
+ /**
4188
+ * Credential-shaped failures that ARRIVE AS 403 and are nonetheless transient.
4189
+ *
4190
+ * Checked BEFORE the blanket 403 rule, which is the only way they can win:
4191
+ * `InvalidAccessKeyId` is the classic blip while an IAM principal or an
4192
+ * assumed-role credential propagates — this repo retries that whole class over
4193
+ * a 47.75 s schedule elsewhere — and an expired or skewed token clears on
4194
+ * refresh. Cached as permanent, ONE such blip on the first purge of a long
4195
+ * deploy would silence the detector for the rest of the process, which is the
4196
+ * "false silence leaves them believing a secret is gone" direction this module
4197
+ * forbids.
4198
+ */
4199
+ const TRANSIENT_CREDENTIAL_CODES = /* @__PURE__ */ new Set([
4200
+ "InvalidAccessKeyId",
4201
+ "ExpiredToken",
4202
+ "ExpiredTokenException",
4203
+ "InvalidToken",
4204
+ "TokenRefreshRequired",
4205
+ "RequestTimeTooSkewed",
4206
+ "RequestExpired"
4207
+ ]);
4208
+ const describe$2 = (error) => error instanceof Error ? error.message : String(error);
4209
+ /**
4210
+ * Every wire error code the value offers, `name` and `Code` alike.
4211
+ *
4212
+ * NO PRECEDENCE, deliberately. An earlier revision collapsed the two to one
4213
+ * string and preferred a specific `name`, which had two failure modes in
4214
+ * opposite directions: preferring `name` unconditionally made the `Code`
4215
+ * fallback dead (`{ name: 'Error', Code: 'AccessDenied' }` classified as
4216
+ * retryable, so a denied principal re-probed on every purge), while
4217
+ * preferring a SPECIFIC `name` let a wrapper's own name hide
4218
+ * `Code: 'InvalidAccessKeyId'` and cache a credential blip as a permanent
4219
+ * denial. Membership is tested against every code the error carries, so
4220
+ * neither field can mask the other. (Verified against real S3: a missing
4221
+ * replication configuration arrives with that string on BOTH fields.)
4222
+ *
4223
+ * Null-safe because a `send` that rejects with `null` used to throw a
4224
+ * TypeError INSIDE the probe's own catch, leaving a REJECTED promise in the
4225
+ * cache forever: every later purge on that bucket then fell into the outer
4226
+ * `catch {}` with no warning and no debug line — a silently dead detector,
4227
+ * which is the worst possible failure for this module.
4228
+ *
4229
+ * This guard and `probe`'s rejection arm both serve one invariant — **no
4230
+ * rejection ever reaches the cache** — and the honest statement of their
4231
+ * relationship, measured one mutation at a time on 2026-09-05, is:
4232
+ *
4233
+ * - removing the ARM alone REDS a case (a rejection whose own `toString`
4234
+ * throws: the guards pass that value through and `describe()` is what
4235
+ * raises), so the arm is independently fenced;
4236
+ * - removing these GUARDS alone leaves the CACHING behaviour identical:
4237
+ * `errorCodes` runs inside `runProbe`'s own catch, so a TypeError here is
4238
+ * caught by the arm and produces the same `unknown` / `retry: true` /
4239
+ * evicted outcome. What DOES change is the debug line's reason — guarded it
4240
+ * reports the rejection (`: null`), unguarded it reports the classifier's
4241
+ * own crash — and that difference is what the `throw null` case asserts, so
4242
+ * the guards are fenced after all.
4243
+ *
4244
+ * They also earn their place structurally: classification is inside the catch
4245
+ * TODAY. An edit that hoists it out — or that adds a caller of `errorCodes`
4246
+ * outside a try — reinstates the original defect, in which a `null` rejection
4247
+ * crashed the classifier and left a REJECTED promise cached for the process,
4248
+ * so every later purge on that bucket fell into the outer `catch {}` with no
4249
+ * warning and no debug line: a silently dead detector.
4250
+ */
4251
+ const errorCodes = (error) => {
4252
+ if (error === null || typeof error !== "object" && typeof error !== "function") return [];
4253
+ const e = error;
4254
+ const codes = [];
4255
+ if (typeof e.name === "string" && e.name.length > 0) codes.push(e.name);
4256
+ if (typeof e.Code === "string" && e.Code.length > 0) codes.push(e.Code);
4257
+ return codes;
4258
+ };
4259
+ /** True when ANY code the error carries is in `set`. */
4260
+ const hasCode = (error, set) => errorCodes(error).some((code) => set.has(code));
4261
+ const httpStatus = (error) => {
4262
+ if (error === null || typeof error !== "object" && typeof error !== "function") return void 0;
4263
+ const meta = error.$metadata;
4264
+ return typeof meta?.httpStatusCode === "number" ? meta.httpStatusCode : void 0;
4265
+ };
4266
+ /**
4267
+ * Strip the ARN wrapper off `Destination.Bucket`, which S3 returns as
4268
+ * `arn:<partition>:s3:::<name>`.
4269
+ *
4270
+ * Partition-agnostic on purpose: `aws-cn` and `aws-us-gov` produce a
4271
+ * different second segment, and a hard-coded `arn:aws:s3:::` would leave the
4272
+ * whole ARN in the message there rather than mis-parse it — survivable, but
4273
+ * the regex costs nothing. A value that is not ARN-shaped is passed through
4274
+ * unchanged rather than rejected; the field is only ever displayed.
4275
+ */
4276
+ function destinationName(bucketArn, account) {
4277
+ if (bucketArn === void 0 || bucketArn === "") return UNNAMED_DESTINATION;
4278
+ const name = bucketArn.replace(/^arn:[^:]*:s3:::/, "");
4279
+ return account !== void 0 && account !== "" ? `${name} (account ${account})` : name;
4280
+ }
4281
+ /**
4282
+ * Reduce a `ReplicationConfiguration` to the ENABLED rules and their effective
4283
+ * prefixes.
4284
+ *
4285
+ * Exported for its own unit tests: the mapping from the four filter shapes S3
4286
+ * accepts onto one prefix string is the part with real cases in it, and
4287
+ * pinning it through a stubbed client would test the plumbing instead.
4288
+ *
4289
+ * Over-approximates DELIBERATELY, in the direction of warning:
4290
+ *
4291
+ * - a rule filtering on a TAG (`Filter.Tag`, or `Filter.And.Tags` with no
4292
+ * `Prefix`) cannot be evaluated without reading each object's tag set, so
4293
+ * its prefix reduces to `''` and it covers everything. A false warning
4294
+ * sends the reader to look at a replica; a false silence leaves them
4295
+ * believing a secret is gone.
4296
+ * - a `Disabled` rule is KEPT, flagged rather than dropped. Disabling a rule
4297
+ * stops FUTURE replication; it does not remove what the rule already
4298
+ * copied, so a bucket whose rule covered `cdkd/` last month and is disabled
4299
+ * today still holds every body ever purged. Dropping it was the first cut
4300
+ * and is the reassured-user failure this module exists to remove, arriving
4301
+ * inside the module that removes it. A rule with no `Status` at all is kept
4302
+ * as ENABLED: `Status` is required by the model, so an absent one means the
4303
+ * response was not what we assumed — the same reason the sibling purge
4304
+ * treats an absent `IsLatest` as "assume the unsafe thing is true".
4305
+ *
4306
+ * Pure: it reads only its argument, so no ambient locale, clock or
4307
+ * environment can change the answer.
4308
+ */
4309
+ function normalizeReplicationRules(config) {
4310
+ const targets = [];
4311
+ for (const rule of config.Rules ?? []) {
4312
+ const prefix = rule.Filter !== void 0 ? rule.Filter.Prefix ?? rule.Filter.And?.Prefix ?? "" : rule.Prefix ?? "";
4313
+ targets.push({
4314
+ prefix,
4315
+ destination: destinationName(rule.Destination?.Bucket, rule.Destination?.Account),
4316
+ enabled: rule.Status !== "Disabled"
4317
+ });
4318
+ }
4319
+ return targets;
4320
+ }
4321
+ async function runProbe(s3Client, bucket, requestFields) {
4322
+ try {
4323
+ const config = (await s3Client.send(new GetBucketReplicationCommand({
4324
+ Bucket: bucket,
4325
+ ...requestFields
4326
+ }))).ReplicationConfiguration;
4327
+ if (config === void 0) return { kind: "none" };
4328
+ const rules = normalizeReplicationRules(config);
4329
+ return rules.length === 0 ? { kind: "none" } : {
4330
+ kind: "rules",
4331
+ rules
4332
+ };
4333
+ } catch (error) {
4334
+ if (errorCodes(error).includes("ReplicationConfigurationNotFoundError")) return { kind: "none" };
4335
+ if (hasCode(error, TRANSIENT_CREDENTIAL_CODES)) return {
4336
+ kind: "unknown",
4337
+ reason: describe$2(error),
4338
+ retry: true
4339
+ };
4340
+ const permanent = hasCode(error, PERMANENT_DENIALS) || httpStatus(error) === 403;
4341
+ return {
4342
+ kind: "unknown",
4343
+ reason: describe$2(error),
4344
+ retry: !permanent
4345
+ };
4346
+ }
4347
+ }
4348
+ function probe(s3Client, bucket, requestFields) {
4349
+ const key = `${bucket}\u0000${requestFields.ExpectedBucketOwner ?? ""}`;
4350
+ const cached = replicationProbeCache.get(key);
4351
+ if (cached) return cached;
4352
+ const pending = runProbe(s3Client, bucket, requestFields).then((result) => {
4353
+ if (result.kind === "unknown" && result.retry) evictIfOwn(key, pending);
4354
+ return result;
4355
+ }, (error) => {
4356
+ evictIfOwn(key, pending);
4357
+ return {
4358
+ kind: "unknown",
4359
+ reason: describe$2(error),
4360
+ retry: true
4361
+ };
4362
+ });
4363
+ replicationProbeCache.set(key, pending);
4364
+ return pending;
4365
+ }
4366
+ /**
4367
+ * Remove a cache entry only when it is still the one we put there.
4368
+ *
4369
+ * The identity check is UNFENCED by a test, deliberately and stated rather
4370
+ * than left to be discovered: the only way a stale probe can settle against a
4371
+ * newer entry under the same key is for something to have cleared the cache
4372
+ * mid-flight, and the sole clearer is `clearReplicationProbeCache()`, a
4373
+ * test-only helper. Writing a test for it would be writing a test for the test
4374
+ * helper. It is kept because the alternative is an unconditional `delete` that
4375
+ * is wrong the moment a non-test clearer appears.
3928
4376
  */
3929
- const DEFAULT_OBJECT_DESCRIPTION = "the body of an object cdkd has just reported as removed";
4377
+ function evictIfOwn(key, own) {
4378
+ if (replicationProbeCache.get(key) === own) replicationProbeCache.delete(key);
4379
+ }
4380
+ /**
4381
+ * Warn if replication on `bucket` covers any of `keys`.
4382
+ *
4383
+ * `keys` are the keys whose bodies were actually removed (or could not be
4384
+ * settled) — NOT everything the caller asked about. The purge scopes them; see
4385
+ * its call site for why announcing a survival that never happened is worse
4386
+ * than saying nothing.
4387
+ *
4388
+ * NEVER THROWS and never rejects: the caller runs on a cleanup path that must
4389
+ * not abort, and this is a diagnostic bolted onto it.
4390
+ */
4391
+ async function warnIfPurgeIsReplicated(s3Client, bucket, keys, options = {}) {
4392
+ if (keys.length === 0) return;
4393
+ const emitDebug = (message) => {
4394
+ const sink = options.logger;
4395
+ if (sink?.debug) sink.debug(message);
4396
+ else getLogger().child("s3-replication-check").debug(message);
4397
+ };
4398
+ try {
4399
+ const safeBucket = displaySafe(bucket, { asciiOnly: true });
4400
+ const result = await probe(s3Client, bucket, options.requestFields ?? {});
4401
+ if (result.kind === "none") return;
4402
+ if (result.kind === "unknown") {
4403
+ emitDebug(`Could not determine whether s3://${safeBucket} is replicated (grant s3:GetReplicationConfiguration to enable the check): ${displaySafe(result.reason)}`);
4404
+ return;
4405
+ }
4406
+ const covered = result.rules.filter((rule) => keys.some((key) => key.startsWith(rule.prefix)));
4407
+ if (covered.length === 0) return;
4408
+ const destinations = [...new Set(covered.map((rule) => rule.enabled ? rule.destination : `${rule.destination} (rule currently Disabled -- it stops FUTURE replication, not what it already copied)`))].sort();
4409
+ const warnKey = [
4410
+ bucket,
4411
+ options.objectDescription ?? "",
4412
+ ...destinations
4413
+ ].join("\0");
4414
+ if (warnedWarnings.has(warnKey)) {
4415
+ emitDebug(`S3 replication on s3://${safeBucket} still covers purged keys (${options.objectDescription ?? "the body of an object cdkd has just reported as removed"}); already reported once this run for ${destinations.map((d) => displaySafe(d, { asciiOnly: true })).join(", ")}.`);
4416
+ return;
4417
+ }
4418
+ const named = destinations.slice(0, MAX_NAMED_DESTINATIONS).map((d) => displaySafe(d, { asciiOnly: true }));
4419
+ const elided = destinations.length - named.length;
4420
+ (options.logger ?? getLogger().child("s3-replication-check")).warn(`S3 replication is enabled on s3://${safeBucket} and covers the key(s) cdkd just purged. S3 NEVER replicates a version-id delete, so the purge removed those versions from THIS bucket only — the copies in the destination bucket survive and remain readable there via GetObject with a VersionId (${options.objectDescription ?? "the body of an object cdkd has just reported as removed"}). cdkd cannot delete them. Remove them in the destination bucket yourself (aws s3api list-object-versions, then delete-object --version-id), or narrow the replication rule so it excludes the prefixes cdkd purges under. Destination(s): ${named.join(", ")}` + (elided > 0 ? ` (and ${elided} more; aws s3api get-bucket-replication --bucket ${safeBucket} lists them all)` : ""));
4421
+ warnedWarnings.add(warnKey);
4422
+ } catch {}
4423
+ }
4424
+
4425
+ //#endregion
4426
+ //#region src/state/s3-noncurrent-version-purge.ts
3930
4427
  /**
3931
4428
  * `objectDescription` for the custom-resource response sidecar.
3932
4429
  *
@@ -4000,17 +4497,27 @@ async function purgeNoncurrentKeyVersions(s3Client, bucket, keys, options = {})
4000
4497
  const prefixes = options.listPrefix !== void 0 ? [options.listPrefix] : keys;
4001
4498
  const failed = /* @__PURE__ */ new Map();
4002
4499
  const unknown = { n: 0 };
4500
+ const purged = /* @__PURE__ */ new Set();
4501
+ const unsettledBodies = /* @__PURE__ */ new Set();
4003
4502
  for (const prefix of prefixes) try {
4004
- await purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields, failed, unknown);
4503
+ await purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields, failed, unknown, purged, unsettledBodies);
4005
4504
  } catch (error) {
4006
4505
  const affected = options.listPrefix !== void 0 ? keys : [prefix];
4007
- for (const key of affected) recordFailure(failed, key, describe$1(error));
4506
+ for (const key of affected) {
4507
+ recordFailure(failed, key, describe$1(error));
4508
+ unsettledBodies.add(key);
4509
+ }
4008
4510
  }
4009
4511
  if (failed.size > 0) {
4010
4512
  const named = [...failed.entries()].slice(0, MAX_NAMED_FAILURES).map(([key, reasons]) => displaySafe(`${key} (${reasons.join("; ")})`));
4011
4513
  const elided = failed.size - named.length;
4012
- logger.warn(`Could not purge noncurrent versions of ${failed.size} key(s) in s3://${bucket}. Their previous versions survive and remain readable via GetObject with a VersionId (${options.objectDescription ?? DEFAULT_OBJECT_DESCRIPTION}). Grant s3:ListBucketVersions and s3:DeleteObjectVersion on the state bucket, or purge the key(s) by hand. Failures: ${named.join(", ")}` + (elided > 0 ? ` (and ${elided} more)` : ""));
4514
+ logger.warn(`Could not purge noncurrent versions of ${failed.size} key(s) in s3://${displaySafe(bucket, { asciiOnly: true })}. Their previous versions survive and remain readable via GetObject with a VersionId (${options.objectDescription ?? "the body of an object cdkd has just reported as removed"}). Grant s3:ListBucketVersions and s3:DeleteObjectVersion on the state bucket, or purge the key(s) by hand. Failures: ${named.join(", ")}` + (elided > 0 ? ` (and ${elided} more)` : ""));
4013
4515
  }
4516
+ await warnIfPurgeIsReplicated(s3Client, bucket, [.../* @__PURE__ */ new Set([...purged, ...unsettledBodies])], {
4517
+ requestFields,
4518
+ logger,
4519
+ ...options.objectDescription !== void 0 && { objectDescription: options.objectDescription }
4520
+ });
4014
4521
  }
4015
4522
  /**
4016
4523
  * Paginate `ListObjectVersions` under one prefix and delete every returned
@@ -4025,7 +4532,7 @@ async function purgeNoncurrentKeyVersions(s3Client, bucket, keys, options = {})
4025
4532
  * id is NOT filtered out on its own, because a bucket whose versioning was
4026
4533
  * SUSPENDED can carry a genuine noncurrent `'null'` version holding the body.
4027
4534
  */
4028
- async function purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields, failed, unknown) {
4535
+ async function purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields, failed, unknown, purged, unsettledBodies) {
4029
4536
  let keyMarker;
4030
4537
  let versionIdMarker;
4031
4538
  do {
@@ -4037,15 +4544,30 @@ async function purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields,
4037
4544
  ...versionIdMarker !== void 0 && { VersionIdMarker: versionIdMarker }
4038
4545
  }));
4039
4546
  const stale = [];
4040
- for (const entry of [...resp.Versions ?? [], ...resp.DeleteMarkers ?? []]) {
4547
+ const entries = [...(resp.Versions ?? []).map((entry) => ({
4548
+ entry,
4549
+ hasBody: true
4550
+ })), ...(resp.DeleteMarkers ?? []).map((entry) => ({
4551
+ entry,
4552
+ hasBody: false
4553
+ }))];
4554
+ for (const { entry, hasBody } of entries) {
4041
4555
  if (entry.Key === void 0 || !wanted.has(entry.Key)) continue;
4042
- if (entry.IsLatest === void 0) recordFailure(failed, entry.Key, `version ${entry.VersionId ?? "<unknown>"}: listing omitted IsLatest, so the entry was left alone rather than risk deleting a current version`);
4556
+ if (entry.IsLatest === void 0) {
4557
+ recordFailure(failed, entry.Key, `version ${entry.VersionId ?? "<unknown>"}: listing omitted IsLatest, so the entry was left alone rather than risk deleting a current version`);
4558
+ if (hasBody) unsettledBodies.add(entry.Key);
4559
+ }
4043
4560
  if (entry.IsLatest !== false) continue;
4044
- if (!entry.VersionId) continue;
4561
+ if (!entry.VersionId) {
4562
+ recordFailure(failed, entry.Key, `listing returned a noncurrent entry with no VersionId, so it could not be deleted`);
4563
+ if (hasBody) unsettledBodies.add(entry.Key);
4564
+ continue;
4565
+ }
4045
4566
  stale.push({
4046
4567
  Key: entry.Key,
4047
4568
  VersionId: entry.VersionId
4048
4569
  });
4570
+ if (hasBody) purged.add(entry.Key);
4049
4571
  }
4050
4572
  for (let i = 0; i < stale.length; i += DELETE_BATCH_SIZE) {
4051
4573
  const batch = stale.slice(i, i + DELETE_BATCH_SIZE);
@@ -4072,7 +4594,10 @@ async function purgeUnderPrefix(s3Client, bucket, prefix, wanted, requestFields,
4072
4594
  }
4073
4595
  }
4074
4596
  if (resp.IsTruncated === true && resp.NextKeyMarker === void 0) {
4075
- for (const key of wanted) if (key.startsWith(prefix)) recordFailure(failed, key, TRUNCATED_NO_MARKER);
4597
+ for (const key of wanted) if (key.startsWith(prefix)) {
4598
+ recordFailure(failed, key, TRUNCATED_NO_MARKER);
4599
+ unsettledBodies.add(key);
4600
+ }
4076
4601
  }
4077
4602
  keyMarker = resp.IsTruncated === true ? resp.NextKeyMarker : void 0;
4078
4603
  versionIdMarker = keyMarker !== void 0 ? resp.NextVersionIdMarker : void 0;
@@ -5963,6 +6488,374 @@ function mergeEnv(overrides) {
5963
6488
  else merged[k] = v;
5964
6489
  return merged;
5965
6490
  }
6491
+ /** Replaces a redacted argv VALUE wherever this module masks one. */
6492
+ const REDACTED_ARGV_VALUE = "***";
6493
+ /**
6494
+ * `docker` argv flags whose NEXT token is a `KEY=VALUE` pair built from
6495
+ * user-supplied template data, and whose VALUE therefore must not reach a
6496
+ * user-visible error string:
6497
+ *
6498
+ * - `-e` / `--env` — a container's `Environment.Variables` (Lambda) or
6499
+ * `ContainerDefinition.Environment` (ECS), plus `--env-vars` overrides.
6500
+ * Values that cdkd classifies as sensitive never get here at all
6501
+ * ({@link partitionSensitiveEnv} emits those as a value-less `-e KEY`),
6502
+ * but everything else does: connection strings, endpoints, API base URLs.
6503
+ * - `--opt` — `DockerVolumeConfiguration.DriverOpts`, which for the `local`
6504
+ * driver carries mount options (`o=addr=…,username=…,password=…`).
6505
+ * - `--label` — `DockerVolumeConfiguration.Labels`, user-authored metadata.
6506
+ *
6507
+ * A flag NOT in this list keeps its value. For most of the argv that is
6508
+ * because the value is cdkd-authored or infrastructure-shaped — a container
6509
+ * id, an image ref, a `--subnet` CIDR, a `--format` template — and is the
6510
+ * diagnostic.
6511
+ *
6512
+ * That is NOT true of the whole remainder, and the residual is recorded here
6513
+ * rather than quietly implied: `--health-cmd`, `--ulimit`, `--link`,
6514
+ * `--entrypoint`, `--workdir` and the positional image + container command are
6515
+ * all template-supplied and still echoed. They stay unmasked deliberately —
6516
+ * each is a COMMAND or a structural knob whose text IS what an operator reads
6517
+ * the failure for, and none is a documented place to put a secret, unlike
6518
+ * `Environment` / `Secrets` / `DriverOpts`. Revisit per flag if a real leak is
6519
+ * found through one; do not widen the set on suspicion, since every addition
6520
+ * trades away diagnostic text.
6521
+ */
6522
+ const ARGV_VALUE_BEARING_FLAGS = /* @__PURE__ */ new Set([
6523
+ "-e",
6524
+ "--env",
6525
+ "--opt",
6526
+ "--label"
6527
+ ]);
6528
+ /**
6529
+ * Structural, whitespace-delimited form of {@link ARGV_VALUE_BEARING_FLAGS}
6530
+ * for scanning a STRING that embeds a space-joined argv. Keyed on the FLAG's
6531
+ * position — never on the secret's value, so an unrelated literal that merely
6532
+ * coincides with a value is left alone.
6533
+ *
6534
+ * What each piece actually buys, since one of them was mis-credited in review:
6535
+ * the `(^|\s)` lead is what stops `-e` matching inside `--env` (there is no
6536
+ * `m` flag, so `^` is start-of-INPUT), NOT the alternation order — reordering
6537
+ * the branches leaves every test green. The order is kept as belt and braces
6538
+ * only. The mandatory `\s+` after the flag IS load-bearing: it is what stops
6539
+ * `-easy` / `--environment` matching. The KEY is `[^\s=]*` rather than `+` so
6540
+ * an EMPTY key (`-e =value`) is masked too.
6541
+ */
6542
+ const ARGV_VALUE_TOKEN_RE = /(^|\s)(--env|--label|--opt|-e)(\s+)([^\s=]*)=\S*/g;
6543
+ /**
6544
+ * Return a copy of `args` with the VALUE of every
6545
+ * {@link ARGV_VALUE_BEARING_FLAGS} pair replaced by `***`. The KEY survives —
6546
+ * "which variable" is the diagnostic, "what it was set to" is the disclosure.
6547
+ *
6548
+ * A value-less `-e KEY` (the form {@link partitionSensitiveEnv} emits for a
6549
+ * sensitive key) has nothing to mask and is returned unchanged. `args` is
6550
+ * never mutated: the redacted copy is for DISPLAY only, never for `spawn`.
6551
+ */
6552
+ function redactDockerArgvValues(args) {
6553
+ const out = [];
6554
+ for (let i = 0; i < args.length; i++) {
6555
+ const cur = args[i];
6556
+ const next = args[i + 1];
6557
+ if (ARGV_VALUE_BEARING_FLAGS.has(cur) && typeof next === "string") {
6558
+ const eqIdx = next.indexOf("=");
6559
+ if (eqIdx >= 0) {
6560
+ out.push(cur, `${next.substring(0, eqIdx)}=${REDACTED_ARGV_VALUE}`);
6561
+ i++;
6562
+ continue;
6563
+ }
6564
+ }
6565
+ out.push(cur);
6566
+ }
6567
+ return out;
6568
+ }
6569
+ /**
6570
+ * Redact argv-borne values inside an error TEXT before it reaches the user.
6571
+ *
6572
+ * **Wrap EVERY `execFile`-derived docker failure text in this**, including one
6573
+ * whose argv carries no user data today — `execFile` puts the WHOLE command
6574
+ * line into `err.message` (`Command failed: <file> <args joined by ' '>\n<stderr>`,
6575
+ * measured on Node 24), so any argv that later gains a `-e` pair starts leaking
6576
+ * with no edit to the error site. That silent-gap shape is exactly what
6577
+ * [#2440](https://github.com/go-to-k/cdkd/issues/2440) reported: the debug log
6578
+ * one screen earlier redacted the same argv and the error path did not.
6579
+ *
6580
+ * Two passes, both structural (positional), never value-based — matching on a
6581
+ * secret's VALUE would also blank an unrelated string that happens to equal it:
6582
+ *
6583
+ * 1. **Exact command-line substitution** (only when `args` is given). The raw
6584
+ * `args.join(' ')` is replaced by {@link redactDockerArgvValues}' rendering
6585
+ * of the same array. This is the pass that matters, because it is the only
6586
+ * one that can mask a value CONTAINING WHITESPACE (`-e CFG={"a": 1}`), which
6587
+ * no token scan of a space-joined string can delimit.
6588
+ * 2. **Token scan** ({@link ARGV_VALUE_TOKEN_RE}) over the result. Catches an
6589
+ * argv echoed in a shape pass 1 cannot see — a future Node message format,
6590
+ * a docker stderr quoting the flag back, or a call site with no `args` to
6591
+ * hand. Deliberately fail-loud rather than fail-open: a `-e X=y` occurring
6592
+ * in unrelated stderr prose loses its value and keeps its key.
6593
+ *
6594
+ * Idempotent (`-e KEY=***` re-masks to itself), so double-wrapping is safe.
6595
+ *
6596
+ * **Call sites should use a composer** ({@link describeDockerFailure} and its
6597
+ * siblings) rather than this function: the composers take a REQUIRED `args`,
6598
+ * which is what makes the redaction impossible to forget. This is the
6599
+ * primitive they are built from, exported so the four passes can be tested
6600
+ * against Node's real message shapes directly — it has no other caller in
6601
+ * `src/`, and that is deliberate rather than an oversight.
6602
+ */
6603
+ function redactDockerArgvInText(text, args) {
6604
+ let out = text;
6605
+ if (args && args.length > 0) {
6606
+ const maskedArgs = redactDockerArgvValues(args);
6607
+ const raw = args.join(" ");
6608
+ const masked = maskedArgs.join(" ");
6609
+ if (masked !== raw) out = out.split(raw).join(masked);
6610
+ for (let i = 0; i < args.length; i++) {
6611
+ const rawArg = args[i];
6612
+ const maskedArg = maskedArgs[i];
6613
+ if (maskedArg === rawArg) continue;
6614
+ if (!isSubstitutableToken(rawArg)) continue;
6615
+ out = out.split(rawArg).join(maskedArg);
6616
+ const escapedRaw = nodeQuotedRendering(rawArg);
6617
+ const escapedMasked = nodeQuotedRendering(maskedArg);
6618
+ if (escapedRaw !== void 0 && escapedMasked !== void 0 && escapedRaw !== rawArg) out = out.split(escapedRaw).join(escapedMasked);
6619
+ }
6620
+ out = repairSpawnRefusal(out, args, maskedArgs);
6621
+ }
6622
+ return out.replace(ARGV_VALUE_TOKEN_RE, (_match, lead, flag, gap, key) => `${lead}${flag}${gap}${key}=${REDACTED_ARGV_VALUE}`);
6623
+ }
6624
+ /**
6625
+ * Shortest VALUE that pass 1b will substitute as a bare token.
6626
+ *
6627
+ * Pass 1b's needle is the whole `KEY=VALUE` element, and it is replaced
6628
+ * EVERYWHERE in the text — so a tiny needle is a liability rather than a
6629
+ * protection. The empty-key case makes that concrete: a non-sensitive
6630
+ * `{ Name: '', Value: '1' }` produces the two-character token `=1`, and
6631
+ * substituting that rewrites every `=1` in the message (`--cpus=1`,
6632
+ * `status=1`, a path segment). Below this floor the value is not worth
6633
+ * protecting by substring match, and passes 1 and 3 still cover it in the
6634
+ * message shapes that carry a `-e ` prefix or the joined command line.
6635
+ */
6636
+ const MIN_SUBSTITUTABLE_VALUE_LENGTH = 4;
6637
+ /** Is this `KEY=VALUE` element long enough to substitute as a bare token? */
6638
+ function isSubstitutableToken(arg) {
6639
+ const eqIdx = arg.indexOf("=");
6640
+ if (eqIdx < 0) return false;
6641
+ return arg.length - eqIdx - 1 >= MIN_SUBSTITUTABLE_VALUE_LENGTH;
6642
+ }
6643
+ /**
6644
+ * Render `value` the way Node renders an argv element it quotes back at you —
6645
+ * i.e. with the SAME function Node used, `util.inspect`, minus the quote
6646
+ * characters it chose.
6647
+ *
6648
+ * This started as a hand-rolled `\xNN` escaper and it was WRONG for 16 of the
6649
+ * first 128 code points. Measured against real `execFile` rejections on Node
6650
+ * 24.19.0 (this repo, 2026-09-05): hand-rolled matched 4 of 13 cases,
6651
+ * `inspect` matched 13 of 13.
6652
+ *
6653
+ * input Node emits hand-rolled built
6654
+ * LF `K=a\nb\x00S` `K=a\x0ab\x00S` (Node uses a short escape)
6655
+ * DEL `K=a\x7Fb\x00S` `K=a\x7fb\x00S` (Node uses UPPERCASE hex)
6656
+ * backslash `K=a\\b\x00S` `K=a\b\x00S` (not escaped at all)
6657
+ *
6658
+ * Every miss printed the whole secret. The trigger for this path is a NUL in
6659
+ * a value — binary-ish data, which almost always carries a second control
6660
+ * byte or a backslash — so the table missed the REALISTIC case and hit only
6661
+ * the synthetic NUL-alone one its own fixture happened to use.
6662
+ *
6663
+ * `slice(1, -1)` is safe across the quote Node picks: `inspect` switches
6664
+ * between `'`, `"` and a backtick depending on content and escapes whichever
6665
+ * it chose; because this is the same call Node made, the rendering matches
6666
+ * whatever it picked (verified for a value containing a single quote, a
6667
+ * double quote, both, and a backtick).
6668
+ *
6669
+ * The general lesson, and why this is no longer a table: do not re-implement
6670
+ * another program's formatter — call it.
6671
+ */
6672
+ function nodeQuotedRendering(value) {
6673
+ const rendered = inspect(value);
6674
+ const quote = rendered[0];
6675
+ if (quote === void 0 || !`'"\``.includes(quote)) return void 0;
6676
+ if (rendered.length < 2 || !rendered.endsWith(quote)) return void 0;
6677
+ const inner = rendered.slice(1, -1);
6678
+ return inner.includes(`${quote} +`) ? void 0 : inner;
6679
+ }
6680
+ /**
6681
+ * Node's refusal to spawn, which QUOTES ONE argv element and names its INDEX:
6682
+ *
6683
+ * The argument 'args[2]' must be a string without null bytes. Received '…'
6684
+ *
6685
+ * The one message shape where the index tells us exactly which of OUR args is
6686
+ * being echoed — so the clause can be REWRITTEN from cdkd's own copy of the
6687
+ * element rather than searched for. That matters because Node does not print
6688
+ * the element whole: it truncates near 200 characters and renders a
6689
+ * newline-bearing value as concatenated chunks, so no needle can match it.
6690
+ *
6691
+ * Global, and scanned to exhaustion rather than bailing on the first match.
6692
+ * Anchoring at `^` was an earlier attempt at forgery resistance and it was
6693
+ * WORSE: it made a refusal anywhere but position 0 unrepairable, which is a
6694
+ * LEAK, and a leak beats a truncated message every time. Pinning Node's
6695
+ * literal prefix keeps the forgery bar high without that trade.
6696
+ *
6697
+ * Pinning the wording has its own cost: if Node rewords the message the repair
6698
+ * silently stops firing. That is why `tests/unit/utils/docker-cmd.test.ts`
6699
+ * drives this path from a REAL rejection — a reword turns CI red instead of
6700
+ * turning the redaction off.
6701
+ */
6702
+ const SPAWN_REFUSAL_RE = /The argument 'args\[(\d+)\]'[^']*?Received /g;
6703
+ /**
6704
+ * The quoted element Node prints after `Received `.
6705
+ *
6706
+ * Three shapes, and the third is the one that matters. `inspect` renders a
6707
+ * newline-bearing value as chunks joined by ` +`, and Node then SLICES the
6708
+ * whole rendering at 128 characters and appends `...` — so the last chunk is
6709
+ * usually UNTERMINATED:
6710
+ *
6711
+ * Received 'DB_URL=AAA…\n' +
6712
+ * 'hunter2SECRETCCC...
6713
+ *
6714
+ * A pattern that only accepts complete chunks stops after the second `'` and
6715
+ * leaves that tail in place. Measured on a real rejection: 272 of 288 probe
6716
+ * shapes leaked the secret verbatim, and the end-of-string version this
6717
+ * replaced did not — the bound was a regression, not a hardening, until the
6718
+ * trailing open chunk was admitted.
6719
+ *
6720
+ * The separator is Node's own (` +\n `) with HORIZONTAL space only, and an
6721
+ * open chunk runs to end of LINE. Both are deliberate: `\s*\+\s*` and an
6722
+ * unbounded tail let a crafted value swallow the diagnostic lines that follow
6723
+ * the clause.
6724
+ */
6725
+ const QUOTED_CHUNK = String.raw`'(?:[^'\\\n]|\\.)*'|"(?:[^"\\\n]|\\.)*"|\`(?:[^\`\\\n]|\\.)*\``;
6726
+ /** An opening quote whose closing one was truncated away; bounded to its line. */
6727
+ const OPEN_CHUNK = String.raw`['"\`](?:[^\\\n]|\\.)*`;
6728
+ /** Node's chunk join: horizontal space, `+`, at most one newline, horizontal space. */
6729
+ const CHUNK_SEPARATOR = String.raw`[^\S\n]*\+[^\S\n]*\n?[^\S\n]*`;
6730
+ const QUOTED_ELEMENT_RE = new RegExp(`^(?:(?:${QUOTED_CHUNK})(?:${CHUNK_SEPARATOR}(?:${QUOTED_CHUNK}))*(?:${CHUNK_SEPARATOR}(?:${OPEN_CHUNK}))?|(?:${OPEN_CHUNK}))`);
6731
+ /**
6732
+ * Replace the quoted argv element in a spawn-refusal message with the masked
6733
+ * rendering of the arg its OWN index names.
6734
+ *
6735
+ * Known cosmetic effect: the replacement prints the FULL key where Node had
6736
+ * truncated, so a pathologically long env-var NAME makes the message longer
6737
+ * than Node's ~200 characters. Keys are not secret, and the width is not worth
6738
+ * a second truncation rule with its own edge cases.
6739
+ */
6740
+ function repairSpawnRefusal(text, args, maskedArgs) {
6741
+ const scanner = new RegExp(SPAWN_REFUSAL_RE.source, "g");
6742
+ let out = "";
6743
+ let cursor = 0;
6744
+ for (let match = scanner.exec(text); match !== null; match = scanner.exec(text)) {
6745
+ if (match.index < cursor) continue;
6746
+ const index = Number(match[1]);
6747
+ const rawArg = args[index];
6748
+ const maskedArg = maskedArgs[index];
6749
+ if (rawArg !== void 0 && maskedArg === rawArg) continue;
6750
+ const clauseEnd = match.index + match[0].length;
6751
+ const quoted = QUOTED_ELEMENT_RE.exec(text.slice(clauseEnd));
6752
+ if (quoted === null) continue;
6753
+ const replacement = maskedArg === void 0 ? `'${REDACTED_ARGV_VALUE}'` : inspect(maskedArg);
6754
+ out += text.slice(cursor, clauseEnd) + replacement;
6755
+ cursor = clauseEnd + quoted[0].length;
6756
+ scanner.lastIndex = cursor;
6757
+ }
6758
+ return out + text.slice(cursor);
6759
+ }
6760
+ /**
6761
+ * Compose a user-visible description of a `child_process` rejection from a
6762
+ * docker call, ALREADY REDACTED against that call's own argv.
6763
+ *
6764
+ * Moved here from `src/local/invoke-agentcore-watch-loop.ts` in issue #2440's
6765
+ * review round 3, and the move is the point: `args` is REQUIRED, so a call
6766
+ * site cannot obtain the text without handing over the argv to redact it
6767
+ * with. That is a stronger guarantee than any text fence over the call sites
6768
+ * — which is what the round-2 reviewers demonstrated, by writing four
6769
+ * spellings of an unredacted read that the fence could not see.
6770
+ *
6771
+ * Shape differs from the `stderr || message` composition the other sites use,
6772
+ * and deliberately so: `err.stderr` is where docker writes its actionable
6773
+ * diagnostics, while `err.message` carries the exit status, so the AgentCore
6774
+ * soft-reload path APPENDS rather than prefers — without stderr the wrapped
6775
+ * error would only say "Command failed with exit code N".
6776
+ */
6777
+ function describeDockerExecFailure(error, args) {
6778
+ const message = thrownMessageText(error) || safeStringify(error);
6779
+ const stderrText = capturedStreamText(error, "stderr");
6780
+ return redactDockerArgvInText(stderrText ? `${message}\n${stderrText}` : message, args);
6781
+ }
6782
+ /**
6783
+ * A captured stream of a `child_process` rejection as trimmed text, or `''`.
6784
+ *
6785
+ * Takes `unknown`, not `Error`, and duck-types the field. Every composer here
6786
+ * is called from a `catch`, where the value is whatever was thrown — and the
6787
+ * shapes the call sites actually see are plain objects
6788
+ * (`{ stderr, message }`), `SpawnError`, and cross-realm `Error`s that
6789
+ * `instanceof` misses. An earlier revision narrowed this to `Error` and the
6790
+ * standard composer then wrapped non-Errors in a FRESH `Error`, which has no
6791
+ * `.stderr` at all — so the whole diagnostic silently became
6792
+ * `'[object Object]'` for exactly the shape most of the call sites throw.
6793
+ *
6794
+ * `execFile` hands back a string under the default encoding and a `Buffer`
6795
+ * under `encoding: 'buffer'`; `ArrayBuffer.isView` covers a plain
6796
+ * `Uint8Array` too.
6797
+ */
6798
+ function capturedStreamText(error, field) {
6799
+ try {
6800
+ const raw = error?.[field];
6801
+ if (typeof raw === "string") return raw.trim();
6802
+ if (ArrayBuffer.isView(raw)) return Buffer.from(raw.buffer, raw.byteOffset, raw.byteLength).toString("utf8").trim();
6803
+ } catch {}
6804
+ return "";
6805
+ }
6806
+ /** A thrown value's `message` as text, or `''`. Duck-typed, for the same reason. */
6807
+ function thrownMessageText(error) {
6808
+ try {
6809
+ const raw = error?.["message"];
6810
+ return typeof raw === "string" ? raw : "";
6811
+ } catch {
6812
+ return "";
6813
+ }
6814
+ }
6815
+ /**
6816
+ * `String(value)` that cannot itself throw. A rejection with a null prototype
6817
+ * (or a `toString` that throws) would otherwise blow up INSIDE a `catch` — and
6818
+ * two of these call sites are in `cleanupEcsRun`, where an exception aborts
6819
+ * the remaining volume / network teardown and leaks real Docker resources.
6820
+ */
6821
+ function safeStringify(error) {
6822
+ try {
6823
+ return String(error);
6824
+ } catch {
6825
+ return "[unstringifiable rejection]";
6826
+ }
6827
+ }
6828
+ /**
6829
+ * The STANDARD composer for a docker failure text: the captured stderr if
6830
+ * there is any, else the error's own message, else its string form —
6831
+ * ALREADY REDACTED against that call's argv.
6832
+ *
6833
+ * Use this at every site that wraps a docker `execFile` / spawn rejection.
6834
+ * `args` is REQUIRED, which is the whole point: a call site cannot obtain the
6835
+ * text without handing over the argv to redact it with, so the guarantee is
6836
+ * type-checked rather than fenced. Issue #2440's review spent three rounds
6837
+ * showing that a text fence over hand-composed sites is evadable — an
6838
+ * intermediate variable, an inline cast, a destructure, a computed member, a
6839
+ * concat — while this shape has nothing to evade.
6840
+ *
6841
+ * Prefer stderr over message because docker writes its actionable diagnostic
6842
+ * there, and `execFile`'s message is mostly the command line plus an exit
6843
+ * status. {@link describeDockerExecFailure} keeps BOTH, for a caller whose
6844
+ * wrapper text needs the status as well.
6845
+ */
6846
+ function describeDockerFailure(error, args) {
6847
+ return redactDockerArgvInText(capturedStreamText(error, "stderr") || thrownMessageText(error) || safeStringify(error), args);
6848
+ }
6849
+ /**
6850
+ * Composer for a captured-output failure where the diagnostic may be on
6851
+ * STDOUT rather than stderr (`runDockerStreaming`'s non-zero-exit path, whose
6852
+ * `SpawnError` carries both). `fallback` is used when neither stream said
6853
+ * anything. Redacted, and `args` is required, for the same reason as
6854
+ * {@link describeDockerFailure}.
6855
+ */
6856
+ function describeDockerCapturedOutput(error, args, fallback) {
6857
+ return redactDockerArgvInText(capturedStreamText(error, "stderr") || capturedStreamText(error, "stdout") || fallback, args);
6858
+ }
5966
6859
 
5967
6860
  //#endregion
5968
6861
  //#region src/assets/docker-build.ts
@@ -21642,7 +22535,8 @@ function ccProtectionProperty(resourceType) {
21642
22535
  const NON_PROVISIONABLE_TYPES = /* @__PURE__ */ new Set([
21643
22536
  "Alexa::ASK::Skill",
21644
22537
  "AWS::AmazonMQ::ConfigurationAssociation",
21645
- "AWS::ApiGatewayV2::ApiGatewayManagedOverrides",
22538
+ "AWS::Amplify::Jobs",
22539
+ "AWS::AmplifyUIBuilder::CodegenJob",
21646
22540
  "AWS::AppMesh::GatewayRoute",
21647
22541
  "AWS::AppMesh::Mesh",
21648
22542
  "AWS::AppMesh::Route",
@@ -21651,64 +22545,90 @@ const NON_PROVISIONABLE_TYPES = /* @__PURE__ */ new Set([
21651
22545
  "AWS::AppMesh::VirtualRouter",
21652
22546
  "AWS::AppMesh::VirtualService",
21653
22547
  "AWS::AppStream::Fleet",
22548
+ "AWS::AppStream::StackFleetAssociation",
21654
22549
  "AWS::AppSync::ApiCache",
21655
22550
  "AWS::Artifact::Report",
21656
22551
  "AWS::Athena::Session",
21657
22552
  "AWS::AutoScalingPlans::ScalingPlan",
21658
22553
  "AWS::BackupSearch::SearchJob",
22554
+ "AWS::BackupSearch::SearchResultExportJob",
21659
22555
  "AWS::BCMDataExports::Table",
22556
+ "AWS::Bedrock::AsyncInvoke",
21660
22557
  "AWS::Bedrock::DefaultPromptRouter",
22558
+ "AWS::Bedrock::FlowExecution",
22559
+ "AWS::Bedrock::FoundationModel",
22560
+ "AWS::Bedrock::ImportedModel",
22561
+ "AWS::Bedrock::ModelImportJob",
21661
22562
  "AWS::Bedrock::ModelInvocationJob",
22563
+ "AWS::BedrockAgentCore::ConfigurationBundleVersion",
22564
+ "AWS::BedrockAgentCore::HarnessVersion",
22565
+ "AWS::BedrockAgentCore::PolicyGeneration",
21662
22566
  "AWS::BedrockAgentCore::TokenVault",
22567
+ "AWS::Braket::Job",
22568
+ "AWS::Cassandra::Stream",
21663
22569
  "AWS::Cloud9::EnvironmentEC2",
21664
22570
  "AWS::CloudFormation::Macro",
21665
22571
  "AWS::CloudFormation::ResourceScan",
21666
22572
  "AWS::CloudFormation::WaitCondition",
21667
22573
  "AWS::CloudFront::StreamingDistribution",
21668
22574
  "AWS::CodeArtifact::Package",
21669
- "AWS::CodeBuild::ReportGroup",
22575
+ "AWS::CodeBuild::Build",
22576
+ "AWS::CodeBuild::BuildBatch",
21670
22577
  "AWS::CodeBuild::Sandbox",
21671
- "AWS::CodeBuild::SourceCredential",
21672
22578
  "AWS::CodeStar::GitHubRepository",
21673
22579
  "AWS::CognitoSync::Dataset",
22580
+ "AWS::Comprehend::DocumentClassificationJob",
22581
+ "AWS::Comprehend::DominantLanguageDetectionJob",
22582
+ "AWS::Comprehend::EntitiesDetectionJob",
22583
+ "AWS::Comprehend::FlywheelDataset",
22584
+ "AWS::Comprehend::SentimentDetectionJob",
22585
+ "AWS::Comprehend::TargetedSentimentDetectionJob",
21674
22586
  "AWS::Config::ConfigurationRecorder",
21675
22587
  "AWS::Config::DeliveryChannel",
21676
22588
  "AWS::Config::OrganizationConfigRule",
22589
+ "AWS::ControlCatalog::CommonControl",
22590
+ "AWS::ControlCatalog::Control",
21677
22591
  "AWS::ControlCatalog::Objective",
22592
+ "AWS::DataExchange::Assets",
22593
+ "AWS::DataExchange::EntitledDataSets",
22594
+ "AWS::DataExchange::Job",
22595
+ "AWS::DataSync::TaskExecution",
21678
22596
  "AWS::DAX::Cluster",
21679
- "AWS::DAX::ParameterGroup",
21680
22597
  "AWS::DAX::SubnetGroup",
22598
+ "AWS::Deadline::Job",
21681
22599
  "AWS::DirectoryService::MicrosoftAD",
21682
- "AWS::DMS::EventSubscription",
21683
22600
  "AWS::DMS::ReplicationInstance",
21684
- "AWS::DMS::ReplicationSubnetGroup",
21685
- "AWS::DMS::ReplicationTask",
21686
- "AWS::DocDB::DBClusterParameterGroup",
22601
+ "AWS::DRS::RecoveryInstance",
21687
22602
  "AWS::DynamoDB::Export",
22603
+ "AWS::DynamoDB::Stream",
21688
22604
  "AWS::EC2::ClientVpnAuthorizationRule",
21689
22605
  "AWS::EC2::ClientVpnEndpoint",
21690
22606
  "AWS::EC2::ClientVpnRoute",
21691
22607
  "AWS::EC2::ClientVpnTargetNetworkAssociation",
22608
+ "AWS::EC2::ExportInstanceTask",
21692
22609
  "AWS::EC2::NetworkInterfacePermission",
22610
+ "AWS::EC2::ReplaceRootVolumeTask",
22611
+ "AWS::EC2::VpnConnectionDeviceType",
21693
22612
  "AWS::EC2::VPNGatewayRoutePropagation",
22613
+ "AWS::ECRPublic::Registry",
22614
+ "AWS::ECS::ContainerInstance",
22615
+ "AWS::ECS::Task",
21694
22616
  "AWS::ElastiCache::ReservedCacheNode",
21695
22617
  "AWS::ElastiCache::SecurityGroup",
21696
22618
  "AWS::ElastiCache::SecurityGroupIngress",
21697
22619
  "AWS::ElasticLoadBalancingV2::ListenerCertificate",
21698
22620
  "AWS::Elasticsearch::Domain",
21699
22621
  "AWS::EMR::NotebookExecution",
22622
+ "AWS::EMRContainers::JobRun",
22623
+ "AWS::EMRServerless::JobRun",
21700
22624
  "AWS::Events::Replay",
22625
+ "AWS::FIS::Experiment",
21701
22626
  "AWS::FIS::SafetyLever",
21702
22627
  "AWS::FSx::Snapshot",
21703
22628
  "AWS::FSx::StorageVirtualMachine",
21704
- "AWS::FSx::Volume",
21705
- "AWS::Glue::Classifier",
21706
- "AWS::Glue::CustomEntityType",
21707
- "AWS::Glue::DataQualityRuleset",
21708
22629
  "AWS::Glue::DevEndpoint",
21709
- "AWS::Glue::MLTransform",
21710
22630
  "AWS::Glue::Partition",
21711
- "AWS::Glue::TableOptimizer",
22631
+ "AWS::Glue::TableVersion",
21712
22632
  "AWS::Greengrass::ConnectorDefinition",
21713
22633
  "AWS::Greengrass::ConnectorDefinitionVersion",
21714
22634
  "AWS::Greengrass::CoreDefinition",
@@ -21730,11 +22650,18 @@ const NON_PROVISIONABLE_TYPES = /* @__PURE__ */ new Set([
21730
22650
  "AWS::IdentityStore::AllGroupMemberships",
21731
22651
  "AWS::ImageBuilder::AllImageBuildVersions",
21732
22652
  "AWS::ImageBuilder::AllWorkflowBuildVersions",
22653
+ "AWS::ImageBuilder::LifecycleExecution",
21733
22654
  "AWS::ImageBuilder::WorkflowExecution",
21734
22655
  "AWS::ImageBuilder::WorkflowStepExecution",
22656
+ "AWS::InternetMonitor::InternetEvent",
22657
+ "AWS::IoT::Index",
21735
22658
  "AWS::IoT::PolicyPrincipalAttachment",
21736
22659
  "AWS::IoT::ThingPrincipalAttachment",
22660
+ "AWS::IoTDeviceAdvisor::SuiteRun",
21737
22661
  "AWS::IoTThingsGraph::FlowTemplate",
22662
+ "AWS::IoTTwinMaker::MetadataTransferJob",
22663
+ "AWS::IVS::Composition",
22664
+ "AWS::KafkaConnect::ConnectorOperation",
21738
22665
  "AWS::KinesisAnalytics::Application",
21739
22666
  "AWS::KinesisAnalytics::ApplicationOutput",
21740
22667
  "AWS::KinesisAnalytics::ApplicationReferenceDataSource",
@@ -21744,22 +22671,32 @@ const NON_PROVISIONABLE_TYPES = /* @__PURE__ */ new Set([
21744
22671
  "AWS::LakeFormation::DataLakeSettings",
21745
22672
  "AWS::LakeFormation::Permissions",
21746
22673
  "AWS::LakeFormation::Resource",
22674
+ "AWS::Lambda::DurableExecution",
22675
+ "AWS::Lightsail::ExportSnapshotRecord",
22676
+ "AWS::Location::Job",
22677
+ "AWS::Macie2::ClassificationJob",
21747
22678
  "AWS::ManagedBlockchain::Member",
21748
22679
  "AWS::ManagedBlockchain::Node",
21749
22680
  "AWS::MediaConnect::Offering",
21750
22681
  "AWS::MediaConnect::Reservation",
21751
22682
  "AWS::MediaConvert::JobTemplate",
21752
- "AWS::MediaConvert::Preset",
21753
22683
  "AWS::MediaConvert::Queue",
21754
22684
  "AWS::MediaLive::Channel",
21755
22685
  "AWS::MediaLive::Input",
21756
22686
  "AWS::MediaLive::InputSecurityGroup",
21757
22687
  "AWS::MediaLive::Offering",
21758
22688
  "AWS::MediaPackage::HarvestJob",
22689
+ "AWS::MediaPackageV2::HarvestJob",
21759
22690
  "AWS::MediaStore::Container",
22691
+ "AWS::MedicalImaging::ImageSet",
21760
22692
  "AWS::MemoryDB::MultiRegionParameterGroup",
21761
22693
  "AWS::MemoryDB::ReservedNode",
22694
+ "AWS::NeptuneGraph::ExportTask",
22695
+ "AWS::NovaAct::WorkflowRun",
22696
+ "AWS::Omics::ReadSet",
21762
22697
  "AWS::Omics::Reference",
22698
+ "AWS::Omics::Run",
22699
+ "AWS::Omics::Task",
21763
22700
  "AWS::OpsWorks::App",
21764
22701
  "AWS::OpsWorks::ElasticLoadBalancerAttachment",
21765
22702
  "AWS::OpsWorks::Instance",
@@ -21767,8 +22704,14 @@ const NON_PROVISIONABLE_TYPES = /* @__PURE__ */ new Set([
21767
22704
  "AWS::OpsWorks::Stack",
21768
22705
  "AWS::OpsWorks::UserProfile",
21769
22706
  "AWS::OpsWorks::Volume",
22707
+ "AWS::Organizations::Root",
21770
22708
  "AWS::OSIS::PipelineBlueprint",
22709
+ "AWS::PartnerCentral::ConnectionPreferences",
22710
+ "AWS::PartnerCentral::Partner",
22711
+ "AWS::Personalize::BatchInferenceJob",
22712
+ "AWS::Personalize::BatchSegmentJob",
21771
22713
  "AWS::Personalize::DataDeletionJob",
22714
+ "AWS::Personalize::DatasetExportJob",
21772
22715
  "AWS::Personalize::Recipe",
21773
22716
  "AWS::Pinpoint::ADMChannel",
21774
22717
  "AWS::Pinpoint::APNSChannel",
@@ -21793,33 +22736,50 @@ const NON_PROVISIONABLE_TYPES = /* @__PURE__ */ new Set([
21793
22736
  "AWS::PinpointEmail::DedicatedIpPool",
21794
22737
  "AWS::PinpointEmail::Identity",
21795
22738
  "AWS::QLDB::Ledger",
22739
+ "AWS::QuickSight::AssetBundleExportJob",
22740
+ "AWS::QuickSight::AssetBundleImportJob",
21796
22741
  "AWS::RDS::DBSecurityGroup",
21797
22742
  "AWS::RDS::DBSecurityGroupIngress",
22743
+ "AWS::RDS::ReservedDBInstance",
21798
22744
  "AWS::Redshift::ClusterSecurityGroup",
21799
22745
  "AWS::Redshift::ClusterSecurityGroupIngress",
22746
+ "AWS::Redshift::DataShare",
21800
22747
  "AWS::RedshiftServerless::RecoveryPoint",
22748
+ "AWS::ResilienceHub::RecommendationTemplate",
21801
22749
  "AWS::Route53::RecordSetGroup",
22750
+ "AWS::Route53Resolver::FirewallConfig",
22751
+ "AWS::SageMaker::AutoMLJob",
21802
22752
  "AWS::SageMaker::CodeRepository",
21803
- "AWS::SageMaker::EndpointConfig",
22753
+ "AWS::SageMaker::ExperimentTrialComponent",
22754
+ "AWS::SageMaker::HubContentVersion",
22755
+ "AWS::SageMaker::HyperParameterTuningJob",
21804
22756
  "AWS::SageMaker::ModelCardExportJob",
21805
22757
  "AWS::SageMaker::MonitoringScheduleAlert",
21806
22758
  "AWS::SageMaker::NotebookInstance",
21807
22759
  "AWS::SageMaker::NotebookInstanceLifecycleConfig",
22760
+ "AWS::SageMaker::OptimizationJob",
22761
+ "AWS::SageMaker::PipelineExecution",
22762
+ "AWS::SageMaker::TrainingJob",
21808
22763
  "AWS::SageMaker::TransformJob",
21809
22764
  "AWS::SageMaker::Workteam",
21810
- "AWS::SDB::Domain",
22765
+ "AWS::SavingsPlans::SavingsPlan",
22766
+ "AWS::SecurityAgent::PentestTask",
21811
22767
  "AWS::ServiceDiscovery::Instance",
21812
- "AWS::SES::ReceiptFilter",
21813
- "AWS::SES::ReceiptRule",
21814
- "AWS::SES::ReceiptRuleSet",
22768
+ "AWS::ServiceQuotas::Quota",
21815
22769
  "AWS::Signer::SigningJob",
22770
+ "AWS::SSM::AutomationExecution",
22771
+ "AWS::SSM::ManagedInstance",
21816
22772
  "AWS::SSM::Session",
21817
22773
  "AWS::SSO::ApplicationProvider",
21818
22774
  "AWS::States::Execution",
21819
22775
  "AWS::StepFunctions::MapRun",
21820
22776
  "AWS::ThinClient::SoftwareSet",
22777
+ "AWS::Transcribe::CallAnalyticsJob",
22778
+ "AWS::Transcribe::MedicalScribeJob",
21821
22779
  "AWS::Transcribe::MedicalTranscriptionJob",
22780
+ "AWS::Transcribe::TranscriptionJob",
21822
22781
  "AWS::UserNotifications::ManagedNotificationConfiguration",
22782
+ "AWS::UserNotifications::NotificationEvent",
21823
22783
  "AWS::WAF::ByteMatchSet",
21824
22784
  "AWS::WAF::IPSet",
21825
22785
  "AWS::WAF::Rule",
@@ -21837,7 +22797,9 @@ const NON_PROVISIONABLE_TYPES = /* @__PURE__ */ new Set([
21837
22797
  "AWS::WAFRegional::SqlInjectionMatchSet",
21838
22798
  "AWS::WAFRegional::WebACL",
21839
22799
  "AWS::WAFRegional::WebACLAssociation",
21840
- "AWS::WAFRegional::XssMatchSet"
22800
+ "AWS::WAFRegional::XssMatchSet",
22801
+ "AWS::Wisdom::Session",
22802
+ "AWS::WorkSpaces::WorkSpaceApplication"
21841
22803
  ]);
21842
22804
 
21843
22805
  //#endregion
@@ -22346,7 +23308,7 @@ var CloudControlProvider = class {
22346
23308
  const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
22347
23309
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
22348
23310
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
22349
- const { ASGProvider } = await import("./asg-provider-BocQJsjl.js").then((n) => n.n);
23311
+ const { ASGProvider } = await import("./asg-provider-uaOnRUMe.js").then((n) => n.n);
22350
23312
  return withIndeterminateGuard(await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
22351
23313
  }
22352
23314
  const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
@@ -29274,6 +30236,7 @@ const STATEFUL_TYPES = /* @__PURE__ */ new Set([
29274
30236
  "AWS::Cognito::UserPool",
29275
30237
  "AWS::SecretsManager::Secret",
29276
30238
  "AWS::SSM::Parameter",
30239
+ "AWS::CloudHSM::Cluster",
29277
30240
  "AWS::KMS::Key",
29278
30241
  "AWS::KMS::ReplicaKey",
29279
30242
  "AWS::CodeCommit::Repository",
@@ -29294,6 +30257,9 @@ const STATEFUL_TYPES = /* @__PURE__ */ new Set([
29294
30257
  "AWS::RedshiftServerless::Snapshot",
29295
30258
  "AWS::NeptuneGraph::Graph",
29296
30259
  "AWS::NeptuneGraph::GraphSnapshot",
30260
+ "AWS::RDS::DBSnapshot",
30261
+ "AWS::RDS::ClusterSnapshot",
30262
+ "AWS::DynamoDB::Backup",
29297
30263
  "AWS::MemoryDB::Cluster",
29298
30264
  "AWS::MemoryDB::MultiRegionCluster",
29299
30265
  "AWS::ElastiCache::ServerlessCache",
@@ -29319,6 +30285,7 @@ const STATEFUL_TYPES = /* @__PURE__ */ new Set([
29319
30285
  "AWS::S3Outposts::Bucket",
29320
30286
  "AWS::HealthImaging::Datastore",
29321
30287
  "AWS::HealthLake::FHIRDatastore",
30288
+ "AWS::FSx::Volume",
29322
30289
  "AWS::SES::MailManagerArchive",
29323
30290
  "AWS::WorkspacesInstances::Volume",
29324
30291
  "AWS::IoTAnalytics::Channel",
@@ -29341,6 +30308,7 @@ const STATEFUL_TYPES = /* @__PURE__ */ new Set([
29341
30308
  "AWS::Connect::DataTable",
29342
30309
  "AWS::AppConfig::ConfigurationProfile",
29343
30310
  "AWS::AIOps::InvestigationGroup",
30311
+ "AWS::IoTSiteWise::Workspace",
29344
30312
  "AWS::Rbin::Rule",
29345
30313
  "AWS::SMSVOICE::PhoneNumber",
29346
30314
  "AWS::SMSVOICE::SenderId"
@@ -29429,7 +30397,7 @@ function isStatefulRecreateTargetForReplace(resourceType, recordedProperties) {
29429
30397
  function renderStatefulReason(reason) {
29430
30398
  switch (reason) {
29431
30399
  case "always": return "destroy loses all data in the resource";
29432
- case "has-objects": return "S3 bucket is non-empty";
30400
+ case "has-objects": return "S3 bucket is not provably empty";
29433
30401
  case "has-retention": return "log group retains data (RetentionInDays > 0)";
29434
30402
  case "has-log-events": return "log group is not provably empty";
29435
30403
  case null: return "(not stateful)";
@@ -34099,42 +35067,64 @@ var DeployEngine = class {
34099
35067
  expectedRegion: this.stackRegion
34100
35068
  })), logicalId, void 0, void 0, updateProvider);
34101
35069
  } catch (updateError) {
34102
- const msg = updateError instanceof Error ? updateError.message : String(updateError);
34103
- const ccUnsupported = msg.includes("UnsupportedActionException") || msg.includes("does not support UPDATE");
35070
+ const ccUnsupported = isUpdateUnsupportedError(updateError, logicalId);
34104
35071
  const replaceOptIn = updateError instanceof ResourceUpdateNotSupportedError && this.options.replace === true;
34105
35072
  if (ccUnsupported || replaceOptIn) {
34106
- const statefulReason = isStatefulRecreateTargetForReplace(resourceType, currentProps);
34107
- if (statefulReason && this.options.forceStatefulRecreation !== true) {
34108
- const retainNote = updateReplacePolicy === "Retain" ? " Note: UpdateReplacePolicy: Retain does NOT protect this path — the replacement deletes the old resource regardless." : "";
34109
- throw markNonRetryable(new CdkdError((replaceOptIn ? `--replace would DELETE + CREATE the stateful resource ${logicalId} (${resourceType}) ${renderStatefulReason(statefulReason)}. Re-run with --force-stateful-recreation to confirm the data loss, or change the resource definition to avoid the immutable-property change.` : `${logicalId} (${resourceType}) cannot be updated in place by the provisioning layer it routes through, so applying this change would DELETE + CREATE it — but it is a stateful resource: ${renderStatefulReason(statefulReason)}. Re-run with --force-stateful-recreation to confirm the data loss, or change the resource definition to avoid the update.`) + retainNote, "STATEFUL_REPLACE_BLOCKED", updateError instanceof Error ? updateError : void 0));
34110
- }
34111
- this.logger.info(`UPDATE not supported for ${logicalId} (${resourceType}), replacing (DELETE CREATE)`);
34112
- const fallbackFinalSnapshotId = await this.prepareFinalSnapshotForDelete(logicalId, resourceType, currentResource, template?.Resources?.[logicalId]?.UpdateReplacePolicy ?? currentResource.updateReplacePolicy);
34113
- let fallbackDeleteResult = void 0;
34114
- try {
34115
- fallbackDeleteResult = await updateProvider.delete(logicalId, currentResource.physicalId, resourceType, currentProps, {
34116
- expectedRegion: this.stackRegion,
34117
- forceDataDelete: this.options.forceStatefulRecreation === true,
34118
- ...fallbackFinalSnapshotId !== void 0 && { finalSnapshotIdentifier: fallbackFinalSnapshotId }
34119
- });
34120
- } catch (deleteError) {
34121
- const deleteMsg = deleteError instanceof Error ? deleteError.message : String(deleteError);
34122
- if (deleteMsg.includes("does not exist") || deleteMsg.includes("not found") || deleteMsg.includes("NotFound")) this.logger.debug(`Old resource ${logicalId} already gone, proceeding with CREATE`);
34123
- else throw deleteError;
35073
+ const retainOldOnReplace = updateReplacePolicy === "Retain";
35074
+ const statefulReason = retainOldOnReplace ? null : isStatefulRecreateTargetForReplace(resourceType, currentProps);
35075
+ if (statefulReason && this.options.forceStatefulRecreation !== true) throw markNonRetryable(new CdkdError(replaceOptIn ? `--replace would DELETE + CREATE the stateful resource ${logicalId} (${resourceType}) — ${renderStatefulReason(statefulReason)}. Re-run with --force-stateful-recreation to confirm the data loss, or change the resource definition to avoid the immutable-property change.` : `${logicalId} (${resourceType}) cannot be updated in place by the provisioning layer it routes through, so applying this change would DELETE + CREATE it but it is a stateful resource: ${renderStatefulReason(statefulReason)}. Re-run with --force-stateful-recreation to confirm the data loss, or change the resource definition to avoid the update.`, "STATEFUL_REPLACE_BLOCKED", updateError instanceof Error ? updateError : void 0));
35076
+ this.logger.info(retainOldOnReplace ? `UPDATE not supported for ${logicalId} (${resourceType}), replacing (CREATE only UpdateReplacePolicy: Retain keeps the old resource)` : `UPDATE not supported for ${logicalId} (${resourceType}), replacing (DELETE CREATE)`);
35077
+ if (!retainOldOnReplace) {
35078
+ const fallbackFinalSnapshotId = await this.prepareFinalSnapshotForDelete(logicalId, resourceType, currentResource, template?.Resources?.[logicalId]?.UpdateReplacePolicy ?? currentResource.updateReplacePolicy);
35079
+ let fallbackDeleteResult = void 0;
35080
+ try {
35081
+ fallbackDeleteResult = await updateProvider.delete(logicalId, currentResource.physicalId, resourceType, currentProps, {
35082
+ expectedRegion: this.stackRegion,
35083
+ forceDataDelete: this.options.forceStatefulRecreation === true,
35084
+ ...fallbackFinalSnapshotId !== void 0 && { finalSnapshotIdentifier: fallbackFinalSnapshotId }
35085
+ });
35086
+ } catch (deleteError) {
35087
+ const deleteMsg = deleteError instanceof Error ? deleteError.message : String(deleteError);
35088
+ if (deleteMsg.includes("does not exist") || deleteMsg.includes("not found") || deleteMsg.includes("NotFound")) this.logger.debug(`Old resource ${logicalId} already gone, proceeding with CREATE`);
35089
+ else throw deleteError;
35090
+ }
35091
+ const fallbackSkipReason = deleteSkipReason(fallbackDeleteResult);
35092
+ if (fallbackSkipReason !== void 0) throw new Error(deleteSkippedMessage(logicalId, currentResource.physicalId, fallbackSkipReason, "during the UPDATE-not-supported replacement"));
34124
35093
  }
34125
- const fallbackSkipReason = deleteSkipReason(fallbackDeleteResult);
34126
- if (fallbackSkipReason !== void 0) throw new Error(deleteSkippedMessage(logicalId, currentResource.physicalId, fallbackSkipReason, "during the UPDATE-not-supported replacement"));
34127
35094
  const replDecision = this.providerRegistry.getProviderFor({
34128
35095
  resourceType,
34129
35096
  properties: resolvedProps
34130
35097
  });
34131
35098
  const replProvider = replDecision.provider;
34132
35099
  const replProps = replDecision.provisionedBy === "cc-api" ? this.preparePropertiesForCcApi(resourceType, resolvedProps, logicalId) : resolvedProps;
34133
- const createResult = await this.withRetry(() => withCurrentResourceSecrets(updateSecrets, () => replProvider.create(logicalId, resourceType, replProps, { maskSecrets: createSecretMasker(updateSecrets) })), logicalId, void 0, void 0, replProvider);
35100
+ let retainedSurvivorReason;
35101
+ let createResult;
35102
+ try {
35103
+ createResult = await this.withRetry(() => withCurrentResourceSecrets(updateSecrets, () => replProvider.create(logicalId, resourceType, replProps, { maskSecrets: createSecretMasker(updateSecrets) })), logicalId, void 0, void 0, replProvider);
35104
+ } catch (createError) {
35105
+ if (!retainOldOnReplace) throw createError;
35106
+ if (!isNameCollisionError(createError instanceof Error ? createError.message : String(createError))) throw createError;
35107
+ const nameOrigin = this.replacementNameOrigin(logicalId, currentResource.physicalId);
35108
+ throw markNonRetryable(new CdkdError(`${logicalId} (${resourceType}) requires replacement because the provisioning layer cannot update it in place — but its physical name is still held by the existing resource AND UpdateReplacePolicy: Retain pins that resource in place. ${nameOrigin.descriptor}. ${nameOrigin.remedy} — with Retain, the old resource keeps the name, so a same-name replacement can never proceed. Removing UpdateReplacePolicy: Retain lets cdkd delete the old resource first, which destroys it and any data it holds.`, "NAMED_REPLACEMENT_COLLISION", createError instanceof Error ? createError : void 0));
35109
+ }
35110
+ if (retainOldOnReplace) {
35111
+ if (createResult.physicalId === currentResource.physicalId) {
35112
+ const idempotentNameOrigin = this.replacementNameOrigin(logicalId, currentResource.physicalId);
35113
+ throw markNonRetryable(new CdkdError(`${logicalId} (${resourceType}) requires replacement, but its Create API is name-idempotent: the create returned the existing resource (${currentResource.physicalId}) instead of creating a new one, and UpdateReplacePolicy: Retain pins that resource in place, so the new properties were not applied. ${idempotentNameOrigin.descriptor}. ${idempotentNameOrigin.remedy} — with Retain, the old resource keeps the name, so a same-name replacement can never proceed.`, "NAMED_REPLACEMENT_IDEMPOTENT_CREATE"));
35114
+ }
35115
+ this.logger.warn(` ⚠ ${logicalId} has UpdateReplacePolicy: Retain — the old physical resource (${currentResource.physicalId}) is RETAINED and is no longer tracked by cdkd: it keeps running and incurring cost, and \`cdkd destroy\` will not remove it. Delete it yourself once you no longer need its data.`);
35116
+ retainedSurvivorReason = `UpdateReplacePolicy: Retain kept the old ${resourceType} (${currentResource.physicalId}), now untracked by cdkd`;
35117
+ }
34134
35118
  const replacementResult = {
34135
35119
  physicalId: createResult.physicalId,
34136
35120
  wasReplaced: true,
34137
- ...createResult.attributes && { attributes: createResult.attributes }
35121
+ ...createResult.attributes && { attributes: createResult.attributes },
35122
+ ...createResult.noEchoAttributes === true && { noEchoAttributes: true },
35123
+ ...createResult.noEchoAttributeNames && { noEchoAttributeNames: createResult.noEchoAttributeNames },
35124
+ ...retainedSurvivorReason !== void 0 ? {
35125
+ outcome: "partial",
35126
+ reason: retainedSurvivorReason
35127
+ } : { outcome: "updated" }
34138
35128
  };
34139
35129
  if (createResult.effectiveProperties) replacementResult.effectiveProperties = createResult.effectiveProperties;
34140
35130
  result = replacementResult;
@@ -34280,8 +35270,12 @@ var DeployEngine = class {
34280
35270
  * connect the message to their code at all.
34281
35271
  *
34282
35272
  * `descriptor` names WHERE the name came from; `remedy` is the accurate
34283
- * first option. Both callers append the shared `--replace` alternative,
34284
- * which is identical in either case.
35273
+ * first option. What each caller appends after it differs: the two
35274
+ * property-driven create-first sites append the shared `--replace`
35275
+ * alternative, while the update-failure fallback's two `Retain` refusals
35276
+ * (issue #2518) append the Retain clause instead — under `Retain` no flag
35277
+ * frees the name, so offering `--replace` there would send the user to a
35278
+ * flag that changes nothing.
34285
35279
  *
34286
35280
  * Classification is best-effort by construction (see
34287
35281
  * {@link looksLikeCdkdGeneratedName}) and falls back to the pre-#1636
@@ -34607,5 +35601,5 @@ var DeployEngine = class {
34607
35601
  };
34608
35602
 
34609
35603
  //#endregion
34610
- export { beginCommandInterruptScope as $, resolveAutoAssetStorage as $n, isTransientServerError as $r, maskSecretsInError as $t, formatResourceLine as A, ensureAssetStorage as An, DependencyError as Ar, requireConfigString as At, isExportAliasCollision as B, dockerSpawnEnvWithSensitive as Bn, ResourceTimeoutError as Br, INTRINSIC_KEYS as Bt, unsupportedFinalSnapshotError as C, loadPublishableAssetManifest as Cn, getAwsClients as Cr, coerceCfnBoolean as Ct, isStatefulRecreateTargetForReplace as D, AssetModeResolver as Dn, CdkdError as Dr, replayWarn as Dt, MULTI_REGION_RECREATE_BLOCKED_TYPES as E, stripControlChars as En, AssetError as Er, readConfigString as Et, red as F, validateAssetBucketName as Fn, LocalStartServiceError as Fr, s3BucketDualStackDomainName as Ft, collectInlinePolicyNamesManagedBySiblings as G, runDockerStreaming as Gn, SynthesisError as Gr, STATE_SOURCED_CROSS_GENERATION_RULES as Gt, stateKeySecretExposure as H, getDockerCmd as Hn, StackHasActiveImportsError as Hr, withRetry as Ht, yellow as I, validateContainerRepoName as In, LockError as Ir, s3BucketRegionalDomainName as It, findActionableSilentDrops as J, Synthesizer as Jn, normalizeAwsError as Jr, carriesSecretMask as Jt, clearOnUpdateRemoval as K, AssetManifestLoader as Kn, formatError as Kr, STATE_SOURCED_READBACK_RULES as Kt, collectDeclaredOutputNames as L, buildDenyExternalAccessPolicy as Ln, NestedStackChildDirectDestroyError as Lr, s3BucketWebsiteUrl as Lt, cyan as M, isCrossRegionRedirect as Mn, DynamicReferenceRegionAmbiguousError as Mr, producerRegionsFromState as Mt, gray as N, parseBootstrapMarker as Nn, IntrinsicResolutionRefusalError as Nr, s3BucketArn as Nt, isStatefulRecreateTargetSync as O, BOOTSTRAP_MARKER_PREFIX as On, ConfigError as Or, requireConfigArray as Ot, green as P, readBootstrapMarkerBody as Pn, LocalInvokeBuildError as Pr, s3BucketDomainName as Pt, maskerOrIdentity as Q, resolveApp as Qn, isThrottlingError as Qr, isSingleDynamicReferenceToken as Qt, collectPublishedOutputNames as R, describeAwsFailure as Rn, PartialFailureError as Rr, applyRoleArnIfSet as Rt, refusesFinalSnapshot as S, createAssetRedirectResolver as Sn, AwsClients as Sr, assertRegionMatch as St, extractDeploymentEventError as T, escapeRegExp$1 as Tn, setAwsClients as Tr, configStringRefusal as Tt, getCurrentResourceSecrets as U, partitionSensitiveEnv as Un, StackTerminationProtectionError as Ur, DagBuilder as Ut, secretBearingStateKeyWarning as V, formatDockerLoginError as Vn, ResourceUpdateNotSupportedError as Vr, describeTypeWithThrottleRetry as Vt, IAMRoleProvider as W, runDockerForeground as Wn, StateError as Wr, TemplateParser as Wt, createMaskedRetryLogger as X, getDefaultStateBucketName as Xn, isMarkedNonRetryable as Xr, dynamicReferenceTokens as Xt, findSilentDropProperties as Y, synthesisStatusMessage as Yn, withErrorHandling as Yr, createSecretMasker as Yt, maskDeep as Z, getLegacyStateBucketName as Zn, isRetryableTransientError as Zr, errorCauseChain as Zt, PRE_DELETE_SNAPSHOT_TYPES as _, shouldRetainResource as _n, derivePartitionAndUrlSuffix as _r, parameterTypeMayLoseSecretIdentity as _t, DeploymentEventsStore as a, LockManager as an, stateBucketExistenceConfirmed as ar, slowCcOperationTimeoutMs as at, createPreDeleteFinalSnapshot as b, WorkGraph as bn, clearBucketRegionCache as br, normalizeAwsTagsToCfn as bt, replayFailedOperations as c, UNRENDERABLE as cn, CFN_TEMPLATE_URL_LIMIT as cr, deleteSkipReason as ct, updatePartialReason as d, forceQuitRecoveryClause as dn, uploadCfnTemplate as dr, IntrinsicFunctionResolver as dt, markNonRetryable as ei, maskSecretsInText as en, resolveCaptureObservedState as er, endCommandInterruptScope as et, withResourceDeadline as f, CUSTOM_RESOURCE_RESPONSE_PREFIX as fn, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as fr, carriesDynamicReference as ft, ATOMIC_FINAL_SNAPSHOT_TYPES as g, importableOutputs as gn, canonicalizeRegion as gr, isUnboundTemplateParameter as gt, computeImplicitDeleteEdges as h, importableOutputKeys as hn, PARTITION_TABLE as hr, getAccountInfo as ht, DeploymentEventsReader as i, scrubResourceRecord as in, resolveUseCdkBootstrapAssets as ir, CloudControlProvider as it, bold as j, getBootstrapMarkerKey as jn, DeployCancelledError as jr, classifyReplaySecretRegion as jt, renderStatefulReason as k, assertAssetBucketRegion as kn, CrossAccountSecretRefusalError as kr, requireConfigObject as kt, replayRollback as l, buildForceUnlockCommand as ln, MIGRATE_TMP_PREFIX as lr, disableInstanceApiTermination as lt, IMPLICIT_DELETE_DEPENDENCIES as m, exportNamesCarriedFrom as mn, expectedOwnerParam as mr, coerceParameterTypedValue as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, retryClassificationText as ni, recoverMaskedOutput as nn, resolveStateBucketWithDefault as nr, isInterruptedWaitError as nt, planFailedOps as o, S3StateBackend as on, warnDeprecatedNoPrefixCliFlag as or, UNSPECIFIED_SKIP_REASON as ot, maskingRetryLogger as p, DEFAULT_STATE_PREFIX as pn, displaySafe as pr, cfnRefValueFromPhysicalId as pt, ProviderRegistry as q, getDockerImageBySourceHash as qn, isCdkdError as qr, TEMPLATE_SOURCED_RULES as qt, DeployEngine as r, __exportAll as ri, redactSecretsForState as rn, resolveStateBucketWithDefaultAndSource as rr, startInterruptWatch as rt, planRollback as s, rebuildClientForBucketRegion as sn, CFN_TEMPLATE_BODY_LIMIT as sr, deleteIndeterminateGuards as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, markRedactedCause as ti, recordMaskOnlyValue as tn, resolveSkipPrefix as tr, interruptWatchListenerCount as tt, updatePartialMessage as u, buildLockContentionMessage as un, findLargeInlineResources as ur, isTerminationProtectionPropagationError as ut, buildFinalSnapshotIdentifier as v, AssetPublisher as vn, AssemblyReader as vr, refStateLookupFromResource as vt, makeCanonicalizePropertiesFn as w, rewriteTemplateAssetReferences as wn, resetAwsClients as wr, configBooleanRefusal as wt, isFinalSnapshotError as x, buildAssetRedirectMap as xn, resolveBucketRegion as xr, resolveExplicitPhysicalId as xt, ccRoutedFinalSnapshotError as y, stringifyValue as yn, processStackMessages as yr, WAFv2WebACLProvider as yt, exportAliasCollisionScrubWarning as z, buildDockerImage as zn, ProvisioningError as zr, DiffCalculator as zt };
34611
- //# sourceMappingURL=deploy-engine-D0RCEQ6D.js.map
35604
+ export { beginCommandInterruptScope as $, synthesisStatusMessage as $n, withErrorHandling as $r, maskSecretsInError as $t, formatResourceLine as A, ensureAssetStorage as An, AssetError as Ar, requireConfigString as At, isExportAliasCollision as B, describeDockerCapturedOutput as Bn, LockError as Br, INTRINSIC_KEYS as Bt, unsupportedFinalSnapshotError as C, loadPublishableAssetManifest as Cn, processStackMessages as Cr, coerceCfnBoolean as Ct, isStatefulRecreateTargetForReplace as D, AssetModeResolver as Dn, getAwsClients as Dr, replayWarn as Dt, MULTI_REGION_RECREATE_BLOCKED_TYPES as E, stripControlChars as En, AwsClients as Er, readConfigString as Et, red as F, validateAssetBucketName as Fn, DeployCancelledError as Fr, s3BucketDualStackDomainName as Ft, collectInlinePolicyNamesManagedBySiblings as G, getDockerCmd as Gn, ResourceUpdateNotSupportedError as Gr, STATE_SOURCED_CROSS_GENERATION_RULES as Gt, stateKeySecretExposure as H, describeDockerFailure as Hn, PartialFailureError as Hr, withRetry as Ht, yellow as I, validateContainerRepoName as In, DynamicReferenceRegionAmbiguousError as Ir, s3BucketRegionalDomainName as It, findActionableSilentDrops as J, runDockerForeground as Jn, StateError as Jr, carriesSecretMask as Jt, clearOnUpdateRemoval as K, partitionSensitiveEnv as Kn, StackHasActiveImportsError as Kr, STATE_SOURCED_READBACK_RULES as Kt, collectDeclaredOutputNames as L, buildDenyExternalAccessPolicy as Ln, IntrinsicResolutionRefusalError as Lr, s3BucketWebsiteUrl as Lt, cyan as M, isCrossRegionRedirect as Mn, ConfigError as Mr, producerRegionsFromState as Mt, gray as N, parseBootstrapMarker as Nn, CrossAccountSecretRefusalError as Nr, s3BucketArn as Nt, isStatefulRecreateTargetSync as O, BOOTSTRAP_MARKER_PREFIX as On, resetAwsClients as Or, requireConfigArray as Ot, green as P, readBootstrapMarkerBody as Pn, DependencyError as Pr, s3BucketDomainName as Pt, maskerOrIdentity as Q, Synthesizer as Qn, normalizeAwsError as Qr, isSingleDynamicReferenceToken as Qt, collectPublishedOutputNames as R, describeAwsFailure as Rn, LocalInvokeBuildError as Rr, applyRoleArnIfSet as Rt, refusesFinalSnapshot as S, createAssetRedirectResolver as Sn, AssemblyReader as Sr, assertRegionMatch as St, extractDeploymentEventError as T, escapeRegExp$1 as Tn, resolveBucketRegion as Tr, configStringRefusal as Tt, getCurrentResourceSecrets as U, dockerSpawnEnvWithSensitive as Un, ProvisioningError as Ur, DagBuilder as Ut, secretBearingStateKeyWarning as V, describeDockerExecFailure as Vn, NestedStackChildDirectDestroyError as Vr, describeTypeWithThrottleRetry as Vt, IAMRoleProvider as W, formatDockerLoginError as Wn, ResourceTimeoutError as Wr, TemplateParser as Wt, createMaskedRetryLogger as X, AssetManifestLoader as Xn, formatError as Xr, dynamicReferenceTokens as Xt, findSilentDropProperties as Y, runDockerStreaming as Yn, SynthesisError as Yr, createSecretMasker as Yt, maskDeep as Z, getDockerImageBySourceHash as Zn, isCdkdError as Zr, errorCauseChain as Zt, PRE_DELETE_SNAPSHOT_TYPES as _, shouldRetainResource as _n, displaySafe as _r, parameterTypeMayLoseSecretIdentity as _t, DeploymentEventsStore as a, markRedactedCause as ai, LockManager as an, resolveSkipPrefix as ar, slowCcOperationTimeoutMs as at, createPreDeleteFinalSnapshot as b, WorkGraph as bn, canonicalizeRegion as br, normalizeAwsTagsToCfn as bt, replayFailedOperations as c, UNRENDERABLE as cn, resolveUseCdkBootstrapAssets as cr, deleteSkipReason as ct, updatePartialReason as d, forceQuitRecoveryClause as dn, CFN_TEMPLATE_BODY_LIMIT as dr, IntrinsicFunctionResolver as dt, isMarkedNonRetryable as ei, maskSecretsInText as en, getDefaultStateBucketName as er, endCommandInterruptScope as et, withResourceDeadline as f, CUSTOM_RESOURCE_RESPONSE_PREFIX as fn, CFN_TEMPLATE_URL_LIMIT as fr, carriesDynamicReference as ft, ATOMIC_FINAL_SNAPSHOT_TYPES as g, importableOutputs as gn, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as gr, isUnboundTemplateParameter as gt, computeImplicitDeleteEdges as h, importableOutputKeys as hn, uploadCfnTemplate as hr, getAccountInfo as ht, DeploymentEventsReader as i, markNonRetryable as ii, scrubResourceRecord as in, resolveCaptureObservedState as ir, CloudControlProvider as it, bold as j, getBootstrapMarkerKey as jn, CdkdError as jr, classifyReplaySecretRegion as jt, renderStatefulReason as k, assertAssetBucketRegion as kn, setAwsClients as kr, requireConfigObject as kt, replayRollback as l, buildForceUnlockCommand as ln, stateBucketExistenceConfirmed as lr, disableInstanceApiTermination as lt, IMPLICIT_DELETE_DEPENDENCIES as m, exportNamesCarriedFrom as mn, findLargeInlineResources as mr, coerceParameterTypedValue as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, isThrottlingError as ni, recoverMaskedOutput as nn, resolveApp as nr, isInterruptedWaitError as nt, planFailedOps as o, retryClassificationText as oi, S3StateBackend as on, resolveStateBucketWithDefault as or, UNSPECIFIED_SKIP_REASON as ot, maskingRetryLogger as p, DEFAULT_STATE_PREFIX as pn, MIGRATE_TMP_PREFIX as pr, cfnRefValueFromPhysicalId as pt, ProviderRegistry as q, redactDockerArgvValues as qn, StackTerminationProtectionError as qr, TEMPLATE_SOURCED_RULES as qt, DeployEngine as r, isTransientServerError as ri, redactSecretsForState as rn, resolveAutoAssetStorage as rr, startInterruptWatch as rt, planRollback as s, __exportAll as si, rebuildClientForBucketRegion as sn, resolveStateBucketWithDefaultAndSource as sr, deleteIndeterminateGuards as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, isRetryableTransientError as ti, recordMaskOnlyValue as tn, getLegacyStateBucketName as tr, interruptWatchListenerCount as tt, updatePartialMessage as u, buildLockContentionMessage as un, warnDeprecatedNoPrefixCliFlag as ur, isTerminationProtectionPropagationError as ut, buildFinalSnapshotIdentifier as v, AssetPublisher as vn, expectedOwnerParam as vr, refStateLookupFromResource as vt, makeCanonicalizePropertiesFn as w, rewriteTemplateAssetReferences as wn, clearBucketRegionCache as wr, configBooleanRefusal as wt, isFinalSnapshotError as x, buildAssetRedirectMap as xn, derivePartitionAndUrlSuffix as xr, resolveExplicitPhysicalId as xt, ccRoutedFinalSnapshotError as y, stringifyValue as yn, PARTITION_TABLE as yr, WAFv2WebACLProvider as yt, exportAliasCollisionScrubWarning as z, buildDockerImage as zn, LocalStartServiceError as zr, DiffCalculator as zt };
35605
+ //# sourceMappingURL=deploy-engine--rkIGhow.js.map