Skip to content

feat(toolkit-lib): simplify deploy approval message and surface auto-confirm#1585

Open
sai-ray wants to merge 20 commits into
mainfrom
sai/require-approval-notice-suppression
Open

feat(toolkit-lib): simplify deploy approval message and surface auto-confirm#1585
sai-ray wants to merge 20 commits into
mainfrom
sai/require-approval-notice-suppression

Conversation

@sai-ray

@sai-ray sai-ray commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Closes #828.

@aws-cdk/toolkit-lib's deploy() previously printed "--require-approval" is enabled and stack includes security-sensitive updates. Do you wish to deploy these changes and then silently auto-confirmed via the default NonInteractiveIoHost. The question-shaped text without a visible answer-shaped action confused programmatic users.

This PR:

  • keeps the I5060 confirmation request in _deploy (it's the IoHost's job to decide what to do with it).
  • drops the --require-approval reference from the library's vocabulary.
  • makes NonInteractiveIoHost's auto-response visible.
  • moves the CLI-side --require-approval framing into a listener registered by the deploy command.

Changes

  • @aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts: motivation drops the --require-approval reference but keeps the existing security-sensitive vs non-security-sensitive branching:

    • Security-sensitive change: Stack includes security-sensitive updates
    • Non-security change: Stack includes updates
  • @aws-cdk/toolkit-lib/lib/toolkit/non-interactive-io-host.ts: requestResponse annotates the message based on the actual default response value:

    • defaultResponse: true: (auto-confirmed)
    • defaultResponse: false: (auto-denied)
    • non-boolean default: (auto-responded with default: <value>)
  • aws-cdk/lib/cli/cdk-toolkit.ts: deploy() registers a rewrite(IO.CDK_TOOLKIT_I5060, …) listener on CliIoHost that reframes the library's bare-fact question with the --require-approval value, keyed off msg.data.permissionChangeType. Both the CLI's own deploy path and toolkit-lib's deploy action resolve through the same host, so one listener covers both. Registered up front and removed in a finally, matching the cdk destroy reroute. The complete sentences it produces:

    • BROADENING + security change: Stack includes security-sensitive updates and "--require-approval" is set to 'broadening'. Do you wish to deploy these changes?
    • ANYCHANGE + security change: Stack includes security-sensitive updates and "--require-approval" is set to 'any-change'. Do you wish to deploy these changes?
    • ANYCHANGE + non-security change: Stack includes updates and "--require-approval" is set to 'any-change'. Do you wish to deploy these changes?
  • @aws-cdk/toolkit-lib/test/actions/deploy.test.ts: motivation assertions updated for the new bare-fact text.

    Example

    Same stack, before vs after this PR:

    Before

    "--require-approval" is enabled and stack includes security-sensitive updates.
    Do you wish to deploy these changes
    Cdk828Probe: deploying... [1/1]
    

    After (cdk deploy, default --require-approval=broadening, broadening change)

    Stack includes security-sensitive updates and "--require-approval" is set to 'broadening'.
    Do you wish to deploy these changes? (y/n)
    

    After (cdk deploy --require-approval=any-change, broadening change)

    Stack includes security-sensitive updates and "--require-approval" is set to 'any-change'.
    Do you wish to deploy these changes? (y/n)
    

    After (cdk deploy --require-approval=any-change, non-security change)

    Stack includes updates and "--require-approval" is set to 'any-change'.
    Do you wish to deploy these changes? (y/n)
    

    After (await toolkit.deploy() programmatic, default NonInteractiveIoHost, security-sensitive change)

    Stack includes security-sensitive updates. 
    Do you wish to deploy these changes? (auto-confirmed)
    Cdk828Probe: deploying... [1/1]
    

    After (await toolkit.deploy() programmatic, default NonInteractiveIoHost, non-security change)

    Stack includes updates. 
    Do you wish to deploy these changes? (auto-confirmed)
    Cdk828Probe: deploying... [1/1]
    

    Test plan

    • Unit tests in test/actions/deploy.test.ts (motivation assertions updated for the bare-fact text).
    • Unit tests in test/cli/io-host/cli-io-host.test.ts: the four wording branches over the rewrite(I5060) listener (broadening/any-change × security/non-security, plus non-I5060 passthrough).
    • End-to-end test in test/commands/deploy.test.ts: runs deploy() and asserts the recorded, listener-reframed prompt, plus an IO snapshot.
    • Unit tests in test/toolkit/non-interactive-io-host.test.ts: the auto-response annotation for true, false, and non-boolean defaults.
    • Verified live against a sandbox account: a real IAM (broadening) diff renders Stack includes security-sensitive updates and "--require-approval" is set to 'broadening'. Do you wish to deploy these changes?

    Checklist

    • This change contains a major version upgrade for a dependency and I confirm all breaking changes are addressed
      • Release notes for the new version:

    By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@aws-cdk-automation aws-cdk-automation requested a review from a team June 1, 2026 22:16
@sai-ray

sai-ray commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @mrgrain and @rix0rrr. Dropped the requireApproval option, so _deploy always emits I5060 on any change and the IoHost decides what to do with it. Simplified the motivation to Changes detected. so the --require-approval reference doesn't leak out of the library. And NonInteractiveIoHost now annotates auto-replies with (auto-confirmed) or (auto-responded with default: …), similar to --yes mode.

@rix0rrr on your second point : if I understand correctly, you're saying the I5060 payload should give the IoHost enough to make its own decision. permissionChangeType and templateDiffs are already on the payload, so the IoHost can correlate the change to whatever flag it exposes (that's what CliIoHost.skipApprovalStep uses today). The message itself still branches on security vs stack diff inside _deploy . should I move that out too, or is it fine where it is? Happy to do it here or as a follow-up.

// in the non-interactive IoHost, no requests are promptable
await this.notify(msg);
const annotation = typeof msg.defaultResponse === 'boolean'
? '(auto-confirmed)'

@rix0rrr rix0rrr Jun 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are assuming that all booleans are always true, apparently.

The defaultResponse boolean could have been false, in which case auto-confirmed is not correct. It could have been auto-denied as well, you don't have enough information to tell.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. (auto-confirmed) for true, (auto-denied) for false, (auto-responded with default: <value>) for non-boolean.

const deployMotivation = hasSecurityChanges
? '"--require-approval" is enabled and stack includes security-sensitive updates.'
: '"--require-approval" is enabled and stack includes updates.';
const deployMotivation = 'Changes detected.';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I prefer the specificy of the original message. If the problem is that you cannot refer to --require-approval, then remove that part. But now you've removed all specificity.

Here is the question I want you to ask yourself, at all times: "What is better/clearer for the user?"

So, now answer that question: why is it better for the user to get a message like:

Changes detected. Do you wish to deploy these changes?

Put yourself in the shoes of the user. You've just made changes. You're now running cdk deploy to deploy your changes. Then the tool triumphantly tells you "there are changes!". What would you think at that point?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the point was: The original message was good and to the point. But the authority that comes up with that message is in the wrong place, because the toolkit-lib has no business talking about command-line arguments.

So:

  • You need to produce an accurate message here that does not refer to command line arguments.
  • You then need to produce an even more accurate message in the CLI, that does refer to command line arguments.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restored the security-vs-non-security branching, just dropped the --require-approval reference. Library now says Stack includes security-sensitive updates. / Stack includes updates..

Comment on lines 865 to 867
? 'Stack includes security-sensitive updates.'
: 'Stack includes updates.';
const diffOutput = hasSecurityChanges ? securityDiff.formattedDiff : stackDiff.formattedDiff;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put the messaging about the flags back in the CliIoHost.

@rix0rrr rix0rrr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there!

return msg;
}

const data = (msg.data ?? {}) as { motivation?: string; permissionChangeType?: string };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there not an existing type definition that models the data field here? I feel like we require this.

In fact, there should be a CDK_TOOLKIT_I5060.is(msg) that will enforce a type guard that will automatically make msg.data have the right type.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the type already exists DeployConfirmationRequest in payloads/deploy.ts. It's been wired to I5060 via make.confirm<DeployConfirmationRequest>(...) in messages.ts since before this PR. The untyped cast you commented on is gone and is replaced by the suggested type guard.

Added is() on IoRequestMaker in message-maker.ts (it was missing, only IoMessageMaker had it) and hasSecurityChanges: boolean on DeployConfirmationRequest. The augmenter now uses IO.CDK_TOOLKIT_I5060.is(msg) to narrow msg.data and reads hasSecurityChanges to pick the right phrasing. So the prompt branch is driven by a typed payload field instead of re-deriving from permissionChangeType.

let prefix: string;
switch (this.requireDeployApproval) {
case RequireApproval.ANYCHANGE:
prefix = `"--require-approval" is set to '${RequireApproval.ANYCHANGE}'. `;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because you want to reuse the original message, and both need to make sense in their own context, the final sentence here will be very staccato:

require-approval is set to anychange. Stack contains updates. Do you want to continue?

Instead, you can include hasSecurityChanges: true|false into the data object and format a complete sentence here, rather than scrapping about with fragments.

You might have noticed already, but I care quite a lot how CDK "feels". Not just: what the CLI says is technically accurate, but also that it speaks in a normal voice to a normal user. No formal IBM-speak, no vague Microsoft-speak.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Added hasSecurityChanges: boolean to the data object, and the augmenter now writes one of three full sentences instead of stitching fragments onto the library's text:

  • BROADENING + has-security:
Stack includes security-sensitive updates and "--require-approval" is set to 'broadening'.
Do you wish to deploy these changes?
  • ANYCHANGE + has-security:
Stack includes security-sensitive updates and "--require-approval" is set to 'any-change'.
Do you wish to deploy these changes?
  • ANYCHANGE + non-security:
Stack includes updates and "--require-approval" is set to 'any-change'.  
Do you wish to deploy these changes?

On the voice, this was what I thought as being both natural and not too informal. Any thoughts/ suggestions?

// The library emits I5060 with a flag-free motivation. The CLI is the
// layer that knows about `--require-approval` and adds it to the
// user-facing message before any downstream rendering.
const promptMessage = this.augmentDeployApprovalMessage(msg).message;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this function to return a full IoRequest object if all we're going to do is pull out a single field from that object and throw the rest away. You can just have this return a string.

/**
* Add the `--require-approval` flag to the deploy approval prompt.
*/
private augmentDeployApprovalMessage(msg: IoRequest<any, any>): string {

@rix0rrr rix0rrr Jun 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm entirely ready to ship this now. HOWEVER, it would be even better if this used the message formatting feature that @mrgrain just formalized here: #1603

So oooooooooone final thing 😉

Comment on lines +37 to +41
/**
* True when the change includes security-sensitive updates (IAM or security
* group changes). IoHosts use this to phrase the prompt accordingly.
*/
readonly hasSecurityChanges: boolean;

@mrgrain mrgrain Jun 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is redundant, permissionChangeType already includes this.

Suggested change
/**
* True when the change includes security-sensitive updates (IAM or security
* group changes). IoHosts use this to phrase the prompt accordingly.
*/
readonly hasSecurityChanges: boolean;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated and removed hasSecurityChanges

motivation: deployMotivation,
concurrency,
permissionChangeType: securityDiff.permissionChangeType,
hasSecurityChanges,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant permissionChangeType: securityDiff.permissionChangeType has the same info

Suggested change
hasSecurityChanges,

return msg.message;
}

const hasSecurityChanges = msg.data.hasSecurityChanges;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check msg.data.permissionChangeType instead

Comment thread packages/aws-cdk/lib/cli/cdk-toolkit.ts Outdated
motivation,
concurrency: this.options.concurrency,
permissionChangeType: securityDiff.permissionChangeType,
hasSecurityChanges,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above

Comment thread packages/aws-cdk/lib/cli/cdk-toolkit.ts Outdated
@@ -2347,6 +2349,7 @@ class WorkGraphDeploymentActions implements WorkGraphActions {

@mrgrain mrgrain Jun 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: For consistency with toolkit-lib change : to .?

}
if (this.requireDeployApproval === RequireApproval.ANYCHANGE && !hasSecurityChanges) {
return 'Stack includes updates and "--require-approval" is set to \'any-change\'.\nDo you wish to deploy these changes?';
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I feel this much harder to read than the old system. I had to read three times to see the differences here. Also this if is going to be annoying if we are adding more states.

I suggest you keep this as a templating string:

const updateTypeText = hasSecurityChanges ? "security-sensitive updates" : "updates";
const requireApproval = this.requireDeployApproval; // maybe? not sure.

return `Stack includes ${updateTypeText} and "--require-approval" is set to '${requireApproval}'.\nDo you wish to deploy these changes?`;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switched to the suggested template string form. it's now in the rewrite listener in cdk-toolkit.ts

sai-ray added 5 commits June 10, 2026 13:00
…notice-suppression

# Conflicts:
#	packages/@aws-cdk/toolkit-lib/lib/api/io/private/message-maker.ts
#	packages/@aws-cdk/toolkit-lib/lib/toolkit/non-interactive-io-host.ts
#	packages/aws-cdk/lib/cli/io-host/cli-io-host.ts
@sai-ray

sai-ray commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Reworked to use the CliIoHost listener mechanism.

--require-approval framing now lives in a rewrite(IO.CDK_TOOLKIT_I5060, …) listener registered in deploy(). toolkit-lib emits the bare motivation, the CLI adds the flag framing. One listener covers both deploy paths.

Updated this PR now that the request-listener support from #1666 and #1679 is available. This also relies on the cork-replay fix in #1701, which has now merged, to avoid re-applying the rewrite during replay.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(toolkit-lib): suppress deployment notice about --require-approval

4 participants