From 80d4a5eb2b0daafea9b9936001669b944066f00d Mon Sep 17 00:00:00 2001 From: Kaizen Conroy Date: Wed, 13 May 2026 19:39:40 -0400 Subject: [PATCH 01/13] feat(toolkit): validate command --- .../@aws-cdk/toolkit-lib/lib/actions/index.ts | 1 + .../toolkit-lib/lib/actions/validate/index.ts | 165 ++++++++++++++++++ .../lib/api/io/private/messages.ts | 19 ++ .../toolkit-lib/lib/api/io/toolkit-action.ts | 3 +- .../toolkit-lib/lib/toolkit/toolkit.ts | 72 ++++++++ 5 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 packages/@aws-cdk/toolkit-lib/lib/actions/validate/index.ts diff --git a/packages/@aws-cdk/toolkit-lib/lib/actions/index.ts b/packages/@aws-cdk/toolkit-lib/lib/actions/index.ts index 45a215a0e..ee126522f 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/actions/index.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/actions/index.ts @@ -11,3 +11,4 @@ export * from './rollback'; export * from './synth'; export * from './watch'; export * from './diagnose'; +export * from './validate'; diff --git a/packages/@aws-cdk/toolkit-lib/lib/actions/validate/index.ts b/packages/@aws-cdk/toolkit-lib/lib/actions/validate/index.ts new file mode 100644 index 000000000..c06c6758c --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/lib/actions/validate/index.ts @@ -0,0 +1,165 @@ +import type { StackSelector } from '../../api/cloud-assembly'; + +export interface ValidateOptions { + /** + * Select the stacks to validate + */ + readonly stacks?: StackSelector; +} + +/** + * Whether validation passed or failed + */ +export type ValidationStatus = 'success' | 'failure'; + +/** + * A resource that violated a validation rule + */ +export interface ViolatingResource { + /** + * The logical ID of the resource in the CloudFormation template + */ + readonly resourceLogicalId: string; + + /** + * The path to the CloudFormation template containing this resource + */ + readonly templatePath: string; + + /** + * Locations within the resource where the violation was detected + */ + readonly locations?: string[]; +} + +/** + * A construct that violated a validation rule + */ +export interface ViolatingConstruct { + /** + * The construct path + */ + readonly constructPath?: string; + + /** + * The construct stack trace + */ + readonly constructStack?: string[]; + + /** + * Locations within the construct where the violation was detected + */ + readonly locations?: string[]; + + /** + * The logical ID of the resource in the CloudFormation template + */ + readonly resourceLogicalId: string; + + /** + * The path to the CloudFormation template containing this resource + */ + readonly templatePath: string; +} + +/** + * A single policy violation found by a validation plugin + */ +export interface ValidationViolation { + /** + * The name of the rule that was violated + */ + readonly ruleName: string; + + /** + * A description of the violation + */ + readonly description: string; + + /** + * How to fix the violation + */ + readonly fix?: string; + + /** + * Additional metadata about the rule + */ + readonly ruleMetadata?: Record; + + /** + * The severity of the violation + */ + readonly severity?: string; + + /** + * Resources that violated the rule + */ + readonly violatingResources: ViolatingResource[]; + + /** + * Constructs that violated the rule + */ + readonly violatingConstructs: ViolatingConstruct[]; +} + +/** + * Summary of a plugin's validation run + */ +export interface PluginReportSummary { + /** + * Name of the validation plugin + */ + readonly pluginName: string; + + /** + * Whether the plugin's validation passed or failed + */ + readonly status: ValidationStatus; + + /** + * Optional metadata from the plugin + */ + readonly metadata?: Record; +} + +/** + * Report from a single validation plugin + */ +export interface PluginValidationReport { + /** + * Version of the plugin + */ + readonly version?: string; + + /** + * Summary of the plugin's validation run + */ + readonly summary: PluginReportSummary; + + /** + * Policy violations found by this plugin + */ + readonly violations: ValidationViolation[]; +} + +/** + * The result of the validate action + */ +export interface ValidateResult { + /** + * Whether validation passed or failed overall. + * 'success' if no plugins reported failures (or no plugins ran). + * 'failure' if any plugin reported a failure. + */ + readonly status: ValidationStatus; + + /** + * The title of the validation report + */ + readonly title?: string; + + /** + * Reports from each validation plugin + */ + readonly pluginReports: PluginValidationReport[]; +} diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/io/private/messages.ts b/packages/@aws-cdk/toolkit-lib/lib/api/io/private/messages.ts index f6998f7eb..a63a51378 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/io/private/messages.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/io/private/messages.ts @@ -2,6 +2,7 @@ import type * as cxapi from '@aws-cdk/cloud-assembly-api'; import * as make from './message-maker'; import type { SpanDefinition } from './span'; import type { DiagnosedStack } from '../../../actions/diagnose'; +import type { ValidateResult } from '../../../actions/validate'; import type { StackDiff, DiffResult } from '../../../payloads'; import type { BootstrapEnvironmentProgress } from '../../../payloads/bootstrap-environment-progress'; import type { MissingContext, UpdatedContext } from '../../../payloads/context'; @@ -504,6 +505,24 @@ export const IO = { interface: 'DiagnosedStack', }), + // validate (96xx) + CDK_TOOLKIT_I9600: make.info({ + code: 'CDK_TOOLKIT_I9600', + description: 'Policy validation passed', + interface: 'ValidateResult', + }), + + CDK_TOOLKIT_E9600: make.error({ + code: 'CDK_TOOLKIT_E9600', + description: 'Policy validation failed', + interface: 'ValidateResult', + }), + + CDK_TOOLKIT_I9601: make.info({ + code: 'CDK_TOOLKIT_I9601', + description: 'No policy validation report found', + }), + // Notices CDK_TOOLKIT_I0100: make.info({ code: 'CDK_TOOLKIT_I0100', diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/io/toolkit-action.ts b/packages/@aws-cdk/toolkit-lib/lib/api/io/toolkit-action.ts index 8a79257d2..aa25e06e7 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/io/toolkit-action.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/io/toolkit-action.ts @@ -22,4 +22,5 @@ export type ToolkitAction = | 'refactor' | 'diagnose' | 'orphan' -| 'flags'; +| 'flags' +| 'validate'; diff --git a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts index e3372bd29..09a9b590e 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts @@ -57,6 +57,7 @@ import type { PublishAssetsOptions, PublishAssetsResult } from '../actions/publi import type { RefactorOptions } from '../actions/refactor'; import { type RollbackOptions } from '../actions/rollback'; import { type SynthOptions } from '../actions/synth'; +import type { ValidateOptions, ValidateResult } from '../actions/validate'; import type { IWatcher, WatchOptions } from '../actions/watch'; import { countAssemblyResults } from './private/count-assembly-results'; import { WATCH_EXCLUDE_DEFAULTS } from '../actions/watch/private'; @@ -649,6 +650,77 @@ export class Toolkit extends CloudAssemblySourceBuilder { return { stacks }; } + /** + * Validate Action + * + * Synthesizes the CDK app and reads the policy validation report + * from the cloud assembly output directory. + */ + public async validate(cx: ICloudAssemblySource, options: ValidateOptions = {}): Promise { + const ioHelper = asIoHelper(this.ioHost, 'validate'); + const selectStacks = stacksOpt(options); + await using assembly = await synthAndMeasure(ioHelper, cx, selectStacks); + + const reportPath = path.join(assembly.directory, 'policy-validation-report.json'); + + if (!await fs.pathExists(reportPath)) { + const result: ValidateResult = { + status: 'success', + pluginReports: [], + }; + await ioHelper.notify(IO.CDK_TOOLKIT_I9601.msg('No policy validation report found')); + return result; + } + + const reportJson = await fs.readJson(reportPath); + + const pluginReports = (reportJson.pluginReports ?? []).map((pr: any) => ({ + version: pr.version, + summary: { + pluginName: pr.summary.pluginName, + status: pr.summary.status as 'success' | 'failure', + metadata: pr.summary.metadata, + }, + violations: (pr.violations ?? []).map((v: any) => ({ + ruleName: v.ruleName, + description: v.description, + fix: v.fix, + ruleMetadata: v.ruleMetadata, + severity: v.severity, + violatingResources: (v.violatingResources ?? []).map((r: any) => ({ + resourceLogicalId: r.resourceLogicalId, + templatePath: r.templatePath, + locations: r.locations, + })), + violatingConstructs: (v.violatingConstructs ?? []).map((c: any) => ({ + constructPath: c.constructPath, + constructStack: c.constructStack, + locations: c.locations, + resourceLogicalId: c.resourceLogicalId, + templatePath: c.templatePath, + })), + })), + })); + + const status = pluginReports.some( + (pr: any) => pr.summary.status === 'failure', + ) ? 'failure' as const : 'success' as const; + + const result: ValidateResult = { + status, + title: reportJson.title, + pluginReports, + }; + + if (status === 'failure') { + await ioHelper.notify(IO.CDK_TOOLKIT_E9600.msg('Policy validation failed', result)); + } else { + await ioHelper.notify(IO.CDK_TOOLKIT_I9600.msg('Policy validation passed', result)); + } + + return result; + } + /** * Deploy Action * From c5ec73e1b24d3740c042d5cff2345672864a5acc Mon Sep 17 00:00:00 2001 From: Kaizen Conroy Date: Wed, 13 May 2026 19:40:12 -0400 Subject: [PATCH 02/13] chore: tests --- .../cdk.out/Stack1.template.json | 9 ++ .../cdk.out/manifest.json | 21 ++++ .../cdk.out/policy-validation-report.json | 13 +++ .../cdk.out/tree.json | 17 ++++ .../cdk.out/Stack1.template.json | 9 ++ .../cdk.out/manifest.json | 21 ++++ .../cdk.out/policy-validation-report.json | 42 ++++++++ .../cdk.out/tree.json | 17 ++++ .../toolkit-lib/test/actions/validate.test.ts | 97 +++++++++++++++++++ 9 files changed, 246 insertions(+) create mode 100644 packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/Stack1.template.json create mode 100644 packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/manifest.json create mode 100644 packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/policy-validation-report.json create mode 100644 packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/tree.json create mode 100644 packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/Stack1.template.json create mode 100644 packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/manifest.json create mode 100644 packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/policy-validation-report.json create mode 100644 packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/tree.json create mode 100644 packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/Stack1.template.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/Stack1.template.json new file mode 100644 index 000000000..520ef7435 --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/Stack1.template.json @@ -0,0 +1,9 @@ +{ + "Resources": { + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + } + } +} diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/manifest.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/manifest.json new file mode 100644 index 000000000..e2a5952cf --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/manifest.json @@ -0,0 +1,21 @@ +{ + "version": "53.0.0", + "artifacts": { + "Stack1": { + "type": "aws:cloudformation:stack", + "environment": "aws://123456789012/us-east-1", + "properties": { + "templateFile": "Stack1.template.json", + "terminationProtection": false, + "validateOnSynth": false + }, + "displayName": "Stack1" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/policy-validation-report.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/policy-validation-report.json new file mode 100644 index 000000000..edc50490b --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/policy-validation-report.json @@ -0,0 +1,13 @@ +{ + "title": "Validation Report", + "pluginReports": [ + { + "version": "1.0.0", + "summary": { + "pluginName": "TestPlugin", + "status": "success" + }, + "violations": [] + } + ] +} diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/tree.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/tree.json new file mode 100644 index 000000000..569cfa7e4 --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/tree.json @@ -0,0 +1,17 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Stack1": { + "id": "Stack1", + "path": "Stack1", + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + } + } +} diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/Stack1.template.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/Stack1.template.json new file mode 100644 index 000000000..520ef7435 --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/Stack1.template.json @@ -0,0 +1,9 @@ +{ + "Resources": { + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + } + } +} diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/manifest.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/manifest.json new file mode 100644 index 000000000..e2a5952cf --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/manifest.json @@ -0,0 +1,21 @@ +{ + "version": "53.0.0", + "artifacts": { + "Stack1": { + "type": "aws:cloudformation:stack", + "environment": "aws://123456789012/us-east-1", + "properties": { + "templateFile": "Stack1.template.json", + "terminationProtection": false, + "validateOnSynth": false + }, + "displayName": "Stack1" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/policy-validation-report.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/policy-validation-report.json new file mode 100644 index 000000000..6899d2bbf --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/policy-validation-report.json @@ -0,0 +1,42 @@ +{ + "title": "Validation Report", + "pluginReports": [ + { + "version": "1.0.0", + "summary": { + "pluginName": "TestPlugin", + "status": "failure" + }, + "violations": [ + { + "ruleName": "no-public-buckets", + "description": "S3 Buckets must not be publicly accessible", + "fix": "Set PublicAccessBlockConfiguration on the bucket", + "severity": "error", + "violatingResources": [ + { + "resourceLogicalId": "MyBucketF68F3FF0", + "templatePath": "Stack1.template.json", + "locations": ["/Resources/MyBucketF68F3FF0"] + } + ], + "violatingConstructs": [ + { + "constructPath": "Stack1/MyBucket/Resource", + "resourceLogicalId": "MyBucketF68F3FF0", + "templatePath": "Stack1.template.json", + "locations": ["/Resources/MyBucketF68F3FF0"] + } + ] + } + ] + }, + { + "summary": { + "pluginName": "Construct Annotations", + "status": "success" + }, + "violations": [] + } + ] +} diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/tree.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/tree.json new file mode 100644 index 000000000..569cfa7e4 --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/tree.json @@ -0,0 +1,17 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Stack1": { + "id": "Stack1", + "path": "Stack1", + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + } + } +} diff --git a/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts b/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts new file mode 100644 index 000000000..f287f918b --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts @@ -0,0 +1,97 @@ +import { StackSelectionStrategy } from '../../lib/api/cloud-assembly'; +import { Toolkit } from '../../lib/toolkit'; +import { cdkOutFixture, TestIoHost } from '../_helpers'; + +let ioHost: TestIoHost; +let toolkit: Toolkit; + +beforeEach(() => { + ioHost = new TestIoHost(); + toolkit = new Toolkit({ ioHost }); +}); + +describe('validate', () => { + test('returns failure when report contains failing plugin', async () => { + const cx = await cdkOutFixture(toolkit, 'stack-with-validation-report'); + const result = await toolkit.validate(cx); + + expect(result.status).toBe('failure'); + expect(result.title).toBe('Validation Report'); + expect(result.pluginReports).toHaveLength(2); + expect(result.pluginReports[0].summary.pluginName).toBe('TestPlugin'); + expect(result.pluginReports[0].summary.status).toBe('failure'); + expect(result.pluginReports[0].violations).toHaveLength(1); + expect(result.pluginReports[0].violations[0].ruleName).toBe('no-public-buckets'); + expect(result.pluginReports[0].violations[0].violatingConstructs[0].constructPath).toBe('Stack1/MyBucket/Resource'); + }); + + test('returns success when all plugins pass', async () => { + const cx = await cdkOutFixture(toolkit, 'stack-with-passing-validation'); + const result = await toolkit.validate(cx); + + expect(result.status).toBe('success'); + expect(result.pluginReports).toHaveLength(1); + expect(result.pluginReports[0].summary.status).toBe('success'); + expect(result.pluginReports[0].violations).toHaveLength(0); + }); + + test('returns success with no reports when no report file exists', async () => { + const cx = await cdkOutFixture(toolkit, 'stack-with-bucket'); + const result = await toolkit.validate(cx); + + expect(result.status).toBe('success'); + expect(result.pluginReports).toHaveLength(0); + ioHost.expectMessage({ containing: 'No policy validation report found', level: 'info' }); + }); + + test('emits error IO message on failure', async () => { + const cx = await cdkOutFixture(toolkit, 'stack-with-validation-report'); + await toolkit.validate(cx); + + ioHost.expectMessage({ containing: 'Policy validation failed', level: 'error' }); + }); + + test('emits info IO message on success', async () => { + const cx = await cdkOutFixture(toolkit, 'stack-with-passing-validation'); + await toolkit.validate(cx); + + ioHost.expectMessage({ containing: 'Policy validation passed', level: 'info' }); + }); + + test('can invoke without options', async () => { + const cx = await cdkOutFixture(toolkit, 'stack-with-bucket'); + const result = await toolkit.validate(cx); + + expect(result.status).toBe('success'); + }); + + test('passes stack selector to synthesis', async () => { + const cx = await cdkOutFixture(toolkit, 'stack-with-validation-report'); + const result = await toolkit.validate(cx, { + stacks: { strategy: StackSelectionStrategy.ALL_STACKS }, + }); + + expect(result.status).toBe('failure'); + }); + + test('parses violation details correctly', async () => { + const cx = await cdkOutFixture(toolkit, 'stack-with-validation-report'); + const result = await toolkit.validate(cx); + + const violation = result.pluginReports[0].violations[0]; + expect(violation.severity).toBe('error'); + expect(violation.fix).toBe('Set PublicAccessBlockConfiguration on the bucket'); + expect(violation.violatingResources).toHaveLength(1); + expect(violation.violatingResources[0].resourceLogicalId).toBe('MyBucketF68F3FF0'); + expect(violation.violatingResources[0].templatePath).toBe('Stack1.template.json'); + expect(violation.violatingResources[0].locations).toEqual(['/Resources/MyBucketF68F3FF0']); + }); + + test('includes plugin version in report', async () => { + const cx = await cdkOutFixture(toolkit, 'stack-with-validation-report'); + const result = await toolkit.validate(cx); + + expect(result.pluginReports[0].version).toBe('1.0.0'); + expect(result.pluginReports[1].version).toBeUndefined(); + }); +}); From fedcdd7a64a9cf0e003307ee0e81df34a08bb9ea Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 23:48:02 +0000 Subject: [PATCH 03/13] chore: self mutation Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- packages/@aws-cdk/toolkit-lib/docs/message-registry.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/@aws-cdk/toolkit-lib/docs/message-registry.md b/packages/@aws-cdk/toolkit-lib/docs/message-registry.md index 5e5866ecb..87b360cca 100644 --- a/packages/@aws-cdk/toolkit-lib/docs/message-registry.md +++ b/packages/@aws-cdk/toolkit-lib/docs/message-registry.md @@ -146,6 +146,9 @@ Please let us know by [opening an issue](https://github.com/aws/aws-cdk-cli/issu | `CDK_TOOLKIT_I9500` | Stack diagnosis (no problems found) | `info` | {@link DiagnosedStack} | | `CDK_TOOLKIT_E9500` | Stack diagnosis (problems found) | `error` | {@link DiagnosedStack} | | `CDK_TOOLKIT_W9501` | Stack diagnosis (diagnosis could not be performed) | `warn` | {@link DiagnosedStack} | +| `CDK_TOOLKIT_I9600` | Policy validation passed | `info` | {@link ValidateResult} | +| `CDK_TOOLKIT_E9600` | Policy validation failed | `error` | {@link ValidateResult} | +| `CDK_TOOLKIT_I9601` | No policy validation report found | `info` | n/a | | `CDK_TOOLKIT_I0100` | Notices decoration (the header or footer of a list of notices) | `info` | n/a | | `CDK_TOOLKIT_W0101` | A notice that is marked as a warning | `warn` | n/a | | `CDK_TOOLKIT_E0101` | A notice that is marked as an error | `error` | n/a | From cba71a4c35a8033518b9030f609e540b97256207 Mon Sep 17 00:00:00 2001 From: Kaizen Conroy Date: Thu, 14 May 2026 14:36:25 -0400 Subject: [PATCH 04/13] refactor(toolkit): use schema types from cloud-assembly-schema for validate Instead of redefining the policy validation report types locally, import them from @aws-cdk/cloud-assembly-schema (added in PR #1515). This removes ~130 lines of duplicate type definitions and simplifies the validate method to a typed cast instead of manual field mapping. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../toolkit-lib/lib/actions/validate/index.ts | 142 +----------------- .../toolkit-lib/lib/toolkit/toolkit.ts | 40 +---- 2 files changed, 11 insertions(+), 171 deletions(-) diff --git a/packages/@aws-cdk/toolkit-lib/lib/actions/validate/index.ts b/packages/@aws-cdk/toolkit-lib/lib/actions/validate/index.ts index c06c6758c..297d8d42f 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/actions/validate/index.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/actions/validate/index.ts @@ -1,3 +1,4 @@ +import type { PolicyValidationReportJson, PolicyValidationReportStatus, PluginReportJson } from '@aws-cdk/cloud-assembly-schema'; import type { StackSelector } from '../../api/cloud-assembly'; export interface ValidateOptions { @@ -7,141 +8,6 @@ export interface ValidateOptions { readonly stacks?: StackSelector; } -/** - * Whether validation passed or failed - */ -export type ValidationStatus = 'success' | 'failure'; - -/** - * A resource that violated a validation rule - */ -export interface ViolatingResource { - /** - * The logical ID of the resource in the CloudFormation template - */ - readonly resourceLogicalId: string; - - /** - * The path to the CloudFormation template containing this resource - */ - readonly templatePath: string; - - /** - * Locations within the resource where the violation was detected - */ - readonly locations?: string[]; -} - -/** - * A construct that violated a validation rule - */ -export interface ViolatingConstruct { - /** - * The construct path - */ - readonly constructPath?: string; - - /** - * The construct stack trace - */ - readonly constructStack?: string[]; - - /** - * Locations within the construct where the violation was detected - */ - readonly locations?: string[]; - - /** - * The logical ID of the resource in the CloudFormation template - */ - readonly resourceLogicalId: string; - - /** - * The path to the CloudFormation template containing this resource - */ - readonly templatePath: string; -} - -/** - * A single policy violation found by a validation plugin - */ -export interface ValidationViolation { - /** - * The name of the rule that was violated - */ - readonly ruleName: string; - - /** - * A description of the violation - */ - readonly description: string; - - /** - * How to fix the violation - */ - readonly fix?: string; - - /** - * Additional metadata about the rule - */ - readonly ruleMetadata?: Record; - - /** - * The severity of the violation - */ - readonly severity?: string; - - /** - * Resources that violated the rule - */ - readonly violatingResources: ViolatingResource[]; - - /** - * Constructs that violated the rule - */ - readonly violatingConstructs: ViolatingConstruct[]; -} - -/** - * Summary of a plugin's validation run - */ -export interface PluginReportSummary { - /** - * Name of the validation plugin - */ - readonly pluginName: string; - - /** - * Whether the plugin's validation passed or failed - */ - readonly status: ValidationStatus; - - /** - * Optional metadata from the plugin - */ - readonly metadata?: Record; -} - -/** - * Report from a single validation plugin - */ -export interface PluginValidationReport { - /** - * Version of the plugin - */ - readonly version?: string; - - /** - * Summary of the plugin's validation run - */ - readonly summary: PluginReportSummary; - - /** - * Policy violations found by this plugin - */ - readonly violations: ValidationViolation[]; -} - /** * The result of the validate action */ @@ -151,7 +17,7 @@ export interface ValidateResult { * 'success' if no plugins reported failures (or no plugins ran). * 'failure' if any plugin reported a failure. */ - readonly status: ValidationStatus; + readonly status: PolicyValidationReportStatus; /** * The title of the validation report @@ -161,5 +27,7 @@ export interface ValidateResult { /** * Reports from each validation plugin */ - readonly pluginReports: PluginValidationReport[]; + readonly pluginReports: PluginReportJson[]; } + +export type { PolicyValidationReportJson, PolicyValidationReportStatus, PluginReportJson }; diff --git a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts index 09a9b590e..1b2680ce6 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts @@ -57,7 +57,7 @@ import type { PublishAssetsOptions, PublishAssetsResult } from '../actions/publi import type { RefactorOptions } from '../actions/refactor'; import { type RollbackOptions } from '../actions/rollback'; import { type SynthOptions } from '../actions/synth'; -import type { ValidateOptions, ValidateResult } from '../actions/validate'; +import type { ValidateOptions, ValidateResult, PolicyValidationReportJson } from '../actions/validate'; import type { IWatcher, WatchOptions } from '../actions/watch'; import { countAssemblyResults } from './private/count-assembly-results'; import { WATCH_EXCLUDE_DEFAULTS } from '../actions/watch/private'; @@ -672,44 +672,16 @@ export class Toolkit extends CloudAssemblySourceBuilder { return result; } - const reportJson = await fs.readJson(reportPath); + const report: PolicyValidationReportJson = await fs.readJson(reportPath); - const pluginReports = (reportJson.pluginReports ?? []).map((pr: any) => ({ - version: pr.version, - summary: { - pluginName: pr.summary.pluginName, - status: pr.summary.status as 'success' | 'failure', - metadata: pr.summary.metadata, - }, - violations: (pr.violations ?? []).map((v: any) => ({ - ruleName: v.ruleName, - description: v.description, - fix: v.fix, - ruleMetadata: v.ruleMetadata, - severity: v.severity, - violatingResources: (v.violatingResources ?? []).map((r: any) => ({ - resourceLogicalId: r.resourceLogicalId, - templatePath: r.templatePath, - locations: r.locations, - })), - violatingConstructs: (v.violatingConstructs ?? []).map((c: any) => ({ - constructPath: c.constructPath, - constructStack: c.constructStack, - locations: c.locations, - resourceLogicalId: c.resourceLogicalId, - templatePath: c.templatePath, - })), - })), - })); - - const status = pluginReports.some( - (pr: any) => pr.summary.status === 'failure', + const status = report.pluginReports.some( + (pr) => pr.summary.status === 'failure', ) ? 'failure' as const : 'success' as const; const result: ValidateResult = { status, - title: reportJson.title, - pluginReports, + title: report.title, + pluginReports: report.pluginReports, }; if (status === 'failure') { From 15f298928e034097929f747ae1798e38e529389c Mon Sep 17 00:00:00 2001 From: Kaizen Conroy Date: Thu, 14 May 2026 14:44:51 -0400 Subject: [PATCH 05/13] test(toolkit): improve validate test coverage - Handle malformed report: throw ToolkitError if pluginReports is missing or not an array - Test constructStack (ConstructTraceJson): add recursive trace to fixture and assert nested id/construct/location fields - Assert IO message data payload contains full ValidateResult - Test missing title field gracefully results in undefined Co-Authored-By: Claude Opus 4.6 (1M context) --- .../toolkit-lib/lib/toolkit/toolkit.ts | 8 ++- .../cdk.out/Stack1.template.json | 9 ++++ .../cdk.out/manifest.json | 21 ++++++++ .../cdk.out/policy-validation-report.json | 4 ++ .../cdk.out/tree.json | 17 +++++++ .../cdk.out/Stack1.template.json | 9 ++++ .../cdk.out/manifest.json | 21 ++++++++ .../cdk.out/policy-validation-report.json | 24 +++++++++ .../cdk.out/tree.json | 17 +++++++ .../cdk.out/policy-validation-report.json | 18 +++++++ .../toolkit-lib/test/actions/validate.test.ts | 50 +++++++++++++++++++ 11 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-malformed-validation-report/cdk.out/Stack1.template.json create mode 100644 packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-malformed-validation-report/cdk.out/manifest.json create mode 100644 packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-malformed-validation-report/cdk.out/policy-validation-report.json create mode 100644 packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-malformed-validation-report/cdk.out/tree.json create mode 100644 packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/Stack1.template.json create mode 100644 packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/manifest.json create mode 100644 packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/policy-validation-report.json create mode 100644 packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/tree.json diff --git a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts index 1b2680ce6..a95ef2be6 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts @@ -672,7 +672,13 @@ export class Toolkit extends CloudAssemblySourceBuilder { return result; } - const report: PolicyValidationReportJson = await fs.readJson(reportPath); + const reportJson = await fs.readJson(reportPath); + + if (!Array.isArray(reportJson.pluginReports)) { + throw new ToolkitError('MalformedValidationReport', `Policy validation report at ${reportPath} is malformed: missing or invalid 'pluginReports' field`); + } + + const report = reportJson as PolicyValidationReportJson; const status = report.pluginReports.some( (pr) => pr.summary.status === 'failure', diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-malformed-validation-report/cdk.out/Stack1.template.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-malformed-validation-report/cdk.out/Stack1.template.json new file mode 100644 index 000000000..520ef7435 --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-malformed-validation-report/cdk.out/Stack1.template.json @@ -0,0 +1,9 @@ +{ + "Resources": { + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + } + } +} diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-malformed-validation-report/cdk.out/manifest.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-malformed-validation-report/cdk.out/manifest.json new file mode 100644 index 000000000..e2a5952cf --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-malformed-validation-report/cdk.out/manifest.json @@ -0,0 +1,21 @@ +{ + "version": "53.0.0", + "artifacts": { + "Stack1": { + "type": "aws:cloudformation:stack", + "environment": "aws://123456789012/us-east-1", + "properties": { + "templateFile": "Stack1.template.json", + "terminationProtection": false, + "validateOnSynth": false + }, + "displayName": "Stack1" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-malformed-validation-report/cdk.out/policy-validation-report.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-malformed-validation-report/cdk.out/policy-validation-report.json new file mode 100644 index 000000000..d949a82a4 --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-malformed-validation-report/cdk.out/policy-validation-report.json @@ -0,0 +1,4 @@ +{ + "title": "Validation Report", + "someUnexpectedField": true +} diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-malformed-validation-report/cdk.out/tree.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-malformed-validation-report/cdk.out/tree.json new file mode 100644 index 000000000..569cfa7e4 --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-malformed-validation-report/cdk.out/tree.json @@ -0,0 +1,17 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Stack1": { + "id": "Stack1", + "path": "Stack1", + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + } + } +} diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/Stack1.template.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/Stack1.template.json new file mode 100644 index 000000000..520ef7435 --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/Stack1.template.json @@ -0,0 +1,9 @@ +{ + "Resources": { + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + } + } +} diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/manifest.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/manifest.json new file mode 100644 index 000000000..e2a5952cf --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/manifest.json @@ -0,0 +1,21 @@ +{ + "version": "53.0.0", + "artifacts": { + "Stack1": { + "type": "aws:cloudformation:stack", + "environment": "aws://123456789012/us-east-1", + "properties": { + "templateFile": "Stack1.template.json", + "terminationProtection": false, + "validateOnSynth": false + }, + "displayName": "Stack1" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/policy-validation-report.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/policy-validation-report.json new file mode 100644 index 000000000..d922e68ce --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/policy-validation-report.json @@ -0,0 +1,24 @@ +{ + "pluginReports": [ + { + "summary": { + "pluginName": "TestPlugin", + "status": "failure" + }, + "violations": [ + { + "ruleName": "no-public-buckets", + "description": "S3 Buckets must not be publicly accessible", + "severity": "error", + "violatingResources": [ + { + "resourceLogicalId": "MyBucketF68F3FF0", + "templatePath": "Stack1.template.json" + } + ], + "violatingConstructs": [] + } + ] + } + ] +} diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/tree.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/tree.json new file mode 100644 index 000000000..569cfa7e4 --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/tree.json @@ -0,0 +1,17 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Stack1": { + "id": "Stack1", + "path": "Stack1", + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + } + } +} diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/policy-validation-report.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/policy-validation-report.json index 6899d2bbf..fb5b791e7 100644 --- a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/policy-validation-report.json +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/policy-validation-report.json @@ -23,6 +23,24 @@ "violatingConstructs": [ { "constructPath": "Stack1/MyBucket/Resource", + "constructStack": { + "id": "App", + "path": "", + "child": { + "id": "Stack1", + "path": "Stack1", + "construct": "aws-cdk-lib.Stack", + "libraryVersion": "2.200.0", + "location": "new MyStack (lib/my-stack.ts:30:5)", + "child": { + "id": "MyBucket", + "path": "Stack1/MyBucket", + "construct": "aws-cdk-lib/aws-s3.Bucket", + "libraryVersion": "2.200.0", + "location": "new Bucket (lib/my-stack.ts:12:5)" + } + } + }, "resourceLogicalId": "MyBucketF68F3FF0", "templatePath": "Stack1.template.json", "locations": ["/Resources/MyBucketF68F3FF0"] diff --git a/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts b/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts index f287f918b..24c255eb8 100644 --- a/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts @@ -94,4 +94,54 @@ describe('validate', () => { expect(result.pluginReports[0].version).toBe('1.0.0'); expect(result.pluginReports[1].version).toBeUndefined(); }); + + test('throws on malformed report missing pluginReports', async () => { + const cx = await cdkOutFixture(toolkit, 'stack-with-malformed-validation-report'); + + await expect(toolkit.validate(cx)).rejects.toThrow(/malformed.*pluginReports/i); + }); + + test('parses constructStack trace correctly', async () => { + const cx = await cdkOutFixture(toolkit, 'stack-with-validation-report'); + const result = await toolkit.validate(cx); + + const construct = result.pluginReports[0].violations[0].violatingConstructs[0]; + expect(construct.constructStack).toBeDefined(); + expect(construct.constructStack!.id).toBe('App'); + expect(construct.constructStack!.child!.id).toBe('Stack1'); + expect(construct.constructStack!.child!.construct).toBe('aws-cdk-lib.Stack'); + expect(construct.constructStack!.child!.location).toBe('new MyStack (lib/my-stack.ts:30:5)'); + expect(construct.constructStack!.child!.child!.id).toBe('MyBucket'); + expect(construct.constructStack!.child!.child!.construct).toBe('aws-cdk-lib/aws-s3.Bucket'); + expect(construct.constructStack!.child!.child!.location).toBe('new Bucket (lib/my-stack.ts:12:5)'); + }); + + test('IO message payload contains full ValidateResult', async () => { + const cx = await cdkOutFixture(toolkit, 'stack-with-validation-report'); + await toolkit.validate(cx); + + const errorMsg = ioHost.messages.find( + (m) => m.code === 'CDK_TOOLKIT_E9600', + ); + expect(errorMsg).toBeDefined(); + expect(errorMsg!.data).toMatchObject({ + status: 'failure', + title: 'Validation Report', + pluginReports: expect.arrayContaining([ + expect.objectContaining({ + summary: expect.objectContaining({ pluginName: 'TestPlugin', status: 'failure' }), + }), + ]), + }); + }); + + test('handles report with missing title field', async () => { + const cx = await cdkOutFixture(toolkit, 'stack-with-no-title-validation'); + const result = await toolkit.validate(cx); + + expect(result.status).toBe('failure'); + expect(result.title).toBeUndefined(); + expect(result.pluginReports).toHaveLength(1); + expect(result.pluginReports[0].violations[0].ruleName).toBe('no-public-buckets'); + }); }); From b3352c56184a8f7663beac48a6c725001f77e113 Mon Sep 17 00:00:00 2001 From: Kaizen Conroy Date: Thu, 14 May 2026 18:24:09 -0400 Subject: [PATCH 06/13] chore(toolkit): cleanup validate method style - Replace 'as const' with explicit PolicyValidationReportStatus type annotation - Extract 'policy-validation-report.json' to a file-level constant Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts index a95ef2be6..564ddf1b5 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts @@ -57,7 +57,7 @@ import type { PublishAssetsOptions, PublishAssetsResult } from '../actions/publi import type { RefactorOptions } from '../actions/refactor'; import { type RollbackOptions } from '../actions/rollback'; import { type SynthOptions } from '../actions/synth'; -import type { ValidateOptions, ValidateResult, PolicyValidationReportJson } from '../actions/validate'; +import type { ValidateOptions, ValidateResult, PolicyValidationReportJson, PolicyValidationReportStatus } from '../actions/validate'; import type { IWatcher, WatchOptions } from '../actions/watch'; import { countAssemblyResults } from './private/count-assembly-results'; import { WATCH_EXCLUDE_DEFAULTS } from '../actions/watch/private'; @@ -114,6 +114,8 @@ import { pLimit } from '../util/concurrency'; import { createIgnoreMatcher } from '../util/glob-matcher'; import { promiseWithResolvers } from '../util/promises'; +const POLICY_VALIDATION_REPORT_FILE = 'policy-validation-report.json'; + export interface ToolkitOptions { /** * The IoHost implementation, handling the inline interactions between the Toolkit and an integration. @@ -661,7 +663,7 @@ export class Toolkit extends CloudAssemblySourceBuilder { const selectStacks = stacksOpt(options); await using assembly = await synthAndMeasure(ioHelper, cx, selectStacks); - const reportPath = path.join(assembly.directory, 'policy-validation-report.json'); + const reportPath = path.join(assembly.directory, POLICY_VALIDATION_REPORT_FILE); if (!await fs.pathExists(reportPath)) { const result: ValidateResult = { @@ -680,9 +682,9 @@ export class Toolkit extends CloudAssemblySourceBuilder { const report = reportJson as PolicyValidationReportJson; - const status = report.pluginReports.some( + const status: PolicyValidationReportStatus = report.pluginReports.some( (pr) => pr.summary.status === 'failure', - ) ? 'failure' as const : 'success' as const; + ) ? 'failure' : 'success'; const result: ValidateResult = { status, From a13e1c8ade142dcf4cfbad11bce16d4ffd3e48b7 Mon Sep 17 00:00:00 2001 From: Kaizen Conroy Date: Mon, 18 May 2026 08:40:15 -0700 Subject: [PATCH 07/13] chore(toolkit): use friendlier validation messages with emojis --- packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts | 4 ++-- packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts index 564ddf1b5..612359b14 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts @@ -693,9 +693,9 @@ export class Toolkit extends CloudAssemblySourceBuilder { }; if (status === 'failure') { - await ioHelper.notify(IO.CDK_TOOLKIT_E9600.msg('Policy validation failed', result)); + await ioHelper.notify(IO.CDK_TOOLKIT_E9600.msg('❌ Validation found policy violations', result)); } else { - await ioHelper.notify(IO.CDK_TOOLKIT_I9600.msg('Policy validation passed', result)); + await ioHelper.notify(IO.CDK_TOOLKIT_I9600.msg('✅ All policy checks passed', result)); } return result; diff --git a/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts b/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts index 24c255eb8..801e8a6cd 100644 --- a/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts @@ -48,14 +48,14 @@ describe('validate', () => { const cx = await cdkOutFixture(toolkit, 'stack-with-validation-report'); await toolkit.validate(cx); - ioHost.expectMessage({ containing: 'Policy validation failed', level: 'error' }); + ioHost.expectMessage({ containing: 'Validation found policy violations', level: 'error' }); }); test('emits info IO message on success', async () => { const cx = await cdkOutFixture(toolkit, 'stack-with-passing-validation'); await toolkit.validate(cx); - ioHost.expectMessage({ containing: 'Policy validation passed', level: 'info' }); + ioHost.expectMessage({ containing: 'All policy checks passed', level: 'info' }); }); test('can invoke without options', async () => { From 437ad347966750045812c07e68e51ecc24e2228f Mon Sep 17 00:00:00 2001 From: Kaizen Conroy Date: Tue, 19 May 2026 10:52:50 -0700 Subject: [PATCH 08/13] refactor(toolkit): align validate with new schema types from PR #1515 - PolicyValidationReportStatus -> PolicyValidationReportConclusion - PluginReportJson.summary.pluginName -> PluginReportJson.pluginName - PluginReportJson.summary.status -> PluginReportJson.conclusion - PluginReportJson.version -> PluginReportJson.pluginVersion - PolicyViolationJson.fix -> PolicyViolationJson.suggestedFix - ViolatingConstructJson restructured: resourceLogicalId/templatePath moved to cloudFormationResource, constructStack -> stackTraces - ValidateResult.status -> ValidateResult.conclusion - Update test fixtures and assertions to match --- .../toolkit-lib/lib/actions/validate/index.ts | 6 +-- .../toolkit-lib/lib/toolkit/toolkit.ts | 12 ++--- .../cdk.out/policy-validation-report.json | 12 +---- .../cdk.out/policy-validation-report.json | 8 ++- .../cdk.out/policy-validation-report.json | 52 ++++++------------- .../toolkit-lib/test/actions/validate.test.ts | 50 ++++++++---------- 6 files changed, 52 insertions(+), 88 deletions(-) diff --git a/packages/@aws-cdk/toolkit-lib/lib/actions/validate/index.ts b/packages/@aws-cdk/toolkit-lib/lib/actions/validate/index.ts index 297d8d42f..93f4b2272 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/actions/validate/index.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/actions/validate/index.ts @@ -1,4 +1,4 @@ -import type { PolicyValidationReportJson, PolicyValidationReportStatus, PluginReportJson } from '@aws-cdk/cloud-assembly-schema'; +import type { PolicyValidationReportJson, PolicyValidationReportConclusion, PluginReportJson } from '@aws-cdk/cloud-assembly-schema'; import type { StackSelector } from '../../api/cloud-assembly'; export interface ValidateOptions { @@ -17,7 +17,7 @@ export interface ValidateResult { * 'success' if no plugins reported failures (or no plugins ran). * 'failure' if any plugin reported a failure. */ - readonly status: PolicyValidationReportStatus; + readonly conclusion: PolicyValidationReportConclusion; /** * The title of the validation report @@ -30,4 +30,4 @@ export interface ValidateResult { readonly pluginReports: PluginReportJson[]; } -export type { PolicyValidationReportJson, PolicyValidationReportStatus, PluginReportJson }; +export type { PolicyValidationReportJson, PolicyValidationReportConclusion, PluginReportJson }; diff --git a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts index 612359b14..6259cb5dd 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts @@ -57,7 +57,7 @@ import type { PublishAssetsOptions, PublishAssetsResult } from '../actions/publi import type { RefactorOptions } from '../actions/refactor'; import { type RollbackOptions } from '../actions/rollback'; import { type SynthOptions } from '../actions/synth'; -import type { ValidateOptions, ValidateResult, PolicyValidationReportJson, PolicyValidationReportStatus } from '../actions/validate'; +import type { ValidateOptions, ValidateResult, PolicyValidationReportJson, PolicyValidationReportConclusion } from '../actions/validate'; import type { IWatcher, WatchOptions } from '../actions/watch'; import { countAssemblyResults } from './private/count-assembly-results'; import { WATCH_EXCLUDE_DEFAULTS } from '../actions/watch/private'; @@ -667,7 +667,7 @@ export class Toolkit extends CloudAssemblySourceBuilder { if (!await fs.pathExists(reportPath)) { const result: ValidateResult = { - status: 'success', + conclusion: 'success', pluginReports: [], }; await ioHelper.notify(IO.CDK_TOOLKIT_I9601.msg('No policy validation report found')); @@ -682,17 +682,17 @@ export class Toolkit extends CloudAssemblySourceBuilder { const report = reportJson as PolicyValidationReportJson; - const status: PolicyValidationReportStatus = report.pluginReports.some( - (pr) => pr.summary.status === 'failure', + const conclusion: PolicyValidationReportConclusion = report.pluginReports.some( + (pr) => pr.conclusion === 'failure', ) ? 'failure' : 'success'; const result: ValidateResult = { - status, + conclusion, title: report.title, pluginReports: report.pluginReports, }; - if (status === 'failure') { + if (conclusion === 'failure') { await ioHelper.notify(IO.CDK_TOOLKIT_E9600.msg('❌ Validation found policy violations', result)); } else { await ioHelper.notify(IO.CDK_TOOLKIT_I9600.msg('✅ All policy checks passed', result)); diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/policy-validation-report.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/policy-validation-report.json index d922e68ce..bb249828d 100644 --- a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/policy-validation-report.json +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/policy-validation-report.json @@ -1,21 +1,13 @@ { "pluginReports": [ { - "summary": { - "pluginName": "TestPlugin", - "status": "failure" - }, + "pluginName": "TestPlugin", + "conclusion": "failure", "violations": [ { "ruleName": "no-public-buckets", "description": "S3 Buckets must not be publicly accessible", "severity": "error", - "violatingResources": [ - { - "resourceLogicalId": "MyBucketF68F3FF0", - "templatePath": "Stack1.template.json" - } - ], "violatingConstructs": [] } ] diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/policy-validation-report.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/policy-validation-report.json index edc50490b..4b3ff4935 100644 --- a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/policy-validation-report.json +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/policy-validation-report.json @@ -2,11 +2,9 @@ "title": "Validation Report", "pluginReports": [ { - "version": "1.0.0", - "summary": { - "pluginName": "TestPlugin", - "status": "success" - }, + "pluginName": "TestPlugin", + "pluginVersion": "1.0.0", + "conclusion": "success", "violations": [] } ] diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/policy-validation-report.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/policy-validation-report.json index fb5b791e7..e17c0fc77 100644 --- a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/policy-validation-report.json +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/policy-validation-report.json @@ -2,58 +2,36 @@ "title": "Validation Report", "pluginReports": [ { - "version": "1.0.0", - "summary": { - "pluginName": "TestPlugin", - "status": "failure" - }, + "pluginName": "TestPlugin", + "pluginVersion": "1.0.0", + "conclusion": "failure", "violations": [ { "ruleName": "no-public-buckets", "description": "S3 Buckets must not be publicly accessible", - "fix": "Set PublicAccessBlockConfiguration on the bucket", + "suggestedFix": "Set PublicAccessBlockConfiguration on the bucket", "severity": "error", - "violatingResources": [ - { - "resourceLogicalId": "MyBucketF68F3FF0", - "templatePath": "Stack1.template.json", - "locations": ["/Resources/MyBucketF68F3FF0"] - } - ], "violatingConstructs": [ { "constructPath": "Stack1/MyBucket/Resource", - "constructStack": { - "id": "App", - "path": "", - "child": { - "id": "Stack1", - "path": "Stack1", - "construct": "aws-cdk-lib.Stack", - "libraryVersion": "2.200.0", - "location": "new MyStack (lib/my-stack.ts:30:5)", - "child": { - "id": "MyBucket", - "path": "Stack1/MyBucket", - "construct": "aws-cdk-lib/aws-s3.Bucket", - "libraryVersion": "2.200.0", - "location": "new Bucket (lib/my-stack.ts:12:5)" - } - } + "constructFqn": "aws-cdk-lib/aws-s3.Bucket", + "libraryVersion": "2.200.0", + "cloudFormationResource": { + "templatePath": "Stack1.template.json", + "logicalId": "MyBucketF68F3FF0", + "propertyPaths": ["/Resources/MyBucketF68F3FF0"] }, - "resourceLogicalId": "MyBucketF68F3FF0", - "templatePath": "Stack1.template.json", - "locations": ["/Resources/MyBucketF68F3FF0"] + "stackTraces": [ + "new Bucket (lib/my-stack.ts:12:5)\nnew MyStack (lib/my-stack.ts:30:5)" + ] } ] } ] }, { - "summary": { - "pluginName": "Construct Annotations", - "status": "success" - }, + "pluginName": "Construct Annotations", + "conclusion": "success", "violations": [] } ] diff --git a/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts b/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts index 801e8a6cd..f0a52b308 100644 --- a/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts @@ -15,11 +15,11 @@ describe('validate', () => { const cx = await cdkOutFixture(toolkit, 'stack-with-validation-report'); const result = await toolkit.validate(cx); - expect(result.status).toBe('failure'); + expect(result.conclusion).toBe('failure'); expect(result.title).toBe('Validation Report'); expect(result.pluginReports).toHaveLength(2); - expect(result.pluginReports[0].summary.pluginName).toBe('TestPlugin'); - expect(result.pluginReports[0].summary.status).toBe('failure'); + expect(result.pluginReports[0].pluginName).toBe('TestPlugin'); + expect(result.pluginReports[0].conclusion).toBe('failure'); expect(result.pluginReports[0].violations).toHaveLength(1); expect(result.pluginReports[0].violations[0].ruleName).toBe('no-public-buckets'); expect(result.pluginReports[0].violations[0].violatingConstructs[0].constructPath).toBe('Stack1/MyBucket/Resource'); @@ -29,9 +29,9 @@ describe('validate', () => { const cx = await cdkOutFixture(toolkit, 'stack-with-passing-validation'); const result = await toolkit.validate(cx); - expect(result.status).toBe('success'); + expect(result.conclusion).toBe('success'); expect(result.pluginReports).toHaveLength(1); - expect(result.pluginReports[0].summary.status).toBe('success'); + expect(result.pluginReports[0].conclusion).toBe('success'); expect(result.pluginReports[0].violations).toHaveLength(0); }); @@ -39,7 +39,7 @@ describe('validate', () => { const cx = await cdkOutFixture(toolkit, 'stack-with-bucket'); const result = await toolkit.validate(cx); - expect(result.status).toBe('success'); + expect(result.conclusion).toBe('success'); expect(result.pluginReports).toHaveLength(0); ioHost.expectMessage({ containing: 'No policy validation report found', level: 'info' }); }); @@ -62,7 +62,7 @@ describe('validate', () => { const cx = await cdkOutFixture(toolkit, 'stack-with-bucket'); const result = await toolkit.validate(cx); - expect(result.status).toBe('success'); + expect(result.conclusion).toBe('success'); }); test('passes stack selector to synthesis', async () => { @@ -71,7 +71,7 @@ describe('validate', () => { stacks: { strategy: StackSelectionStrategy.ALL_STACKS }, }); - expect(result.status).toBe('failure'); + expect(result.conclusion).toBe('failure'); }); test('parses violation details correctly', async () => { @@ -80,19 +80,19 @@ describe('validate', () => { const violation = result.pluginReports[0].violations[0]; expect(violation.severity).toBe('error'); - expect(violation.fix).toBe('Set PublicAccessBlockConfiguration on the bucket'); - expect(violation.violatingResources).toHaveLength(1); - expect(violation.violatingResources[0].resourceLogicalId).toBe('MyBucketF68F3FF0'); - expect(violation.violatingResources[0].templatePath).toBe('Stack1.template.json'); - expect(violation.violatingResources[0].locations).toEqual(['/Resources/MyBucketF68F3FF0']); + expect(violation.suggestedFix).toBe('Set PublicAccessBlockConfiguration on the bucket'); + expect(violation.violatingConstructs).toHaveLength(1); + expect(violation.violatingConstructs[0].cloudFormationResource?.logicalId).toBe('MyBucketF68F3FF0'); + expect(violation.violatingConstructs[0].cloudFormationResource?.templatePath).toBe('Stack1.template.json'); + expect(violation.violatingConstructs[0].cloudFormationResource?.propertyPaths).toEqual(['/Resources/MyBucketF68F3FF0']); }); test('includes plugin version in report', async () => { const cx = await cdkOutFixture(toolkit, 'stack-with-validation-report'); const result = await toolkit.validate(cx); - expect(result.pluginReports[0].version).toBe('1.0.0'); - expect(result.pluginReports[1].version).toBeUndefined(); + expect(result.pluginReports[0].pluginVersion).toBe('1.0.0'); + expect(result.pluginReports[1].pluginVersion).toBeUndefined(); }); test('throws on malformed report missing pluginReports', async () => { @@ -101,19 +101,14 @@ describe('validate', () => { await expect(toolkit.validate(cx)).rejects.toThrow(/malformed.*pluginReports/i); }); - test('parses constructStack trace correctly', async () => { + test('parses stack traces correctly', async () => { const cx = await cdkOutFixture(toolkit, 'stack-with-validation-report'); const result = await toolkit.validate(cx); const construct = result.pluginReports[0].violations[0].violatingConstructs[0]; - expect(construct.constructStack).toBeDefined(); - expect(construct.constructStack!.id).toBe('App'); - expect(construct.constructStack!.child!.id).toBe('Stack1'); - expect(construct.constructStack!.child!.construct).toBe('aws-cdk-lib.Stack'); - expect(construct.constructStack!.child!.location).toBe('new MyStack (lib/my-stack.ts:30:5)'); - expect(construct.constructStack!.child!.child!.id).toBe('MyBucket'); - expect(construct.constructStack!.child!.child!.construct).toBe('aws-cdk-lib/aws-s3.Bucket'); - expect(construct.constructStack!.child!.child!.location).toBe('new Bucket (lib/my-stack.ts:12:5)'); + expect(construct.stackTraces).toBeDefined(); + expect(construct.stackTraces![0]).toContain('new Bucket (lib/my-stack.ts:12:5)'); + expect(construct.stackTraces![0]).toContain('new MyStack (lib/my-stack.ts:30:5)'); }); test('IO message payload contains full ValidateResult', async () => { @@ -125,11 +120,12 @@ describe('validate', () => { ); expect(errorMsg).toBeDefined(); expect(errorMsg!.data).toMatchObject({ - status: 'failure', + conclusion: 'failure', title: 'Validation Report', pluginReports: expect.arrayContaining([ expect.objectContaining({ - summary: expect.objectContaining({ pluginName: 'TestPlugin', status: 'failure' }), + pluginName: 'TestPlugin', + conclusion: 'failure', }), ]), }); @@ -139,7 +135,7 @@ describe('validate', () => { const cx = await cdkOutFixture(toolkit, 'stack-with-no-title-validation'); const result = await toolkit.validate(cx); - expect(result.status).toBe('failure'); + expect(result.conclusion).toBe('failure'); expect(result.title).toBeUndefined(); expect(result.pluginReports).toHaveLength(1); expect(result.pluginReports[0].violations[0].ruleName).toBe('no-public-buckets'); From d94c2880a81fe0bbc43dc7716006337c68b7eb20 Mon Sep 17 00:00:00 2001 From: Kaizen Conroy Date: Tue, 19 May 2026 11:03:06 -0700 Subject: [PATCH 09/13] chore(toolkit): address review feedback on validate method - Use friendlier message when no validation report exists: explain that no plugins are configured rather than stating a file is missing - Remove manual pluginReports array check; trust the report format since it's written by aws-cdk-lib (not user-authored) --- packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts | 10 ++-------- .../@aws-cdk/toolkit-lib/test/actions/validate.test.ts | 7 +------ 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts index 6259cb5dd..922a46ab0 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts @@ -670,17 +670,11 @@ export class Toolkit extends CloudAssemblySourceBuilder { conclusion: 'success', pluginReports: [], }; - await ioHelper.notify(IO.CDK_TOOLKIT_I9601.msg('No policy validation report found')); + await ioHelper.notify(IO.CDK_TOOLKIT_I9601.msg('No validation plugins configured. Add a plugin to your CDK app to enable policy validation.')); return result; } - const reportJson = await fs.readJson(reportPath); - - if (!Array.isArray(reportJson.pluginReports)) { - throw new ToolkitError('MalformedValidationReport', `Policy validation report at ${reportPath} is malformed: missing or invalid 'pluginReports' field`); - } - - const report = reportJson as PolicyValidationReportJson; + const report = await fs.readJson(reportPath) as PolicyValidationReportJson; const conclusion: PolicyValidationReportConclusion = report.pluginReports.some( (pr) => pr.conclusion === 'failure', diff --git a/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts b/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts index f0a52b308..3fdbd0d77 100644 --- a/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts @@ -41,7 +41,7 @@ describe('validate', () => { expect(result.conclusion).toBe('success'); expect(result.pluginReports).toHaveLength(0); - ioHost.expectMessage({ containing: 'No policy validation report found', level: 'info' }); + ioHost.expectMessage({ containing: 'No validation plugins configured', level: 'info' }); }); test('emits error IO message on failure', async () => { @@ -95,11 +95,6 @@ describe('validate', () => { expect(result.pluginReports[1].pluginVersion).toBeUndefined(); }); - test('throws on malformed report missing pluginReports', async () => { - const cx = await cdkOutFixture(toolkit, 'stack-with-malformed-validation-report'); - - await expect(toolkit.validate(cx)).rejects.toThrow(/malformed.*pluginReports/i); - }); test('parses stack traces correctly', async () => { const cx = await cdkOutFixture(toolkit, 'stack-with-validation-report'); From 4520aaba240e92247db889fa063844912b12c439 Mon Sep 17 00:00:00 2001 From: Kaizen Conroy Date: Tue, 19 May 2026 11:20:50 -0700 Subject: [PATCH 10/13] refactor(toolkit): use Manifest.loadValidationReport for schema-validated loading --- packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts index 922a46ab0..2ad127d4f 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts @@ -2,7 +2,7 @@ import '../private/dispose-polyfill'; import * as path from 'node:path'; import * as cxapi from '@aws-cdk/cloud-assembly-api'; import type { FeatureFlagReportProperties } from '@aws-cdk/cloud-assembly-schema'; -import { ArtifactType } from '@aws-cdk/cloud-assembly-schema'; +import { ArtifactType, Manifest } from '@aws-cdk/cloud-assembly-schema'; import type { TemplateDiff } from '@aws-cdk/cloudformation-diff'; import * as chalk from 'chalk'; import * as chokidar from 'chokidar'; @@ -57,7 +57,7 @@ import type { PublishAssetsOptions, PublishAssetsResult } from '../actions/publi import type { RefactorOptions } from '../actions/refactor'; import { type RollbackOptions } from '../actions/rollback'; import { type SynthOptions } from '../actions/synth'; -import type { ValidateOptions, ValidateResult, PolicyValidationReportJson, PolicyValidationReportConclusion } from '../actions/validate'; +import type { ValidateOptions, ValidateResult, PolicyValidationReportConclusion } from '../actions/validate'; import type { IWatcher, WatchOptions } from '../actions/watch'; import { countAssemblyResults } from './private/count-assembly-results'; import { WATCH_EXCLUDE_DEFAULTS } from '../actions/watch/private'; @@ -674,7 +674,7 @@ export class Toolkit extends CloudAssemblySourceBuilder { return result; } - const report = await fs.readJson(reportPath) as PolicyValidationReportJson; + const report = Manifest.loadValidationReport(reportPath); const conclusion: PolicyValidationReportConclusion = report.pluginReports.some( (pr) => pr.conclusion === 'failure', From a77be5fa64ed2216c0387214f4f8c7b843da5512 Mon Sep 17 00:00:00 2001 From: Kaizen Conroy Date: Wed, 20 May 2026 10:37:00 -0400 Subject: [PATCH 11/13] chore(toolkit): use human-friendly messages for validate - "No validation plugins configured" instead of "No policy validation report found" - "cdk validate found problems" instead of "Policy validation failed" - "No problems found" instead of "All policy checks passed" --- packages/@aws-cdk/toolkit-lib/docs/message-registry.md | 6 +++--- .../@aws-cdk/toolkit-lib/lib/api/io/private/messages.ts | 6 +++--- packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts | 6 +++--- packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/@aws-cdk/toolkit-lib/docs/message-registry.md b/packages/@aws-cdk/toolkit-lib/docs/message-registry.md index 87b360cca..2fba211b7 100644 --- a/packages/@aws-cdk/toolkit-lib/docs/message-registry.md +++ b/packages/@aws-cdk/toolkit-lib/docs/message-registry.md @@ -146,9 +146,9 @@ Please let us know by [opening an issue](https://github.com/aws/aws-cdk-cli/issu | `CDK_TOOLKIT_I9500` | Stack diagnosis (no problems found) | `info` | {@link DiagnosedStack} | | `CDK_TOOLKIT_E9500` | Stack diagnosis (problems found) | `error` | {@link DiagnosedStack} | | `CDK_TOOLKIT_W9501` | Stack diagnosis (diagnosis could not be performed) | `warn` | {@link DiagnosedStack} | -| `CDK_TOOLKIT_I9600` | Policy validation passed | `info` | {@link ValidateResult} | -| `CDK_TOOLKIT_E9600` | Policy validation failed | `error` | {@link ValidateResult} | -| `CDK_TOOLKIT_I9601` | No policy validation report found | `info` | n/a | +| `CDK_TOOLKIT_I9600` | Validate passed with no problems | `info` | {@link ValidateResult} | +| `CDK_TOOLKIT_E9600` | Validate found problems | `error` | {@link ValidateResult} | +| `CDK_TOOLKIT_I9601` | No validation plugins configured | `info` | n/a | | `CDK_TOOLKIT_I0100` | Notices decoration (the header or footer of a list of notices) | `info` | n/a | | `CDK_TOOLKIT_W0101` | A notice that is marked as a warning | `warn` | n/a | | `CDK_TOOLKIT_E0101` | A notice that is marked as an error | `error` | n/a | diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/io/private/messages.ts b/packages/@aws-cdk/toolkit-lib/lib/api/io/private/messages.ts index a63a51378..9b33e4347 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/io/private/messages.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/io/private/messages.ts @@ -508,19 +508,19 @@ export const IO = { // validate (96xx) CDK_TOOLKIT_I9600: make.info({ code: 'CDK_TOOLKIT_I9600', - description: 'Policy validation passed', + description: 'Validate passed with no problems', interface: 'ValidateResult', }), CDK_TOOLKIT_E9600: make.error({ code: 'CDK_TOOLKIT_E9600', - description: 'Policy validation failed', + description: 'Validate found problems', interface: 'ValidateResult', }), CDK_TOOLKIT_I9601: make.info({ code: 'CDK_TOOLKIT_I9601', - description: 'No policy validation report found', + description: 'No validation plugins configured', }), // Notices diff --git a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts index 2ad127d4f..2be20f742 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts @@ -670,7 +670,7 @@ export class Toolkit extends CloudAssemblySourceBuilder { conclusion: 'success', pluginReports: [], }; - await ioHelper.notify(IO.CDK_TOOLKIT_I9601.msg('No validation plugins configured. Add a plugin to your CDK app to enable policy validation.')); + await ioHelper.notify(IO.CDK_TOOLKIT_I9601.msg('No validation plugins configured. Add a plugin to your CDK app to enable validation.')); return result; } @@ -687,9 +687,9 @@ export class Toolkit extends CloudAssemblySourceBuilder { }; if (conclusion === 'failure') { - await ioHelper.notify(IO.CDK_TOOLKIT_E9600.msg('❌ Validation found policy violations', result)); + await ioHelper.notify(IO.CDK_TOOLKIT_E9600.msg('❌ cdk validate found problems', result)); } else { - await ioHelper.notify(IO.CDK_TOOLKIT_I9600.msg('✅ All policy checks passed', result)); + await ioHelper.notify(IO.CDK_TOOLKIT_I9600.msg('✅ No problems found', result)); } return result; diff --git a/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts b/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts index 3fdbd0d77..c61ca90e2 100644 --- a/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts @@ -48,14 +48,14 @@ describe('validate', () => { const cx = await cdkOutFixture(toolkit, 'stack-with-validation-report'); await toolkit.validate(cx); - ioHost.expectMessage({ containing: 'Validation found policy violations', level: 'error' }); + ioHost.expectMessage({ containing: 'cdk validate found problems', level: 'error' }); }); test('emits info IO message on success', async () => { const cx = await cdkOutFixture(toolkit, 'stack-with-passing-validation'); await toolkit.validate(cx); - ioHost.expectMessage({ containing: 'All policy checks passed', level: 'info' }); + ioHost.expectMessage({ containing: 'No problems found', level: 'info' }); }); test('can invoke without options', async () => { From 152e0264346b55e26ef15565ec74f31a532de382 Mon Sep 17 00:00:00 2001 From: Kaizen Conroy Date: Wed, 20 May 2026 13:36:13 -0400 Subject: [PATCH 12/13] fix(toolkit): add version field to validation report test fixtures The policy-validation-report.json fixtures were missing the required `version` field, causing Manifest.loadValidationReport to throw "Invalid semver string: undefined" during schema validation. --- .../cdk.out/policy-validation-report.json | 1 + .../cdk.out/policy-validation-report.json | 1 + .../cdk.out/policy-validation-report.json | 1 + 3 files changed, 3 insertions(+) diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/policy-validation-report.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/policy-validation-report.json index bb249828d..f42957765 100644 --- a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/policy-validation-report.json +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-no-title-validation/cdk.out/policy-validation-report.json @@ -1,4 +1,5 @@ { + "version": "1.0.0", "pluginReports": [ { "pluginName": "TestPlugin", diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/policy-validation-report.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/policy-validation-report.json index 4b3ff4935..09675e364 100644 --- a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/policy-validation-report.json +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-passing-validation/cdk.out/policy-validation-report.json @@ -1,4 +1,5 @@ { + "version": "1.0.0", "title": "Validation Report", "pluginReports": [ { diff --git a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/policy-validation-report.json b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/policy-validation-report.json index e17c0fc77..726e8f9cc 100644 --- a/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/policy-validation-report.json +++ b/packages/@aws-cdk/toolkit-lib/test/_fixtures/stack-with-validation-report/cdk.out/policy-validation-report.json @@ -1,4 +1,5 @@ { + "version": "1.0.0", "title": "Validation Report", "pluginReports": [ { From 85e1659105dca5be1fd71d78885c140505426cf6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 17:50:06 +0000 Subject: [PATCH 13/13] chore: self mutation Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts b/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts index c61ca90e2..38c9ad239 100644 --- a/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/actions/validate.test.ts @@ -95,7 +95,6 @@ describe('validate', () => { expect(result.pluginReports[1].pluginVersion).toBeUndefined(); }); - test('parses stack traces correctly', async () => { const cx = await cdkOutFixture(toolkit, 'stack-with-validation-report'); const result = await toolkit.validate(cx);