@venturekit/infra 0.0.0-dev.20260514025219 → 0.0.0-dev.20260515022321
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdk/app-stack.d.ts +330 -0
- package/dist/cdk/app-stack.d.ts.map +1 -0
- package/dist/cdk/app-stack.js +875 -0
- package/dist/cdk/app-stack.js.map +1 -0
- package/dist/cdk/config-stack.d.ts +104 -0
- package/dist/cdk/config-stack.d.ts.map +1 -0
- package/dist/cdk/config-stack.js +161 -0
- package/dist/cdk/config-stack.js.map +1 -0
- package/dist/cdk/data-stack.d.ts +198 -0
- package/dist/cdk/data-stack.d.ts.map +1 -0
- package/dist/cdk/data-stack.js +511 -0
- package/dist/cdk/data-stack.js.map +1 -0
- package/dist/cdk/edge-stack.d.ts +91 -0
- package/dist/cdk/edge-stack.d.ts.map +1 -0
- package/dist/cdk/edge-stack.js +364 -0
- package/dist/cdk/edge-stack.js.map +1 -0
- package/dist/cdk/identity-stack.d.ts +98 -0
- package/dist/cdk/identity-stack.d.ts.map +1 -0
- package/dist/cdk/identity-stack.js +254 -0
- package/dist/cdk/identity-stack.js.map +1 -0
- package/dist/cdk/index.d.ts +16 -0
- package/dist/cdk/index.d.ts.map +1 -1
- package/dist/cdk/index.js +17 -0
- package/dist/cdk/index.js.map +1 -1
- package/dist/cdk/messaging-stack.d.ts +109 -0
- package/dist/cdk/messaging-stack.d.ts.map +1 -0
- package/dist/cdk/messaging-stack.js +320 -0
- package/dist/cdk/messaging-stack.js.map +1 -0
- package/dist/cdk/network-stack.d.ts +87 -0
- package/dist/cdk/network-stack.d.ts.map +1 -0
- package/dist/cdk/network-stack.js +265 -0
- package/dist/cdk/network-stack.js.map +1 -0
- package/dist/cdk/shared/cross-stack-refs.d.ts +278 -0
- package/dist/cdk/shared/cross-stack-refs.d.ts.map +1 -0
- package/dist/cdk/shared/cross-stack-refs.js +326 -0
- package/dist/cdk/shared/cross-stack-refs.js.map +1 -0
- package/dist/cdk/shared/lambda-helpers.d.ts +82 -0
- package/dist/cdk/shared/lambda-helpers.d.ts.map +1 -0
- package/dist/cdk/shared/lambda-helpers.js +201 -0
- package/dist/cdk/shared/lambda-helpers.js.map +1 -0
- package/dist/cdk/shared/ssm-keys.d.ts +460 -0
- package/dist/cdk/shared/ssm-keys.d.ts.map +1 -0
- package/dist/cdk/shared/ssm-keys.js +291 -0
- package/dist/cdk/shared/ssm-keys.js.map +1 -0
- package/dist/cdk/stack.d.ts +3 -11
- package/dist/cdk/stack.d.ts.map +1 -1
- package/dist/cdk/stack.js +184 -41
- package/dist/cdk/stack.js.map +1 -1
- package/package.json +2 -2
package/dist/cdk/stack.js
CHANGED
|
@@ -131,26 +131,11 @@ function djb2Hash(input) {
|
|
|
131
131
|
}
|
|
132
132
|
return h >>> 0;
|
|
133
133
|
}
|
|
134
|
-
/**
|
|
135
|
-
* Smallest power of two that is `>= n`. Used to pick the nested-stack
|
|
136
|
-
* bucket count, so that growing past a threshold doubles the count
|
|
137
|
-
* rather than incrementing it — the doubled-size hash bucketing only
|
|
138
|
-
* relocates ~half the routes, and growth is geometric (rare events)
|
|
139
|
-
* rather than additive (every deploy after a route added).
|
|
140
|
-
*/
|
|
141
|
-
export function nextPowerOfTwo(n) {
|
|
142
|
-
if (n <= 1)
|
|
143
|
-
return 1;
|
|
144
|
-
let p = 1;
|
|
145
|
-
while (p < n)
|
|
146
|
-
p <<= 1;
|
|
147
|
-
return p;
|
|
148
|
-
}
|
|
149
134
|
/**
|
|
150
135
|
* Map a route slug to a nested-stack slot index in `[0, numSlots)`.
|
|
151
|
-
* `numSlots` MUST be a power of two so the modulo
|
|
152
|
-
* hash output evenly. Exported for unit tests
|
|
153
|
-
* stability across route-set changes.
|
|
136
|
+
* `numSlots` MUST be a power of two so the bitwise-AND modulo
|
|
137
|
+
* distributes the hash output evenly. Exported for unit tests
|
|
138
|
+
* asserting bucketing stability across route-set changes.
|
|
154
139
|
*/
|
|
155
140
|
export function stableSlotFor(slug, numSlots) {
|
|
156
141
|
return djb2Hash(slug) & (numSlots - 1);
|
|
@@ -833,6 +818,19 @@ export class VentureStack extends cdk.Stack {
|
|
|
833
818
|
},
|
|
834
819
|
disableExecuteApiEndpoint: false,
|
|
835
820
|
});
|
|
821
|
+
// Well-Architected: Reliability — RETAIN the HttpApi under strict
|
|
822
|
+
// dataSafety. The `*.execute-api.{region}.amazonaws.com` URL is unique
|
|
823
|
+
// to the API ID, so deleting the API invalidates every consumer that
|
|
824
|
+
// hardcoded that URL (mobile apps shipped with the URL baked in,
|
|
825
|
+
// partner integrations, webhook subscribers, etc.). Custom-domain
|
|
826
|
+
// mappings live on this same API ID — losing the API also breaks the
|
|
827
|
+
// mapping until manually re-attached. RETAIN means a deleted CFN
|
|
828
|
+
// stack leaves the API in place for `cdk import` recovery.
|
|
829
|
+
const cfnApi = api.node.defaultChild;
|
|
830
|
+
if (this.dataSafetyConfig.removalPolicy === 'retain') {
|
|
831
|
+
cfnApi.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.RETAIN;
|
|
832
|
+
cfnApi.cfnOptions.updateReplacePolicy = cdk.CfnDeletionPolicy.RETAIN;
|
|
833
|
+
}
|
|
836
834
|
// Well-Architected: Operational Excellence — access logging for API
|
|
837
835
|
const apiLogGroup = new logs.LogGroup(this, 'ApiAccessLog', {
|
|
838
836
|
logGroupName: `/venturekit/${projectName}/${stage}/api-access`,
|
|
@@ -1221,35 +1219,58 @@ export class VentureStack extends cdk.Stack {
|
|
|
1221
1219
|
// automatically via SSM Parameter passthrough.
|
|
1222
1220
|
const NESTED_STACK_THRESHOLD = 80;
|
|
1223
1221
|
const ROUTES_PER_NESTED_STACK = 50;
|
|
1222
|
+
// Fixed, immutable nested-stack slot count.
|
|
1223
|
+
//
|
|
1224
|
+
// The bucketing function `stableSlotFor(slug, numSlots)` is a
|
|
1225
|
+
// pure function of these two inputs. As long as `numSlots`
|
|
1226
|
+
// never changes, every previously-deployed route keeps its
|
|
1227
|
+
// slot — which means CloudFormation never has to migrate a
|
|
1228
|
+
// route's `ApiGatewayV2::Route` resource across nested stack
|
|
1229
|
+
// boundaries between deploys.
|
|
1230
|
+
//
|
|
1231
|
+
// That invariant is load-bearing. `ApiGatewayV2::Route` carries
|
|
1232
|
+
// a unique `(method, path)` key at the API Gateway level, not
|
|
1233
|
+
// just the CloudFormation level. If the same route key lives in
|
|
1234
|
+
// two nested stacks (old + new) at the same moment, the create
|
|
1235
|
+
// call fails with `Route with key <METHOD> <path> already exists
|
|
1236
|
+
// for this API` because CFN updates nested stacks in parallel
|
|
1237
|
+
// and has no cross-stack delete-before-create ordering.
|
|
1238
|
+
//
|
|
1239
|
+
// 16 slots × ~80 routes/slot (the CloudFormation per-stack 500-
|
|
1240
|
+
// resource limit at ~5 CFN resources per route) gives ~1280
|
|
1241
|
+
// routes of headroom — comfortably above anything we've seen
|
|
1242
|
+
// in practice. Raising this number later would force a
|
|
1243
|
+
// one-shot mass migration of every nested-stack route in every
|
|
1244
|
+
// already-deployed project; don't do it casually.
|
|
1245
|
+
const NUM_NESTED_STACK_SLOTS = 16;
|
|
1224
1246
|
if (discoveredRoutes.length <= NESTED_STACK_THRESHOLD) {
|
|
1225
1247
|
for (const route of discoveredRoutes) {
|
|
1226
1248
|
this.createRouteFunction(route, projectName, stage, api, lambdaRole, lambdaSg, vpc);
|
|
1227
1249
|
}
|
|
1228
1250
|
}
|
|
1229
1251
|
else {
|
|
1230
|
-
// Stable hash-based bucketing
|
|
1252
|
+
// Stable hash-based bucketing into a fixed, never-changing slot
|
|
1253
|
+
// pool.
|
|
1231
1254
|
//
|
|
1232
|
-
// The previous index-based scheme (`routes.slice(i, i+50)`)
|
|
1233
|
-
// the discovered-route array into fixed 50-route windows.
|
|
1234
|
-
// removing, or renaming any single route shifted every
|
|
1235
|
-
// by one position, which moved a tail of routes
|
|
1236
|
-
// boundary into a different `RoutesN` nested
|
|
1237
|
-
//
|
|
1238
|
-
//
|
|
1239
|
-
//
|
|
1240
|
-
//
|
|
1255
|
+
// The previous index-based scheme (`routes.slice(i, i+50)`)
|
|
1256
|
+
// sliced the discovered-route array into fixed 50-route windows.
|
|
1257
|
+
// Adding, removing, or renaming any single route shifted every
|
|
1258
|
+
// later route by one position, which moved a tail of routes
|
|
1259
|
+
// across a 50-route boundary into a different `RoutesN` nested
|
|
1260
|
+
// stack. Combined with the unique `(method, path)` key carried
|
|
1261
|
+
// by `ApiGatewayV2::Route`, this caused
|
|
1262
|
+
// `Route with key <METHOD> <path> already exists for this API`
|
|
1263
|
+
// deploy failures — CFN updates nested stacks in parallel, so
|
|
1264
|
+
// the new route resource tries to register at API Gateway
|
|
1265
|
+
// before the old one in the other nested stack is deleted.
|
|
1241
1266
|
//
|
|
1242
|
-
// Hashing the slug into a
|
|
1243
|
-
//
|
|
1244
|
-
// moves
|
|
1245
|
-
//
|
|
1246
|
-
//
|
|
1247
|
-
//
|
|
1248
|
-
|
|
1249
|
-
// `RoutesNestedStack.createRouteFunction`), CloudFormation can
|
|
1250
|
-
// migrate them across stacks without a name collision.
|
|
1251
|
-
const targetSlots = Math.max(2, Math.ceil(discoveredRoutes.length / ROUTES_PER_NESTED_STACK));
|
|
1252
|
-
const numSlots = nextPowerOfTwo(targetSlots);
|
|
1267
|
+
// Hashing the slug into a fixed slot pool pins each route to a
|
|
1268
|
+
// slot that's determined the first time it's deployed and
|
|
1269
|
+
// never moves. Adding or removing siblings doesn't reshuffle
|
|
1270
|
+
// anything; changing slot count is the only event that would —
|
|
1271
|
+
// which is exactly why `NUM_NESTED_STACK_SLOTS` is a hard-coded
|
|
1272
|
+
// constant rather than something derived from the route count.
|
|
1273
|
+
const numSlots = NUM_NESTED_STACK_SLOTS;
|
|
1253
1274
|
const buckets = Array.from({ length: numSlots }, () => []);
|
|
1254
1275
|
for (const route of discoveredRoutes) {
|
|
1255
1276
|
const slot = stableSlotFor(route.slug, numSlots);
|
|
@@ -1626,6 +1647,17 @@ export class VentureStack extends cdk.Stack {
|
|
|
1626
1647
|
enabled: true,
|
|
1627
1648
|
...aliasProps,
|
|
1628
1649
|
});
|
|
1650
|
+
// Well-Architected: Reliability — RETAIN the distribution under
|
|
1651
|
+
// strict dataSafety. Distributions are slow to create (~15-30 min
|
|
1652
|
+
// for the global edge rollout), have a stable `*.cloudfront.net`
|
|
1653
|
+
// domain that consumers may cache, and re-issuing the ACM cert
|
|
1654
|
+
// requires re-doing DNS validation. RETAIN means an accidental
|
|
1655
|
+
// stack delete leaves the distribution in place for `cdk import`.
|
|
1656
|
+
if (this.dataSafetyConfig.removalPolicy === 'retain') {
|
|
1657
|
+
const cfnDistribution = distribution.node.defaultChild;
|
|
1658
|
+
cfnDistribution.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.RETAIN;
|
|
1659
|
+
cfnDistribution.cfnOptions.updateReplacePolicy = cdk.CfnDeletionPolicy.RETAIN;
|
|
1660
|
+
}
|
|
1629
1661
|
new cdk.CfnOutput(this, `${logicalId}-cdn-domain`, {
|
|
1630
1662
|
value: distribution.distributionDomainName,
|
|
1631
1663
|
description: `CloudFront domain for ${intent.id}`,
|
|
@@ -1713,6 +1745,16 @@ export class VentureStack extends cdk.Stack {
|
|
|
1713
1745
|
: cognito.Mfa.OFF,
|
|
1714
1746
|
// Well-Architected: Reliability — dataSafety removal policy
|
|
1715
1747
|
removalPolicy: this.toRemovalPolicy(),
|
|
1748
|
+
// Well-Architected: Reliability — native deletion protection on top of
|
|
1749
|
+
// RETAIN. `DeletionPolicy=Retain` only blocks CFN-driven deletes; the
|
|
1750
|
+
// user pool can still be deleted via console / API / CLI. Cognito's
|
|
1751
|
+
// native deletionProtection flag blocks that path too. Both layers
|
|
1752
|
+
// must be explicitly disabled before the pool can be deleted, and
|
|
1753
|
+
// every flip is CloudTrail-logged. Losing a Cognito User Pool means
|
|
1754
|
+
// losing every user identity + password hash — the most catastrophic
|
|
1755
|
+
// recoverable state in a VentureKit deploy, since users must re-
|
|
1756
|
+
// register via password reset.
|
|
1757
|
+
deletionProtection: this.dataSafetyConfig.removalPolicy === 'retain',
|
|
1716
1758
|
// Well-Architected: Security — advanced security mode for medium+ presets
|
|
1717
1759
|
advancedSecurityMode: this.envConfig.lambda.memoryMb >= 512
|
|
1718
1760
|
? cognito.AdvancedSecurityMode.ENFORCED
|
|
@@ -1817,6 +1859,14 @@ export class VentureStack extends cdk.Stack {
|
|
|
1817
1859
|
clientId: cdk.SecretValue.unsafePlainText('PLACEHOLDER'),
|
|
1818
1860
|
clientSecret: cdk.SecretValue.unsafePlainText('PLACEHOLDER'),
|
|
1819
1861
|
},
|
|
1862
|
+
// Well-Architected: Reliability — federated provider credentials
|
|
1863
|
+
// are operator-set values pasted from the third-party developer
|
|
1864
|
+
// console (Google Cloud, Meta for Developers, Apple Developer).
|
|
1865
|
+
// They can be re-generated by the operator but require manually
|
|
1866
|
+
// revisiting that console; we'd rather not force that on every
|
|
1867
|
+
// stack-replace event. Promote to RETAIN under strict dataSafety
|
|
1868
|
+
// so accidental stack deletes don't wipe them.
|
|
1869
|
+
removalPolicy: this.toRemovalPolicy(),
|
|
1820
1870
|
});
|
|
1821
1871
|
this.federatedAuthSecrets.push(secret);
|
|
1822
1872
|
const envVar = `COGNITO_FEDERATED_${provider.toUpperCase()}_SECRET_ARN`;
|
|
@@ -1876,6 +1926,18 @@ export class VentureStack extends cdk.Stack {
|
|
|
1876
1926
|
// for a topic only consumed by our own
|
|
1877
1927
|
// Lambda + the operator's SNS console.
|
|
1878
1928
|
});
|
|
1929
|
+
// Well-Architected: Reliability — RETAIN the events topic under
|
|
1930
|
+
// strict dataSafety. SNS topics carry durable subscriptions (the
|
|
1931
|
+
// bounce-handler Lambda is one; operators may attach additional
|
|
1932
|
+
// subscribers via the console for observability tools or paging).
|
|
1933
|
+
// Deleting the topic silently drops every subscription; re-creating
|
|
1934
|
+
// the topic does NOT restore them. RETAIN means an accidental stack
|
|
1935
|
+
// delete leaves the topic + subscriptions in place for recovery.
|
|
1936
|
+
if (this.dataSafetyConfig.removalPolicy === 'retain') {
|
|
1937
|
+
const cfnEventsTopic = eventsTopic.node.defaultChild;
|
|
1938
|
+
cfnEventsTopic.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.RETAIN;
|
|
1939
|
+
cfnEventsTopic.cfnOptions.updateReplacePolicy = cdk.CfnDeletionPolicy.RETAIN;
|
|
1940
|
+
}
|
|
1879
1941
|
new cdk.CfnOutput(this, `${logicalId}-events-topic-arn`, {
|
|
1880
1942
|
value: eventsTopic.topicArn,
|
|
1881
1943
|
description: `SES events SNS topic for notify '${intent.id}'`,
|
|
@@ -2120,6 +2182,12 @@ export class VentureStack extends cdk.Stack {
|
|
|
2120
2182
|
queueName: `${projectName}-${stage}-${dispatcherFnId}-dlq`,
|
|
2121
2183
|
retentionPeriod: cdk.Duration.days(14),
|
|
2122
2184
|
encryption: sqs.QueueEncryption.SQS_MANAGED,
|
|
2185
|
+
// Well-Architected: Reliability — queues hold accepted-but-not-yet-
|
|
2186
|
+
// processed work. CDK's default removalPolicy for sqs.Queue is
|
|
2187
|
+
// DESTROY, which would silently delete the DLQ and all its failed-
|
|
2188
|
+
// message diagnostics on stack delete. Under dataSafety='strict' we
|
|
2189
|
+
// promote to RETAIN so the queue + messages survive any stack churn.
|
|
2190
|
+
removalPolicy: this.toRemovalPolicy(),
|
|
2123
2191
|
});
|
|
2124
2192
|
const dispatcherFn = new lambda.Function(this, dispatcherFnId, {
|
|
2125
2193
|
functionName: `${projectName}-${stage}-${dispatcherFnId}`,
|
|
@@ -2165,6 +2233,10 @@ export class VentureStack extends cdk.Stack {
|
|
|
2165
2233
|
queueName: `${projectName}-${stage}-${bounceFnId}-dlq`,
|
|
2166
2234
|
retentionPeriod: cdk.Duration.days(14),
|
|
2167
2235
|
encryption: sqs.QueueEncryption.SQS_MANAGED,
|
|
2236
|
+
// Same rationale as the dispatcher DLQ above: bounced/complained
|
|
2237
|
+
// notifications are the canonical record of what failed and why;
|
|
2238
|
+
// dropping them on stack delete loses the audit trail.
|
|
2239
|
+
removalPolicy: this.toRemovalPolicy(),
|
|
2168
2240
|
});
|
|
2169
2241
|
const bounceFn = new lambda.Function(this, bounceFnId, {
|
|
2170
2242
|
functionName: `${projectName}-${stage}-${bounceFnId}`,
|
|
@@ -2213,6 +2285,12 @@ export class VentureStack extends cdk.Stack {
|
|
|
2213
2285
|
retentionPeriod: cdk.Duration.days(14),
|
|
2214
2286
|
// Well-Architected: Security — encryption at rest
|
|
2215
2287
|
encryption: sqs.QueueEncryption.SQS_MANAGED,
|
|
2288
|
+
// Well-Architected: Reliability — DLQs are the durable record of every
|
|
2289
|
+
// message a consumer Lambda failed to process. Default CDK removalPolicy
|
|
2290
|
+
// for sqs.Queue is DESTROY; under dataSafety='strict' we promote to
|
|
2291
|
+
// RETAIN so investigation of past failures survives any redeploy or
|
|
2292
|
+
// stack-delete event.
|
|
2293
|
+
removalPolicy: this.toRemovalPolicy(),
|
|
2216
2294
|
});
|
|
2217
2295
|
const queue = new sqs.Queue(this, logicalId, {
|
|
2218
2296
|
queueName: `${projectName}-${intent.id}-${stage}${intent.type === 'fifo' ? '.fifo' : ''}`,
|
|
@@ -2228,6 +2306,15 @@ export class VentureStack extends cdk.Stack {
|
|
|
2228
2306
|
queue: dlq,
|
|
2229
2307
|
maxReceiveCount: intent.maxReceiveCount ?? 3,
|
|
2230
2308
|
},
|
|
2309
|
+
// Well-Architected: Reliability — primary work queue holds accepted
|
|
2310
|
+
// jobs that have not yet been processed. CDK's default is DESTROY,
|
|
2311
|
+
// which would lose every in-flight message on any stack delete or
|
|
2312
|
+
// logical-ID rename. Under dataSafety='strict' we promote to RETAIN.
|
|
2313
|
+
// Combined with the upcoming multi-stack messaging-tier split, the
|
|
2314
|
+
// queue then survives even an app-stack tear-down — new ESMs in the
|
|
2315
|
+
// redeployed app stack resume consumption from where the old one
|
|
2316
|
+
// stopped.
|
|
2317
|
+
removalPolicy: this.toRemovalPolicy(),
|
|
2231
2318
|
});
|
|
2232
2319
|
new cdk.CfnOutput(this, `${logicalId}-queue-url`, {
|
|
2233
2320
|
value: queue.queueUrl,
|
|
@@ -2337,6 +2424,12 @@ export class VentureStack extends cdk.Stack {
|
|
|
2337
2424
|
const secret = new secretsmanager.Secret(this, resourceName, {
|
|
2338
2425
|
secretName: `${projectName}/${stage}/${intent.id}`,
|
|
2339
2426
|
description: intent.description || `Secret for ${intent.id}`,
|
|
2427
|
+
// Well-Architected: Reliability — operator-declared secrets hold
|
|
2428
|
+
// values the operator pasted in manually (API keys, third-party
|
|
2429
|
+
// integration tokens). They cannot be re-generated by VentureKit
|
|
2430
|
+
// and an accidental delete forces a manual rotation of every
|
|
2431
|
+
// downstream credential. RETAIN under strict dataSafety.
|
|
2432
|
+
removalPolicy: this.toRemovalPolicy(),
|
|
2340
2433
|
});
|
|
2341
2434
|
new cdk.CfnOutput(this, `${resourceName}-secret-arn`, {
|
|
2342
2435
|
value: secret.secretArn,
|
|
@@ -2351,6 +2444,13 @@ export class VentureStack extends cdk.Stack {
|
|
|
2351
2444
|
stringValue: 'PLACEHOLDER — set value after deploy',
|
|
2352
2445
|
tier: ssm.ParameterTier.STANDARD,
|
|
2353
2446
|
});
|
|
2447
|
+
// SSM parameters don't carry their own DeletionPolicy attribute
|
|
2448
|
+
// (the resource type doesn't support it in CFN), and AWS doesn't
|
|
2449
|
+
// expose a "deletion protection" flag for them either. Operators
|
|
2450
|
+
// relying on these for production secret-like config should
|
|
2451
|
+
// either move to Secrets Manager via `store: 'secretsmanager'`
|
|
2452
|
+
// (which we now RETAIN above) or accept that an accidental
|
|
2453
|
+
// stack delete loses the value.
|
|
2354
2454
|
new cdk.CfnOutput(this, `${resourceName}-param-name`, {
|
|
2355
2455
|
value: param.parameterName,
|
|
2356
2456
|
description: `SSM Parameter name for ${intent.id}`,
|
|
@@ -2386,12 +2486,30 @@ export class VentureStack extends cdk.Stack {
|
|
|
2386
2486
|
domainName: intent.domain,
|
|
2387
2487
|
certificate,
|
|
2388
2488
|
});
|
|
2489
|
+
// Well-Architected: Reliability — RETAIN both the domain name and the
|
|
2490
|
+
// mapping under strict dataSafety. The custom domain is the *public*
|
|
2491
|
+
// contract with the world; deleting it forces DNS reconfiguration on
|
|
2492
|
+
// every client/integration. RETAIN means an accidental stack delete
|
|
2493
|
+
// leaves the domain claimed to this account and ready for `cdk
|
|
2494
|
+
// import`. The ApiMapping rides along — without it the domain points
|
|
2495
|
+
// nowhere, but mappings recreate cleanly so RETAIN is mostly a
|
|
2496
|
+
// convenience for the recovery path.
|
|
2497
|
+
if (this.dataSafetyConfig.removalPolicy === 'retain') {
|
|
2498
|
+
const cfnDomain = domainName.node.defaultChild;
|
|
2499
|
+
cfnDomain.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.RETAIN;
|
|
2500
|
+
cfnDomain.cfnOptions.updateReplacePolicy = cdk.CfnDeletionPolicy.RETAIN;
|
|
2501
|
+
}
|
|
2389
2502
|
// API mapping
|
|
2390
|
-
new apigatewayv2.ApiMapping(this, `${resourceName}-mapping`, {
|
|
2503
|
+
const apiMapping = new apigatewayv2.ApiMapping(this, `${resourceName}-mapping`, {
|
|
2391
2504
|
api,
|
|
2392
2505
|
domainName,
|
|
2393
2506
|
stage: api.defaultStage,
|
|
2394
2507
|
});
|
|
2508
|
+
if (this.dataSafetyConfig.removalPolicy === 'retain') {
|
|
2509
|
+
const cfnMapping = apiMapping.node.defaultChild;
|
|
2510
|
+
cfnMapping.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.RETAIN;
|
|
2511
|
+
cfnMapping.cfnOptions.updateReplacePolicy = cdk.CfnDeletionPolicy.RETAIN;
|
|
2512
|
+
}
|
|
2395
2513
|
// Route53 A record if hosted zone provided
|
|
2396
2514
|
if (intent.hostedZoneId) {
|
|
2397
2515
|
const hostedZone = route53.HostedZone.fromHostedZoneAttributes(this, `${resourceName}-zone-record`, {
|
|
@@ -2437,6 +2555,13 @@ export class VentureStack extends cdk.Stack {
|
|
|
2437
2555
|
timeToLiveAttribute: 'ttl',
|
|
2438
2556
|
// Well-Architected: Operational Excellence — contributor insights for medium+
|
|
2439
2557
|
contributorInsightsEnabled: this.envConfig.lambda.memoryMb >= 512,
|
|
2558
|
+
// Well-Architected: Reliability — native deletion protection on top of
|
|
2559
|
+
// RETAIN. DeletionPolicy=Retain only blocks CFN-driven deletes; turning
|
|
2560
|
+
// on deletionProtectionEnabled also blocks the AWS console / API / CLI
|
|
2561
|
+
// "delete table" path. Together they make accidental data loss require
|
|
2562
|
+
// two explicit operator actions (disable protection AND override the
|
|
2563
|
+
// deletion policy), each loggable in CloudTrail.
|
|
2564
|
+
deletionProtection: this.dataSafetyConfig.removalPolicy === 'retain',
|
|
2440
2565
|
});
|
|
2441
2566
|
// Well-Architected: Reliability — prevent accidental replacement
|
|
2442
2567
|
const cfnTable = table.node.defaultChild;
|
|
@@ -2477,10 +2602,28 @@ export class VentureStack extends cdk.Stack {
|
|
|
2477
2602
|
tableName,
|
|
2478
2603
|
partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
|
|
2479
2604
|
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
|
|
2480
|
-
|
|
2605
|
+
// Well-Architected: Reliability — rate-limit counters are per-user
|
|
2606
|
+
// state. Hardcoding DESTROY would erase every user's current rate-
|
|
2607
|
+
// limit window on any stack churn, surfacing as a simultaneous
|
|
2608
|
+
// "all users get a fresh quota" event — minor but observable under
|
|
2609
|
+
// load and not what dataSafety='strict' implies. Track dataSafety
|
|
2610
|
+
// the same way every other stateful resource does.
|
|
2611
|
+
removalPolicy: this.toRemovalPolicy(),
|
|
2481
2612
|
timeToLiveAttribute: 'ttl',
|
|
2482
2613
|
encryption: dynamodb.TableEncryption.AWS_MANAGED,
|
|
2614
|
+
// Native AWS deletion protection alongside RETAIN. See the
|
|
2615
|
+
// user-declared DDB path above for the two-layer rationale.
|
|
2616
|
+
deletionProtection: this.dataSafetyConfig.removalPolicy === 'retain',
|
|
2483
2617
|
});
|
|
2618
|
+
// Well-Architected: Reliability — UpdateReplacePolicy=Retain so that a
|
|
2619
|
+
// property change requiring REPLACE (e.g. changing the partition-key
|
|
2620
|
+
// schema, which CFN executes as delete-then-create) preserves the old
|
|
2621
|
+
// table rather than dropping every active rate-limit window. Matches
|
|
2622
|
+
// the user-declared DDB path.
|
|
2623
|
+
const cfnRateLimitTable = table.node.defaultChild;
|
|
2624
|
+
if (this.dataSafetyConfig.removalPolicy === 'retain') {
|
|
2625
|
+
cfnRateLimitTable.cfnOptions.updateReplacePolicy = cdk.CfnDeletionPolicy.RETAIN;
|
|
2626
|
+
}
|
|
2484
2627
|
this.ventureBaseEnv['VENTURE_RATE_LIMIT_TABLE'] = table.tableName;
|
|
2485
2628
|
new cdk.CfnOutput(this, `${logicalId}-table-name`, {
|
|
2486
2629
|
value: table.tableName,
|