@intentius/chant-lexicon-aws 0.46.0 → 0.49.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 (114) 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/composites/agentcore-agent.d.ts +26 -1
  4. package/dist/composites/agentcore-agent.d.ts.map +1 -1
  5. package/dist/composites/bucket-deployment.d.ts +82 -0
  6. package/dist/composites/bucket-deployment.d.ts.map +1 -0
  7. package/dist/composites/dynamodb-table.d.ts +45 -0
  8. package/dist/composites/dynamodb-table.d.ts.map +1 -0
  9. package/dist/composites/ec2-instance-bundle.d.ts +61 -0
  10. package/dist/composites/ec2-instance-bundle.d.ts.map +1 -0
  11. package/dist/composites/ecr-repository.d.ts +49 -0
  12. package/dist/composites/ecr-repository.d.ts.map +1 -0
  13. package/dist/composites/fargate-service.d.ts +16 -0
  14. package/dist/composites/fargate-service.d.ts.map +1 -1
  15. package/dist/composites/index.d.ts +16 -2
  16. package/dist/composites/index.d.ts.map +1 -1
  17. package/dist/composites/monitoring-stack.d.ts +99 -0
  18. package/dist/composites/monitoring-stack.d.ts.map +1 -0
  19. package/dist/composites/nlb-service.d.ts +48 -0
  20. package/dist/composites/nlb-service.d.ts.map +1 -0
  21. package/dist/composites/step-functions-workflow.d.ts +36 -0
  22. package/dist/composites/step-functions-workflow.d.ts.map +1 -0
  23. package/dist/deep-observe.d.ts +45 -0
  24. package/dist/deep-observe.d.ts.map +1 -1
  25. package/dist/deep-topology.d.ts +104 -0
  26. package/dist/deep-topology.d.ts.map +1 -0
  27. package/dist/effect-receipt-row.d.ts +92 -0
  28. package/dist/effect-receipt-row.d.ts.map +1 -0
  29. package/dist/index.d.ts +6 -2
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/integrity.json +7 -4
  32. package/dist/lint/audit-catalog.d.ts.map +1 -1
  33. package/dist/lint/post-synth/cf-refs.d.ts +17 -0
  34. package/dist/lint/post-synth/cf-refs.d.ts.map +1 -1
  35. package/dist/lint/post-synth/index.d.ts.map +1 -1
  36. package/dist/lint/post-synth/waw061.d.ts +19 -0
  37. package/dist/lint/post-synth/waw061.d.ts.map +1 -0
  38. package/dist/lint/post-synth/waw062.d.ts +19 -0
  39. package/dist/lint/post-synth/waw062.d.ts.map +1 -0
  40. package/dist/lint/post-synth/waw063.d.ts +35 -0
  41. package/dist/lint/post-synth/waw063.d.ts.map +1 -0
  42. package/dist/manifest.json +1 -1
  43. package/dist/okf/index.md +3 -0
  44. package/dist/okf/rules/WAW061.md +17 -0
  45. package/dist/okf/rules/WAW062.md +27 -0
  46. package/dist/okf/rules/WAW063.md +19 -0
  47. package/dist/okf/types/Action.md +1 -0
  48. package/dist/okf/types/AutoScalingGroup.md +1 -0
  49. package/dist/okf/types/Bucket.md +1 -0
  50. package/dist/okf/types/ECRRepository.md +1 -0
  51. package/dist/okf/types/Function.md +1 -0
  52. package/dist/okf/types/IamPolicy.md +1 -0
  53. package/dist/okf/types/LaunchTemplate.md +1 -0
  54. package/dist/okf/types/LoadBalancer.md +1 -0
  55. package/dist/okf/types/ManagedPolicy.md +2 -0
  56. package/dist/okf/types/Map.md +1 -0
  57. package/dist/okf/types/Role.md +2 -0
  58. package/dist/okf/types/SecurityGroup.md +1 -0
  59. package/dist/okf/types/StateMachine.md +4 -0
  60. package/dist/okf/types/Subnet.md +4 -0
  61. package/dist/okf/types/Table.md +1 -0
  62. package/dist/okf/types/Type.md +3 -0
  63. package/dist/okf/types/Vpc.md +1 -0
  64. package/dist/op/activities/index.d.ts +6 -0
  65. package/dist/op/activities/index.d.ts.map +1 -1
  66. package/dist/plugin.d.ts.map +1 -1
  67. package/dist/receipt-store.d.ts +109 -0
  68. package/dist/receipt-store.d.ts.map +1 -0
  69. package/dist/rules/cf-refs.ts +40 -0
  70. package/dist/rules/waw061.ts +67 -0
  71. package/dist/rules/waw062.ts +0 -0
  72. package/dist/rules/waw063.ts +0 -0
  73. package/dist/serializer.d.ts.map +1 -1
  74. package/dist/skills/chant-aws-carve-terraform.md +26 -23
  75. package/package.json +3 -3
  76. package/src/api/read-client.test.ts +9 -0
  77. package/src/api/read-client.ts +15 -2
  78. package/src/composites/agentcore-agent.ts +35 -13
  79. package/src/composites/bucket-deployment.test.ts +84 -0
  80. package/src/composites/bucket-deployment.ts +153 -0
  81. package/src/composites/composites.test.ts +325 -0
  82. package/src/composites/dynamodb-table.test.ts +154 -0
  83. package/src/composites/dynamodb-table.ts +124 -0
  84. package/src/composites/ec2-instance-bundle.ts +134 -0
  85. package/src/composites/ecr-repository.test.ts +118 -0
  86. package/src/composites/ecr-repository.ts +117 -0
  87. package/src/composites/fargate-service.ts +19 -3
  88. package/src/composites/index.ts +21 -2
  89. package/src/composites/monitoring-stack.test.ts +130 -0
  90. package/src/composites/monitoring-stack.ts +164 -0
  91. package/src/composites/nlb-service.ts +117 -0
  92. package/src/composites/step-functions-workflow.ts +127 -0
  93. package/src/deep-observe.test.ts +201 -1
  94. package/src/deep-observe.ts +204 -0
  95. package/src/deep-topology.test.ts +392 -0
  96. package/src/deep-topology.ts +237 -0
  97. package/src/effect-receipt-row.test.ts +164 -0
  98. package/src/effect-receipt-row.ts +147 -0
  99. package/src/index.ts +28 -7
  100. package/src/lint/audit-catalog.ts +6 -0
  101. package/src/lint/post-synth/cf-refs.ts +40 -0
  102. package/src/lint/post-synth/index.ts +6 -0
  103. package/src/lint/post-synth/waw061.test.ts +99 -0
  104. package/src/lint/post-synth/waw061.ts +67 -0
  105. package/src/lint/post-synth/waw062.test.ts +109 -0
  106. package/src/lint/post-synth/waw062.ts +0 -0
  107. package/src/lint/post-synth/waw063.test.ts +220 -0
  108. package/src/lint/post-synth/waw063.ts +0 -0
  109. package/src/op/activities/index.ts +15 -0
  110. package/src/plugin.ts +41 -10
  111. package/src/receipt-store.test.ts +204 -0
  112. package/src/receipt-store.ts +315 -0
  113. package/src/serializer.ts +105 -2
  114. package/src/skills/chant-aws-carve-terraform.md +26 -23
@@ -57,3 +57,4 @@ resource_type: AWS::AutoScaling::AutoScalingGroup
57
57
  ## Governed by
58
58
 
59
59
  - [WAW036](/rules/WAW036.md): Non-ASCII characters in EC2/IAM/CW string properties — rejected at changeset time
60
+ - [WAW062](/rules/WAW062.md): Duplicate export name or explicit resource name within a template — fails at deploy time
@@ -64,3 +64,4 @@ resource_type: AWS::S3::Bucket
64
64
  - [WAW018](/rules/WAW018.md): S3 bucket missing public access block — all public access should be blocked
65
65
  - [WAW042](/rules/WAW042.md): S3 bucket missing a TLS-only bucket policy — deny requests over plaintext
66
66
  - [WAW059](/rules/WAW059.md): Wildcard Resource where the declared graph enumerates the touched set — tighten to the consumers' declared Arns
67
+ - [WAW062](/rules/WAW062.md): Duplicate export name or explicit resource name within a template — fails at deploy time
@@ -31,3 +31,4 @@ resource_type: AWS::ECR::Repository
31
31
 
32
32
  - [WAW053](/rules/WAW053.md): ECR repository does not scan images on push
33
33
  - [WAW054](/rules/WAW054.md): ECR repository does not have immutable image tags
34
+ - [WAW062](/rules/WAW062.md): Duplicate export name or explicit resource name within a template — fails at deploy time
@@ -56,3 +56,4 @@ resource_type: AWS::Lambda::Function
56
56
  - [WAW011](/rules/WAW011.md): Deprecated Lambda runtime — flags deprecated or approaching-EOL Lambda runtimes
57
57
  - [WAW022](/rules/WAW022.md): Lambda function is not configured with a VPC — consider adding VpcConfig for network isolation
58
58
  - [WAW036](/rules/WAW036.md): Non-ASCII characters in EC2/IAM/CW string properties — rejected at changeset time
59
+ - [WAW062](/rules/WAW062.md): Duplicate export name or explicit resource name within a template — fails at deploy time
@@ -26,3 +26,4 @@ resource_type: AWS::IAM::Policy
26
26
  - [WAW020](/rules/WAW020.md): IAM policy uses wildcard Action — use specific actions following least privilege
27
27
  - [WAW059](/rules/WAW059.md): Wildcard Resource where the declared graph enumerates the touched set — tighten to the consumers' declared Arns
28
28
  - [WAW060](/rules/WAW060.md): IAM policy attached to no principal — it grants nothing
29
+ - [WAW063](/rules/WAW063.md): IAM policy denies an action another attached policy on the same role allows — explicit Deny wins, runtime 403
@@ -27,3 +27,4 @@ resource_type: AWS::EC2::LaunchTemplate
27
27
  ## Governed by
28
28
 
29
29
  - [WAW036](/rules/WAW036.md): Non-ASCII characters in EC2/IAM/CW string properties — rejected at changeset time
30
+ - [WAW062](/rules/WAW062.md): Duplicate export name or explicit resource name within a template — fails at deploy time
@@ -41,3 +41,4 @@ resource_type: AWS::ElasticLoadBalancingV2::LoadBalancer
41
41
  ## Governed by
42
42
 
43
43
  - [WAW024](/rules/WAW024.md): Application Load Balancer does not have access logging enabled
44
+ - [WAW062](/rules/WAW062.md): Duplicate export name or explicit resource name within a template — fails at deploy time
@@ -42,3 +42,5 @@ resource_type: AWS::IAM::ManagedPolicy
42
42
  - [WAW020](/rules/WAW020.md): IAM policy uses wildcard Action — use specific actions following least privilege
43
43
  - [WAW059](/rules/WAW059.md): Wildcard Resource where the declared graph enumerates the touched set — tighten to the consumers' declared Arns
44
44
  - [WAW060](/rules/WAW060.md): IAM policy attached to no principal — it grants nothing
45
+ - [WAW062](/rules/WAW062.md): Duplicate export name or explicit resource name within a template — fails at deploy time
46
+ - [WAW063](/rules/WAW063.md): IAM policy denies an action another attached policy on the same role allows — explicit Deny wins, runtime 403
@@ -35,3 +35,4 @@ resource_type: AWS::Location::Map
35
35
  - [WAW016](/rules/WAW016.md): Deprecated property usage — flags properties marked as deprecated in the CloudFormation Registry
36
36
  - [WAW036](/rules/WAW036.md): Non-ASCII characters in EC2/IAM/CW string properties — rejected at changeset time
37
37
  - [WAW059](/rules/WAW059.md): Wildcard Resource where the declared graph enumerates the touched set — tighten to the consumers' declared Arns
38
+ - [WAW062](/rules/WAW062.md): Duplicate export name or explicit resource name within a template — fails at deploy time
@@ -32,3 +32,5 @@ resource_type: AWS::IAM::Role
32
32
  - [WAW020](/rules/WAW020.md): IAM policy uses wildcard Action — use specific actions following least privilege
33
33
  - [WAW036](/rules/WAW036.md): Non-ASCII characters in EC2/IAM/CW string properties — rejected at changeset time
34
34
  - [WAW059](/rules/WAW059.md): Wildcard Resource where the declared graph enumerates the touched set — tighten to the consumers' declared Arns
35
+ - [WAW062](/rules/WAW062.md): Duplicate export name or explicit resource name within a template — fails at deploy time
36
+ - [WAW063](/rules/WAW063.md): IAM policy denies an action another attached policy on the same role allows — explicit Deny wins, runtime 403
@@ -29,3 +29,4 @@ resource_type: AWS::EC2::SecurityGroup
29
29
  - [WAW019](/rules/WAW019.md): Security group allows unrestricted ingress on sensitive ports (SSH, RDP, database)
30
30
  - [WAW036](/rules/WAW036.md): Non-ASCII characters in EC2/IAM/CW string properties — rejected at changeset time
31
31
  - [WAW049](/rules/WAW049.md): Security group allows unrestricted ingress on a port other than ALB:80/443
32
+ - [WAW062](/rules/WAW062.md): Duplicate export name or explicit resource name within a template — fails at deploy time
@@ -30,3 +30,7 @@ resource_type: AWS::StepFunctions::StateMachine
30
30
  - `Arn`
31
31
  - `Name`
32
32
  - `StateMachineRevisionId`
33
+
34
+ ## Governed by
35
+
36
+ - [WAW062](/rules/WAW062.md): Duplicate export name or explicit resource name within a template — fails at deploy time
@@ -38,3 +38,7 @@ resource_type: AWS::EC2::Subnet
38
38
  - `Ipv6CidrBlocks`
39
39
  - `NetworkAclAssociationId`
40
40
  - `SubnetId`
41
+
42
+ ## Governed by
43
+
44
+ - [WAW061](/rules/WAW061.md): Subnet CidrBlock falls outside its VPC's CidrBlock — fails at deploy time
@@ -43,3 +43,4 @@ 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
@@ -75,3 +75,6 @@ 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
@@ -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"}
@@ -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
  */
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAsC,MAAM,6BAA6B,CAAC;AAkZlG;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,UAoE3B,CAAC"}
1
+ {"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAsC,MAAM,6BAA6B,CAAC;AAiflG;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,UA4E3B,CAAC"}
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  skill: chant-aws-carve-terraform
3
- description: Demo carving a resource out of Terraform into native chant — advise, emit, bridge, apply — fully offline
3
+ description: Demo carving a resource out of Terraform into native chant — advise, emit, audit, bridge, apply — fully offline
4
4
  user-invocable: true
5
5
  ---
6
6
 
@@ -15,7 +15,7 @@ graduation.
15
15
  ## When to use
16
16
 
17
17
  - Someone asks "how does chant move things off Terraform?"
18
- - You want to show the advise → emit → bridge → apply loop end to end.
18
+ - You want to show the advise → emit → audit → bridge → apply loop end to end.
19
19
  - You are evaluating whether a Terraform estate is worth carving.
20
20
 
21
21
  ## Preconditions
@@ -27,7 +27,7 @@ graduation.
27
27
  ## The fastest path: run the bundled demo
28
28
 
29
29
  The `examples/terraform-carve-out` example ships a runnable estate + state and a
30
- script that runs all four steps with commentary:
30
+ script that runs all five steps with commentary:
31
31
 
32
32
  ```bash
33
33
  cd examples/terraform-carve-out
@@ -54,28 +54,30 @@ From `examples/terraform-carve-out`, with `TF=./terraform`:
54
54
  --state ./terraform/terraform.tfstate --output ./carveout
55
55
  ```
56
56
  Show `./carveout/src/assets.ts` — a real `new Bucket({ BucketName, Tags })` with
57
- CloudFormation-style properties mapped from the Terraform state attributes.
58
- Emit also scaffolds `./carveout` into a buildable chant project
59
- (`chant.config.ts`, `package.json`), so `npm install && npm run build` works
60
- there as-is. Explain: a Terraform-managed resource is not in any
61
- CloudFormation stack, so the correct source of its live shape is the state
62
- file, not a cloud read.
63
-
64
- 3. **Lint the inherited resource — optional, offer it.**
57
+ CloudFormation-style properties mapped from the Terraform state attributes,
58
+ and the `aws_s3_bucket_versioning` sub-resource folded into
59
+ `VersioningConfiguration`. Emit also scaffolds `./carveout` into a buildable
60
+ chant project (`chant.config.ts`, `package.json`) and persists a carve
61
+ manifest (`*.carve.json`) bridge and apply read the target from it, so
62
+ `--select` is only needed once. Explain: a Terraform-managed resource is not
63
+ in any CloudFormation stack, so the correct source of its live shape is the
64
+ state file, not a cloud read.
65
+
66
+ 3. **Audit the inherited resource.**
65
67
  ```bash
66
- chant lint ./carveout/src --lexicon aws
68
+ chant build ./carveout/src --lexicon aws
67
69
  ```
68
- After emit, offer to lint the carved source. chant audits the resource you
69
- inherited from Terraform against the AWS lexicon's rules. Findings have a
70
- severity: `error` (must fix before `chant build` will emit e.g. an S3
71
- bucket with no public-access block) and `warning`/`info` (advisory — e.g.
72
- DynamoDB point-in-time recovery). This is a feature of carving: chant
73
- immediately tells the person what is wrong with what they adopted. Whether to
74
- fix an advisory finding is their call; errors block the build until resolved.
70
+ The first build fails, deliberately: the post-synth audit refuses the
71
+ adopted bucket because Terraform managed it without a public-access block or
72
+ a TLS-only policy. This is a feature of carving: chant immediately tells the
73
+ person what is wrong with what they adopted. Show the fix (add
74
+ `PublicAccessBlockConfiguration` and a companion `S3BucketPolicy` with an
75
+ `aws:SecureTransport = false` Deny the tutorial page has the exact code),
76
+ then re-run the build and show the valid CloudFormation template.
75
77
 
76
78
  4. **Bridge — patch the surviving Terraform.**
77
79
  ```bash
78
- chant carve bridge --from ./terraform --select aws_s3_bucket.assets --output ./carveout
80
+ chant carve bridge --from ./terraform --output ./carveout
79
81
  ```
80
82
  Show the generated `data "aws_s3_bucket" "assets"` and the rewired survivor.
81
83
  Emphasize it is dry-run: nothing in `./terraform` changed. `--apply-rewrites`
@@ -83,11 +85,12 @@ From `examples/terraform-carve-out`, with `TF=./terraform`:
83
85
 
84
86
  5. **Apply — graduation plan.**
85
87
  ```bash
86
- chant carve apply --from ./terraform --select aws_s3_bucket.assets --env prod --stack assets
88
+ chant carve apply --from ./terraform --output ./carveout --env prod --stack assets
87
89
  ```
88
90
  Show the ownership marker (`chant:managed-by/stack/env`) and the finalized
89
- runbook. Stress this makes no cloud call the apply is the person's own
90
- lifecycle; chant just plans it.
91
+ runbook. `--write-source` additionally stamps the marker tags into the
92
+ emitted source. Stress this makes no cloud call — the apply is the person's
93
+ own lifecycle; chant just plans it.
91
94
 
92
95
  ## Key points to land
93
96
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-aws",
3
- "version": "0.46.0",
3
+ "version": "0.49.0",
4
4
  "description": "AWS CloudFormation lexicon for chant — declarative IaC in TypeScript",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -73,14 +73,14 @@
73
73
  },
74
74
  "dependencies": {
75
75
  "fflate": "^0.8.2",
76
- "js-yaml": "^4.1.0"
76
+ "js-yaml": "^4.3.1"
77
77
  },
78
78
  "devDependencies": {
79
79
  "@intentius/chant": "*",
80
80
  "typescript": "^5.9.3"
81
81
  },
82
82
  "peerDependencies": {
83
- "@intentius/chant": "^0.46.0",
83
+ "@intentius/chant": "^0.49.0",
84
84
  "typescript": "^5.9.3"
85
85
  }
86
86
  }
@@ -198,6 +198,15 @@ describe("Cloud Control", () => {
198
198
  expect(await listResources("AWS::S3::Bucket", { http })).toEqual([]);
199
199
  });
200
200
 
201
+ test("listResources scopes a child listing with ResourceModel, JSON-encoded as the API takes it", async () => {
202
+ const { http, calls } = recording(() => respond(JSON.stringify({ ResourceDescriptions: [] })));
203
+ await listResources("AWS::IAM::RolePolicy", { http }, { RoleName: "app-role" });
204
+ expect(JSON.parse(calls[0].body)).toEqual({
205
+ TypeName: "AWS::IAM::RolePolicy",
206
+ ResourceModel: JSON.stringify({ RoleName: "app-role" }),
207
+ });
208
+ });
209
+
201
210
  test("parseResourceDescription refuses anything that is not a model object", () => {
202
211
  expect(parseResourceDescription(null)).toBeNull();
203
212
  expect(parseResourceDescription({ Identifier: "a" })).toBeNull();
@@ -434,17 +434,30 @@ export async function getResource(
434
434
  return parseResourceDescription(body.ResourceDescription);
435
435
  }
436
436
 
437
- /** `ListResources` — every live resource of one type, paginated to exhaustion. */
437
+ /**
438
+ * `ListResources` — every live resource of one type, paginated to exhaustion.
439
+ *
440
+ * `resourceModel` is Cloud Control's `ResourceModel`: the scope a child-typed
441
+ * listing requires (`{ RoleName }` for `AWS::IAM::RolePolicy`,
442
+ * `{ TopicArn }` for `AWS::SNS::Subscription`). The API takes it as a JSON
443
+ * *string*, the same double encoding `GetResource` answers with; this takes
444
+ * the object and encodes it so no caller repeats that detail.
445
+ */
438
446
  export async function listResources(
439
447
  typeName: string,
440
448
  options: AwsReadClientOptions = {},
449
+ resourceModel?: Record<string, unknown>,
441
450
  ): Promise<CloudControlDescription[]> {
442
451
  const out: CloudControlDescription[] = [];
443
452
  let nextToken: string | undefined;
444
453
  do {
445
454
  const body = await cloudControl(
446
455
  "ListResources",
447
- { TypeName: typeName, ...(nextToken ? { NextToken: nextToken } : {}) },
456
+ {
457
+ TypeName: typeName,
458
+ ...(resourceModel ? { ResourceModel: JSON.stringify(resourceModel) } : {}),
459
+ ...(nextToken ? { NextToken: nextToken } : {}),
460
+ },
448
461
  options,
449
462
  );
450
463
  const descriptions = Array.isArray(body.ResourceDescriptions) ? body.ResourceDescriptions : [];
@@ -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);