@intentius/chant-lexicon-aws 0.46.0 → 0.50.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.
Files changed (183) hide show
  1. package/dist/api/read-client.d.ts +10 -2
  2. package/dist/api/read-client.d.ts.map +1 -1
  3. package/dist/codegen/generate-lexicon.d.ts +5 -0
  4. package/dist/codegen/generate-lexicon.d.ts.map +1 -1
  5. package/dist/components/host-delivery.d.ts +4 -3
  6. package/dist/components/host-delivery.d.ts.map +1 -1
  7. package/dist/composites/agentcore-agent.d.ts +26 -1
  8. package/dist/composites/agentcore-agent.d.ts.map +1 -1
  9. package/dist/composites/bucket-deployment.d.ts +82 -0
  10. package/dist/composites/bucket-deployment.d.ts.map +1 -0
  11. package/dist/composites/dynamodb-table.d.ts +45 -0
  12. package/dist/composites/dynamodb-table.d.ts.map +1 -0
  13. package/dist/composites/ec2-instance-bundle.d.ts +61 -0
  14. package/dist/composites/ec2-instance-bundle.d.ts.map +1 -0
  15. package/dist/composites/ecr-repository.d.ts +49 -0
  16. package/dist/composites/ecr-repository.d.ts.map +1 -0
  17. package/dist/composites/fargate-service.d.ts +16 -0
  18. package/dist/composites/fargate-service.d.ts.map +1 -1
  19. package/dist/composites/index.d.ts +16 -2
  20. package/dist/composites/index.d.ts.map +1 -1
  21. package/dist/composites/monitoring-stack.d.ts +99 -0
  22. package/dist/composites/monitoring-stack.d.ts.map +1 -0
  23. package/dist/composites/nlb-service.d.ts +48 -0
  24. package/dist/composites/nlb-service.d.ts.map +1 -0
  25. package/dist/composites/step-functions-workflow.d.ts +36 -0
  26. package/dist/composites/step-functions-workflow.d.ts.map +1 -0
  27. package/dist/deep-observe.d.ts +45 -0
  28. package/dist/deep-observe.d.ts.map +1 -1
  29. package/dist/deep-topology.d.ts +104 -0
  30. package/dist/deep-topology.d.ts.map +1 -0
  31. package/dist/effect-receipt-row.d.ts +92 -0
  32. package/dist/effect-receipt-row.d.ts.map +1 -0
  33. package/dist/index.d.ts +7 -2
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/integrity.json +14 -6
  36. package/dist/lint/audit-catalog.d.ts.map +1 -1
  37. package/dist/lint/post-synth/cf-refs.d.ts +17 -0
  38. package/dist/lint/post-synth/cf-refs.d.ts.map +1 -1
  39. package/dist/lint/post-synth/index.d.ts.map +1 -1
  40. package/dist/lint/post-synth/waw016.d.ts +10 -2
  41. package/dist/lint/post-synth/waw016.d.ts.map +1 -1
  42. package/dist/lint/post-synth/waw061.d.ts +19 -0
  43. package/dist/lint/post-synth/waw061.d.ts.map +1 -0
  44. package/dist/lint/post-synth/waw062.d.ts +19 -0
  45. package/dist/lint/post-synth/waw062.d.ts.map +1 -0
  46. package/dist/lint/post-synth/waw063.d.ts +35 -0
  47. package/dist/lint/post-synth/waw063.d.ts.map +1 -0
  48. package/dist/lint/post-synth/waw064.d.ts +17 -0
  49. package/dist/lint/post-synth/waw064.d.ts.map +1 -0
  50. package/dist/lint/post-synth/waw065.d.ts +23 -0
  51. package/dist/lint/post-synth/waw065.d.ts.map +1 -0
  52. package/dist/lint/post-synth/waw066.d.ts +25 -0
  53. package/dist/lint/post-synth/waw066.d.ts.map +1 -0
  54. package/dist/lint/post-synth/waw067.d.ts +24 -0
  55. package/dist/lint/post-synth/waw067.d.ts.map +1 -0
  56. package/dist/lint/post-synth/waw068.d.ts +20 -0
  57. package/dist/lint/post-synth/waw068.d.ts.map +1 -0
  58. package/dist/manifest.json +1 -1
  59. package/dist/meta.json +34 -0
  60. package/dist/okf/index.md +8 -0
  61. package/dist/okf/rules/WAW061.md +17 -0
  62. package/dist/okf/rules/WAW062.md +27 -0
  63. package/dist/okf/rules/WAW063.md +19 -0
  64. package/dist/okf/rules/WAW064.md +18 -0
  65. package/dist/okf/rules/WAW065.md +25 -0
  66. package/dist/okf/rules/WAW066.md +22 -0
  67. package/dist/okf/rules/WAW067.md +20 -0
  68. package/dist/okf/rules/WAW068.md +19 -0
  69. package/dist/okf/types/Action.md +1 -0
  70. package/dist/okf/types/AutoScalingGroup.md +1 -0
  71. package/dist/okf/types/Bucket.md +1 -0
  72. package/dist/okf/types/EC2Route.md +2 -0
  73. package/dist/okf/types/ECRRepository.md +1 -0
  74. package/dist/okf/types/Function.md +1 -0
  75. package/dist/okf/types/IamPolicy.md +1 -0
  76. package/dist/okf/types/InternetGateway.md +4 -0
  77. package/dist/okf/types/LaunchTemplate.md +1 -0
  78. package/dist/okf/types/LoadBalancer.md +1 -0
  79. package/dist/okf/types/ManagedPolicy.md +2 -0
  80. package/dist/okf/types/Map.md +5 -0
  81. package/dist/okf/types/NatGateway.md +4 -0
  82. package/dist/okf/types/Role.md +2 -0
  83. package/dist/okf/types/RouteTable.md +4 -0
  84. package/dist/okf/types/SecurityGroup.md +1 -0
  85. package/dist/okf/types/StateMachine.md +4 -0
  86. package/dist/okf/types/Subnet.md +6 -0
  87. package/dist/okf/types/SubnetRouteTableAssociation.md +5 -0
  88. package/dist/okf/types/Table.md +4 -0
  89. package/dist/okf/types/TransitGateway.md +6 -0
  90. package/dist/okf/types/TransitGatewayAttachment.md +4 -0
  91. package/dist/okf/types/TransitGatewayPeeringAttachment.md +4 -0
  92. package/dist/okf/types/TransitGatewayRoute.md +5 -0
  93. package/dist/okf/types/TransitGatewayRouteTable.md +4 -0
  94. package/dist/okf/types/TransitGatewayRouteTableAssociation.md +4 -0
  95. package/dist/okf/types/TransitGatewayRouteTablePropagation.md +4 -0
  96. package/dist/okf/types/TransitGatewayVpcAttachment.md +4 -0
  97. package/dist/okf/types/Type.md +8 -0
  98. package/dist/okf/types/VPNConnection.md +4 -0
  99. package/dist/okf/types/VPNGateway.md +4 -0
  100. package/dist/okf/types/Vpc.md +1 -0
  101. package/dist/op/activities/index.d.ts +6 -0
  102. package/dist/op/activities/index.d.ts.map +1 -1
  103. package/dist/op/builders.d.ts +47 -0
  104. package/dist/op/builders.d.ts.map +1 -0
  105. package/dist/plugin.d.ts.map +1 -1
  106. package/dist/receipt-store.d.ts +109 -0
  107. package/dist/receipt-store.d.ts.map +1 -0
  108. package/dist/rules/cf-refs.ts +40 -0
  109. package/dist/rules/waw016.ts +37 -16
  110. package/dist/rules/waw061.ts +67 -0
  111. package/dist/rules/waw062.ts +0 -0
  112. package/dist/rules/waw063.ts +0 -0
  113. package/dist/rules/waw064.ts +52 -0
  114. package/dist/rules/waw065.ts +110 -0
  115. package/dist/rules/waw066.ts +155 -0
  116. package/dist/rules/waw067.ts +114 -0
  117. package/dist/rules/waw068.ts +78 -0
  118. package/dist/serializer.d.ts.map +1 -1
  119. package/dist/skills/chant-aws-carve-terraform.md +26 -23
  120. package/dist/spec/parse.d.ts +7 -0
  121. package/dist/spec/parse.d.ts.map +1 -1
  122. package/package.json +3 -3
  123. package/src/api/read-client.test.ts +9 -0
  124. package/src/api/read-client.ts +15 -2
  125. package/src/codegen/fallback.ts +1 -0
  126. package/src/codegen/generate-lexicon.ts +6 -0
  127. package/src/codegen/sam.ts +9 -0
  128. package/src/components/host-delivery.ts +4 -3
  129. package/src/composites/agentcore-agent.ts +35 -13
  130. package/src/composites/bucket-deployment.test.ts +84 -0
  131. package/src/composites/bucket-deployment.ts +153 -0
  132. package/src/composites/composites.test.ts +325 -0
  133. package/src/composites/dynamodb-table.test.ts +154 -0
  134. package/src/composites/dynamodb-table.ts +124 -0
  135. package/src/composites/ec2-instance-bundle.ts +134 -0
  136. package/src/composites/ecr-repository.test.ts +118 -0
  137. package/src/composites/ecr-repository.ts +117 -0
  138. package/src/composites/fargate-service.ts +19 -3
  139. package/src/composites/index.ts +21 -2
  140. package/src/composites/monitoring-stack.test.ts +130 -0
  141. package/src/composites/monitoring-stack.ts +164 -0
  142. package/src/composites/nlb-service.ts +117 -0
  143. package/src/composites/step-functions-workflow.ts +127 -0
  144. package/src/deep-observe.test.ts +201 -1
  145. package/src/deep-observe.ts +204 -0
  146. package/src/deep-topology.test.ts +392 -0
  147. package/src/deep-topology.ts +237 -0
  148. package/src/effect-receipt-row.test.ts +164 -0
  149. package/src/effect-receipt-row.ts +147 -0
  150. package/src/generated/lexicon-aws.json +34 -0
  151. package/src/index.ts +36 -7
  152. package/src/lint/audit-catalog.ts +16 -0
  153. package/src/lint/post-synth/cf-refs.ts +40 -0
  154. package/src/lint/post-synth/index.ts +16 -0
  155. package/src/lint/post-synth/waw016.test.ts +61 -4
  156. package/src/lint/post-synth/waw016.ts +37 -16
  157. package/src/lint/post-synth/waw061.test.ts +99 -0
  158. package/src/lint/post-synth/waw061.ts +67 -0
  159. package/src/lint/post-synth/waw062.test.ts +109 -0
  160. package/src/lint/post-synth/waw062.ts +0 -0
  161. package/src/lint/post-synth/waw063.test.ts +220 -0
  162. package/src/lint/post-synth/waw063.ts +0 -0
  163. package/src/lint/post-synth/waw064.test.ts +79 -0
  164. package/src/lint/post-synth/waw064.ts +52 -0
  165. package/src/lint/post-synth/waw065.test.ts +90 -0
  166. package/src/lint/post-synth/waw065.ts +110 -0
  167. package/src/lint/post-synth/waw066.test.ts +108 -0
  168. package/src/lint/post-synth/waw066.ts +155 -0
  169. package/src/lint/post-synth/waw067.test.ts +79 -0
  170. package/src/lint/post-synth/waw067.ts +114 -0
  171. package/src/lint/post-synth/waw068.test.ts +74 -0
  172. package/src/lint/post-synth/waw068.ts +78 -0
  173. package/src/lsp/hover.ts +17 -2
  174. package/src/op/activities/index.ts +15 -0
  175. package/src/op/builders.test.ts +61 -0
  176. package/src/op/builders.ts +72 -0
  177. package/src/plugin.ts +41 -10
  178. package/src/receipt-store.test.ts +204 -0
  179. package/src/receipt-store.ts +315 -0
  180. package/src/serializer.ts +105 -2
  181. package/src/skills/chant-aws-carve-terraform.md +26 -23
  182. package/src/spec/parse.test.ts +74 -0
  183. package/src/spec/parse.ts +26 -6
@@ -43,3 +43,7 @@ resource_type: AWS::DynamoDB::Table
43
43
  - [WAW027](/rules/WAW027.md): DynamoDB table does not have point-in-time recovery enabled
44
44
  - [WAW030](/rules/WAW030.md): Missing DependsOn for known CloudFormation ordering patterns
45
45
  - [WAW059](/rules/WAW059.md): Wildcard Resource where the declared graph enumerates the touched set — tighten to the consumers' declared Arns
46
+ - [WAW062](/rules/WAW062.md): Duplicate export name or explicit resource name within a template — fails at deploy time
47
+ - [WAW064](/rules/WAW064.md): Transit Gateway route table declares a Blackhole route — confirm the traffic drop is intentional, not an accident
48
+ - [WAW065](/rules/WAW065.md): Transit Gateway route table has associations but no propagations, or an attachment wired into no route table
49
+ - [WAW066](/rules/WAW066.md): Private subnet's route table has no default route, or its default route targets a gateway that does not exist in the template
@@ -33,3 +33,9 @@ resource_type: AWS::EC2::TransitGateway
33
33
  - `EncryptionSupportState`
34
34
  - `Id`
35
35
  - `TransitGatewayArn`
36
+
37
+ ## Governed by
38
+
39
+ - [WAW064](/rules/WAW064.md): Transit Gateway route table declares a Blackhole route — confirm the traffic drop is intentional, not an accident
40
+ - [WAW065](/rules/WAW065.md): Transit Gateway route table has associations but no propagations, or an attachment wired into no route table
41
+ - [WAW068](/rules/WAW068.md): VPN Gateway or Transit Gateway has only one attached VPN Connection — no redundant path for hybrid connectivity
@@ -20,3 +20,7 @@ resource_type: AWS::EC2::TransitGatewayAttachment
20
20
  ## Attributes
21
21
 
22
22
  - `Id`
23
+
24
+ ## Governed by
25
+
26
+ - [WAW065](/rules/WAW065.md): Transit Gateway route table has associations but no propagations, or an attachment wired into no route table
@@ -26,3 +26,7 @@ resource_type: AWS::EC2::TransitGatewayPeeringAttachment
26
26
  - `State`
27
27
  - `Status`
28
28
  - `TransitGatewayAttachmentId`
29
+
30
+ ## Governed by
31
+
32
+ - [WAW065](/rules/WAW065.md): Transit Gateway route table has associations but no propagations, or an attachment wired into no route table
@@ -14,3 +14,8 @@ resource_type: AWS::EC2::TransitGatewayRoute
14
14
  - `TransitGatewayRouteTableId` (`string`, required): The ID of transit gateway route table.
15
15
  - `Blackhole` (`boolean`, optional): Indicates whether to drop traffic that matches this route.
16
16
  - `TransitGatewayAttachmentId` (`string`, optional): The ID of transit gateway attachment.
17
+
18
+ ## Governed by
19
+
20
+ - [WAW064](/rules/WAW064.md): Transit Gateway route table declares a Blackhole route — confirm the traffic drop is intentional, not an accident
21
+ - [WAW065](/rules/WAW065.md): Transit Gateway route table has associations but no propagations, or an attachment wired into no route table
@@ -17,3 +17,7 @@ resource_type: AWS::EC2::TransitGatewayRouteTable
17
17
  ## Attributes
18
18
 
19
19
  - `TransitGatewayRouteTableId`
20
+
21
+ ## Governed by
22
+
23
+ - [WAW065](/rules/WAW065.md): Transit Gateway route table has associations but no propagations, or an attachment wired into no route table
@@ -12,3 +12,7 @@ resource_type: AWS::EC2::TransitGatewayRouteTableAssociation
12
12
 
13
13
  - `TransitGatewayAttachmentId` (`string`, required): The ID of transit gateway attachment.
14
14
  - `TransitGatewayRouteTableId` (`string`, required): The ID of transit gateway route table.
15
+
16
+ ## Governed by
17
+
18
+ - [WAW065](/rules/WAW065.md): Transit Gateway route table has associations but no propagations, or an attachment wired into no route table
@@ -12,3 +12,7 @@ resource_type: AWS::EC2::TransitGatewayRouteTablePropagation
12
12
 
13
13
  - `TransitGatewayAttachmentId` (`string`, required): The ID of transit gateway attachment.
14
14
  - `TransitGatewayRouteTableId` (`string`, required): The ID of transit gateway route table.
15
+
16
+ ## Governed by
17
+
18
+ - [WAW065](/rules/WAW065.md): Transit Gateway route table has associations but no propagations, or an attachment wired into no route table
@@ -22,3 +22,7 @@ resource_type: AWS::EC2::TransitGatewayVpcAttachment
22
22
  ## Attributes
23
23
 
24
24
  - `Id`
25
+
26
+ ## Governed by
27
+
28
+ - [WAW065](/rules/WAW065.md): Transit Gateway route table has associations but no propagations, or an attachment wired into no route table
@@ -75,3 +75,11 @@ resource_type: AWS::Cassandra::Type
75
75
  - [WAW058](/rules/WAW058.md): Organization audit trail missing, not logging, or scoped down to a single region
76
76
  - [WAW059](/rules/WAW059.md): Wildcard Resource where the declared graph enumerates the touched set — tighten to the consumers' declared Arns
77
77
  - [WAW060](/rules/WAW060.md): IAM policy attached to no principal — it grants nothing
78
+ - [WAW061](/rules/WAW061.md): Subnet CidrBlock falls outside its VPC's CidrBlock — fails at deploy time
79
+ - [WAW062](/rules/WAW062.md): Duplicate export name or explicit resource name within a template — fails at deploy time
80
+ - [WAW063](/rules/WAW063.md): IAM policy denies an action another attached policy on the same role allows — explicit Deny wins, runtime 403
81
+ - [WAW064](/rules/WAW064.md): Transit Gateway route table declares a Blackhole route — confirm the traffic drop is intentional, not an accident
82
+ - [WAW065](/rules/WAW065.md): Transit Gateway route table has associations but no propagations, or an attachment wired into no route table
83
+ - [WAW066](/rules/WAW066.md): Private subnet's route table has no default route, or its default route targets a gateway that does not exist in the template
84
+ - [WAW067](/rules/WAW067.md): A single-AZ NAT gateway serves private subnets across multiple Availability Zones — single point of failure for cross-AZ egress
85
+ - [WAW068](/rules/WAW068.md): VPN Gateway or Transit Gateway has only one attached VPN Connection — no redundant path for hybrid connectivity
@@ -33,3 +33,7 @@ resource_type: AWS::EC2::VPNConnection
33
33
  ## Attributes
34
34
 
35
35
  - `VpnConnectionId`
36
+
37
+ ## Governed by
38
+
39
+ - [WAW068](/rules/WAW068.md): VPN Gateway or Transit Gateway has only one attached VPN Connection — no redundant path for hybrid connectivity
@@ -18,3 +18,7 @@ resource_type: AWS::EC2::VPNGateway
18
18
  ## Attributes
19
19
 
20
20
  - `VPNGatewayId`
21
+
22
+ ## Governed by
23
+
24
+ - [WAW068](/rules/WAW068.md): VPN Gateway or Transit Gateway has only one attached VPN Connection — no redundant path for hybrid connectivity
@@ -64,3 +64,4 @@ resource_type: AWS::EC2::VPC
64
64
  ## Governed by
65
65
 
66
66
  - [WAW030](/rules/WAW030.md): Missing DependsOn for known CloudFormation ordering patterns
67
+ - [WAW061](/rules/WAW061.md): Subnet CidrBlock falls outside its VPC's CidrBlock — fails at deploy time
@@ -18,6 +18,12 @@ export { flociUp, flociDown, flociRunCommand, flociRmCommand, flociExistsCommand
18
18
  export type { FlociUpArgs, FlociDownArgs } from "./floci.js";
19
19
  export { awsApply, awsDelete, rollbackStack, waitForStackSettled, cfnUrl, cfnForm, capabilityParams, xmlField, stackStatus, stackId, cfnErrorMessage, isStackMissing, isNoUpdates, isSuccessStatus, isFailureStatus, isTerminalStatus, } from "./aws-apply.js";
20
20
  export type { AwsApplyArgs, RollbackStackArgs, AwsHttp } from "./aws-apply.js";
21
+ export declare const receiptRead: (args: import("@intentius/chant/op/receipt-store").ReceiptReadArgs, signal?: AbortSignal) => Promise<import("@intentius/chant/op/receipt-store").ReceiptReadResult>;
22
+ export declare const receiptWrite: (args: import("@intentius/chant/op/receipt-store").ReceiptWriteArgs, signal?: AbortSignal) => Promise<{
23
+ written: true;
24
+ receipt: string;
25
+ }>;
26
+ export declare const receiptStaleness: (args: import("@intentius/chant/op/receipt-store").ReceiptStalenessArgs, signal?: AbortSignal) => Promise<import("@intentius/chant/op/receipt-store").ReceiptStalenessResult>;
21
27
  export { awsAgentCoreFetchTrace } from "../../agentcore/trace-fetch.js";
22
28
  export type { AgentCoreTraceSource, AwsAgentCoreFetchTraceArgs, AwsAgentCoreFetchTraceResult, } from "../../agentcore/trace-fetch.js";
23
29
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/op/activities/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EACL,OAAO,EACP,SAAS,EACT,eAAe,EACf,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,QAAQ,EACR,YAAY,GACb,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE1D,OAAO,EACL,QAAQ,EACR,SAAS,EACT,aAAa,EACb,mBAAmB,EACnB,MAAM,EACN,OAAO,EACP,gBAAgB,EAChB,QAAQ,EACR,WAAW,EACX,OAAO,EACP,eAAe,EACf,cAAc,EACd,WAAW,EACX,eAAe,EACf,eAAe,EACf,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE5E,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,YAAY,EACV,oBAAoB,EACpB,0BAA0B,EAC1B,4BAA4B,GAC7B,MAAM,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/op/activities/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EACL,OAAO,EACP,SAAS,EACT,eAAe,EACf,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,QAAQ,EACR,YAAY,GACb,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE1D,OAAO,EACL,QAAQ,EACR,SAAS,EACT,aAAa,EACb,mBAAmB,EACnB,MAAM,EACN,OAAO,EACP,gBAAgB,EAChB,QAAQ,EACR,WAAW,EACX,OAAO,EACP,eAAe,EACf,cAAc,EACd,WAAW,EACX,eAAe,EACf,eAAe,EACf,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAa5E,eAAO,MAAM,WAAW,qKAAqC,CAAC;AAC9D,eAAO,MAAM,YAAY;;;EAAsC,CAAC;AAChE,eAAO,MAAM,gBAAgB,+KAA0C,CAAC;AAExE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,YAAY,EACV,oBAAoB,EACpB,0BAA0B,EAC1B,4BAA4B,GAC7B,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Typed step-builder wrappers for this lexicon's activities (chant #1288
3
+ * Stage 2). See `lexicons/k8s/src/op/builders.ts`'s module doc for why these
4
+ * live beside their `*Args` interfaces rather than in core or the temporal
5
+ * barrel: core cannot import a lexicon's types (lexicons depend on core, not
6
+ * the reverse), and wiring them into the cloud-agnostic temporal barrel
7
+ * would make it depend on every cloud lexicon at runtime. `opts`'s type in
8
+ * each wrapper below IS the activity's own `*Args` interface (via
9
+ * `Omit`/`WithStepRefs`) — never restated. `core`'s own
10
+ * `awsApply`/`awsDelete`/`flociUp`/`flociDown` are unchanged and produce
11
+ * byte-identical `ActivityStep` output; these are purely additive.
12
+ */
13
+ import { type ActivityStep, type NamedActivityStep, type WithStepRefs } from "@intentius/chant/op";
14
+ import type { AwsApplyArgs, AwsDeleteArgs } from "./activities/aws-apply.js";
15
+ import type { FlociUpArgs, FlociDownArgs } from "./activities/floci.js";
16
+ /** Extra opts every wrapper below accepts alongside its activity's own fields. */
17
+ type StepOpts = {
18
+ profile?: ActivityStep["profile"];
19
+ id?: string;
20
+ };
21
+ /**
22
+ * Deploy a built CloudFormation template directly against the CFN API — the
23
+ * fully typed twin of core's `awsApply`. `opts` is {@link AwsApplyArgs}
24
+ * itself, minus the positional `templatePath`. Defaults to the `longInfra`
25
+ * profile.
26
+ */
27
+ export declare const awsApply: (templatePath: string, opts: WithStepRefs<Omit<AwsApplyArgs, "templatePath">> & StepOpts) => NamedActivityStep;
28
+ /**
29
+ * Delete a CloudFormation stack — the inverse of {@link awsApply}, fully
30
+ * typed twin of core's `awsDelete`. `opts` is {@link AwsDeleteArgs} itself,
31
+ * minus the positional `templatePath`. Defaults to the `longInfra` profile.
32
+ */
33
+ export declare const awsDelete: (templatePath: string, opts: WithStepRefs<Omit<AwsDeleteArgs, "templatePath">> & StepOpts) => NamedActivityStep;
34
+ /**
35
+ * Boot the local Floci AWS emulator — the fully typed twin of core's
36
+ * `flociUp`. `opts` is {@link FlociUpArgs} itself. Defaults to the
37
+ * `longInfra` profile.
38
+ */
39
+ export declare const flociUp: (opts?: WithStepRefs<FlociUpArgs> & StepOpts) => NamedActivityStep;
40
+ /**
41
+ * Stop and remove the local Floci emulator container — the fully typed twin
42
+ * of core's `flociDown`. `opts` is {@link FlociDownArgs} itself. Defaults to
43
+ * the `fastIdempotent` profile.
44
+ */
45
+ export declare const flociDown: (opts?: WithStepRefs<FlociDownArgs> & StepOpts) => NamedActivityStep;
46
+ export {};
47
+ //# sourceMappingURL=builders.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builders.d.ts","sourceRoot":"","sources":["../../src/op/builders.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAErE,kFAAkF;AAClF,KAAK,QAAQ,GAAG;IAAE,OAAO,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnE;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GACnB,cAAc,MAAM,EACpB,MAAM,YAAY,CAAC,IAAI,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC,GAAG,QAAQ,KAChE,iBAGF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,SAAS,GACpB,cAAc,MAAM,EACpB,MAAM,YAAY,CAAC,IAAI,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC,GAAG,QAAQ,KACjE,iBAGF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,OAAO,GAAI,OAAO,YAAY,CAAC,WAAW,CAAC,GAAG,QAAQ,KAAG,iBAGrE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,SAAS,GAAI,OAAO,YAAY,CAAC,aAAa,CAAC,GAAG,QAAQ,KAAG,iBAGzE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAgL,MAAM,0BAA0B,CAAC;AAqC5O;yEACyE;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAiBnD,eAAO,MAAM,SAAS,EAAE,aAm7BvB,CAAC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAgL,MAAM,0BAA0B,CAAC;AAqC5O;yEACyE;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAiBnD,eAAO,MAAM,SAAS,EAAE,aAk9BvB,CAAC"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * The aws `ReceiptStore` (#1835, epic #1703) — core's injectable receipt seam
3
+ * (#1834, `@intentius/chant/op/receipt-store`) implemented over SSM Parameter
4
+ * Store, plain `String`, at the path identity ./effect-receipt-row.ts derives
5
+ * from the ownership marker fields.
6
+ *
7
+ * The transport is the lexicon's own read/apply transport (./api/read-client,
8
+ * #1206), pointed at the SSM JSON API: `fetch`, SigV4 when credentials
9
+ * resolve, and the one endpoint-override rule (#1694) — the `endpoint`
10
+ * option, else `AWS_ENDPOINT_URL_SSM`, else `AWS_ENDPOINT_URL` — so a local
11
+ * emulator lane reads and writes receipts without any store-specific wiring.
12
+ *
13
+ * Write discipline (epic decision 3): `write` exists for the `effect()` step
14
+ * alone — the step's read-compare-run-write is the only path that reaches it,
15
+ * on success, last. `PutParameter` is `Type: "String"` always; the first
16
+ * write creates the parameter with the ownership tags, and a later write
17
+ * overwrites the value (SSM refuses `Overwrite` and `Tags` in one call, so
18
+ * tags ride creation only — they never change after).
19
+ *
20
+ * Identity: the parameter name needs `<stack>` and `<env>`, which the
21
+ * activity args deliberately do not carry (the `EffectReceiptRef` is
22
+ * identity-of-the-effect, not identity-of-the-deployment). The store resolves
23
+ * them once, lazily, at first use: an explicit option, else `CHANT_ENV` (what
24
+ * `chant run --env` sets) and the project's `ownership` block — the same
25
+ * fields that stamp markers (epic decision 4). Nothing resolving is an error,
26
+ * never a guessed segment.
27
+ */
28
+ import type { ReceiptStore } from "@intentius/chant/op/receipt-store";
29
+ import type { ResourceMetadata } from "@intentius/chant/lexicon";
30
+ import type { UnobservedReason } from "@intentius/chant/observation";
31
+ import { type AwsCredentialSource, type AwsReadClientOptions, type AwsReadHttp } from "./api/read-client.js";
32
+ /** Options for {@link awsReceiptStore}. All optional: the default store reads
33
+ * its identity from the project and its endpoint from the environment. */
34
+ export interface AwsReceiptStoreOptions {
35
+ /** The path's `<stack>` segment. Omitted, the project's `ownership.stack`
36
+ * (chant.config.ts, found upward from `cwd`) answers. */
37
+ stack?: string;
38
+ /** The path's `<env>` segment — explicit by decision 4. Omitted, `CHANT_ENV`
39
+ * (set by `chant run --env`) answers, then a literal `ownership.env`. */
40
+ environment?: string;
41
+ /** Where to look for chant.config.ts. Defaults to the working directory. */
42
+ cwd?: string;
43
+ /** Endpoint override; omitted, `AWS_ENDPOINT_URL[_SSM]` answers (#1694). */
44
+ endpoint?: string;
45
+ /** Region for the real-AWS host. */
46
+ region?: string;
47
+ /** Injectable HTTP, mirroring the read client's. Tests avoid the network. */
48
+ http?: AwsReadHttp;
49
+ /** Environment record the endpoint/credential/identity fallbacks read.
50
+ * Defaults to `process.env`; injectable for tests. */
51
+ env?: Record<string, string | undefined>;
52
+ /** What to sign with — same seam as the read client. */
53
+ credentials?: AwsCredentialSource;
54
+ /** Sign even against an endpoint override — for an override that is real AWS. */
55
+ signEndpointOverride?: boolean;
56
+ }
57
+ /** One SSM JSON call. Exported for the observation leg (plugin.ts), which
58
+ * reads the same parameters the store writes. */
59
+ export declare function ssmCall(action: string, payload: Record<string, unknown>, options: AwsReadClientOptions): Promise<{
60
+ status: number;
61
+ json: Record<string, unknown>;
62
+ }>;
63
+ /** The API's own error code from a JSON-protocol error body — `__type`, with
64
+ * any `namespace#` prefix stripped. */
65
+ export declare function ssmErrorCode(json: Record<string, unknown>): string | undefined;
66
+ /**
67
+ * `GetParameter` by name. Absent (`ParameterNotFound`) is `undefined` — a real
68
+ * answer, distinct from a failed read, which throws.
69
+ */
70
+ export declare function ssmGetParameter(name: string, options?: AwsReadClientOptions): Promise<string | undefined>;
71
+ /**
72
+ * `PutParameter`, plain `String`. Creation carries `tags`; an existing
73
+ * parameter is overwritten (`Overwrite: true`) without them — SSM refuses
74
+ * `Overwrite` and `Tags` in the same call, and ownership tags never change.
75
+ */
76
+ export declare function ssmPutParameter(name: string, value: string, tags: Record<string, string>, options?: AwsReadClientOptions): Promise<void>;
77
+ /** What the observation leg learned about the declared receipt rows. */
78
+ export interface ReceiptRowObservation {
79
+ resources: Record<string, ResourceMetadata>;
80
+ unobserved: Record<string, {
81
+ type: string;
82
+ reason: UnobservedReason;
83
+ detail: string;
84
+ }>;
85
+ }
86
+ /**
87
+ * The plan-side live read of the receipt rows (#1835's observation leg).
88
+ *
89
+ * A receipt is not a stack member — the applier never writes it (#1832) — so
90
+ * `describe-stack-resources` honestly reports it absent even while the
91
+ * parameter exists. The serializer renders each receipt's derived path into
92
+ * the template's `Metadata` (./serializer.ts), so this leg reads the paths
93
+ * back from the build output — one derivation, decision 4 — and asks SSM
94
+ * `GetParameter` for each. Present maps the stored value onto
95
+ * `attributes.value` (core's `RECEIPT_VALUE_ATTRIBUTE`); `ParameterNotFound`
96
+ * is a real absence and stays one; a failed read is an `unobserved` hole,
97
+ * never a wrong answer — a receipt nobody could read must not arrive
98
+ * downstream as "the effect never ran".
99
+ */
100
+ export declare function observeReceiptRows(entityNames: string[], buildOutput: string, options?: AwsReadClientOptions): Promise<ReceiptRowObservation>;
101
+ /**
102
+ * The `ReceiptStore` over SSM. Bind it once in the op activities barrel —
103
+ * `receiptActivities(awsReceiptStore())` — and the registry resolves
104
+ * `receiptRead`/`receiptWrite`/`receiptStaleness` by name, exactly like
105
+ * `ensureSecret` (#1830). Identity and endpoint resolve lazily at first use,
106
+ * so module load never reads the project or the environment.
107
+ */
108
+ export declare function awsReceiptStore(options?: AwsReceiptStoreOptions): ReceiptStore;
109
+ //# sourceMappingURL=receipt-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"receipt-store.d.ts","sourceRoot":"","sources":["../src/receipt-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,OAAO,KAAK,EAAoB,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAKL,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,WAAW,EACjB,MAAM,mBAAmB,CAAC;AAO3B;0EAC0E;AAC1E,MAAM,WAAW,sBAAsB;IACrC;6DACyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;6EACyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB;0DACsD;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,wDAAwD;IACxD,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,iFAAiF;IACjF,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;iDACiD;AACjD,wBAAsB,OAAO,CAC3B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAyB5D;AAED;uCACuC;AACvC,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,SAAS,CAI9E;AAQD;;;GAGG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ7B;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,IAAI,CAAC,CAiBf;AA4CD,wEAAwE;AACxE,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC5C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,gBAAgB,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,EAAE,EACrB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,qBAAqB,CAAC,CA+ChC;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,YAAY,CAyBlF"}
@@ -169,6 +169,46 @@ export function isFullTierEnv(env: string | undefined): boolean {
169
169
  return env === "prod" || env === "production" || env === "full";
170
170
  }
171
171
 
172
+ /**
173
+ * Parse an IPv4 CIDR literal ("10.0.0.0/16") into its numeric base address
174
+ * and prefix length. Returns null for anything that isn't a plain IPv4 CIDR
175
+ * (IPv6, malformed octets, out-of-range prefix) — callers treat that as
176
+ * "can't prove statically" rather than an error.
177
+ */
178
+ export function parseIpv4Cidr(cidr: string): { base: number; prefix: number } | null {
179
+ const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\/(\d{1,2})$/.exec(cidr.trim());
180
+ if (!m) return null;
181
+ const octets = m.slice(1, 5).map(Number);
182
+ if (octets.some((o) => o < 0 || o > 255)) return null;
183
+ const prefix = Number(m[5]);
184
+ if (prefix < 0 || prefix > 32) return null;
185
+ const base = ((octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3]) >>> 0;
186
+ return { base, prefix };
187
+ }
188
+
189
+ /** [networkAddress, broadcastAddress] for a parsed IPv4 CIDR block. */
190
+ function ipv4Range(cidr: { base: number; prefix: number }): [number, number] {
191
+ const maskBits = cidr.prefix === 0 ? 0 : (0xffffffff << (32 - cidr.prefix)) >>> 0;
192
+ const network = (cidr.base & maskBits) >>> 0;
193
+ const broadcast = (network | (~maskBits >>> 0)) >>> 0;
194
+ return [network, broadcast];
195
+ }
196
+
197
+ /**
198
+ * Whether `inner` (an IPv4 CIDR literal) falls entirely within `outer`.
199
+ * Returns null — not false — when either literal isn't a plain, parseable
200
+ * IPv4 CIDR (e.g. IPv6, or a CloudFormation intrinsic already stringified
201
+ * elsewhere); a null means "statically unprovable", not "violation".
202
+ */
203
+ export function ipv4CidrContains(outer: string, inner: string): boolean | null {
204
+ const outerCidr = parseIpv4Cidr(outer);
205
+ const innerCidr = parseIpv4Cidr(inner);
206
+ if (!outerCidr || !innerCidr) return null;
207
+ const [oStart, oEnd] = ipv4Range(outerCidr);
208
+ const [iStart, iEnd] = ipv4Range(innerCidr);
209
+ return iStart >= oStart && iEnd <= oEnd;
210
+ }
211
+
172
212
  /**
173
213
  * Check if a port range [fromPort, toPort] contains any of the sensitive ports.
174
214
  */
@@ -2,7 +2,13 @@
2
2
  * WAW016: Deprecated Property Usage
3
3
  *
4
4
  * Flags properties marked as deprecated in the CloudFormation Registry.
5
- * Sources: explicit `deprecatedProperties` array + description text mining.
5
+ *
6
+ * Two bases feed this, and the finding says which one it stands on (#1701).
7
+ * A `declared` name comes from the Registry schema's own `deprecatedProperties`
8
+ * array. An `inferred` name comes from a regex over the property description,
9
+ * which also matches descriptions that mention the deprecation of a sibling
10
+ * property, an enum value, or the thing the property configures. Declared is a
11
+ * warning; inferred is reported at info and worded as a guess.
6
12
  */
7
13
 
8
14
  import { readFileSync } from "fs";
@@ -10,18 +16,23 @@ import { join } from "path";
10
16
  import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
11
17
  import { parseCFTemplate } from "./cf-refs";
12
18
 
19
+ /** What a deprecation classification rests on. */
20
+ export type DeprecationBasis = "declared" | "inferred";
21
+
13
22
  interface LexiconEntry {
14
23
  kind: string;
15
24
  resourceType: string;
16
25
  deprecatedProperties?: string[];
26
+ inferredDeprecations?: string[];
17
27
  [key: string]: unknown;
18
28
  }
19
29
 
20
30
  /**
21
- * Load deprecated properties per resource type from the lexicon JSON.
31
+ * Load deprecated properties per resource type from the lexicon JSON, each
32
+ * tagged with the basis it was classified on.
22
33
  */
23
- function loadDeprecatedProperties(): Map<string, Set<string>> {
24
- const map = new Map<string, Set<string>>();
34
+ function loadDeprecatedProperties(): Map<string, Map<string, DeprecationBasis>> {
35
+ const map = new Map<string, Map<string, DeprecationBasis>>();
25
36
  try {
26
37
  const pkgDir = join(__dirname, "..", "..", "..");
27
38
  const lexiconPath = join(pkgDir, "src", "generated", "lexicon-aws.json");
@@ -29,9 +40,15 @@ function loadDeprecatedProperties(): Map<string, Set<string>> {
29
40
  const data = JSON.parse(content) as Record<string, LexiconEntry>;
30
41
 
31
42
  for (const [_name, entry] of Object.entries(data)) {
32
- if (entry.kind === "resource" && entry.resourceType && entry.deprecatedProperties && entry.deprecatedProperties.length > 0) {
33
- map.set(entry.resourceType, new Set(entry.deprecatedProperties));
43
+ if (entry.kind !== "resource" || !entry.resourceType) continue;
44
+ if (!entry.deprecatedProperties?.length) continue;
45
+
46
+ const inferred = new Set(entry.inferredDeprecations ?? []);
47
+ const byProp = new Map<string, DeprecationBasis>();
48
+ for (const propName of entry.deprecatedProperties) {
49
+ byProp.set(propName, inferred.has(propName) ? "inferred" : "declared");
34
50
  }
51
+ map.set(entry.resourceType, byProp);
35
52
  }
36
53
  } catch {
37
54
  // Lexicon not available — skip
@@ -44,7 +61,7 @@ function loadDeprecatedProperties(): Map<string, Set<string>> {
44
61
  */
45
62
  export function checkDeprecatedProperties(
46
63
  ctx: PostSynthContext,
47
- deprecated: Map<string, Set<string>>,
64
+ deprecated: Map<string, Map<string, DeprecationBasis>>,
48
65
  ): PostSynthDiagnostic[] {
49
66
  if (deprecated.size === 0) return [];
50
67
 
@@ -60,15 +77,19 @@ export function checkDeprecatedProperties(
60
77
 
61
78
  const props = resource.Properties ?? {};
62
79
  for (const propName of Object.keys(props)) {
63
- if (deprProps.has(propName)) {
64
- diagnostics.push({
65
- checkId: "WAW016",
66
- severity: "warning",
67
- message: `Resource "${logicalId}" (${resource.Type}) uses deprecated property "${propName}" — consider alternatives`,
68
- entity: logicalId,
69
- lexicon: "aws",
70
- });
71
- }
80
+ const basis = deprProps.get(propName);
81
+ if (!basis) continue;
82
+
83
+ diagnostics.push({
84
+ checkId: "WAW016",
85
+ severity: basis === "declared" ? "warning" : "info",
86
+ message:
87
+ basis === "declared"
88
+ ? `Resource "${logicalId}" (${resource.Type}) uses deprecated property "${propName}" — consider alternatives`
89
+ : `Resource "${logicalId}" (${resource.Type}) uses property "${propName}", whose description reads as deprecated — the CloudFormation Registry does not declare it deprecated, so confirm before changing it`,
90
+ entity: logicalId,
91
+ lexicon: "aws",
92
+ });
72
93
  }
73
94
  }
74
95
  }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * WAW061: Subnet CIDR Not Contained In VPC CIDR
3
+ *
4
+ * A subnet's CidrBlock must fall entirely inside its VPC's CidrBlock.
5
+ * Stock CDK does not check this — CloudFormation accepts the template and
6
+ * only fails the `AWS::EC2::Subnet` resource at deploy time
7
+ * ("The CIDR ... is invalid" / "does not fall within the CIDR range of the
8
+ * VPC"), after the rest of the stack may have already started rolling out.
9
+ * This is a cheap static graph check: follow the subnet's VpcId Ref to a
10
+ * declared VPC in the same template and do IPv4 CIDR-range math.
11
+ *
12
+ * Stays quiet whenever the graph can't prove it: intrinsic CIDR blocks,
13
+ * IPv6, a VpcId that isn't a simple Ref to a declared AWS::EC2::VPC, or a
14
+ * VPC whose own CidrBlock isn't a literal string.
15
+ */
16
+
17
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
18
+ import { parseCFTemplate, findResourceRefs, ipv4CidrContains } from "./cf-refs";
19
+
20
+ export function checkSubnetCidrContainment(ctx: PostSynthContext): PostSynthDiagnostic[] {
21
+ const diagnostics: PostSynthDiagnostic[] = [];
22
+
23
+ for (const [_lexicon, output] of ctx.outputs) {
24
+ const template = parseCFTemplate(output);
25
+ if (!template?.Resources) continue;
26
+
27
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
28
+ if (resource.Type !== "AWS::EC2::Subnet") continue;
29
+
30
+ const props = resource.Properties ?? {};
31
+ const subnetCidr = props.CidrBlock;
32
+ if (typeof subnetCidr !== "string") continue;
33
+
34
+ const vpcRefs = findResourceRefs(props.VpcId);
35
+ if (vpcRefs.size !== 1) continue;
36
+ const [vpcId] = vpcRefs;
37
+
38
+ const vpc = template.Resources[vpcId];
39
+ if (!vpc || vpc.Type !== "AWS::EC2::VPC") continue;
40
+
41
+ const vpcCidr = vpc.Properties?.CidrBlock;
42
+ if (typeof vpcCidr !== "string") continue;
43
+
44
+ const contained = ipv4CidrContains(vpcCidr, subnetCidr);
45
+ if (contained === false) {
46
+ diagnostics.push({
47
+ checkId: "WAW061",
48
+ severity: "error",
49
+ message: `Subnet "${logicalId}" CidrBlock ${subnetCidr} is not contained within VPC "${vpcId}"'s CidrBlock ${vpcCidr} — CloudFormation rejects this at deploy time`,
50
+ entity: logicalId,
51
+ lexicon: "aws",
52
+ });
53
+ }
54
+ }
55
+ }
56
+
57
+ return diagnostics;
58
+ }
59
+
60
+ export const waw061: PostSynthCheck = {
61
+ id: "WAW061",
62
+ description: "Subnet CidrBlock falls outside its VPC's CidrBlock — fails at deploy time",
63
+
64
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
65
+ return checkSubnetCidrContainment(ctx);
66
+ },
67
+ };
Binary file
Binary file
@@ -0,0 +1,52 @@
1
+ /**
2
+ * WAW064: Transit Gateway Route Table Declares A Blackhole Route
3
+ *
4
+ * `AWS::EC2::TransitGatewayRoute` with `Blackhole: true` silently drops any
5
+ * traffic matching its destination CIDR instead of forwarding it —
6
+ * CloudFormation deploys this without complaint, and the packet loss only
7
+ * surfaces downstream as unreachable hosts or a timeout nobody can explain
8
+ * from the template alone. A blackhole route is sometimes deliberate — an
9
+ * explicit deny for a CIDR range, or a placeholder pending real
10
+ * infrastructure — but it is also exactly what a copy-pasted route table or
11
+ * a forgotten cleanup step leaves behind. Warn so it stays a conscious
12
+ * choice rather than a silent accident.
13
+ */
14
+
15
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
16
+ import { parseCFTemplate } from "./cf-refs";
17
+
18
+ export function checkTgwBlackholeRoute(ctx: PostSynthContext): PostSynthDiagnostic[] {
19
+ const diagnostics: PostSynthDiagnostic[] = [];
20
+
21
+ for (const [_lexicon, output] of ctx.outputs) {
22
+ const template = parseCFTemplate(output);
23
+ if (!template?.Resources) continue;
24
+
25
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
26
+ if (resource.Type !== "AWS::EC2::TransitGatewayRoute") continue;
27
+
28
+ const props = resource.Properties ?? {};
29
+ if (props.Blackhole !== true) continue;
30
+
31
+ const cidr = typeof props.DestinationCidrBlock === "string" ? props.DestinationCidrBlock : "(unknown destination)";
32
+ diagnostics.push({
33
+ checkId: "WAW064",
34
+ severity: "warning",
35
+ message: `TransitGatewayRoute "${logicalId}" declares Blackhole: true for destination ${cidr} — traffic matching this route is silently dropped; confirm this is intentional`,
36
+ entity: logicalId,
37
+ lexicon: "aws",
38
+ });
39
+ }
40
+ }
41
+
42
+ return diagnostics;
43
+ }
44
+
45
+ export const waw064: PostSynthCheck = {
46
+ id: "WAW064",
47
+ description: "Transit Gateway route table declares a Blackhole route — confirm the traffic drop is intentional, not an accident",
48
+
49
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
50
+ return checkTgwBlackholeRoute(ctx);
51
+ },
52
+ };