@go-to-k/cdkd 0.290.31 → 0.290.32

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,6 +1,6 @@
1
- import { C as displayAwsMessage, E as truncateCodePoints, S as UNRENDERABLE, T as displaySafe, d as applyDefaultNameForFallback, f as explicitNamePropertyFor, g as looksLikeCdkdGeneratedName, h as getCurrentStackName, m as generateResourceNameWithFallback, n as getLogger, p as generateResourceName, r as isStdoutReservedForPayload, s as getLiveRenderer, v as withStackName, w as displayIdent, x as STACK_REF_MAX_CODE_POINTS, y as withoutGeneratedFallbackName } from "./logger-BjWM-UVj.js";
1
+ import { C as displayAwsMessage, D as truncateCodePoints, E as isPasteableIdent, S as UNRENDERABLE, T as displaySafe, d as applyDefaultNameForFallback, f as explicitNamePropertyFor, g as looksLikeCdkdGeneratedName, h as getCurrentStackName, m as generateResourceNameWithFallback, n as getLogger, p as generateResourceName, r as isStdoutReservedForPayload, s as getLiveRenderer, v as withStackName, w as displayIdent, x as STACK_REF_MAX_CODE_POINTS, y as withoutGeneratedFallbackName } from "./logger-BX4pJk0T.js";
2
2
  import { a as setAssumedRoleCredentials, i as getPreAssumeEnvCredentials, n as awsClientDefaults, o as setPreAssumeEnvCredentials, r as getAssumedRoleCredentials, t as SECOND_ROLE_PUBLISH_REFUSAL } from "./aws-client-defaults-BoIxyCir.js";
3
- import { t as getCdkdVersion } from "./version-IwTfNQ-K.js";
3
+ import { t as getCdkdVersion } from "./version-F41ektvu.js";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  import { createHash, randomUUID } from "node:crypto";
6
6
  import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetBucketReplicationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
@@ -9980,18 +9980,54 @@ function buildForceUnlockCommand(stackName, region, recovery) {
9980
9980
  const stackIsExact = safeStack === stackName;
9981
9981
  const safeRegion = region === void 0 ? void 0 : displaySafe(region, { asciiOnly: true });
9982
9982
  const regionIsExact = region === void 0 || safeRegion === region;
9983
- const safeRecovery = {
9984
- profile: sanitizeRecoveryValue(recovery?.profile),
9985
- stateBucket: sanitizeRecoveryValue(recovery?.stateBucket),
9986
- statePrefix: sanitizeRecoveryValue(recovery?.statePrefix)
9983
+ const recoveryFlags = recoveryCommandFlags(recovery);
9984
+ if (!safeStack || safeRegion === "" || !stackIsExact || !regionIsExact || !recoveryFlags.exact) return "";
9985
+ return [safeRegion === void 0 ? `cdkd force-unlock ${shellQuote(safeStack)}` : `cdkd force-unlock ${shellQuote(safeStack)} --stack-region ${shellQuote(safeRegion)}`, ...recoveryFlags.flags].join(" ");
9986
+ }
9987
+ /**
9988
+ * A placeholder for a value a pasteable command could not name, QUOTED.
9989
+ *
9990
+ * A bare `<profile>` is two shell redirections, not a word: pasted, `<profile`
9991
+ * reads stdin from a file named `profile` and `>` sends stdout to whatever
9992
+ * word follows. While the hole was the command's LAST word that was only a
9993
+ * syntax error; with flags appended after it (`--profile <profile>
9994
+ * --state-bucket b`) it ran the command with `--state-bucket` swallowed as a
9995
+ * redirect target and `b` as the profile — a delete against the ambient
9996
+ * bucket, with nothing printed (measured by the maintainer, M4 of the
9997
+ * go-to-k/cdkd#3363 review). Quoted, it pastes as one literal argument and
9998
+ * nothing else — no redirection, no swallowed flag. It is not refused: an
9999
+ * UNFILLED hole runs as that literal value (a stack named `<stack>`, a prefix
10000
+ * `<prefix>`).
10001
+ */
10002
+ function commandHole(name) {
10003
+ return `'<${name}>'`;
10004
+ }
10005
+ /**
10006
+ * The flags that pin a pasteable state command to the caller's account and
10007
+ * bucket: `--profile`, the resolved `--state-bucket`, and a NON-default
10008
+ * `--state-prefix` (see {@link LockRecoveryContext} for why each is emitted
10009
+ * when it is). ONE spelling, shared by {@link buildForceUnlockCommand} and the
10010
+ * `cdkd orphan` properties refusal `malformed-resources-bag.ts` builds
10011
+ * (go-to-k/cdkd#3363): after `cdkd orphan --profile prod ...` a remedy that
10012
+ * carried none of these resolved the DEFAULT profile's bucket when pasted.
10013
+ * Each fragment takes {@link sanitizeRecoveryValue}'s sanitize + exactness
10014
+ * pair (go-to-k/cdkd#3377); what a caller does with an inexact one is its
10015
+ * own call — `buildForceUnlockCommand` suppresses the whole command, the orphan
10016
+ * refusal prints the hole.
10017
+ */
10018
+ function recoveryCommandFlags(recovery) {
10019
+ const profile = sanitizeRecoveryValue(recovery?.profile);
10020
+ const stateBucket = sanitizeRecoveryValue(recovery?.stateBucket);
10021
+ const statePrefix = sanitizeRecoveryValue(recovery?.statePrefix);
10022
+ const flags = [];
10023
+ if (profile.text || !profile.exact) flags.push(profile.exact ? `--profile ${shellQuote(profile.text)}` : `--profile ${commandHole("profile")}`);
10024
+ if (stateBucket.text || !stateBucket.exact) flags.push(stateBucket.exact ? `--state-bucket ${shellQuote(stateBucket.text)}` : `--state-bucket ${commandHole("bucket")}`);
10025
+ const prefix = recovery?.statePrefix;
10026
+ if (prefix !== void 0 && prefix !== "cdkd") flags.push(statePrefix.exact ? `--state-prefix ${shellQuote(statePrefix.text)}` : `--state-prefix ${commandHole("prefix")}`);
10027
+ return {
10028
+ flags,
10029
+ exact: profile.exact && stateBucket.exact && statePrefix.exact
9987
10030
  };
9988
- const recoveryIsExact = Object.values(safeRecovery).every((v) => v.exact);
9989
- if (!safeStack || safeRegion === "" || !stackIsExact || !regionIsExact || !recoveryIsExact) return "";
9990
- const parts = [safeRegion === void 0 ? `cdkd force-unlock ${shellQuote(safeStack)}` : `cdkd force-unlock ${shellQuote(safeStack)} --stack-region ${shellQuote(safeRegion)}`];
9991
- if (safeRecovery.profile.text) parts.push(`--profile ${shellQuote(safeRecovery.profile.text)}`);
9992
- if (safeRecovery.stateBucket.text) parts.push(`--state-bucket ${shellQuote(safeRecovery.stateBucket.text)}`);
9993
- if (safeRecovery.statePrefix.text && safeRecovery.statePrefix.text !== "cdkd") parts.push(`--state-prefix ${shellQuote(safeRecovery.statePrefix.text)}`);
9994
- return parts.join(" ");
9995
10031
  }
9996
10032
  /**
9997
10033
  * Sanitize ONE {@link LockRecoveryContext} fragment and report whether that
@@ -21847,19 +21883,70 @@ function stackClause(stackName, region) {
21847
21883
  * `destroy-runner.ts`; since go-to-k/cdkd#3328 the first operand is the S3
21848
21884
  * KEY's region rather than the record body's, which is a key SEGMENT and so
21849
21885
  * still plantable) -- its stack name is the caller's, so the region alone is
21850
- * what would aim a destructive command using a value cdkd does not own. This one is reached only with the caller's
21851
- * synthesized stack and `pickStackRegion`'s answer, so substituting is safe and
21852
- * omitting the region is the wider action. Two opposite precedents in one
21853
- * module; neither is the general rule.
21886
+ * what would aim a destructive command using a value cdkd does not own. This
21887
+ * one is reached only with the caller's synthesized stack and
21888
+ * `pickStackRegion`'s answer, so omitting the region is the wider action. Two
21889
+ * opposite precedents in one module; neither is the general rule.
21890
+ *
21891
+ * **Substitution is GATED on the identity rendering EXACTLY, at
21892
+ * `STACK_REF_MAX_CODE_POINTS`** (go-to-k/cdkd#3360, and the M0 finding of the
21893
+ * go-to-k/cdkd#3363 review). The synthesized name is not validated: a prebuilt
21894
+ * cloud assembly's manifest reaches `cdkd orphan` unread, so a name `safeIdentifier`
21895
+ * would TRIM (`'prod-api '`) substitutes as a healthy sibling's, and one it would
21896
+ * TRUNCATE (a 166-code-point nested name at the 128 default) becomes a command
21897
+ * `cdkd state orphan` resolves to zero records and reports as a skip at exit 0.
21898
+ * Since go-to-k/cdkd#3359 the legacy shape carries no `--stack-region`, so the
21899
+ * name is the ONLY thing narrowing this DELETE. The template arm is the fallback,
21900
+ * and the caller says where to take the name from instead
21901
+ * ({@link rendersExactly} is the test, shared with the inspect line and the
21902
+ * object path).
21903
+ *
21904
+ * `recovery` qualifies EVERY arm, the no-identity template included, the way
21905
+ * {@link buildForceUnlockCommand} is qualified, through the same `recoveryCommandFlags`: `cdkd state orphan`
21906
+ * re-resolves the bucket from the ambient profile, so a remedy pasted after
21907
+ * `cdkd orphan --profile prod ...` would otherwise address the default
21908
+ * profile's account. The template keeps them too: the identity is the hole,
21909
+ * not the account, and an operator who fills only the hole would otherwise run
21910
+ * an UNQUALIFIED delete — which `cdkd state orphan` reports as a skip at exit 0
21911
+ * when the default bucket has no such record, and offers to delete when it has
21912
+ * one (the M3 finding of the go-to-k/cdkd#3363 review).
21913
+ */
21914
+ function dropRecordCommand(stackName, region, recovery) {
21915
+ if (stackName === void 0 || stackName === "") return [`cdkd state orphan ${commandHole("stack")} --stack-region ${commandHole("region")}`, ...recoveryCommandFlags(recovery).flags].join(" ");
21916
+ const known = region === void 0 || region === "" ? void 0 : region;
21917
+ const regionExact = known === void 0 || rendersExactly(known);
21918
+ if (!rendersExactly(stackName) || !regionExact) {
21919
+ const flag = known === void 0 ? "" : regionExact ? ` --stack-region ${shellQuote(known)}` : ` --stack-region ${commandHole("region")}`;
21920
+ return [`cdkd state orphan ${commandHole("stack")}${flag}`, ...recoveryCommandFlags(recovery).flags].join(" ");
21921
+ }
21922
+ const flag = known === void 0 ? "" : ` --stack-region ${shellQuote(known)}`;
21923
+ return [`cdkd state orphan ${shellQuote(stackName)}${flag}`, ...recoveryCommandFlags(recovery).flags].join(" ");
21924
+ }
21925
+ /**
21926
+ * Whether an identity survives {@link safeIdentifier} UNCHANGED at the
21927
+ * state-record grammar's cap, so a command or a key built from it addresses the
21928
+ * record the message is about. The cap is `STACK_REF_MAX_CODE_POINTS`, not the
21929
+ * 128 the prose uses: `cdkd orphan` reads a PREBUILT assembly's stack name
21930
+ * unvalidated, so a manifest can carry one past 128 that is nonetheless the
21931
+ * record's real key, and truncating it names nothing — the same bound
21932
+ * `malformedDestroyResourcesRefusalMessage` uses, reached from a different
21933
+ * source.
21934
+ *
21935
+ * What it covers: every identity the `cdkd orphan` properties refusal builds
21936
+ * something PASTEABLE from — the drop command ({@link dropRecordCommand}), the
21937
+ * `cdkd state show` line ({@link orphanInspectCommand}) and the object path
21938
+ * ({@link orphanInspectClause}) — so no two of them can disagree about a name
21939
+ * (go-to-k/cdkd#3363 review, M0 and M2). What it does NOT cover yet: the
21940
+ * destroy refusal and the divergent-region refusal above still spell the same
21941
+ * test inline, and the shared {@link inspectCommand} is ungated at 128 for its
21942
+ * other callers.
21854
21943
  */
21855
- function dropRecordCommand(stackName, region) {
21856
- if (stackName === void 0 || stackName === "") return "cdkd state orphan <stack> --stack-region <region>";
21857
- const flag = region === void 0 || region === "" ? "" : ` --stack-region ${shellQuote(safeIdentifier(region))}`;
21858
- return `cdkd state orphan ${shellQuote(safeIdentifier(stackName))}${flag}`;
21944
+ function rendersExactly(value) {
21945
+ return safeIdentifier(value, STACK_REF_MAX_CODE_POINTS) === value;
21859
21946
  }
21860
21947
  /** The remedy command {@link stackClause}'s message ends on. */
21861
21948
  function inspectCommand(stackName, region) {
21862
- if (stackName === void 0) return "cdkd state show <stack> --stack-region <region> --json";
21949
+ if (stackName === void 0) return `cdkd state show ${commandHole("stack")} --stack-region ${commandHole("region")} --json`;
21863
21950
  const flag = region === void 0 ? "" : ` --stack-region ${shellQuote(safeIdentifier(region))}`;
21864
21951
  return `cdkd state show ${shellQuote(safeIdentifier(stackName))}${flag} --json`;
21865
21952
  }
@@ -22164,10 +22251,144 @@ function malformedResourcePropertiesRefusalMessage(stackName, region, logicalIds
22164
22251
  * stack name and the region `pickStackRegion` settled on, never the record's
22165
22252
  * own unvalidated self-report. See {@link stackClause}.
22166
22253
  */
22167
- function malformedOrphanResourcePropertiesRefusalMessage(rawStackName, rawRegion, logicalIds) {
22254
+ function malformedOrphanResourcePropertiesRefusalMessage(rawStackName, rawRegion, logicalIds, recovery) {
22168
22255
  const stackName = rawStackName === "" ? void 0 : rawStackName;
22169
22256
  const region = rawRegion === "" ? void 0 : rawRegion;
22170
- return `${namedPropertyBagsClause(stackName, region, logicalIds)} 'cdkd orphan' REWRITES and SAVES every record it keeps, so it refuses rather than continuing — under '--dry-run' too, because the rewrite audit table a dry run prints is the wrong one. It is not that the save would fabricate a map: an unreadable bag is carried through VERBATIM. It is that this command exists to leave the record deployable by rewriting every reference to an orphaned resource, and a map it cannot read hides whichever references it holds — a string or a number presents none to find, and a list is walked, so its rewrites are recorded into a container that is still not a map. Continuing would rewrite, save, and report success over a record 'cdkd deploy' then REFUSES. No state was written. Two ways out need no CDK app: repair the record by hand, or drop it whole with '${dropRecordCommand(stackName, region)}', which leaves the live AWS resources standing. A third works only while the CDK app STILL DECLARES the named resource — this refusal covers just the records that would SURVIVE the save, so 'cdkd orphan <its construct path>' removes it and repairs the rest; construct paths come from the synthesized template, so a resource the app no longer declares has none and must take one of the first two. Inspect the record with: ${inspectCommand(stackName, region)}`;
22257
+ const inspect = orphanInspectClause(stackName, region, recovery);
22258
+ const listCommand = withheldIdentityListCommand(stackName, region, recovery);
22259
+ const commands = [
22260
+ `Drop the record: ${dropRecordCommand(stackName, region, recovery)}`,
22261
+ ...listCommand === void 0 ? [] : [`Find the exact name: ${listCommand}`],
22262
+ ...inspect.command === void 0 ? [] : [`Inspect the record: ${inspect.command}`]
22263
+ ];
22264
+ return [
22265
+ `${namedPropertyBagsClause(stackName, region, logicalIds)} 'cdkd orphan' REWRITES and SAVES every record it keeps, so it refuses rather than continuing — under '--dry-run' too, because the rewrite audit table a dry run prints is the wrong one. It is not that the save would fabricate a map: an unreadable bag is carried through VERBATIM. It is that this command exists to leave the record deployable by rewriting every reference to an orphaned resource, and a map it cannot read hides whichever references it holds — a string or a number presents none to find, and a list is walked, so its rewrites are recorded into a container that is still not a map. Continuing would rewrite, save, and report success over a record 'cdkd deploy' then REFUSES. No state was written. Two ways out need no CDK app: repair the record by hand, or drop it whole with the 'Drop the record' command below, which leaves the live AWS resources standing${withheldIdentityClause(stackName, region)}${withheldRecoveryClause(recovery)}. A third works only while the CDK app STILL DECLARES the named resource — this refusal covers just the records that would SURVIVE the save, so 'cdkd orphan <its construct path>' removes it and repairs the rest; construct paths come from the synthesized template, so a resource the app no longer declares has none and must take one of the first two.` + (inspect.sentence === void 0 ? "" : ` ${inspect.sentence}`),
22266
+ ...commands,
22267
+ ...inspect.locations ?? []
22268
+ ].join("\n");
22269
+ }
22270
+ /**
22271
+ * The half-sentence that accompanies a TEMPLATE drop remedy when a stack name
22272
+ * WAS known but it, or the region beside it, did not render exactly. The prose
22273
+ * above it prints the sanitized spelling, and an operator filling the template
22274
+ * from that spelling would be aiming at the healthy sibling the gate exists to
22275
+ * protect (`'prod-api '` renders as `prod-api`; a padded region is the same
22276
+ * misdirection one flag over), so the message says where to take them from
22277
+ * instead: `cdkd state list --json`, which writes the raw name
22278
+ * through `JSON.stringify` — `--long` renders through `displayIdent`, which
22279
+ * trims, so it would hand back the same spelling. Empty when the command was
22280
+ * substituted, and when no name was known at all — that arm's template already
22281
+ * reads as a hole to fill.
22282
+ */
22283
+ function withheldIdentityClause(stackName, region) {
22284
+ if (!identityWithheld(stackName, region)) return "";
22285
+ return " — the stack name or region above did not render exactly, so take them from the 'Find the exact name' command below — replace each quoted hole, quotes included, with the shell-quoted value — rather than from this message";
22286
+ }
22287
+ /**
22288
+ * The half-sentence for the OTHER thing that can print as a hole: one of the
22289
+ * account fragments `recoveryCommandFlags` appends.
22290
+ *
22291
+ * Separate from {@link withheldIdentityClause} rather than folded into it,
22292
+ * because the two holes have different SOURCES and therefore different remedies.
22293
+ * An identity hole is filled from `cdkd state list --json`, which is why that
22294
+ * clause points at a command; an account fragment came from the operator's own
22295
+ * argv, so they already hold it and no listing would help. Conflating them would
22296
+ * send someone to a listing that does not carry their `--profile`.
22297
+ *
22298
+ * Without this, a run whose PREFIX or BUCKET was the inexact value printed
22299
+ * `--state-prefix '<prefix>'` with nothing in the message saying the hole was a
22300
+ * hole: `identityWithheld` reads the stack name and the region only, and the
22301
+ * object-path note is suppressed whenever a prefix WAS supplied. That is
22302
+ * {@link withheldIdentityClause}'s own defect class, one fragment over.
22303
+ */
22304
+ function withheldRecoveryClause(recovery) {
22305
+ if (recoveryCommandFlags(recovery).exact) return "";
22306
+ return " — an account fragment ('--profile', '--state-bucket' or '--state-prefix') did not render exactly, so the command lines below print a quoted hole in its place, which you replace whole — quotes included — with the SHELL-QUOTED value you passed this run";
22307
+ }
22308
+ /**
22309
+ * The listing {@link withheldIdentityClause} points at, printed as its own
22310
+ * trailing command line. Qualified like the remedy it stands in for: a bare
22311
+ * listing after `cdkd orphan --profile prod ...` would read the DEFAULT
22312
+ * profile's bucket, and the name it hands back would then be a different
22313
+ * bucket's.
22314
+ */
22315
+ function withheldIdentityListCommand(stackName, region, recovery) {
22316
+ if (!identityWithheld(stackName, region)) return void 0;
22317
+ return ["cdkd state list --json", ...recoveryCommandFlags(recovery).flags].join(" ");
22318
+ }
22319
+ /** A name WAS known, and it or the region beside it did not render exactly. */
22320
+ function identityWithheld(stackName, region) {
22321
+ if (stackName === void 0 || stackName === "") return false;
22322
+ const known = region === "" ? void 0 : region;
22323
+ return !(rendersExactly(stackName) && (known === void 0 || rendersExactly(known)));
22324
+ }
22325
+ /**
22326
+ * The `cdkd state show` command on the orphan properties refusal's trailing
22327
+ * `Inspect the record:` line, for a record listed WITH a region — gated and
22328
+ * qualified exactly as the drop command on the line above it is, because it is
22329
+ * the same identity in the same message.
22330
+ * Gating only the drop command left the message contradicting itself: it said
22331
+ * the name did not render exactly, pointed at `cdkd state list --json`, and then
22332
+ * printed `cdkd state show prod-api ...` with the trimmed spelling of
22333
+ * `'prod-api '`, naming the healthy sibling (the M2 finding of the
22334
+ * go-to-k/cdkd#3363 review). Holes follow {@link dropRecordCommand}'s rule: an
22335
+ * altered region makes both a hole, an altered name only the name.
22336
+ *
22337
+ * Built here rather than by widening {@link inspectCommand}, which serves four
22338
+ * other builders whose identities come from different sources (and this one no
22339
+ * longer calls it at all).
22340
+ */
22341
+ function orphanInspectCommand(stackName, region, recovery) {
22342
+ const regionExact = rendersExactly(region);
22343
+ return [`cdkd state show ${regionExact && rendersExactly(stackName) ? shellQuote(stackName) : commandHole("stack")} --stack-region ${regionExact ? shellQuote(region) : commandHole("region")} --json`, ...recoveryCommandFlags(recovery).flags].join(" ");
22344
+ }
22345
+ /**
22346
+ * How {@link malformedOrphanResourcePropertiesRefusalMessage} tells the operator
22347
+ * to inspect the record: a `command` it prints on its trailing
22348
+ * `Inspect the record:` line, or — for the legacy region-less shape, where no
22349
+ * command can serve — a prose `sentence` naming the S3 object.
22350
+ *
22351
+ * A KNOWN stack with NO region is a legacy record (`<prefix>/<stack>/state.json`)
22352
+ * that `listStacks` lists with no region — `cdkd orphan` hands this module the
22353
+ * region the record is LISTED under since go-to-k/cdkd#3359, and that is
22354
+ * `undefined` only for such a ref (its body names no region, or the probe that
22355
+ * reads it could not). `cdkd state show` refuses such a record with or without
22356
+ * `--stack-region` ("has only a legacy state record without a region"), and the
22357
+ * migration it suggests is a `cdkd deploy`, which refuses this very record
22358
+ * (go-to-k/cdkd#3191). So ending on `cdkd state show` would hand the operator a
22359
+ * command that cannot serve them; the line names the S3 object instead. Teaching
22360
+ * `state show` to read a legacy record was the alternative, declined as a wider
22361
+ * change to a command that is not the one refusing.
22362
+ *
22363
+ * The object path is printed only when the stack name {@link rendersExactly}: a
22364
+ * path with a sanitized or truncated segment names an object that does not
22365
+ * exist. The name comes from the assembly, and `cdkd orphan` accepts a PREBUILT
22366
+ * one whose manifest nothing validates, which is what makes an inexact name
22367
+ * reachable at all (go-to-k/cdkd#3360); an S3 key's `Parent~Child` name, the
22368
+ * case the destroy refusal's gate was written for, cannot appear here.
22369
+ *
22370
+ * With `recovery` the path carries the REAL prefix and names the bucket, the
22371
+ * way the drop remedy above it is qualified; without it the prefix is a
22372
+ * placeholder and the sentence says how to fill it.
22373
+ */
22374
+ function orphanInspectClause(stackName, region, recovery) {
22375
+ if (stackName === void 0 || stackName === "") return { command: [`cdkd state show ${commandHole("stack")} --stack-region ${commandHole("region")} --json`, ...recoveryCommandFlags(recovery).flags].join(" ") };
22376
+ if (region !== void 0) return { command: orphanInspectCommand(stackName, region, recovery) };
22377
+ const lead = "The record is listed with no region — a legacy 'state.json' whose body names none, or one the listing could not read — which 'cdkd state show' cannot read and a 'cdkd deploy' will not migrate while it is torn, so inspect the object directly";
22378
+ const exactOrUndefined = (v) => v !== void 0 && sanitizeRecoveryValue(v).exact ? v : void 0;
22379
+ const bucket = exactOrUndefined(recovery?.stateBucket) || void 0;
22380
+ const locations = bucket === void 0 ? [] : [`State bucket: ${shellQuote(bucket)}`];
22381
+ const where = bucket === void 0 ? `, in the state bucket ('cdkd state info' names it)` : `, in the bucket on the 'State bucket' line below`;
22382
+ if (!rendersExactly(stackName)) return {
22383
+ sentence: `${lead}: it is the legacy 'state.json' under this stack name, which did not render exactly${where}.`,
22384
+ locations
22385
+ };
22386
+ const prefix = exactOrUndefined(recovery?.statePrefix);
22387
+ const key = shellQuote(`${prefix ?? "<prefix>"}/${stackName}/state.json`);
22388
+ return {
22389
+ sentence: `${lead}: the 'Object key' line below names it${where}.${recovery?.statePrefix === void 0 ? ` Its prefix is 'cdkd' unless '--state-prefix' was given.` : prefix === void 0 ? " The prefix you passed did not render exactly, so the key shows the hole '<prefix>' where it belongs; put your own value there, inside the outer quotes." : ""}`,
22390
+ locations: [`Object key: ${key}`, ...locations]
22391
+ };
22171
22392
  }
22172
22393
  /**
22173
22394
  * For `cdkd orphan`: refuse a record whose SURVIVING resource entries carry a
@@ -22221,11 +22442,11 @@ function malformedOrphanResourcePropertiesRefusalMessage(rawStackName, rawRegion
22221
22442
  * The orphan set is resolved from the synthesized template before the state is
22222
22443
  * loaded, so nothing forces this call any lower.
22223
22444
  */
22224
- function refuseMalformedResourcePropertiesForOrphan(state, removedLogicalIds, stackName, region) {
22445
+ function refuseMalformedResourcePropertiesForOrphan(state, removedLogicalIds, stackName, region, recovery) {
22225
22446
  const removed = new Set(removedLogicalIds);
22226
22447
  const unreadable = unreadableResourcePropertyBags(state).filter((id) => !removed.has(id));
22227
22448
  if (unreadable.length === 0) return;
22228
- throw new CdkdError(malformedOrphanResourcePropertiesRefusalMessage(stackName, region, unreadable), STATE_RESOURCES_MALFORMED);
22449
+ throw new CdkdError(malformedOrphanResourcePropertiesRefusalMessage(stackName, region, unreadable, recovery), STATE_RESOURCES_MALFORMED);
22229
22450
  }
22230
22451
  /**
22231
22452
  * The warning a caller of {@link repairMalformedResourcePropertiesForReadOnly}
@@ -25781,7 +26002,7 @@ function detectUnknownIntrinsicKey(obj) {
25781
26002
  function buildUnknownIntrinsicError(key) {
25782
26003
  const title = `Support intrinsic ${key}`;
25783
26004
  const issueUrl = `https://github.com/go-to-k/cdkd/issues/new?title=${encodeURIComponent(title)}&labels=intrinsic-support`;
25784
- return /* @__PURE__ */ new Error(`Unsupported CloudFormation intrinsic function "${key}": cdkd does not support resolving it yet. Deploying this template would produce a broken value. Please request support by opening an issue: ${issueUrl}`);
26005
+ return /* @__PURE__ */ new Error(`Unsupported CloudFormation intrinsic function "${displayIdent(key)}": cdkd does not support resolving it yet. Deploying this template would produce a broken value. Please request support by opening an issue: ${issueUrl}`);
25785
26006
  }
25786
26007
  /**
25787
26008
  * The same context with the `producerRegions` EVIDENCE removed (issue #2134
@@ -27277,13 +27498,13 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27277
27498
  let referencedNames;
27278
27499
  for (const [name, definition] of Object.entries(templateParameters)) {
27279
27500
  const paramDef = definition;
27280
- if (isUnboundTemplateParameter(name, template, userParameters)) throw new Error(`Parameter ${name} is required but no value was provided and no default exists`);
27501
+ if (isUnboundTemplateParameter(name, template, userParameters)) throw new Error(`Parameter ${maskInherited(name)} is required but no value was provided and no default exists`);
27281
27502
  if (userParameters && Object.hasOwn(userParameters, name)) {
27282
27503
  const userValue = userParameters[name];
27283
27504
  if (userValue !== void 0) {
27284
27505
  this.refuseCoercedInheritedSecret(name, paramDef, userValue, inheritedSecrets);
27285
27506
  parameters[name] = this.coerceParameterValue(userValue, paramDef.Type);
27286
- this.logger.debug(`Parameter ${name}: using user-provided value ${maskInherited(stringifyParameterForLog(paramDef, this.maskValueLeaves(userValue, inheritedLogContext)))}`);
27507
+ this.logger.debug(`Parameter ${maskInherited(name)}: using user-provided value ${maskInherited(stringifyParameterForLog(paramDef, this.maskValueLeaves(userValue, inheritedLogContext)))}`);
27287
27508
  continue;
27288
27509
  }
27289
27510
  }
@@ -27291,19 +27512,19 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27291
27512
  if (paramDef.Type.startsWith("AWS::SSM::Parameter::Value")) {
27292
27513
  referencedNames ??= collectReferencedParameterNames(template);
27293
27514
  if (!referencedNames.has(name)) {
27294
- this.logger.debug(`Parameter ${name}: skipping SSM resolution (not referenced by Resources/Outputs/Conditions)`);
27515
+ this.logger.debug(`Parameter ${maskInherited(name)}: skipping SSM resolution (not referenced by Resources/Outputs/Conditions)`);
27295
27516
  continue;
27296
27517
  }
27297
27518
  const ssmPath = String(paramDef.Default);
27298
- this.logger.debug(`Parameter ${name}: resolving SSM parameter path ${ssmPath}`);
27519
+ this.logger.debug(`Parameter ${maskInherited(name)}: resolving SSM parameter path ${maskInherited(ssmPath)}`);
27299
27520
  const resolved = await this.resolveSSMParameter(ssmPath);
27300
27521
  const resolvedType = ssmResolvedValueType(paramDef.Type);
27301
27522
  parameters[name] = resolvedType === void 0 ? resolved : this.coerceParameterValue(resolved, resolvedType);
27302
- this.logger.debug(`Parameter ${name}: resolved SSM value ${maskInherited(stringifyParameterForLog(paramDef, resolved))}`);
27523
+ this.logger.debug(`Parameter ${maskInherited(name)}: resolved SSM value ${maskInherited(stringifyParameterForLog(paramDef, resolved))}`);
27303
27524
  continue;
27304
27525
  }
27305
27526
  parameters[name] = coerceParameterDefault(paramDef.Default, paramDef.Type);
27306
- this.logger.debug(`Parameter ${name}: using default value ${maskInherited(stringifyParameterForLog(paramDef, paramDef.Default))}`);
27527
+ this.logger.debug(`Parameter ${maskInherited(name)}: using default value ${maskInherited(stringifyParameterForLog(paramDef, paramDef.Default))}`);
27307
27528
  continue;
27308
27529
  }
27309
27530
  }
@@ -27345,11 +27566,11 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27345
27566
  const inProgress = /* @__PURE__ */ new Set();
27346
27567
  const evaluateByName = async (name) => {
27347
27568
  if (Object.hasOwn(conditions, name)) return conditions[name];
27348
- if (inProgress.has(name)) throw new Error(`Circular condition reference detected involving condition "${name}"`);
27569
+ if (inProgress.has(name)) throw new Error(`Circular condition reference detected involving condition "${this.displayMasked(name, maskingContext)}"`);
27349
27570
  const bag = templateConditions;
27350
27571
  const definition = Object.hasOwn(bag, name) ? bag[name] : void 0;
27351
27572
  if (definition === void 0) {
27352
- this.logger.warn(`Condition ${name} not found in template, assuming false`);
27573
+ this.logger.warn(`Condition ${this.displayMasked(name, maskingContext)} not found in template, assuming false`);
27353
27574
  conditions[name] = false;
27354
27575
  return false;
27355
27576
  }
@@ -27361,7 +27582,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27361
27582
  });
27362
27583
  const value = Boolean(result);
27363
27584
  conditions[name] = value;
27364
- this.logger.debug(`Evaluated condition ${name} = ${value}`);
27585
+ this.logger.debug(`Evaluated condition ${this.displayMasked(name, maskingContext)} = ${value}`);
27365
27586
  return value;
27366
27587
  } finally {
27367
27588
  inProgress.delete(name);
@@ -27474,7 +27695,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27474
27695
  configurable: true
27475
27696
  });
27476
27697
  else resolved[key] = resolvedVal;
27477
- } else this.logger.debug(`Property ${key} resolved to AWS::NoValue, omitting from object`);
27698
+ } else this.logger.debug(`Property ${this.displayMasked(key, context)} resolved to AWS::NoValue, omitting from object`);
27478
27699
  }
27479
27700
  return resolved;
27480
27701
  }
@@ -27637,7 +27858,8 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27637
27858
  const carriedBefore = inheritedSecretsCarriedBy(userValue, inherited).length;
27638
27859
  if (carriedBefore === 0) return;
27639
27860
  if (inheritedSecretsCarriedBy(this.coerceParameterValue(userValue, paramDef.Type), inherited).length >= carriedBefore) return;
27640
- throw markNonRetryable(new IntrinsicResolutionRefusalError(`Nested-stack parameter '${name}' is declared 'Type: ${paramDef.Type}', but the parent stack resolved a SECRET dynamic reference into it. cdkd keeps a resolved secret out of persisted state by rewriting STRING leaves back to their {{resolve:...}} expression; coercing this value to '${paramDef.Type}' destroys the plaintext cdkd would have matched on, so the DECRYPTED secret would be left in the child stack's state.json with nothing to redact it back to. Declare '${name}' as 'Type: String' in the nested stack's template (CDK does this by default for cross-stack references), or stop passing a secret reference into it.`, void 0, "NESTED_STACK_SECRET_PARAMETER_TYPE"));
27861
+ const loggedName = this.displayMasked(name);
27862
+ throw markNonRetryable(new IntrinsicResolutionRefusalError(`Nested-stack parameter '${loggedName}' is declared 'Type: ${paramDef.Type}', but the parent stack resolved a SECRET dynamic reference into it. cdkd keeps a resolved secret out of persisted state by rewriting STRING leaves back to their {{resolve:...}} expression; coercing this value to '${paramDef.Type}' destroys the plaintext cdkd would have matched on, so the DECRYPTED secret would be left in the child stack's state.json with nothing to redact it back to. Declare '${loggedName}' as 'Type: String' in the nested stack's template (CDK does this by default for cross-stack references), or stop passing a secret reference into it.`, void 0, "NESTED_STACK_SECRET_PARAMETER_TYPE"));
27641
27863
  }
27642
27864
  /**
27643
27865
  * Resolve Ref intrinsic function
@@ -27651,14 +27873,14 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27651
27873
  const resource = Object.hasOwn(context.resources, logicalId) ? context.resources[logicalId] : void 0;
27652
27874
  if (resource) {
27653
27875
  const refValue = this.resolveRefValue(logicalId, resource, context);
27654
- this.logger.debug(`Resolved Ref to resource: ${logicalId} -> ${refValue}`);
27876
+ this.logger.debug(`Resolved Ref to resource: ${this.displayMasked(logicalId, context)} -> ${refValue}`);
27655
27877
  return refValue;
27656
27878
  }
27657
27879
  if (context.parameters && Object.hasOwn(context.parameters, logicalId)) {
27658
27880
  const value = context.parameters[logicalId];
27659
27881
  const declared = context.template.Parameters;
27660
27882
  const paramDef = declared != null && Object.hasOwn(declared, logicalId) ? declared[logicalId] : void 0;
27661
- this.logger.debug(`Resolved Ref to parameter: ${logicalId} -> ${this.displayMasked(stringifyParameterForLog(paramDef, this.maskValueLeaves(value, context)), context)}`);
27883
+ this.logger.debug(`Resolved Ref to parameter: ${this.displayMasked(logicalId, context)} -> ${this.displayMasked(stringifyParameterForLog(paramDef, this.maskValueLeaves(value, context)), context)}`);
27662
27884
  this.recordInheritedParameterSecrets(logicalId, value, context);
27663
27885
  return value;
27664
27886
  }
@@ -27758,10 +27980,29 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27758
27980
  * nothing to do at this site: the value has ALREADY been masked in state, so
27759
27981
  * there is no plaintext to register.
27760
27982
  *
27761
- * The SPELLING (`Ref <LogicalId> (state key <Key>)`) is what
27762
- * `DeployEngine.maskedRecordRemedyFor` partitions on, so it is load-bearing
27763
- * rather than cosmetic — that helper reads the logical id out of it to emit
27764
- * the re-import command, and pins the shape in a test.
27983
+ * THE SPELLING IS NO LONGER PARSED, and this paragraph used to say the
27984
+ * opposite (corrected in go-to-k/cdkd#3432). It read: "`Ref <LogicalId>
27985
+ * (state key <Key>)` is what `DeployEngine.maskedRecordRemedyFor` partitions
27986
+ * on ... that helper reads the logical id out of it". That was true when it
27987
+ * was written and stopped being true at that helper's round-4 review, which
27988
+ * moved the partition onto the `kind` / `logicalId` FIELDS precisely because
27989
+ * two successive regexes over this rendering each shipped a defect — the
27990
+ * second an `[A-Za-z0-9]+` id class that a HYPHENATED logical id falls out
27991
+ * of. `targetOf` reads `read.logicalId`; no regex touches `display`.
27992
+ *
27993
+ * What the shape IS still load-bearing for is a SENTENCE: the refusal's
27994
+ * `hasRefStateRead` arm tells the reader that "a 'Ref <LogicalId> (state key
27995
+ * <Key>)' entry above is CDKD's own read", so the `Ref ` prefix and the
27996
+ * `(state key ...)` clause have to keep appearing. Sanitizing the ID inside
27997
+ * them leaves both intact.
27998
+ *
27999
+ * THE ID IS SANITIZED, the attribute-read sibling's judgement one branch over
28000
+ * (go-to-k/cdkd#3432). `displayMasked` rather than `displayIdent`: the
28001
+ * builder neither quotes nor bounds, so every legitimate id renders exactly
28002
+ * as it did, and the deliberate collision `intrinsic-ref-state-mask.test.ts`
28003
+ * pins between this rendering and `noteAttributeSecrecy`'s is preserved
28004
+ * because BOTH sites take the same builder. The `logicalId` FIELD stays raw —
28005
+ * it is the routing key and the `--resource <id>=` argument.
27765
28006
  *
27766
28007
  * `key` is never masked before interpolation because it is not template text:
27767
28008
  * it comes from the fixed key lists `cfnRefValueFromPhysicalId` passes
@@ -27773,7 +28014,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27773
28014
  kind: "ref-state-key",
27774
28015
  logicalId,
27775
28016
  key,
27776
- display: `Ref ${logicalId} (state key ${key})`
28017
+ display: `Ref ${this.displayMasked(logicalId, context)} (state key ${key})`
27777
28018
  });
27778
28019
  }
27779
28020
  /**
@@ -27815,7 +28056,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27815
28056
  const [rawLogicalId, rawAttributeName] = getAtt;
27816
28057
  logicalId = rawLogicalId;
27817
28058
  const resolvedAttributeName = await this.resolveValue(rawAttributeName, context);
27818
- if (typeof resolvedAttributeName !== "string") throw new Error(`Fn::GetAtt attribute name for ${logicalId} must resolve to a string, got ${typeof resolvedAttributeName}: ${stringifyValue(this.maskValueLeaves(resolvedAttributeName, context))}`);
28059
+ if (typeof resolvedAttributeName !== "string") throw new Error(`Fn::GetAtt attribute name for ${this.displayMasked(logicalId, context)} must resolve to a string, got ${typeof resolvedAttributeName}: ${stringifyValue(this.maskValueLeaves(resolvedAttributeName, context))}`);
27819
28060
  attributeName = resolvedAttributeName;
27820
28061
  } else {
27821
28062
  const split = splitGetAttStringForm(getAtt);
@@ -27824,7 +28065,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27824
28065
  attributeName = split.attributeName;
27825
28066
  }
27826
28067
  const resource = Object.hasOwn(context.resources, logicalId) ? context.resources[logicalId] : void 0;
27827
- if (!resource) throw markNonRetryable(/* @__PURE__ */ new Error(`Resource ${logicalId} not found for Fn::GetAtt`));
28068
+ if (!resource) throw markNonRetryable(/* @__PURE__ */ new Error(`Resource ${this.displayMasked(logicalId, context)} not found for Fn::GetAtt`));
27828
28069
  if (!(resource.resourceType === "AWS::EC2::VPC" && attributeName === "Ipv6CidrBlocks") && resource.attributes !== void 0) {
27829
28070
  const storedValue = Object.hasOwn(resource.attributes, attributeName) ? resource.attributes[attributeName] : void 0;
27830
28071
  const flatValue = isImpossibleEmptyStoredAttribute(resource.resourceType, attributeName, storedValue) ? void 0 : storedValue;
@@ -27832,10 +28073,10 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27832
28073
  this.rejectPlaceholderArnAttribute(resource, attributeName, flatValue, logicalId, context);
27833
28074
  if (resource.resourceType === "AWS::Route53::HostedZone" && attributeName === "NameServers" && typeof flatValue === "string") {
27834
28075
  const nameServers = flatValue === "" ? [] : flatValue.split(",");
27835
- this.logger.debug(`Normalized legacy Fn::GetAtt attribute: ${logicalId}.${this.displayMasked(attributeName, context)} -> ${this.displayMasked(stringifyAttributeForLog(attributeName, this.maskValueLeaves(nameServers, context)), context)}`);
28076
+ this.logger.debug(`Normalized legacy Fn::GetAtt attribute: ${this.displayMasked(logicalId, context)}.${this.displayMasked(attributeName, context)} -> ${this.displayMasked(stringifyAttributeForLog(attributeName, this.maskValueLeaves(nameServers, context)), context)}`);
27836
28077
  return this.noteAttributeSecrecy(logicalId, attributeName, nameServers, context);
27837
28078
  }
27838
- this.logger.debug(`Resolved Fn::GetAtt from attributes: ${logicalId}.${this.displayMasked(attributeName, context)} -> ${this.displayMasked(stringifyAttributeForLog(attributeName, this.maskValueLeaves(flatValue, context)), context)}`);
28079
+ this.logger.debug(`Resolved Fn::GetAtt from attributes: ${this.displayMasked(logicalId, context)}.${this.displayMasked(attributeName, context)} -> ${this.displayMasked(stringifyAttributeForLog(attributeName, this.maskValueLeaves(flatValue, context)), context)}`);
27839
28080
  if (resource.resourceType === NESTED_STACK_RESOURCE_TYPE$1 && attributeName.startsWith(NESTED_STACK_OUTPUT_ATTRIBUTE_PREFIX) && carriesDynamicReference(flatValue)) return await this.reresolveCrossStackValue(flatValue, nestedStackChildRegionFromLocalArn(resource.physicalId), context, `nested stack ${logicalId} ${this.displayLeaf(attributeName, context)}`, crossStackSourceKey({ "Fn::GetAtt": getAtt }));
27840
28081
  return this.noteAttributeSecrecy(logicalId, attributeName, flatValue, context);
27841
28082
  }
@@ -27848,7 +28089,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27848
28089
  break;
27849
28090
  }
27850
28091
  if (cursor !== void 0) {
27851
- this.logger.debug(`Resolved Fn::GetAtt from nested attributes: ${logicalId}.${this.displayMasked(attributeName, context)} -> ${this.displayMasked(stringifyAttributeForLog(attributeName, this.maskValueLeaves(cursor, context)), context)}`);
28092
+ this.logger.debug(`Resolved Fn::GetAtt from nested attributes: ${this.displayMasked(logicalId, context)}.${this.displayMasked(attributeName, context)} -> ${this.displayMasked(stringifyAttributeForLog(attributeName, this.maskValueLeaves(cursor, context)), context)}`);
27852
28093
  return this.noteAttributeSecrecy(logicalId, attributeName, cursor, context);
27853
28094
  }
27854
28095
  }
@@ -27856,10 +28097,11 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27856
28097
  if (resource.resourceType === NESTED_STACK_RESOURCE_TYPE$1 && attributeName.startsWith(NESTED_STACK_OUTPUT_ATTRIBUTE_PREFIX)) {
27857
28098
  const declared = Object.keys(resource.attributes ?? {}).filter((k) => k.startsWith(NESTED_STACK_OUTPUT_ATTRIBUTE_PREFIX)).sort().map((k) => this.displayMasked(this.outputNameLogText(k, context), context));
27858
28099
  const declaredText = declared.length > 0 ? declared.join(", ") : "(none)";
27859
- throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.displayMasked(attributeName, context)}]: the nested stack '${logicalId}' declares no output named '${this.displayMasked(this.outputNameLogText(attributeName, context), context)}'. Its outputs are ${this.displayMasked(declaredText, context)}. Check the output name in the nested stack's template, and deploy the child stack again if you have just added it.`));
28100
+ const loggedLogicalId = this.displayMasked(logicalId, context);
28101
+ throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${loggedLogicalId}, ${this.displayMasked(attributeName, context)}]: the nested stack '${loggedLogicalId}' declares no output named '${this.displayMasked(this.outputNameLogText(attributeName, context), context)}'. Its outputs are ${this.displayMasked(declaredText, context)}. Check the output name in the nested stack's template, and deploy the child stack again if you have just added it.`));
27860
28102
  }
27861
28103
  const value = await this.constructGuardedAttribute(resource, attributeName, context, logicalId);
27862
- this.logger.debug(`Resolved Fn::GetAtt: ${logicalId}.${this.displayMasked(attributeName, context)} -> ${this.displayMasked(stringifyAttributeForLog(attributeName, this.maskValueLeaves(value, context)), context)}`);
28104
+ this.logger.debug(`Resolved Fn::GetAtt: ${this.displayMasked(logicalId, context)}.${this.displayMasked(attributeName, context)} -> ${this.displayMasked(stringifyAttributeForLog(attributeName, this.maskValueLeaves(value, context)), context)}`);
27863
28105
  return value;
27864
28106
  }
27865
28107
  /**
@@ -27915,7 +28157,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27915
28157
  kind: "attribute",
27916
28158
  logicalId,
27917
28159
  key: maskedAttributeName,
27918
- display: `${logicalId}.${maskedAttributeName}`
28160
+ display: `${this.displayMasked(logicalId, context)}.${maskedAttributeName}`
27919
28161
  });
27920
28162
  }
27921
28163
  return value;
@@ -27952,7 +28194,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27952
28194
  rejectPlaceholderArnAttribute(resource, attributeName, value, logicalId, context) {
27953
28195
  if (!REF_RETURNS_ARN_FROM_STATE.get(resource.resourceType)?.includes(attributeName)) return;
27954
28196
  if (typeof value !== "string" || !isPlaceholderArn(value)) return;
27955
- throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.displayMasked(attributeName, context)}] for ${resource.resourceType}: the recorded value "${stringifyValue(this.maskValueLeaves(value, context))}" is a placeholder written by a cdkd version older than issue #1681 — its region and account fields are literal wildcards, so it is not a usable ARN. Deploy the stack again so the resource's next update heals the record (cdkd now records the real ARN), or re-import the resource.`));
28197
+ throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${this.displayMasked(logicalId, context)}, ${this.displayMasked(attributeName, context)}] for ${resource.resourceType}: the recorded value "${stringifyValue(this.maskValueLeaves(value, context))}" is a placeholder written by a cdkd version older than issue #1681 — its region and account fields are literal wildcards, so it is not a usable ARN. Deploy the stack again so the resource's next update heals the record (cdkd now records the real ARN), or re-import the resource.`));
27956
28198
  }
27957
28199
  /**
27958
28200
  * Construct resource attribute value based on resource type, refusing to
@@ -27997,7 +28239,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27997
28239
  async constructGuardedAttribute(resource, attributeName, context, logicalId) {
27998
28240
  const accountInfo = await getAccountInfo(this.resolverRegion);
27999
28241
  const value = await this.constructAttribute(resource, attributeName, context, logicalId, accountInfo);
28000
- if (accountInfo.fabricated && embedsAccountId(value, accountInfo.accountId)) throw new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.displayMasked(attributeName, context)}] for ${resource.resourceType}: STS did not report this deploy's account id, so cdkd would build the value from the placeholder account ${accountInfo.accountId} — structurally valid, naming a different account, and indistinguishable downstream from a real one. Fix the AWS credentials (or set AWS_ACCOUNT_ID to this deploy's account) and deploy again.`);
28242
+ if (accountInfo.fabricated && embedsAccountId(value, accountInfo.accountId)) throw new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${this.displayMasked(logicalId, context)}, ${this.displayMasked(attributeName, context)}] for ${resource.resourceType}: STS did not report this deploy's account id, so cdkd would build the value from the placeholder account ${accountInfo.accountId} — structurally valid, naming a different account, and indistinguishable downstream from a real one. Fix the AWS credentials (or set AWS_ACCOUNT_ID to this deploy's account) and deploy again.`);
28001
28243
  return value;
28002
28244
  }
28003
28245
  /**
@@ -28059,7 +28301,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
28059
28301
  return [];
28060
28302
  }
28061
28303
  case "DefaultSecurityGroup": {
28062
- if (!/^vpc-[0-9a-f]+$/.test(physicalId)) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, DefaultSecurityGroup] for AWS::EC2::VPC: the state record's physical id "${this.displayMasked(physicalId, context).slice(0, 64)}" is not a VPC id (vpc-<hex>), so cdkd will not use it as an EC2 filter value. Repair the record (cdkd import, or re-create the VPC) and deploy again.`));
28304
+ if (!/^vpc-[0-9a-f]+$/.test(physicalId)) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${this.displayMasked(logicalId, context)}, DefaultSecurityGroup] for AWS::EC2::VPC: the state record's physical id "${this.displayMasked(physicalId, context).slice(0, 64)}" is not a VPC id (vpc-<hex>), so cdkd will not use it as an EC2 filter value. Repair the record (cdkd import, or re-create the VPC) and deploy again.`));
28063
28305
  const cachedGroupId = cachedVpcDefaultSecurityGroups[physicalId];
28064
28306
  if (cachedGroupId !== void 0) return cachedGroupId;
28065
28307
  let groupId;
@@ -28274,7 +28516,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
28274
28516
  if (resourceType === "AWS::EC2::SecurityGroup") switch (attributeName) {
28275
28517
  case "GroupId": return physicalId;
28276
28518
  case "VpcId": {
28277
- if (!/^sg-[0-9a-f]+$/.test(physicalId)) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, VpcId] for AWS::EC2::SecurityGroup: the state record's physical id "${this.displayMasked(physicalId, context).slice(0, 64)}" is not a security group id (sg-<hex>), so cdkd will not look it up. Repair the record (cdkd import, or re-create the security group) and deploy again.`));
28519
+ if (!/^sg-[0-9a-f]+$/.test(physicalId)) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${this.displayMasked(logicalId, context)}, VpcId] for AWS::EC2::SecurityGroup: the state record's physical id "${this.displayMasked(physicalId, context).slice(0, 64)}" is not a security group id (sg-<hex>), so cdkd will not look it up. Repair the record (cdkd import, or re-create the security group) and deploy again.`));
28278
28520
  const cachedVpcId = cachedSecurityGroupVpcIds[physicalId];
28279
28521
  if (cachedVpcId !== void 0) return cachedVpcId;
28280
28522
  let vpcId;
@@ -28391,7 +28633,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
28391
28633
  if (resourceType === "AWS::CloudFront::Distribution") switch (attributeName) {
28392
28634
  case "Id": return physicalId;
28393
28635
  case "DomainName": {
28394
- if (!/^[A-Z0-9]+$/.test(physicalId)) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, DomainName] for AWS::CloudFront::Distribution: the state record's physical id "${this.displayMasked(physicalId, context).slice(0, 64)}" is not a distribution id (upper-case alphanumerics), so cdkd will not look it up. Repair the record (cdkd import, or re-create the distribution) and deploy again.`));
28636
+ if (!/^[A-Z0-9]+$/.test(physicalId)) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${this.displayMasked(logicalId, context)}, DomainName] for AWS::CloudFront::Distribution: the state record's physical id "${this.displayMasked(physicalId, context).slice(0, 64)}" is not a distribution id (upper-case alphanumerics), so cdkd will not look it up. Repair the record (cdkd import, or re-create the distribution) and deploy again.`));
28395
28637
  const cachedDomainName = cachedCloudFrontDomainNames[physicalId];
28396
28638
  if (cachedDomainName !== void 0) return cachedDomainName;
28397
28639
  let domainName;
@@ -28425,7 +28667,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
28425
28667
  default: return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId, context);
28426
28668
  }
28427
28669
  if (resourceType === "AWS::RDS::DBProxy" || resourceType === "AWS::RDS::DBProxyEndpoint") {
28428
- if (attributeName === "VpcId") throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.displayMasked(attributeName, context)}] for ${resourceType}: the state record holds no VpcId for it (the read-back that would have recorded it reported none), and the physical id "${this.displayMasked(physicalId, context)}" is a name, not a VPC id, so cdkd refuses to substitute it. Update the resource so its next deploy records the value, or reference the VPC directly.`));
28670
+ if (attributeName === "VpcId") throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${this.displayMasked(logicalId, context)}, ${this.displayMasked(attributeName, context)}] for ${resourceType}: the state record holds no VpcId for it (the read-back that would have recorded it reported none), and the physical id "${this.displayMasked(physicalId, context)}" is a name, not a VPC id, so cdkd refuses to substitute it. Update the resource so its next deploy records the value, or reference the VPC directly.`));
28429
28671
  return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId, context);
28430
28672
  }
28431
28673
  return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId, context);
@@ -28484,7 +28726,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
28484
28726
  */
28485
28727
  refuseUnservedAttribute(site) {
28486
28728
  const { logicalId, attributeName, resourceType, physicalId, context, observed, remedy } = site;
28487
- throw new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.displayMasked(attributeName, context)}] for ${resourceType}: ${observed}. The physical id "${this.displayMasked(physicalId, context)}" is not a usable ${this.displayMasked(attributeName, context)}, so cdkd refuses to substitute it. ${remedy}`);
28729
+ throw new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${this.displayMasked(logicalId, context)}, ${this.displayMasked(attributeName, context)}] for ${resourceType}: ${observed}. The physical id "${this.displayMasked(physicalId, context)}" is not a usable ${this.displayMasked(attributeName, context)}, so cdkd refuses to substitute it. ${remedy}`);
28488
28730
  }
28489
28731
  /**
28490
28732
  * The `observed` clause of {@link refuseUnservedAttribute} for a live read
@@ -28531,9 +28773,9 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
28531
28773
  const expectsUrlShape = attributeName.endsWith("Url") && !/^https?:\/\//.test(physicalId);
28532
28774
  if (expectsArnShape || expectsUrlShape) {
28533
28775
  const expectedShape = expectsArnShape ? "an ARN (arn:...)" : "a URL (http(s)://...)";
28534
- throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.displayMasked(attributeName, context)}] for ${resourceType}: attributes are not enriched for this resource type, and the physical ID fallback "${this.displayMasked(physicalId, context)}" is not ${expectedShape}. CloudFormation would return a different value here, so falling back to the physical ID would silently produce a wrong value (e.g. in stack Outputs). Avoid this Fn::GetAtt, or file an issue at https://github.com/go-to-k/cdkd/issues so cdkd can enrich ${resourceType}.${this.displayMasked(attributeName, context)}.`));
28776
+ throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${this.displayMasked(logicalId, context)}, ${this.displayMasked(attributeName, context)}] for ${resourceType}: attributes are not enriched for this resource type, and the physical ID fallback "${this.displayMasked(physicalId, context)}" is not ${expectedShape}. CloudFormation would return a different value here, so falling back to the physical ID would silently produce a wrong value (e.g. in stack Outputs). Avoid this Fn::GetAtt, or file an issue at https://github.com/go-to-k/cdkd/issues so cdkd can enrich ${resourceType}.${this.displayMasked(attributeName, context)}.`));
28535
28777
  }
28536
- if (this.strictGetAtt) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.displayMasked(attributeName, context)}] for ${resourceType}: attributes are not enriched for this resource type, and --strict-getatt rejects the physical ID fallback "${this.displayMasked(physicalId, context)}" (which may not be the value CloudFormation would return). Drop --strict-getatt to fall back with a warning, avoid this Fn::GetAtt, or file an issue at https://github.com/go-to-k/cdkd/issues so cdkd can enrich ${resourceType}.${this.displayMasked(attributeName, context)}.`));
28778
+ if (this.strictGetAtt) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${this.displayMasked(logicalId, context)}, ${this.displayMasked(attributeName, context)}] for ${resourceType}: attributes are not enriched for this resource type, and --strict-getatt rejects the physical ID fallback "${this.displayMasked(physicalId, context)}" (which may not be the value CloudFormation would return). Drop --strict-getatt to fall back with a warning, avoid this Fn::GetAtt, or file an issue at https://github.com/go-to-k/cdkd/issues so cdkd can enrich ${resourceType}.${this.displayMasked(attributeName, context)}.`));
28537
28779
  this.physicalIdFallbackCount++;
28538
28780
  this.logger.warn(`Unknown attribute ${this.displayMasked(attributeName, context)} for resource type ${resourceType}, returning physical ID`);
28539
28781
  return physicalId;
@@ -29009,7 +29251,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
29009
29251
  const resolvedValue = await this.resolveValue(value, context);
29010
29252
  if (typeof resolvedValue !== "string") {
29011
29253
  const source = this.describeSplitValueSource(value);
29012
- const sourceClause = source ? ` (from ${source.label})` : "";
29254
+ const sourceClause = source ? ` (from ${this.displayMasked(source.label, context)})` : "";
29013
29255
  if (Array.isArray(resolvedValue)) {
29014
29256
  const remedy = source?.kind === "ref" ? "A list-typed parameter — any List<...> type (List<AWS::EC2::Subnet::Id>, List<Number>, …) or CommaDelimitedList — is already a list." : source?.kind === "getatt" ? "A list-valued Fn::GetAtt (for example AWS::Route53::HostedZone.NameServers or AWS::EC2::VPC.Ipv6CidrBlocks) already returns a list. If you wrote the Fn::Split as a workaround for cdkd resolving that attribute to a comma-delimited string, that bug is fixed (PR #1868) and the workaround is no longer needed." : "Several intrinsics already return a list — among them a list-valued Fn::GetAtt, a Ref to a list-typed parameter (any List<...> type or CommaDelimitedList), Fn::GetAZs, Fn::Cidr, and Fn::Split itself.";
29015
29257
  throw markNonRetryable(new IntrinsicResolutionRefusalError(`Fn::Split: the value to split${sourceClause} is ALREADY a list (an array of ${resolvedValue.length} item${resolvedValue.length === 1 ? "" : "s"}), not a string. CloudFormation rejects Fn::Split over a list too, so this template is not valid CloudFormation either. Remove the Fn::Split and use the value directly. ${remedy}`));
@@ -29030,12 +29272,12 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
29030
29272
  async resolveIf(ifArgs, context) {
29031
29273
  const [conditionName, valueIfTrue, valueIfFalse] = ifArgs;
29032
29274
  if (!context.conditions || !Object.hasOwn(context.conditions, conditionName)) {
29033
- this.logger.warn(`Condition ${conditionName} not found in context, assuming false`);
29275
+ this.logger.warn(`Condition ${this.displayMasked(String(conditionName), context)} not found in context, assuming false`);
29034
29276
  return await this.resolveValue(valueIfFalse, context);
29035
29277
  }
29036
29278
  const conditionValue = context.conditions[conditionName];
29037
29279
  const selectedValue = conditionValue ? valueIfTrue : valueIfFalse;
29038
- this.logger.debug(`Resolved Fn::If: condition ${conditionName} = ${conditionValue}, selected ${conditionValue ? "true" : "false"} branch`);
29280
+ this.logger.debug(`Resolved Fn::If: condition ${this.displayMasked(String(conditionName), context)} = ${conditionValue}, selected ${conditionValue ? "true" : "false"} branch`);
29039
29281
  return await this.resolveValue(selectedValue, context);
29040
29282
  }
29041
29283
  /**
@@ -29066,7 +29308,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
29066
29308
  async resolveConditionReference(conditionName, context) {
29067
29309
  if (context.conditionResolver) return await context.conditionResolver(conditionName);
29068
29310
  if (context.conditions && Object.hasOwn(context.conditions, conditionName)) return context.conditions[conditionName];
29069
- this.logger.warn(`Condition ${conditionName} not found in context, assuming false`);
29311
+ this.logger.warn(`Condition ${this.displayMasked(conditionName, context)} not found in context, assuming false`);
29070
29312
  return false;
29071
29313
  }
29072
29314
  /**
@@ -30003,11 +30245,14 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
30003
30245
  *
30004
30246
  * Reachable from ONE place, {@link maskThenStripThenMask}, which is itself
30005
30247
  * reachable only from {@link displayMasked}. That containment is the whole
30006
- * mechanism and it is enforced, not conventional:
30007
- * `scripts/check-resolver-mask-coverage.ts` does not list this method in
30008
- * `MASKERS`, so its AST walk — which RESOLVES an interpolated identifier to
30009
- * its declaration — reports any render that reaches it, through a binding or
30010
- * directly. Do not add it to that list, and do not call it from a new site.
30248
+ * mechanism. It used to be enforced by an AST walk that resolved an
30249
+ * interpolated identifier to its declaration; go-to-k/cdkd#3435 deleted that
30250
+ * checker, and what holds the containment now is
30251
+ * `tests/unit/deployment/resolver-display-masked-population.test.ts`, which
30252
+ * asserts the exact reference COUNTS from the AST (one caller each) and
30253
+ * refuses a direct `${this.<raw masker>(...)}` interpolation by line rule.
30254
+ * Do not call this from a new site: a binding of it interpolated LATER is
30255
+ * the shape neither survivor can see.
30011
30256
  *
30012
30257
  * ## What the masking itself does
30013
30258
  *
@@ -30102,7 +30347,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
30102
30347
  * with the twin's positions, so the line gives both up for `***`: never
30103
30348
  * more text than either mask alone would print. Returns the text BEFORE the
30104
30349
  * needle mask; each log line wraps it in `displayMasked` itself, so the
30105
- * resolver's mask-coverage checker sees a masker at the site.
30350
+ * sanitization happens at the render rather than here.
30106
30351
  */
30107
30352
  logTwinText(result, twin, context) {
30108
30353
  return twin !== result && this.maskNeedlesForLog(result, context) !== result ? "***" : twin;
@@ -30450,9 +30695,12 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
30450
30695
  * ONE CALLER, {@link displayMasked}, since go-to-k/cdkd#3426. It is most of
30451
30696
  * the answer and reads like all of it — it omits `displaySafe`, and
30452
30697
  * therefore `U+2028` / `U+2029` and the bidi overrides — so a site reaching
30453
- * for it directly is the near-miss back onto the treadmill. It is not in
30454
- * `MASKERS` in `scripts/check-resolver-mask-coverage.ts`, so a render that
30455
- * reaches it (through a binding or directly) is reported there.
30698
+ * for it directly is the near-miss back onto the treadmill. A DIRECT
30699
+ * `${this.maskThenStripThenMask(...)}` interpolation is refused by
30700
+ * `resolver-display-masked-population.test.ts`'s line rule; a BINDING of it
30701
+ * rendered later is not seen by anything since go-to-k/cdkd#3435 deleted the
30702
+ * AST checker, which is why the one-caller containment above is the control
30703
+ * that matters.
30456
30704
  *
30457
30705
  * THE BOUND, since this file's job is to state them: this covers a needle
30458
30706
  * split by a character `stripControlChars` removes. A needle split by
@@ -30500,15 +30748,19 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
30500
30748
  *
30501
30749
  * ## What keeps it closed
30502
30750
  *
30503
- * `scripts/check-resolver-mask-coverage.ts` — an AST walk that already
30504
- * RESOLVES an interpolated identifier to its declaration and judges the
30505
- * initializer. Its `MASKERS` list names the functions a render may reach,
30506
- * and since go-to-k/cdkd#3426 every entry on it SANITIZES as well as masks.
30507
- * So a binding of a raw masker is a finding wherever it is later
30508
- * interpolated, by the same mechanism that already answered the secret
30509
- * question — no second scanner, and nothing line-shaped.
30510
- * `tests/unit/deployment/resolver-display-masked-population.test.ts` pins the
30511
- * population and the builder bodies beside it.
30751
+ * `tests/unit/deployment/resolver-display-masked-population.test.ts`, which
30752
+ * asserts from the AST that `maskSecretsRaw` is referenced only inside
30753
+ * `maskThenStripThenMask` and that helper only inside this builder — exact
30754
+ * reference counts, no inference — and refuses a direct
30755
+ * `${this.<raw masker>(...)}` interpolation by line rule.
30756
+ *
30757
+ * An AST walk that ALSO resolved an interpolated identifier to its
30758
+ * declaration used to cover the BINDING shape. go-to-k/cdkd#3435 deleted it
30759
+ * as high-maintenance tooling (23 commits across it and its suite in nine
30760
+ * days; a widening attempted one PR earlier produced nine defects inside
30761
+ * itself and was withdrawn), so that shape is now held by the containment
30762
+ * above — there is no bare masker to bind, because nothing outside this
30763
+ * builder may call one.
30512
30764
  *
30513
30765
  * KNOWN BOUND, stated rather than implied away: a site carrying an
30514
30766
  * exclusion marker is exempt from that walk, because the marker answers the
@@ -30533,9 +30785,9 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
30533
30785
  * Trojan-Source bidi overrides. That composition is what the `physicalId`
30534
30786
  * renders here already spelled by hand; this names it, and they now share it.
30535
30787
  *
30536
- * It is listed in `MASKERS` in `scripts/check-resolver-mask-coverage.ts`,
30537
- * which is a SECURITY decision and a sound one: every path through it passes
30538
- * the needle-and-twin mask, twice, so it is strictly stronger than the bare
30788
+ * It is the ONE sanctioned exit from the masking machinery, which is a
30789
+ * SECURITY decision and a sound one: every path through it passes the
30790
+ * needle-and-twin mask, twice, so it is strictly stronger than the bare
30539
30791
  * masker it replaced and cannot be weaker at any input.
30540
30792
  */
30541
30793
  displayMasked(value, context) {
@@ -32635,7 +32887,7 @@ var CloudControlProvider = class {
32635
32887
  const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
32636
32888
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
32637
32889
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
32638
- const { ASGProvider } = await import("./asg-provider-DNzgk-BS.js").then((n) => n.n);
32890
+ const { ASGProvider } = await import("./asg-provider-BuEvX2d9.js").then((n) => n.n);
32639
32891
  const asgProvider = new ASGProvider();
32640
32892
  return withIndeterminateGuard(await asgProvider.delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
32641
32893
  }
@@ -43271,11 +43523,38 @@ var DeployEngine = class DeployEngine {
43271
43523
  * cdkd never EXECUTES this string — it is advice in an error message — so
43272
43524
  * this is about the pasted command being correct, not about injection into
43273
43525
  * cdkd itself (issue #2847 round-5 review).
43526
+ *
43527
+ * SINCE go-to-k/cdkd#3435 no id reaching this arm can contain a `'`:
43528
+ * `isPasteableIdent` admits `[A-Za-z0-9][A-Za-z0-9~_.-]*` only, and the
43529
+ * command is not emitted for anything else. Kept anyway rather than
43530
+ * deleted — dropping a guard on the strength of a NEW guard one line up is
43531
+ * how the pair later comes apart, and this one costs a `replaceAll` on a
43532
+ * short string.
43274
43533
  */
43275
43534
  const quoteSafe = (id) => id.replaceAll("'", `'\\''`);
43535
+ /**
43536
+ * A logical id rendered as a NAME rather than as a command argument
43537
+ * (go-to-k/cdkd#3435 security round).
43538
+ *
43539
+ * Every sentence below is joined into a `ProvisioningError` message that
43540
+ * `handleError` prints at DEFAULT verbosity, and neither `formatError` nor
43541
+ * the logger sanitizes `error.message` — they cover a `cause` and the extra
43542
+ * ARGS respectively. So a `Resources` KEY carrying `ESC[2K` + CR reached the
43543
+ * terminal raw, which is the same class this PR closed at 18 resolver
43544
+ * renders and missed one module out.
43545
+ */
43546
+ const shown = (id) => displayIdent(id);
43547
+ /** CloudFormation's own logical-id length bound. */
43548
+ const CFN_LOGICAL_ID_MAX_LENGTH = 255;
43276
43549
  const parts = [];
43277
- if (localTargets.length > 0) parts.push(`Re-import the record that HOLDS the mask: ` + localTargets.map((id) => `'cdkd import <stack> --resource ${quoteSafe(id)}=<physicalId> --force'`).join(", ") + `.`);
43278
- if (unclearableTargets.length > 0) parts.push(`Do NOT re-import ${unclearableTargets.join(", ")}: a custom resource's import records no attributes, so the masked bag is carried forward unchanged and the refusal repeats. Cause (1) above is the one that applies to it.`);
43550
+ if (localTargets.length > 0) {
43551
+ const pasteableHere = (id) => isPasteableIdent(id) && id.length <= CFN_LOGICAL_ID_MAX_LENGTH;
43552
+ const pasteable = localTargets.filter(pasteableHere);
43553
+ const withheld = localTargets.filter((id) => !pasteableHere(id));
43554
+ if (pasteable.length > 0) parts.push(`Re-import the record that HOLDS the mask: ` + pasteable.map((id) => `'cdkd import <stack> --resource ${quoteSafe(id)}=<physicalId> --force'`).join(", ") + `.`);
43555
+ if (withheld.length > 0) parts.push(`Re-import the record that HOLDS the mask for ${withheld.map(shown).join(", ")}, but the command is withheld: that is not a plain CloudFormation logical id, so a pasted 'cdkd import <stack> --resource <id>=<physicalId> --force' could be reshaped by the shell or name a different resource. Read the id from 'cdkd state show' and quote it yourself.`);
43556
+ }
43557
+ if (unclearableTargets.length > 0) parts.push(`Do NOT re-import ${unclearableTargets.map(shown).join(", ")}: a custom resource's import records no attributes, so the masked bag is carried forward unchanged and the refusal repeats. Cause (1) above is the one that applies to it.`);
43279
43558
  if (foreignReads.length > 0) {
43280
43559
  const subject = reads.length === 1 ? "The read above resolves" : foreignReads.length === 1 ? "One of the reads above resolves" : "Some of the reads above resolve";
43281
43560
  parts.push(`${subject} through ANOTHER stack (an Fn::ImportValue, an Fn::GetStackOutput, or a nested stack's Outputs), whose masked record lives in that stack's state — re-importing anything in this stack cannot clear it; act on the producer stack instead.`);
@@ -45702,4 +45981,4 @@ var DeployEngine = class DeployEngine {
45702
45981
 
45703
45982
  //#endregion
45704
45983
  export { secretBearing as $, SynthesisError as $i, recordMaskOnlyValue as $n, AssetManifestLoader as $r, s3BucketArn as $t, extractDeploymentEventError as A, getAwsClients as Ai, refuseMalformedState as An, BOOTSTRAP_MARKER_PREFIX as Ar, isImpossibleEmptyStoredAttribute as At, toCfnInteger as B, IntrinsicResolutionRefusalError as Bi, STATE_SOURCED_BASELINE_RULES as Bn, buildDockerImage as Br, coerceCfnBoolean as Bt, buildFinalSnapshotIdentifier as C, canonicalizeRegion as Ci, producerRecordKey as Cn, stringifyValue as Cr, isTerminationProtectionPropagationError as Ct, refusesFinalSnapshot as D, clearBucketRegionCache as Di, refuseMalformedOutputsForDestroy as Dn, loadPublishableAssetManifest as Dr, cfnRefValueFromPhysicalId as Dt, isFinalSnapshotError as E, processStackMessages as Ei, refuseMalformedOutputs as En, createAssetRedirectResolver as Er, carriesFetchableDynamicReference as Et, isStatefulRecreateTargetSync as F, ConfigError as Fi, findSilentDropProperties as Fn, parseBootstrapMarker as Fr, isSettledInstanceState as Ft, gray as G, NestedStackChildDirectDestroyError as Gi, createSecretMasker as Gn, formatDockerLoginError as Gr, readConfigString as Gt, formatResourceLine as H, LocalStartServiceError as Hi, STATE_SOURCED_READBACK_RULES as Hn, describeDockerExecFailure as Hr, configBooleanRefusal as Ht, renderStatefulReason as I, CrossAccountSecretRefusalError as Ii, describeTypeWithThrottleRetry as In, readBootstrapMarkerBody as Ir, WAFv2WebACLProvider as It, yellow as J, ResourceTimeoutError as Ji, identityKeyFor as Jn, redactDockerArgvValues as Jr, requireConfigObject as Jt, green as K, PartialFailureError as Ki, dynamicReferenceTokens as Kn, getDockerCmd as Kr, replayWarn as Kt, WARM_THROUGHPUT_MEMBERS as L, DependencyError as Li, withRetry as Ln, validateAssetBucketName as Lr, normalizeAwsTagsToCfn as Lt, planOrphanAdoption as M, setAwsClients as Mi, repairMalformedResourcePropertiesForReadOnly as Mn, ensureAssetStorage as Mr, isUnboundTemplateParameter as Mt, MULTI_REGION_RECREATE_BLOCKED_TYPES as N, AssetError as Ni, repairMalformedResourcesForReadOnly as Nn, getBootstrapMarkerKey as Nr, parameterTypeMayLoseSecretIdentity as Nt, unsupportedFinalSnapshotError as O, resolveBucketRegion as Oi, refuseMalformedResourcePropertiesForOrphan as On, rewriteTemplateAssetReferences as Or, coerceParameterTypedValue as Ot, isStatefulRecreateTargetForReplace as P, CdkdError as Pi, findActionableSilentDrops as Pn, isCrossRegionRedirect as Pr, refStateLookupFromResource as Pt, isExportAliasCollision as Q, StateError as Qi, maskSecretsInText as Qn, stripControlChars as Qr, withSharedDrainBudget as Qt, coerceWarmThroughput as R, DeployCancelledError as Ri, DagBuilder as Rn, validateContainerRepoName as Rr, resolveExplicitPhysicalId as Rt, PRE_DELETE_SNAPSHOT_TYPES as S, PARTITION_TABLE as Si, malformedStateRefusalMessage as Sn, AssetPublisher as Sr, disableInstanceApiTermination as St, createPreDeleteFinalSnapshot as T, AssemblyReader as Ti, refuseMalformedNestedChildOutputs as Tn, buildAssetRedirectMap as Tr, carriesDynamicReference as Tt, bold as U, LockError as Ui, TEMPLATE_SOURCED_RULES as Un, describeDockerFailure as Ur, configIntegerRefusal as Ut, toFiniteNumber as V, LocalInvokeBuildError as Vi, STATE_SOURCED_CROSS_GENERATION_RULES as Vn, describeDockerCapturedOutput as Vr, coerceCfnInteger as Vt, cyan as W, MalformedProducerRecordRefusalError as Wi, carriesSecretMask as Wn, dockerSpawnEnvWithSensitive as Wr, configStringRefusal as Wt, collectPublishedOutputNames as X, StackHasActiveImportsError as Xi, markSameGenerationBag as Xn, runDockerStreaming as Xr, classifyReplaySecretRegion as Xt, collectDeclaredOutputNames as Y, ResourceUpdateNotSupportedError as Yi, isSingleDynamicReferenceToken as Yn, runDockerForeground as Yr, requireConfigString as Yt, exportAliasCollisionScrubWarning as Z, StackTerminationProtectionError as Zi, maskSecretsInError as Zn, escapeRegExp$1 as Zr, producerRegionsFromState as Zt, isSecretBearingReferenceString as _, findLargeInlineResources as _i, malformedOutputsRefusalMessage as _n, importableOutputs as _r, isWaitAbandonedError as _t, DeploymentEventsStore as a, isRetryableTransientError as aa, resolveApp as ai, AWS_CREDENTIAL_ENV_KEYS as an, rebuildClientForBucketRegion as ar, clearOnUpdateRemoval as at, bindingSkippedOutputs as b, decodeListingKey as bi, malformedResourcePropertiesWarning as bn, orphansCarriedFrom as br, deleteIndeterminateGuards as bt, replayFailedOperations as c, markNonRetryable as ca, resolveSkipPrefix as ci, envCredentialsAreAssumedRole as cn, buildLockContentionMessage as cr, createMaskedRetryLogger as ct, updatePartialReason as d, describeAwsFailure as da, resolveUseCdkBootstrapAssets as di, STATE_RESOURCES_MALFORMED as dn, shellQuote as dr, beginCommandInterruptScope as dt, formatError as ea, getDockerImageBySourceHash as ei, s3BucketDomainName as en, recoverMaskedOutput as er, secretBearingStateKeyWarning as et, withResourceDeadline as f, safeStringify$1 as fa, stateBucketExistenceConfirmed as fi, hasReadableOutputs as fn, CUSTOM_RESOURCE_RESPONSE_PREFIX as fr, endCommandInterruptScope as ft, bagHoldsSecretExpression as g, MIGRATE_TMP_PREFIX as gi, malformedLocalOutputsWarning as gn, importableOutputKeys as gr, CloudControlProvider as gt, computeImplicitDeleteEdges as h, CFN_TEMPLATE_URL_LIMIT as hi, malformedExportSourceWarning as hn, hasReadableExportSet as hr, startInterruptWatch as ht, DeploymentEventsReader as i, isMarkedNonRetryable as ia, getLegacyStateBucketName as ii, applyRoleArnIfSet as in, S3StateBackend as ir, collectInlinePolicyNamesManagedBySiblings as it, makeSiblingClaimReader as j, resetAwsClients as ji, repairMalformedOutputsForReadOnly as jn, assertAssetBucketRegion as jr, isNamelessDynamicReferenceError as jt, makeCanonicalizePropertiesFn as k, AwsClients as ki, refuseMalformedResourcesForDestroy as kn, AssetModeResolver as kr, getAccountInfo as kt, replayRollback as l, markRedactedCause as la, resolveStateBucketWithDefault as li, DiffCalculator as ln, forceQuitRecoveryClause as lr, maskDeep as lt, IMPLICIT_DELETE_DEPENDENCIES as m, CFN_TEMPLATE_BODY_LIMIT as mi, malformedExportNamesWarning as mn, exportNamesCarriedFrom as mr, isInterruptedWaitError as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, normalizeAwsError as na, synthesisStatusMessage as ni, s3BucketRegionalDomainName as nn, scrubResourceRecord as nr, getCurrentResourceSecrets as nt, planFailedOps as o, isThrottlingError as oa, resolveAutoAssetStorage as oi, applyCallerIdentityCredentials as on, UNREPRODUCIBLE_LOCK_CLAUSE as or, ProviderRegistry as ot, maskingRetryLogger as p, __exportAll as pa, warnDeprecatedNoPrefixCliFlag as pi, hasReadableResources as pn, DEFAULT_STATE_PREFIX as pr, interruptWatchListenerCount as pt, red as q, ProvisioningError as qi, errorCauseChain as qn, partitionSensitiveEnv as qr, requireConfigArray as qt, DeployEngine as r, withErrorHandling as ra, getDefaultStateBucketName as ri, s3BucketWebsiteUrl as rn, LockManager as rr, IAMRoleProvider as rt, planRollback as s, isTransientServerError as sa, resolveCaptureObservedState as si, callerEnvCredentials as sn, buildForceUnlockCommand as sr, wouldReturnToSdkProvider as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, isCdkdError as ta, Synthesizer as ti, s3BucketDualStackDomainName as tn, redactSecretsForState as tr, secretSafeKeyDisplay as tt, updatePartialMessage as u, retryClassificationText as ua, resolveStateBucketWithDefaultAndSource as ui, INTRINSIC_KEYS as un, formatLockExpiry as ur, maskerOrIdentity as ut, keptWholeReasonText as v, uploadCfnTemplate as vi, malformedOutputsWarning as vn, isReadableBag as vr, slowCcOperationTimeoutMs as vt, ccRoutedFinalSnapshotError as w, derivePartitionAndUrlSuffix as wi, refuseDivergentRecordRegionForDestroy as wn, WorkGraph as wr, IntrinsicFunctionResolver as wt, ATOMIC_FINAL_SNAPSHOT_TYPES as x, expectedOwnerParam as xi, malformedResourcesWarning as xn, shouldRetainResource as xr, deleteSkipReason as xt, mergeNoChangeOutputs as y, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as yi, malformedRenderedContainersWarning as yn, orphansAfterRollback as yr, UNSPECIFIED_SKIP_REASON as yt, isWarmThroughputDecrease as z, DynamicReferenceRegionAmbiguousError as zi, TemplateParser as zn, buildDenyExternalAccessPolicy as zr, assertRegionMatch as zt };
45705
- //# sourceMappingURL=deploy-engine-21iNqwR3.js.map
45984
+ //# sourceMappingURL=deploy-engine-CfOsVRQB.js.map