@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
@@ -26,6 +26,11 @@ export interface LexiconEntry {
26
26
  writeOnly?: string[];
27
27
  primaryIdentifier?: string[];
28
28
  deprecatedProperties?: string[];
29
+ /**
30
+ * Subset of `deprecatedProperties` that only a regex over the property
31
+ * description supports; the rest is declared by the Registry schema (#1701).
32
+ */
33
+ inferredDeprecations?: string[];
29
34
  conditionalCreateOnly?: string[];
30
35
  replacementStrategy?: "delete_then_create" | "create_then_delete";
31
36
  tagging?: { taggable: boolean; tagOnCreate: boolean; tagUpdatable: boolean };
@@ -72,6 +77,7 @@ export function generateLexiconJSON(
72
77
  ...(r.resource.writeOnly.length > 0 && { writeOnly: r.resource.writeOnly }),
73
78
  ...(r.resource.primaryIdentifier.length > 0 && { primaryIdentifier: r.resource.primaryIdentifier }),
74
79
  ...(r.resource.deprecatedProperties?.length && { deprecatedProperties: r.resource.deprecatedProperties }),
80
+ ...(r.resource.inferredDeprecations?.length && { inferredDeprecations: r.resource.inferredDeprecations }),
75
81
  ...(r.resource.conditionalCreateOnly?.length && { conditionalCreateOnly: r.resource.conditionalCreateOnly }),
76
82
  ...(r.resource.replacementStrategy && { replacementStrategy: r.resource.replacementStrategy }),
77
83
  ...(r.resource.tagging && { tagging: r.resource.tagging }),
@@ -59,6 +59,7 @@ function samFunction(): SchemaParseResult {
59
59
  writeOnly: [],
60
60
  primaryIdentifier: [],
61
61
  deprecatedProperties: [],
62
+ inferredDeprecations: [],
62
63
  conditionalCreateOnly: [],
63
64
  },
64
65
  propertyTypes: [
@@ -147,6 +148,7 @@ function samApi(): SchemaParseResult {
147
148
  writeOnly: [],
148
149
  primaryIdentifier: [],
149
150
  deprecatedProperties: [],
151
+ inferredDeprecations: [],
150
152
  conditionalCreateOnly: [],
151
153
  },
152
154
  propertyTypes: [
@@ -200,6 +202,7 @@ function samHttpApi(): SchemaParseResult {
200
202
  writeOnly: [],
201
203
  primaryIdentifier: [],
202
204
  deprecatedProperties: [],
205
+ inferredDeprecations: [],
203
206
  conditionalCreateOnly: [],
204
207
  },
205
208
  propertyTypes: [
@@ -236,6 +239,7 @@ function samSimpleTable(): SchemaParseResult {
236
239
  writeOnly: [],
237
240
  primaryIdentifier: [],
238
241
  deprecatedProperties: [],
242
+ inferredDeprecations: [],
239
243
  conditionalCreateOnly: [],
240
244
  },
241
245
  propertyTypes: [
@@ -273,6 +277,7 @@ function samLayerVersion(): SchemaParseResult {
273
277
  writeOnly: [],
274
278
  primaryIdentifier: [],
275
279
  deprecatedProperties: [],
280
+ inferredDeprecations: [],
276
281
  conditionalCreateOnly: [],
277
282
  },
278
283
  propertyTypes: [],
@@ -306,6 +311,7 @@ function samStateMachine(): SchemaParseResult {
306
311
  writeOnly: [],
307
312
  primaryIdentifier: [],
308
313
  deprecatedProperties: [],
314
+ inferredDeprecations: [],
309
315
  conditionalCreateOnly: [],
310
316
  },
311
317
  propertyTypes: [
@@ -339,6 +345,7 @@ function samApplication(): SchemaParseResult {
339
345
  writeOnly: [],
340
346
  primaryIdentifier: [],
341
347
  deprecatedProperties: [],
348
+ inferredDeprecations: [],
342
349
  conditionalCreateOnly: [],
343
350
  },
344
351
  propertyTypes: [],
@@ -360,6 +367,7 @@ function samConnector(): SchemaParseResult {
360
367
  writeOnly: [],
361
368
  primaryIdentifier: [],
362
369
  deprecatedProperties: [],
370
+ inferredDeprecations: [],
363
371
  conditionalCreateOnly: [],
364
372
  },
365
373
  propertyTypes: [],
@@ -397,6 +405,7 @@ function samGraphQLApi(): SchemaParseResult {
397
405
  writeOnly: [],
398
406
  primaryIdentifier: [],
399
407
  deprecatedProperties: [],
408
+ inferredDeprecations: [],
400
409
  conditionalCreateOnly: [],
401
410
  },
402
411
  propertyTypes: [],
@@ -73,9 +73,10 @@ function resolveTargets(
73
73
  * Deploy a revision to a host (fleet) via AWS CodeDeploy: create the
74
74
  * deployment, then wait for a terminal status. Rollback re-invokes
75
75
  * `stopAndRollback` against the deployment id this step created (captured in
76
- * a closure-scoped map keyed by application/deploymentGroup, since the
77
- * driver's saga rollback calls `rollback(ctx, sameInput)` rather than passing
78
- * `run`'s output) riding CodeDeploy's own native automatic rollback to the
76
+ * a closure-scoped map keyed by application/deploymentGroup; the driver now
77
+ * also passes `run`'s output as `rollback`'s optional third arg (#1944), but
78
+ * this capability's input-derived key needs neither channel to resolve
79
+ * identity) — riding CodeDeploy's own native automatic rollback to the
79
80
  * last known-good revision rather than a chant-scripted compensation, the
80
81
  * "auto" rollback style the Neo4j pilot's docs comment contrasts with the
81
82
  * ALB/ECS pilot's component-declared one.
@@ -45,19 +45,36 @@ export function agentCoreDefaultEndpointArn(runtime: InstanceType<typeof Runtime
45
45
  }
46
46
 
47
47
  /**
48
- * AgentCore's `Runtime`/`RuntimeEndpoint`/`Memory` `Name`/`AgentRuntimeName`
49
- * fields are all bound to the CFN registry pattern
50
- * `^[a-zA-Z][a-zA-Z0-9_]{0,47}$` (spot-checked against the CloudFormation
51
- * schema for #882 — no hyphens, unlike chant's usual kebab-case component
52
- * names). `Gateway`/`GatewayTarget`/`WorkloadIdentity` names accept hyphens,
53
- * so only the Runtime family needs sanitizing. Non-identifier characters
54
- * become `_`, and a leading digit/underscore gets an `A` prefix so the result
55
- * always starts with a letter.
48
+ * Bedrock AgentCore's real limits, verified against the CloudFormation
49
+ * registry schema for `AWS::BedrockAgentCore::{Runtime,RuntimeEndpoint,Memory}`
50
+ * (#882).
51
+ *
52
+ * Exported for the same reason as `MICROVM_LIMITS` (#1374, #1420): a
53
+ * consumer driving AgentCore through another control plane needs the same
54
+ * numbers, and copying them is how two sources of truth start.
55
+ */
56
+ export const AGENTCORE_LIMITS = {
57
+ /**
58
+ * `Runtime`/`RuntimeEndpoint`/`Memory` `Name`/`AgentRuntimeName` fields
59
+ * are bound to this pattern — no hyphens, unlike chant's usual kebab-case
60
+ * component names. `Gateway`/`GatewayTarget`/`WorkloadIdentity` names
61
+ * accept hyphens, so only the Runtime family needs sanitizing.
62
+ */
63
+ runtimeNamePattern: /^[a-zA-Z][a-zA-Z0-9_]{0,47}$/,
64
+ maxRuntimeNameLength: 48,
65
+ /** `Memory` event retention bounds, in days. */
66
+ memoryEventExpiryDays: { min: 3, max: 365, default: 30 },
67
+ } as const;
68
+
69
+ /**
70
+ * Non-identifier characters become `_`, and a leading digit/underscore gets
71
+ * an `A` prefix so the result always starts with a letter — then truncated
72
+ * to {@link AGENTCORE_LIMITS}'s `maxRuntimeNameLength`.
56
73
  */
57
74
  function toRuntimeIdentifier(name: string): string {
58
75
  const cleaned = name.replace(/[^a-zA-Z0-9_]/g, "_");
59
76
  const identifier = /^[a-zA-Z]/.test(cleaned) ? cleaned : `A${cleaned}`;
60
- return identifier.slice(0, 48);
77
+ return identifier.slice(0, AGENTCORE_LIMITS.maxRuntimeNameLength);
61
78
  }
62
79
 
63
80
  /**
@@ -117,7 +134,7 @@ export interface AgentCoreAgentProps {
117
134
  * managed one and fails on a real apply (#978, see the composite doc).
118
135
  */
119
136
  endpointName?: string;
120
- /** Memory event retention, in days. CFN bounds: 3-365. Default: 30. */
137
+ /** Memory event retention, in days. Bounds: {@link AGENTCORE_LIMITS.memoryEventExpiryDays}. */
121
138
  memoryEventExpiryDays?: number;
122
139
  /** Gateway authorizer. Mirrors the generated `BedrockAgentCoreGateway_AuthorizerType` CFN enum. Default: "AWS_IAM". */
123
140
  gatewayAuthorizerType?: "CUSTOM_JWT" | "AWS_IAM" | "NONE" | "AUTHENTICATE_ONLY";
@@ -194,9 +211,14 @@ export const AgentCoreAgent = Composite<AgentCoreAgentProps, AgentCoreAgentResul
194
211
  if (networkMode === "VPC" && (props.vpcSubnetIds === undefined || props.vpcSecurityGroupIds === undefined)) {
195
212
  throw new Error("AgentCoreAgent requires vpcSubnetIds and vpcSecurityGroupIds when networkMode is \"VPC\"");
196
213
  }
197
- const memoryEventExpiryDays = props.memoryEventExpiryDays ?? 30;
198
- if (memoryEventExpiryDays < 3 || memoryEventExpiryDays > 365) {
199
- throw new Error("AgentCoreAgent memoryEventExpiryDays must be between 3 and 365");
214
+ const memoryEventExpiryDays = props.memoryEventExpiryDays ?? AGENTCORE_LIMITS.memoryEventExpiryDays.default;
215
+ if (
216
+ memoryEventExpiryDays < AGENTCORE_LIMITS.memoryEventExpiryDays.min ||
217
+ memoryEventExpiryDays > AGENTCORE_LIMITS.memoryEventExpiryDays.max
218
+ ) {
219
+ throw new Error(
220
+ `AgentCoreAgent memoryEventExpiryDays must be between ${AGENTCORE_LIMITS.memoryEventExpiryDays.min} and ${AGENTCORE_LIMITS.memoryEventExpiryDays.max}`,
221
+ );
200
222
  }
201
223
 
202
224
  const runtimeName = toRuntimeIdentifier(props.name);
@@ -0,0 +1,84 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { BucketDeployment } from "./bucket-deployment";
3
+
4
+ describe("BucketDeployment", () => {
5
+ test("private-by-default: encrypted, public access fully blocked, retained on delete, no policy member", () => {
6
+ const c = BucketDeployment({});
7
+ expect(c.bucket).toBeDefined();
8
+ expect((c as unknown as Record<string, unknown>).bucketPolicy).toBeUndefined();
9
+
10
+ const props = (c.bucket as any).props;
11
+ expect(props.BucketEncryption.props.ServerSideEncryptionConfiguration).toHaveLength(1);
12
+ expect(props.PublicAccessBlockConfiguration.props).toEqual({
13
+ BlockPublicAcls: true,
14
+ BlockPublicPolicy: true,
15
+ IgnorePublicAcls: true,
16
+ RestrictPublicBuckets: true,
17
+ });
18
+ expect(props.VersioningConfiguration).toBeUndefined();
19
+ expect(props.WebsiteConfiguration).toBeUndefined();
20
+
21
+ const attrs = (c.bucket as any).attributes;
22
+ expect(attrs.DeletionPolicy).toBe("Retain");
23
+ });
24
+
25
+ test("bucketName threads through, versioning enables Status: Enabled", () => {
26
+ const c = BucketDeployment({ bucketName: "my-content-bucket", versioned: true });
27
+ const props = (c.bucket as any).props;
28
+ expect(props.BucketName).toBe("my-content-bucket");
29
+ expect(props.VersioningConfiguration.props).toEqual({ Status: "Enabled" });
30
+ });
31
+
32
+ test("removalPolicy: destroy maps to DeletionPolicy: Delete", () => {
33
+ const c = BucketDeployment({ removalPolicy: "destroy" });
34
+ expect((c.bucket as any).attributes.DeletionPolicy).toBe("Delete");
35
+ });
36
+
37
+ test("website shape: opens the public-access block, sets WebsiteConfiguration, and attaches a scoped public-read bucketPolicy", () => {
38
+ const c = BucketDeployment({
39
+ website: { indexDocument: "index.html", errorDocument: "error.html" },
40
+ }) as unknown as { bucket: any; bucketPolicy: any };
41
+
42
+ const bucketProps = c.bucket.props;
43
+ expect(bucketProps.PublicAccessBlockConfiguration.props).toEqual({
44
+ BlockPublicAcls: false,
45
+ BlockPublicPolicy: false,
46
+ IgnorePublicAcls: false,
47
+ RestrictPublicBuckets: false,
48
+ });
49
+ expect(bucketProps.WebsiteConfiguration.props).toEqual({
50
+ IndexDocument: "index.html",
51
+ ErrorDocument: "error.html",
52
+ });
53
+
54
+ expect(c.bucketPolicy).toBeDefined();
55
+ expect(c.bucketPolicy.props.Bucket).toBe(c.bucket); // bucket.Ref is the resource instance itself
56
+ const statement = c.bucketPolicy.props.PolicyDocument.Statement[0];
57
+ expect(statement).toEqual({ Effect: "Allow", Principal: "*", Action: "s3:GetObject", Resource: expect.anything() });
58
+ });
59
+
60
+ test("website without errorDocument omits it rather than writing undefined", () => {
61
+ const c = BucketDeployment({ website: { indexDocument: "index.html" } });
62
+ const websiteProps = (c.bucket as any).props.WebsiteConfiguration.props;
63
+ expect(websiteProps).toEqual({ IndexDocument: "index.html" });
64
+ expect("ErrorDocument" in websiteProps).toBe(false);
65
+ });
66
+
67
+ test("tags pass straight through to the bucket", () => {
68
+ const c = BucketDeployment({ tags: [{ Key: "env", Value: "prod" }] });
69
+ expect((c.bucket as any).props.Tags).toEqual([{ Key: "env", Value: "prod" }]);
70
+ });
71
+
72
+ test("defaults escape hatch reaches the bucket (e.g. object-lock)", () => {
73
+ const c = BucketDeployment({ defaults: { bucket: { ObjectLockEnabled: true } } });
74
+ expect((c.bucket as any).props.ObjectLockEnabled).toBe(true);
75
+ });
76
+
77
+ test("defaults escape hatch reaches the bucket policy", () => {
78
+ const c = BucketDeployment({
79
+ website: { indexDocument: "index.html" },
80
+ defaults: { bucketPolicy: { Bucket: "explicit-bucket-name" } },
81
+ }) as unknown as { bucketPolicy: any };
82
+ expect(c.bucketPolicy.props.Bucket).toBe("explicit-bucket-name");
83
+ });
84
+ });
@@ -0,0 +1,153 @@
1
+ /**
2
+ * BucketDeployment — the seeded-bucket shape: an S3 bucket sized to receive
3
+ * deploy-time content, encrypted and access-blocked by default, its
4
+ * deletion behaviour and (optionally) its public-read policy declared up
5
+ * front rather than left to CloudFormation's own defaults.
6
+ *
7
+ * `s3deploy.BucketDeployment` is the most-used CDK pattern across the
8
+ * aws-bench corpus (37 instantiations across 8 apps, chant#1139) and it
9
+ * names two things CDK bundles together that chant keeps apart on purpose:
10
+ *
11
+ * - The bucket itself: `AWS::S3::Bucket` plus its encryption, access-block
12
+ * and (for the static-site shape) website/public-policy configuration.
13
+ * That is a fixed set of CloudFormation resources, so it is what this
14
+ * composite declares — the same shape `LambdaS3` (../composites) already
15
+ * gives a bucket, minus the Lambda trigger wiring.
16
+ * - Uploading local files into it at deploy time. CDK does this with a
17
+ * Lambda-backed custom resource (`cr.AwsCustomResource`, the same family
18
+ * the epic notes maps to chant Ops rather than composites — chant has no
19
+ * CloudFormation custom-resource machinery, and inventing one just to
20
+ * reproduce a `PutObject` loop would be new plumbing for a solved
21
+ * problem). chant already ships this as a deploy-time **capability**,
22
+ * `s3-sync` (`../components/apply.ts`'s `createS3SyncCapability`, backed
23
+ * by the real `aws s3 sync`/`aws s3 cp` CLI through `CloudExecutor`, with
24
+ * the typed step-builder `s3Sync` in `../components/builders.ts`) — it
25
+ * was defined for exactly this and, before this composite, had no
26
+ * composite pairing it with a bucket to sync into. A component composes
27
+ * the two:
28
+ *
29
+ * const seeded = BucketDeployment({ bucketName: Sub`${AWS.StackName}-site` });
30
+ * // ...component `deploy` phase:
31
+ * s3Sync({ from: "archive:site-build", to: Sub`s3://${seeded.bucket.Ref}` })
32
+ *
33
+ * That split — graph-time declaration here, deploy-time action in the
34
+ * component layer — is the existing precedent (`s3-sync` shipped in #566
35
+ * unused by any composite until this one), not a new one invented for this
36
+ * composite.
37
+ *
38
+ * "Cleanup" (the epic's third word) is `removalPolicy`, CloudFormation's own
39
+ * `DeletionPolicy` on the bucket resource. CDK's `autoDeleteObjects` — the
40
+ * Lambda-backed hook that empties a bucket before CloudFormation is allowed
41
+ * to delete it — has the same custom-resource shape as content upload and
42
+ * the same answer: out of scope here, and worth an `s3 rm --recursive`
43
+ * step ahead of a stack teardown in the component that owns the deploy.
44
+ */
45
+ import { Composite, mergeDefaults, type Value } from "@intentius/chant";
46
+ import {
47
+ Bucket,
48
+ Bucket_BucketEncryption,
49
+ Bucket_ServerSideEncryptionRule,
50
+ Bucket_ServerSideEncryptionByDefault,
51
+ Bucket_PublicAccessBlockConfiguration,
52
+ Bucket_VersioningConfiguration,
53
+ Bucket_WebsiteConfiguration,
54
+ S3BucketPolicy,
55
+ } from "../generated";
56
+ import { Sub } from "../intrinsics";
57
+
58
+ export interface BucketDeploymentProps {
59
+ /** `Value<string>`: a name is routinely built with `Sub`/`Ref` (#1366). Omitted lets CloudFormation generate one. */
60
+ bucketName?: Value<string>;
61
+ /** Enable S3 object versioning. Default: false. */
62
+ versioned?: boolean;
63
+ /**
64
+ * Static-website hosting: sets `WebsiteConfiguration` and — since a
65
+ * website bucket is read by anonymous visitors, not IAM principals —
66
+ * opens the public-access block and attaches a public-read
67
+ * `S3BucketPolicy` scoped to `s3:GetObject`. Omitted, the bucket stays
68
+ * fully private, matching every other composite bucket's default
69
+ * (`LambdaS3`, ../composites).
70
+ */
71
+ website?: {
72
+ indexDocument: string;
73
+ errorDocument?: string;
74
+ };
75
+ /** What CloudFormation does to the bucket when the stack (or the resource) is removed. Default: "retain" — CloudFormation's own default, and the safe one for a bucket a deploy has just put content into. */
76
+ removalPolicy?: "retain" | "destroy";
77
+ tags?: Array<{ Key: string; Value: string }>;
78
+ defaults?: {
79
+ bucket?: Partial<ConstructorParameters<typeof Bucket>[0]>;
80
+ bucketPolicy?: Partial<ConstructorParameters<typeof S3BucketPolicy>[0]>;
81
+ };
82
+ }
83
+
84
+ export type BucketDeploymentResult =
85
+ | { bucket: InstanceType<typeof Bucket> }
86
+ | { bucket: InstanceType<typeof Bucket>; bucketPolicy: InstanceType<typeof S3BucketPolicy> };
87
+
88
+ export const BucketDeployment = Composite<BucketDeploymentProps>((props) => {
89
+ const { defaults } = props;
90
+ const publicRead = props.website !== undefined;
91
+
92
+ const encryptionDefault = new Bucket_ServerSideEncryptionByDefault({ SSEAlgorithm: "AES256" });
93
+ const encryptionRule = new Bucket_ServerSideEncryptionRule({ ServerSideEncryptionByDefault: encryptionDefault });
94
+ const bucketEncryption = new Bucket_BucketEncryption({ ServerSideEncryptionConfiguration: [encryptionRule] });
95
+
96
+ // A ternary, not an `if`, so the block-scoped `new` stays an expression
97
+ // (EVL002) — the same reasoning eks-cluster.ts's addon slots document.
98
+ const publicAccessBlock = new Bucket_PublicAccessBlockConfiguration(
99
+ publicRead
100
+ ? { BlockPublicAcls: false, BlockPublicPolicy: false, IgnorePublicAcls: false, RestrictPublicBuckets: false }
101
+ : { BlockPublicAcls: true, BlockPublicPolicy: true, IgnorePublicAcls: true, RestrictPublicBuckets: true },
102
+ );
103
+
104
+ const versioningConfiguration = props.versioned
105
+ ? new Bucket_VersioningConfiguration({ Status: "Enabled" })
106
+ : undefined;
107
+
108
+ const websiteConfiguration = props.website
109
+ ? new Bucket_WebsiteConfiguration({
110
+ IndexDocument: props.website.indexDocument,
111
+ ...(props.website.errorDocument ? { ErrorDocument: props.website.errorDocument } : {}),
112
+ })
113
+ : undefined;
114
+
115
+ const bucket = new Bucket(
116
+ mergeDefaults(
117
+ {
118
+ BucketName: props.bucketName,
119
+ BucketEncryption: bucketEncryption,
120
+ PublicAccessBlockConfiguration: publicAccessBlock,
121
+ ...(versioningConfiguration ? { VersioningConfiguration: versioningConfiguration } : {}),
122
+ ...(websiteConfiguration ? { WebsiteConfiguration: websiteConfiguration } : {}),
123
+ Tags: props.tags,
124
+ },
125
+ defaults?.bucket,
126
+ ),
127
+ { DeletionPolicy: props.removalPolicy === "destroy" ? "Delete" : "Retain" },
128
+ );
129
+
130
+ const bucketPolicy = publicRead
131
+ ? new S3BucketPolicy(
132
+ mergeDefaults(
133
+ {
134
+ Bucket: bucket.Ref,
135
+ PolicyDocument: {
136
+ Version: "2012-10-17",
137
+ Statement: [
138
+ { Effect: "Allow", Principal: "*", Action: "s3:GetObject", Resource: Sub`${bucket.Arn}/*` },
139
+ ],
140
+ },
141
+ },
142
+ defaults?.bucketPolicy,
143
+ ),
144
+ )
145
+ : undefined;
146
+
147
+ // Spread rather than a ternary between two object shapes (EksCluster's
148
+ // addon slots, ../eks-cluster.ts, hit the same union-return typing wall):
149
+ // a key that is sometimes present, never a key whose value is sometimes
150
+ // `undefined` — `CompositeMembers`' index signature requires `Declarable`,
151
+ // not `Declarable | undefined`.
152
+ return { bucket, ...(bucketPolicy ? { bucketPolicy } : {}) };
153
+ }, "BucketDeployment");