@aws/cloudformation-validate 1.8.0-beta → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,15 @@
1
1
  # CloudFormation Validate for Node.js
2
2
 
3
- Validate AWS CloudFormation templates from JavaScript or TypeScript and catch schema violations, security risks, and
4
- best-practice findings before deployment - in your editor, build, or CI.
3
+ Validate AWS CloudFormation templates from JavaScript or TypeScript and catch schema violations, semantic errors,
4
+ security risks, and best-practice findings before deployment - in your editor, build, service, or CI.
5
5
 
6
- - **Offline** - all rules and resource schemas are bundled.
7
- - **Fast** - sub-second validation per template.
6
+ - **Offline** - all rules and CloudFormation resource schemas are bundled; nothing is fetched at runtime and no AWS
7
+ credentials are needed.
8
+ - **Fast** - engines and schemas compile once and are reused across validations; typical templates validate in under a
9
+ second.
10
+ - **Self-contained** - the WebAssembly module is bundled in the package; there are no native dependencies.
11
+
12
+ All types are exported from the `@aws/cloudformation-validate` package.
8
13
 
9
14
  ## Installation
10
15
 
@@ -14,6 +19,8 @@ Available on [npm](https://www.npmjs.com/package/@aws/cloudformation-validate) a
14
19
  npm install @aws/cloudformation-validate
15
20
  ```
16
21
 
22
+ Requires Node.js 20 or later. The package has no runtime dependencies.
23
+
17
24
  ## Quick start
18
25
 
19
26
  Engines, models, and validators hold off-heap memory - call `.free()` when done with each object:
@@ -23,7 +30,7 @@ import { RegoEngine, TemplateFile } from "@aws/cloudformation-validate";
23
30
 
24
31
  const engine = new RegoEngine();
25
32
  try {
26
- const report = engine.validateStandard(new TemplateFile("template.yaml"));
33
+ const report = engine.validateTemplate(new TemplateFile("template.yaml"));
27
34
  for (const d of report.diagnostics) {
28
35
  console.log(`[${d.severity}] ${d.ruleId}: ${d.message}`);
29
36
  }
@@ -32,35 +39,54 @@ try {
32
39
  }
33
40
  ```
34
41
 
35
- Each diagnostic identifies the rule, severity, affected resource and property, and source location - see
36
- [StandardDiagnostic](#standarddiagnostic). A complete, runnable project is in
42
+ Each diagnostic identifies the rule, severity, affected entity and property, and source location - see
43
+ [Diagnostic](#diagnostic). A complete, runnable project is in
37
44
  [examples](https://github.com/aws-cloudformation/cloudformation-validate/tree/main/src/bindings-wasm/examples).
38
45
 
46
+ Engines are expensive to construct (rules compile once) and cheap to reuse - create one engine and validate many
47
+ templates. Every fallible call throws on failure - the thrown value is the error message string from the validation
48
+ core, so handle it with `catch (error) { String(error) }`; internal panics are caught at the WASM boundary and surface
49
+ the same way, never a process abort. `version()` returns the version of the bundled validation core.
50
+
51
+ A template is passed as a `TemplateFile`, which wraps a filesystem path: the engine reads the bytes and uses the path
52
+ for diagnostic source locations.
53
+
39
54
  ## Engine
40
55
 
41
56
  `RegoEngine` and `CelEngine` both implement the `Engine` interface and are interchangeable - they produce identical
42
- diagnostics for the same template and config.
57
+ diagnostics for the same template and config. `CompositeEngine` implements the same interface and layers custom Rego,
58
+ CEL, and Guard rules on top of the built-in rules - see [CompositeEngine](#compositeengine).
43
59
 
44
60
  ### `Engine` interface
45
61
 
46
- | Method | Returns | Description |
47
- |---------------------------------------|------------------|------------------------------------------------------------------------------------------------------------------|
48
- | `validateStandard(template, config?)` | `StandardReport` | Validates and returns diagnostics without extended context |
49
- | `validateDetailed(template, config?)` | `DetailedReport` | Validates and returns diagnostics with documentation URLs, rule descriptions, phase tags, and `ViolationContext` |
50
- | `listRules()` | `RuleInfo[]` | Returns metadata for every built-in and loaded custom rule |
51
- | `engineName()` | `string` | `"rego"` or `"cel"` |
52
- | `free()` | `void` | Releases the engine's off-heap memory |
62
+ ```typescript
63
+ interface Engine {
64
+ validateTemplate(template: TemplateFile, config?: ValidateConfig): ValidationReport;
65
+ validateAwsCliCommand(request: AwsCliCommand): AwsCliCommandValidation;
66
+ listRules(): RuleInfo[];
67
+ engineName(): string;
68
+ free(): void;
69
+ }
70
+ ```
71
+
72
+ | Method | Returns | Description |
73
+ |---------------------------------------|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
74
+ | `validateTemplate(template, config?)` | `ValidationReport` | Validates the template and returns a report. `config.detailLevel` (default `DETAILED`) selects how much per-diagnostic context is populated: `DETAILED` adds documentation URLs, rule descriptions, phase tags, and `ViolationContext`; `STANDARD` leaves those enrichment fields absent |
75
+ | `validateAwsCliCommand(request)` | `AwsCliCommandValidation` | Models an AWS CLI command as CloudFormation resource state and validates it - see [AWS CLI command validation](#aws-cli-command-validation) |
76
+ | `listRules()` | `RuleInfo[]` | Returns metadata for every built-in and loaded custom rule |
77
+ | `engineName()` | `string` | `"rego"`, `"cel"`, or `"composite"` |
78
+ | `free()` | `void` | Releases the engine's off-heap memory; the engine must not be used afterwards |
53
79
 
54
80
  ### `EngineConfig`
55
81
 
56
- Passed to the constructor. All fields are optional; omitted rule arrays are empty and an omitted
82
+ Passed to the constructor. All fields are optional: the rule lists default to empty and an omitted
57
83
  `schemaValidatorConfig` uses only the bundled schemas.
58
84
 
59
85
  ```typescript
60
86
  interface EngineConfig {
61
- customRules?: RuleSource[]; // engine-native rules (Rego for RegoEngine, CEL for CelEngine)
62
- guardRules?: RuleSource[]; // CloudFormation Guard DSL rules - translated internally
63
- schemaValidatorConfig?: SchemaValidatorConfig; // schema validation and overlay configuration
87
+ customRules?: RuleSource[]; // engine-native rules (Rego for RegoEngine, CEL for CelEngine)
88
+ guardRules?: RuleSource[]; // CloudFormation Guard DSL rules - evaluated by the Guard evaluator
89
+ schemaValidatorConfig?: SchemaValidatorConfig; // additional resource provider schemas
64
90
  }
65
91
 
66
92
  interface SchemaValidatorConfig {
@@ -70,14 +96,6 @@ interface SchemaValidatorConfig {
70
96
  type RuleSource = ExternalRuleSource | RuleFile;
71
97
  type SchemaSource = AdditionalSchemaSource | SchemaFile;
72
98
 
73
- class RuleFile {
74
- constructor(path: string); // rule file read from disk; the path becomes the rule source name
75
- }
76
-
77
- class SchemaFile {
78
- constructor(path: string, typeName?: string); // schema file; typeName defaults to the value inside the JSON
79
- }
80
-
81
99
  interface ExternalRuleSource {
82
100
  name: string; // identifier shown in diagnostics (e.g. file path)
83
101
  content: string; // full rule source text
@@ -87,17 +105,35 @@ interface AdditionalSchemaSource {
87
105
  typeName?: string; // omit to use the typeName inside the schema JSON
88
106
  schema: string; // complete resource provider schema JSON
89
107
  }
90
- ```
91
108
 
92
- Pass a `RuleFile` to load a rule from disk - the same pattern as `TemplateFile` for templates - or an
93
- `ExternalRuleSource` when you already have the rule text in memory. `SchemaFile` does the same for an additional
94
- resource provider schema. Its optional constructor `typeName` may be omitted when the schema JSON contains its own
95
- `typeName`.
109
+ class RuleFile {
110
+ constructor(path: string); // rule file read from disk; the path becomes the rule source name
111
+ }
96
112
 
97
- The generated `AdditionalSchemaSource` record exposes `typeName` as an optional field. Omit it (or leave the
98
- `SchemaFile` constructor argument unset) for an in-memory schema whose JSON already contains its own `typeName`.
113
+ class SchemaFile {
114
+ constructor(path: string, typeName?: string); // schema file; typeName defaults to the value inside the JSON
115
+ }
116
+ ```
117
+
118
+ | Field | Default | Description |
119
+ |-------------------------|-------------|-----------------------------------------------------------------------------------------------|
120
+ | `customRules` | `[]` | Engine-native rules: Rego source for `RegoEngine`, CEL JSON for `CelEngine` |
121
+ | `guardRules` | `[]` | CloudFormation Guard DSL rules, evaluated by the Guard evaluator identically in every engine |
122
+ | `schemaValidatorConfig` | `undefined` | Optional `SchemaValidatorConfig` whose `additionalSchemas` are merged over the bundled schemas |
123
+
124
+ Each rule is an `ExternalRuleSource` - `name` identifies the rule in diagnostics and `content` is the full rule source
125
+ text. Pass a `RuleFile` to load one from disk (the same pattern as `TemplateFile` for templates), or an
126
+ `ExternalRuleSource` when you already have the rule text in memory. Each additional schema is an
127
+ `AdditionalSchemaSource` - a complete resource provider schema JSON plus an optional `typeName` that may be omitted when
128
+ the schema JSON contains its own `typeName`; `SchemaFile` loads one from disk. Additional schemas extend the bundled
129
+ schemas or register resource types CloudFormation has not published yet; a malformed, contradictory, or unsupported
130
+ schema fails engine construction rather than silently weakening validation. Guard rules are evaluated by the
131
+ CloudFormation Guard evaluator itself against the template as written, so every engine reports exactly what `cfn-guard
132
+ validate` reports; a Guard file that does not parse also fails engine construction. The two forms can be mixed freely:
99
133
 
100
134
  ```typescript
135
+ import { CelEngine, RuleFile, SchemaFile } from "@aws/cloudformation-validate";
136
+
101
137
  const engine = new CelEngine({
102
138
  customRules: [new RuleFile("rules/s3_encryption.json")],
103
139
  guardRules: [new RuleFile("rules/compliance.guard")],
@@ -107,15 +143,66 @@ const engine = new CelEngine({
107
143
  });
108
144
  ```
109
145
 
146
+ See [Custom Rules](../CUSTOM_RULES.md) for the Rego, CEL, and Guard rule formats and
147
+ [Additional Resource Provider Schemas](../validation-engine/API.md#additional-resource-provider-schemas) for the schema
148
+ merge model.
149
+
150
+ ### `CompositeEngine`
151
+
152
+ `CompositeEngine` implements the same `Engine` interface but takes a `CompositeEngineConfig`. It evaluates every
153
+ built-in rule with a fixed built-in CEL evaluator and layers the caller-supplied custom rules on top: custom CEL and
154
+ Guard rules run alongside that built-in engine, while custom Rego rules run in a separate external engine that is
155
+ constructed only when Rego rules are supplied. With no custom rules it produces the same built-in diagnostics as
156
+ `RegoEngine` and `CelEngine`, and `engineName()` returns `"composite"`. Because the composite fixes which engine owns
157
+ the built-ins, the config has no `customRules` field - it carries only the custom rules layered on top:
158
+
159
+ ```typescript
160
+ interface CompositeEngineConfig {
161
+ regoRules?: RuleSource[]; // custom Rego rules, run by the external engine
162
+ celRules?: RuleSource[]; // custom CEL rules, run by the built-in engine
163
+ guardRules?: RuleSource[]; // CloudFormation Guard DSL rules, evaluated alongside the built-in engine
164
+ schemaValidatorConfig?: SchemaValidatorConfig; // additional resource provider schemas, observed by both inner engines
165
+ }
166
+ ```
167
+
168
+ | Field | Default | Description |
169
+ |-------------------------|-------------|--------------------------------------------------------------------------------------------------|
170
+ | `regoRules` | `[]` | Custom Rego rules layered on top of the built-in rules, run by the external engine |
171
+ | `celRules` | `[]` | Custom CEL rules layered on top of the built-in rules, run by the built-in engine |
172
+ | `guardRules` | `[]` | CloudFormation Guard DSL rules layered on top of the built-in rules, evaluated alongside the built-in engine |
173
+ | `schemaValidatorConfig` | `undefined` | Optional `SchemaValidatorConfig`, observed by both inner engines |
174
+
175
+ ```typescript
176
+ import { CompositeEngine, RuleFile, TemplateFile } from "@aws/cloudformation-validate";
177
+
178
+ const engine = new CompositeEngine({
179
+ regoRules: [new RuleFile("rules/s3_naming.rego")],
180
+ guardRules: [new RuleFile("rules/compliance.guard")],
181
+ });
182
+ try {
183
+ const report = engine.validateTemplate(new TemplateFile("template.yaml"));
184
+ } finally {
185
+ engine.free();
186
+ }
187
+ ```
188
+
110
189
  ## ValidateConfig
111
190
 
112
- Controls filtering, severity, parameter overrides, and behavior. All fields optional - omitting the config or passing
113
- `{}` uses defaults.
191
+ Controls filtering, detail, severity, parameter overrides, and behavior for one validation call. All fields have
192
+ defaults - omitting the config or passing `{}` uses them.
193
+
194
+ ```typescript
195
+ const report = engine.validateTemplate(new TemplateFile("template.yaml"), {
196
+ exclude: { ids: ["I1002"] },
197
+ severityLevel: "WARN",
198
+ });
199
+ ```
114
200
 
115
201
  ```typescript
116
202
  interface ValidateConfig {
117
203
  include?: RuleFilterConfig;
118
204
  exclude?: RuleFilterConfig;
205
+ detailLevel?: DetailLevel;
119
206
  severityLevel?: Severity;
120
207
  parameterOverrides?: Record<string, string>;
121
208
  pseudoParameterOverrides?: PseudoParameterOverrides;
@@ -124,15 +211,16 @@ interface ValidateConfig {
124
211
  }
125
212
  ```
126
213
 
127
- | Field | Default | Description |
128
- |----------------------------|-------------------------|--------------------------------------------------------------------------------------------------------------------------|
129
- | `include` | `{}` (all rules) | When set, only matching rules produce diagnostics. Empty means include everything. |
130
- | `exclude` | `{}` (nothing excluded) | Matching rules are suppressed. Applied after `include`. |
131
- | `severityLevel` | `"INFO"` | Minimum severity threshold. Diagnostics below this level are dropped. Values: `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`. |
132
- | `parameterOverrides` | `{}` | Override template parameter values during resolution. Keys are parameter logical IDs. |
133
- | `pseudoParameterOverrides` | all `undefined` | Override CloudFormation pseudo-parameters (`AWS::AccountId`, `AWS::Region`, etc.). |
134
- | `strict` | `false` | When `true`, `WARN`-severity diagnostics are upgraded to `ERROR`. |
135
- | `disableBuiltinRules` | `false` | When `true`, all built-in rules (schema validation, Step Functions, engine rules) are skipped; only custom and Guard rules are evaluated. |
214
+ | Field | Default | Description |
215
+ |----------------------------|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
216
+ | `include` | `{}` (all rules) | When set, only matching rules produce diagnostics. Empty means include everything. |
217
+ | `exclude` | `{}` (nothing excluded) | Matching rules are suppressed. Applied after `include`. |
218
+ | `detailLevel` | `"DETAILED"` | Per-diagnostic context. `"DETAILED"` populates documentation URLs, rule descriptions, phase tags, and `ViolationContext`; `"STANDARD"` leaves those enrichment fields absent. |
219
+ | `severityLevel` | `"INFO"` | Minimum severity threshold. Diagnostics below this level are dropped. Values: `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`. |
220
+ | `parameterOverrides` | `{}` | Override template parameter values during resolution. Keys are parameter logical IDs. |
221
+ | `pseudoParameterOverrides` | all `undefined` | Override CloudFormation pseudo-parameters (`AWS::AccountId`, `AWS::Region`, etc.). |
222
+ | `strict` | `false` | When `true`, `WARN`-severity diagnostics are upgraded to `ERROR`. |
223
+ | `disableBuiltinRules` | `false` | When `true`, all built-in rules (schema validation, Step Functions, engine rules) are skipped; only custom and Guard rules are evaluated. |
136
224
 
137
225
  ### RuleFilterConfig
138
226
 
@@ -140,14 +228,14 @@ Both `include` and `exclude` use this structure. All fields are additive - a rul
140
228
 
141
229
  ```typescript
142
230
  interface RuleFilterConfig {
143
- ids?: string[]; // exact rule IDs, e.g. ["E3012", "W3010"]
144
- categories?: string[]; // category names, e.g. ["security", "best_practices"]
145
- idRanges?: IdRange[]; // numeric ranges, e.g. { prefix: "E", start: 3000, end: 3099 }
146
- idPatterns?: string[]; // regex patterns matched against rule IDs
147
- resourceIds?: ResourceIdFilter[]; // a rule (or every rule) on a logical resource ID
148
- logicalIds?: LogicalIdFilter[]; // a rule (or every rule) on a named template entity
231
+ ids?: string[]; // exact rule IDs, e.g. ["E3012", "W3010"]
232
+ categories?: string[]; // category names, e.g. ["security", "best_practices"]
233
+ idRanges?: IdRange[]; // numeric ranges, e.g. { prefix: "E", start: 3000, end: 3099 }
234
+ idPatterns?: string[]; // regex patterns matched against rule IDs
235
+ resourceIds?: ResourceIdFilter[]; // a rule (or every rule) on a logical resource ID
236
+ logicalIds?: LogicalIdFilter[]; // a rule (or every rule) on a named template entity
149
237
  resourceTypes?: ResourceTypeFilter[]; // a rule (or every rule) on a resource type
150
- services?: ServiceFilter[]; // a rule (or every rule) on a service, e.g. "AWS::AutoScaling"
238
+ services?: ServiceFilter[]; // a rule (or every rule) on a service, e.g. "AWS::AutoScaling"
151
239
  }
152
240
 
153
241
  // resourceIds / logicalIds / resourceTypes / services each carry an optional ruleId:
@@ -168,7 +256,7 @@ the same value). An optional `entityType` scopes a `LogicalIdFilter` to entities
168
256
 
169
257
  ### PseudoParameterOverrides
170
258
 
171
- Override CloudFormation pseudo-parameters used during intrinsic function resolution. All fields optional - when
259
+ Override CloudFormation pseudo-parameters used during intrinsic function resolution. All fields are optional - when
172
260
  `undefined`, the engine uses built-in defaults (e.g. region defaults to `us-east-1`).
173
261
 
174
262
  ```typescript
@@ -183,14 +271,6 @@ interface PseudoParameterOverrides {
183
271
  }
184
272
  ```
185
273
 
186
- ## TemplateFile
187
-
188
- Wraps a filesystem path. Engines read the file bytes internally.
189
-
190
- ```typescript
191
- const template = new TemplateFile("path/to/template.yaml");
192
- ```
193
-
194
274
  ## TemplateModel
195
275
 
196
276
  Parses a template into the resolved `SemanticModel` for direct inspection - the same model the engines evaluate rules
@@ -207,58 +287,135 @@ const model = new TemplateModel(new TemplateFile("template.yaml"));
207
287
  | `outputs()` | `Record<string, ResolvedOutput>` | Outputs with resolved values and export names |
208
288
  | `conditions()` | `string[]` | Condition names defined in the template |
209
289
  | `transforms()` | `string[]` | Transform declarations (e.g. `AWS::Serverless-2016-10-31`) |
210
- | `formatVersion()` | `string \ undefined` | `AWSTemplateFormatVersion` value |
211
- | `description()` | `string \ undefined` | Template description |
290
+ | `formatVersion()` | `string \| undefined` | `AWSTemplateFormatVersion` value |
291
+ | `description()` | `string \| undefined` | Template description |
212
292
  | `toDiagnosticModel()` | `DiagnosticModel` | Full diagnostic model including reference graph, condition implications, and resolution sources |
213
- | `sourceLocation(path)` | `SourceSpan \ null` | Source line/column span for a JSON path (e.g. `Resources/MyBucket/Properties/BucketName`) |
214
- | `free()` | `void` | Releases WASM memory |
293
+ | `sourceLocation(path)` | `SourceSpan \| null` | Source line/column span for a JSON path (e.g. `Resources/MyBucket/Properties/BucketName`) |
294
+ | `free()` | `void` | Releases the model's off-heap memory; the model must not be used afterwards |
215
295
 
216
296
  ## SchemaValidator
217
297
 
218
- Runs schema validation independently from the rule engines. Checks each resource against compiled CloudFormation
219
- provider schemas and produces `FATAL`-severity diagnostics for structural violations.
298
+ Runs schema validation independently from the rule engines. Checks each resource against the compiled CloudFormation
299
+ provider schemas and produces `FATAL`-severity diagnostics for structural violations. The optional constructor argument
300
+ is the same `SchemaValidatorConfig` accepted by `EngineConfig`; omitting it uses only the bundled schemas.
220
301
 
221
302
  ```typescript
222
303
  const validator = new SchemaValidator();
223
- const diagnostics = validator.validate(new TemplateFile("template.yaml"), "us-east-1");
224
- validator.free();
304
+ try {
305
+ const diagnostics = validator.validate(new TemplateFile("template.yaml"), "us-east-1");
306
+ } finally {
307
+ validator.free();
308
+ }
225
309
  ```
226
310
 
227
- | Method | Returns | Description |
228
- |-------------------------------|------------------------|---------------------------------------------------------|
229
- | `validate(template, region?)` | `StandardDiagnostic[]` | Schema diagnostics. `region` defaults to `"us-east-1"`. |
230
- | `listRules()` | `RuleInfo[]` | Schema rule metadata |
231
- | `schemaCount()` | `number` | Number of compiled provider schemas |
232
- | `free()` | `void` | Releases WASM memory |
311
+ | Method | Returns | Description |
312
+ |--------------------------------|-------------------|------------------------------------------------------------------------------------------------------------------|
313
+ | `new SchemaValidator(config?)` | `SchemaValidator` | Constructs a validator; an omitted `SchemaValidatorConfig` uses only the bundled schemas |
314
+ | `validate(template, region?)` | `Diagnostic[]` | Schema diagnostics at `STANDARD` detail - the enrichment fields are absent. `region` defaults to `"us-east-1"`. |
315
+ | `listRules()` | `RuleInfo[]` | Schema rule metadata |
316
+ | `schemaCount()` | `number` | Number of compiled provider schemas |
317
+ | `free()` | `void` | Releases the validator's off-heap memory; the validator must not be used afterwards |
318
+
319
+ ## AWS CLI command validation
320
+
321
+ `validateAwsCliCommand` models an AWS CLI (or SDK) API call as CloudFormation resource state and validates it offline
322
+ before it is sent. It classifies the operation, maps it to a CloudFormation resource type through a closed, generated
323
+ adapter catalog, synthesizes a template from the supplied parameters, and runs the normal template pipeline on it. A
324
+ `TemplateBody` parameter of a CloudFormation operation is validated as-is. Any request that cannot be modeled exactly -
325
+ an unregistered operation, a parameter without a lossless property mapping, or a value outside a CloudFormation
326
+ constraint the API itself does not enforce - is skipped with a reason, never guessed.
327
+
328
+ ```typescript
329
+ import { AwsCliCommand, RegoEngine } from "@aws/cloudformation-validate";
330
+
331
+ const engine = new RegoEngine();
332
+ try {
333
+ const request = new AwsCliCommand("s3", "CreateBucket", { Bucket: "example-bucket" });
334
+ const validation = engine.validateAwsCliCommand(request);
335
+ if (validation.status === "VALIDATED") {
336
+ for (const d of validation.report!.diagnostics) {
337
+ console.log(`[${d.severity}] ${d.ruleId}: ${d.message}`);
338
+ }
339
+ } else {
340
+ console.log(`skipped (${validation.operationKind}): ${validation.reason}`);
341
+ }
342
+ } finally {
343
+ engine.free();
344
+ }
345
+ ```
346
+
347
+ ```typescript
348
+ class AwsCliCommand {
349
+ constructor(
350
+ serviceName: string, // canonical botocore service name, e.g. "s3" or "cloudformation"
351
+ operationName: string, // API operation name, e.g. "CreateBucket"
352
+ parameters: Record<string, unknown>, // request parameters (a plain object with string keys)
353
+ options?: AwsCliCommandOptions,
354
+ );
355
+ }
356
+
357
+ interface AwsCliCommandOptions {
358
+ servicePrefix?: string; // signing prefix; context only
359
+ httpMethod?: string; // classification hint ("GET"/"HEAD"/"DELETE") for unrecognized verbs
360
+ isReadOnly?: boolean; // true classifies the operation as READ_ONLY
361
+ }
362
+ ```
363
+
364
+ - `serviceName` is matched case-insensitively. Signing names, endpoint aliases, and ARN prefixes are never resolved;
365
+ translate an SDK's service identity first.
366
+ - `parameters` accepts nested plain objects and arrays, strings, numbers, `bigint`, booleans, `null`, `Uint8Array`,
367
+ and `Date` (serialized as ISO 8601). Any other value is carried as an explicit unsupported marker, and because
368
+ synthesis is all-or-nothing the request is then skipped with a reason naming the offending parameter - no parameter
369
+ is ever silently dropped.
370
+
371
+ The result is an `AwsCliCommandValidation`:
372
+
373
+ | Field | Description |
374
+ |------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
375
+ | `operationKind` | `AwsCliOperationKind`: `"READ_ONLY"`, `"CLOUD_FORMATION_CREATE"`, `"CLOUD_FORMATION_UPDATE"`, `"CLOUD_FORMATION_DELETE"`, `"DATA_PLANE_MUTATION"`, or `"UNMAPPED_MUTATION"` |
376
+ | `status` | `AwsCliCommandValidationStatus`: `"VALIDATED"` when the modeled template ran through the pipeline, `"SKIPPED"` otherwise |
377
+ | `templateSource` | `AwsCliTemplateSource \| null`: `"TEMPLATE_BODY"`, `"CLOUD_CONTROL_DESIRED_STATE"`, `"SYNTHESIZED_CREATE"`, or `"SYNTHESIZED_UPDATE"`; `null` when skipped |
378
+ | `resourceTypes` | `string[]` - CloudFormation resource types the operation maps to |
379
+ | `reason` | `string` - why the request was validated or skipped |
380
+ | `report` | `ValidationReport \| null` - present when `"VALIDATED"`. The configuration is fixed: `STANDARD` detail level and a `WARN` severity floor |
381
+ | `template` | `Uint8Array \| null` - the exact template bytes that were validated (the caller's `TemplateBody` unchanged, or the synthesized JSON); `null` when skipped |
382
+
383
+ The full contract - the adapter catalog, all-or-nothing mapping, and which rules are dropped for synthesized state -
384
+ is documented in [validation-engine/API.md](../validation-engine/API.md#validating-an-aws-cli-command).
233
385
 
234
386
  ## Report Types
235
387
 
236
- ### StandardReport / DetailedReport
388
+ ### ValidationReport
389
+
390
+ `validateTemplate` always returns a `ValidationReport` - a template syntax failure is returned as a report with
391
+ status `"ERROR"` and an `F1101` diagnostic; only infrastructure or engine failures throw:
237
392
 
238
393
  ```typescript
239
- interface StandardReport {
394
+ interface ValidationReport {
240
395
  filePath: string;
241
396
  status: "OK" | "ANALYSIS_INCOMPLETE" | "ERROR"; // ERROR is a pipeline failure; ANALYSIS_INCOMPLETE may omit findings
242
397
  version: string;
243
398
  metadata: ReportMetadata;
244
399
  performance: PerformanceMetrics;
245
- diagnostics: StandardDiagnostic[];
400
+ diagnostics: Diagnostic[];
246
401
  }
247
402
  ```
248
403
 
249
- `DetailedReport` has the same structure but its diagnostics include additional fields: `documentationUrl`,
250
- `ruleDescription`, `phase` (`PARSE` | `SCHEMA` | `LINT`), and `context` (`ViolationContext` with
251
- `actualValue`, `expectedConstraint`, `resolutionSource`, etc.).
404
+ Every finding is a `Diagnostic` (see [Diagnostic](#diagnostic)). Its enrichment fields - `documentationUrl`,
405
+ `ruleDescription`, `phase` (`PARSE` | `SCHEMA` | `LINT`), and `context` (`ViolationContext` with `actualValue`,
406
+ `expectedConstraint`, `resolutionSource`, etc.) - are populated only at `detailLevel` `"DETAILED"` (the default);
407
+ validating at `"STANDARD"` leaves them absent, keeping the base diagnostic fields.
252
408
 
253
- Each optional budget-exhaustion record retains a stable machine-readable kind and also includes a
254
- human-readable description sentence, the numeric limit, and whether that specific exhaustion makes analysis
255
- incomplete. `requiredPropertyCombinations` is context-only, so its `analysisIncomplete` value is `false` and the
256
- report can remain `"OK"`.
409
+ `metadata` carries the summary counts, the number of suppressed diagnostics, the resources scanned and rules
410
+ evaluated, the strict flag and severity threshold used, and optional budget-exhaustion records. Each budget-exhaustion
411
+ record retains a stable machine-readable kind and also includes a human-readable description sentence, the numeric
412
+ limit, and whether that specific exhaustion makes analysis incomplete. `requiredPropertyCombinations` is context-only,
413
+ so its `analysisIncomplete` value is `false` and the report can remain `"OK"`.
257
414
 
258
- ### StandardDiagnostic
415
+ ### Diagnostic
259
416
 
260
417
  ```typescript
261
- interface StandardDiagnostic {
418
+ interface Diagnostic {
262
419
  ruleId: string; // e.g. "E3012", "F1001", "W3010"
263
420
  severity: Severity; // "FATAL" | "ERROR" | "WARN" | "INFO" | "DEBUG"
264
421
  message: string;
@@ -273,6 +430,11 @@ interface StandardDiagnostic {
273
430
  endColumn?: number;
274
431
  relatedResources?: RelatedResource[];
275
432
  conditionScenario?: Record<string, boolean>; // condition truth assignment that triggers this diagnostic
433
+ // Enrichment fields: populated at detailLevel "DETAILED" (the default), absent at "STANDARD".
434
+ documentationUrl?: string;
435
+ ruleDescription?: string;
436
+ phase?: "PARSE" | "SCHEMA" | "LINT"; // pipeline stage that produced the finding
437
+ context?: ViolationContext; // actualValue, expectedConstraint, resolutionSource, etc.
276
438
  }
277
439
 
278
440
  // The named template entity a diagnostic is attributed to. The entity type is the
@@ -286,3 +448,6 @@ interface Entity {
286
448
  type EntityType = "Resource" | "Parameter" | "Output" | "Mapping" | "Metadata"
287
449
  | "Rule" | "Condition" | "Transform" | "FormatVersion" | "Description";
288
450
  ```
451
+
452
+ `Severity`, `RuleOrigin`, and `DetailLevel` are string-literal union types (`"WARN"`, `"GUARD"`, `"STANDARD"`, ...), as
453
+ is the report `status` (`"OK"`, `"ANALYSIS_INCOMPLETE"`, `"ERROR"`).