@fjall/components-infrastructure 0.96.0 → 0.99.1

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 (209) hide show
  1. package/dist/lib/app.d.ts +68 -1
  2. package/dist/lib/app.js +113 -4
  3. package/dist/lib/config/aws/__t17fixture.d.ts +1 -0
  4. package/dist/lib/config/aws/__t17fixture.js +3 -0
  5. package/dist/lib/config/aws/__t17fixtureType.d.ts +2 -0
  6. package/dist/lib/config/aws/__t17fixtureType.js +1 -0
  7. package/dist/lib/config/aws/alarmTopic.js +8 -4
  8. package/dist/lib/config/aws/cloudTrail.js +1 -1
  9. package/dist/lib/config/aws/disasterRecovery.js +11 -16
  10. package/dist/lib/config/aws/ecrDefaultImage.d.ts +0 -1
  11. package/dist/lib/config/aws/ecrDefaultImage.js +13 -23
  12. package/dist/lib/config/aws/identityCenter.d.ts +10 -3
  13. package/dist/lib/config/aws/identityCenter.js +101 -37
  14. package/dist/lib/config/aws/identityCenterGroupMembership.js +8 -2
  15. package/dist/lib/config/aws/identityCenterMembership.d.ts +11 -0
  16. package/dist/lib/config/aws/identityCenterMembership.js +61 -0
  17. package/dist/lib/config/aws/index.d.ts +1 -1
  18. package/dist/lib/config/aws/index.js +1 -1
  19. package/dist/lib/config/aws/ipam.js +6 -11
  20. package/dist/lib/config/aws/oidcConnector.js +5 -1
  21. package/dist/lib/config/aws/scpPreset.js +4 -1
  22. package/dist/lib/patterns/aws/_eslint_test_tmp/leak.d.ts +1 -0
  23. package/dist/lib/patterns/aws/_eslint_test_tmp/leak.js +4 -0
  24. package/dist/lib/patterns/aws/account.js +2 -4
  25. package/dist/lib/patterns/aws/apexDomainPattern.js +10 -10
  26. package/dist/lib/patterns/aws/bastionFactory.d.ts +10 -0
  27. package/dist/lib/patterns/aws/bastionFactory.js +29 -0
  28. package/dist/lib/patterns/aws/buildkite.d.ts +2 -2
  29. package/dist/lib/patterns/aws/buildkite.js +51 -97
  30. package/dist/lib/patterns/aws/cdn.js +1 -1
  31. package/dist/lib/patterns/aws/clickhouseDatabase.d.ts +172 -0
  32. package/dist/lib/patterns/aws/clickhouseDatabase.js +600 -0
  33. package/dist/lib/patterns/aws/compute.d.ts +4 -6
  34. package/dist/lib/patterns/aws/compute.js +7 -13
  35. package/dist/lib/patterns/aws/computeEcs.d.ts +93 -5
  36. package/dist/lib/patterns/aws/computeEcs.js +867 -37
  37. package/dist/lib/patterns/aws/computeEcsTypes.d.ts +528 -25
  38. package/dist/lib/patterns/aws/computeEcsTypes.js +10 -0
  39. package/dist/lib/patterns/aws/computeLambda.d.ts +0 -5
  40. package/dist/lib/patterns/aws/computeLambda.js +1 -2
  41. package/dist/lib/patterns/aws/database.d.ts +50 -8
  42. package/dist/lib/patterns/aws/database.js +183 -27
  43. package/dist/lib/patterns/aws/domain.js +6 -4
  44. package/dist/lib/patterns/aws/index.d.ts +1 -0
  45. package/dist/lib/patterns/aws/index.js +1 -0
  46. package/dist/lib/patterns/aws/interfaces/compute.d.ts +7 -1
  47. package/dist/lib/patterns/aws/interfaces/database.d.ts +187 -8
  48. package/dist/lib/patterns/aws/interfaces/database.js +17 -3
  49. package/dist/lib/patterns/aws/interfaces/index.d.ts +2 -1
  50. package/dist/lib/patterns/aws/interfaces/index.js +3 -1
  51. package/dist/lib/patterns/aws/interfaces/messaging.d.ts +7 -0
  52. package/dist/lib/patterns/aws/interfaces/migrationContributor.d.ts +47 -0
  53. package/dist/lib/patterns/aws/interfaces/migrationContributor.js +9 -0
  54. package/dist/lib/patterns/aws/messaging.d.ts +66 -10
  55. package/dist/lib/patterns/aws/messaging.js +115 -20
  56. package/dist/lib/patterns/aws/network.js +16 -7
  57. package/dist/lib/patterns/aws/organisation.d.ts +4 -0
  58. package/dist/lib/patterns/aws/organisation.js +22 -4
  59. package/dist/lib/patterns/aws/storage.d.ts +1 -2
  60. package/dist/lib/patterns/aws/storage.js +3 -2
  61. package/dist/lib/patterns/aws/vpcPeer.js +3 -1
  62. package/dist/lib/resources/aws/analytics/clickhouse.js +18 -9
  63. package/dist/lib/resources/aws/analytics/clickhouseAlarms.d.ts +24 -9
  64. package/dist/lib/resources/aws/analytics/clickhouseAlarms.js +61 -10
  65. package/dist/lib/resources/aws/analytics/clickhouseConstants.d.ts +3 -3
  66. package/dist/lib/resources/aws/analytics/clickhouseConstants.js +3 -3
  67. package/dist/lib/resources/aws/analytics/clickhouseTypes.d.ts +7 -1
  68. package/dist/lib/resources/aws/analytics/clickhouseUserData.d.ts +1 -1
  69. package/dist/lib/resources/aws/analytics/clickhouseUserData.js +53 -3
  70. package/dist/lib/resources/aws/base/awsStack.js +4 -2
  71. package/dist/lib/resources/aws/compute/__tmp__/regression-shape.d.ts +2 -0
  72. package/dist/lib/resources/aws/compute/__tmp__/regression-shape.js +11 -0
  73. package/dist/lib/resources/aws/compute/asgInlineLifecycleHook.d.ts +52 -0
  74. package/dist/lib/resources/aws/compute/asgInlineLifecycleHook.js +60 -0
  75. package/dist/lib/resources/aws/compute/blockDeviceVolume.d.ts +8 -0
  76. package/dist/lib/resources/aws/compute/blockDeviceVolume.js +10 -0
  77. package/dist/lib/resources/aws/compute/ec2.d.ts +132 -12
  78. package/dist/lib/resources/aws/compute/ec2.js +163 -23
  79. package/dist/lib/resources/aws/compute/ec2GracefulTerminationHandler.d.ts +41 -0
  80. package/dist/lib/resources/aws/compute/ec2GracefulTerminationHandler.js +194 -0
  81. package/dist/lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs +458 -0
  82. package/dist/lib/resources/aws/compute/ecs.d.ts +27 -1
  83. package/dist/lib/resources/aws/compute/ecs.js +42 -2
  84. package/dist/lib/resources/aws/compute/ecsConstants.d.ts +9 -0
  85. package/dist/lib/resources/aws/compute/ecsConstants.js +16 -0
  86. package/dist/lib/resources/aws/compute/ecsImages.js +32 -20
  87. package/dist/lib/resources/aws/compute/ecsLifecycleHookMigration.d.ts +96 -0
  88. package/dist/lib/resources/aws/compute/ecsLifecycleHookMigration.js +113 -0
  89. package/dist/lib/resources/aws/compute/ecsNetworking.d.ts +2 -1
  90. package/dist/lib/resources/aws/compute/ecsNetworking.js +18 -6
  91. package/dist/lib/resources/aws/compute/ecsServiceFactory.d.ts +13 -4
  92. package/dist/lib/resources/aws/compute/ecsServiceFactory.js +155 -33
  93. package/dist/lib/resources/aws/compute/ecsTaskDefinition.d.ts +31 -1
  94. package/dist/lib/resources/aws/compute/ecsTaskDefinition.js +102 -6
  95. package/dist/lib/resources/aws/compute/ecsTypes.d.ts +173 -13
  96. package/dist/lib/resources/aws/compute/ecsValidation.d.ts +9 -0
  97. package/dist/lib/resources/aws/compute/ecsValidation.js +63 -0
  98. package/dist/lib/resources/aws/compute/index.d.ts +2 -0
  99. package/dist/lib/resources/aws/compute/index.js +2 -0
  100. package/dist/lib/resources/aws/compute/lambda.d.ts +7 -13
  101. package/dist/lib/resources/aws/compute/lambda.js +30 -38
  102. package/dist/lib/resources/aws/compute/lifecycleHookLambda.source.cjs +192 -0
  103. package/dist/lib/resources/aws/compute/persistentDataVolume.d.ts +104 -0
  104. package/dist/lib/resources/aws/compute/persistentDataVolume.js +245 -0
  105. package/dist/lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs +398 -0
  106. package/dist/lib/resources/aws/compute/samApplication.d.ts +15 -0
  107. package/dist/lib/resources/aws/compute/samApplication.js +27 -0
  108. package/dist/lib/resources/aws/database/clickhouseConstants.d.ts +159 -0
  109. package/dist/lib/resources/aws/database/clickhouseConstants.js +181 -0
  110. package/dist/lib/resources/aws/database/clickhouseSchemas.d.ts +71 -0
  111. package/dist/lib/resources/aws/database/clickhouseSchemas.js +157 -0
  112. package/dist/lib/resources/aws/database/clickhouseSecurityGroup.d.ts +14 -0
  113. package/dist/lib/resources/aws/database/clickhouseSecurityGroup.js +23 -0
  114. package/dist/lib/resources/aws/database/clickhouseUserData.d.ts +69 -0
  115. package/dist/lib/resources/aws/database/clickhouseUserData.js +371 -0
  116. package/dist/lib/resources/aws/database/clickhouseXmlRenderer.d.ts +56 -0
  117. package/dist/lib/resources/aws/database/clickhouseXmlRenderer.js +112 -0
  118. package/dist/lib/resources/aws/database/rdsAurora.d.ts +8 -1
  119. package/dist/lib/resources/aws/database/rdsAurora.js +42 -32
  120. package/dist/lib/resources/aws/database/rdsAuroraGlobal.d.ts +15 -2
  121. package/dist/lib/resources/aws/database/rdsAuroraGlobal.js +39 -43
  122. package/dist/lib/resources/aws/database/rdsDefaults.d.ts +6 -0
  123. package/dist/lib/resources/aws/database/rdsDefaults.js +7 -1
  124. package/dist/lib/resources/aws/database/rdsHelpers.d.ts +3 -3
  125. package/dist/lib/resources/aws/database/rdsHelpers.js +1 -0
  126. package/dist/lib/resources/aws/database/rdsInstance.d.ts +8 -1
  127. package/dist/lib/resources/aws/database/rdsInstance.js +51 -34
  128. package/dist/lib/resources/aws/database/rdsProxyOutput.d.ts +1 -1
  129. package/dist/lib/resources/aws/database/rdsProxyOutput.js +1 -1
  130. package/dist/lib/resources/aws/iam/delegationRole.js +1 -1
  131. package/dist/lib/resources/aws/iam/identityCenter/groupMembership.d.ts +9 -0
  132. package/dist/lib/resources/aws/iam/identityCenter/groupMembership.js +12 -0
  133. package/dist/lib/resources/aws/iam/identityCenter/index.d.ts +1 -0
  134. package/dist/lib/resources/aws/iam/identityCenter/index.js +1 -0
  135. package/dist/lib/resources/aws/iam/identityCenter/permissionSet.d.ts +1 -0
  136. package/dist/lib/resources/aws/iam/identityCenter/permissionSet.js +1 -0
  137. package/dist/lib/resources/aws/logging/logGroup.d.ts +0 -8
  138. package/dist/lib/resources/aws/logging/logGroup.js +0 -11
  139. package/dist/lib/resources/aws/messaging/defaultEventBus.d.ts +7 -0
  140. package/dist/lib/resources/aws/messaging/defaultEventBus.js +21 -0
  141. package/dist/lib/resources/aws/messaging/eventBridgeRule.d.ts +96 -0
  142. package/dist/lib/resources/aws/messaging/eventBridgeRule.js +110 -0
  143. package/dist/lib/resources/aws/messaging/eventTargets.d.ts +84 -0
  144. package/dist/lib/resources/aws/messaging/eventTargets.js +152 -0
  145. package/dist/lib/resources/aws/messaging/eventbridge.d.ts +25 -2
  146. package/dist/lib/resources/aws/messaging/eventbridge.js +22 -10
  147. package/dist/lib/resources/aws/messaging/index.d.ts +5 -0
  148. package/dist/lib/resources/aws/messaging/index.js +2 -0
  149. package/dist/lib/resources/aws/messaging/schedule.d.ts +118 -0
  150. package/dist/lib/resources/aws/messaging/schedule.js +64 -0
  151. package/dist/lib/resources/aws/messaging/sns.d.ts +2 -1
  152. package/dist/lib/resources/aws/messaging/sqs.d.ts +2 -1
  153. package/dist/lib/resources/aws/messaging/subscription.d.ts +112 -0
  154. package/dist/lib/resources/aws/messaging/subscription.js +67 -0
  155. package/dist/lib/resources/aws/messaging/utils.d.ts +6 -0
  156. package/dist/lib/resources/aws/messaging/utils.js +10 -0
  157. package/dist/lib/resources/aws/monitoring/clickhouseAlarms.d.ts +60 -0
  158. package/dist/lib/resources/aws/monitoring/clickhouseAlarms.js +139 -0
  159. package/dist/lib/resources/aws/monitoring/index.d.ts +2 -0
  160. package/dist/lib/resources/aws/monitoring/index.js +2 -0
  161. package/dist/lib/resources/aws/monitoring/scheduleAlarms.d.ts +47 -0
  162. package/dist/lib/resources/aws/monitoring/scheduleAlarms.js +106 -0
  163. package/dist/lib/resources/aws/networking/crossAccountDelegationRecord.js +6 -4
  164. package/dist/lib/resources/aws/networking/crossAccountReturnRoutes.js +17 -13
  165. package/dist/lib/resources/aws/networking/dnsRecord/dnsRecordBase.js +7 -5
  166. package/dist/lib/resources/aws/networking/domainCertificate.d.ts +2 -2
  167. package/dist/lib/resources/aws/networking/domainCertificate.js +6 -4
  168. package/dist/lib/resources/aws/networking/hostedZone.js +6 -5
  169. package/dist/lib/resources/aws/networking/serviceDiscovery.d.ts +96 -0
  170. package/dist/lib/resources/aws/networking/serviceDiscovery.js +96 -0
  171. package/dist/lib/resources/aws/networking/vpc.d.ts +4 -1
  172. package/dist/lib/resources/aws/networking/vpc.js +4 -1
  173. package/dist/lib/resources/aws/networking/vpcPeeringConnection.js +21 -3
  174. package/dist/lib/resources/aws/organisation/costAllocationTagActivator.d.ts +16 -5
  175. package/dist/lib/resources/aws/organisation/costAllocationTagActivator.js +17 -3
  176. package/dist/lib/resources/aws/organisation/index.d.ts +1 -1
  177. package/dist/lib/resources/aws/organisation/organisationPolicy.d.ts +2 -0
  178. package/dist/lib/resources/aws/organisation/organisationPolicy.js +3 -2
  179. package/dist/lib/resources/aws/secrets/secret.d.ts +7 -0
  180. package/dist/lib/resources/aws/secrets/secret.js +4 -3
  181. package/dist/lib/resources/aws/storage/bucketDeployment.d.ts +16 -0
  182. package/dist/lib/resources/aws/storage/bucketDeployment.js +17 -0
  183. package/dist/lib/resources/aws/storage/ecr.js +5 -5
  184. package/dist/lib/resources/aws/storage/index.d.ts +1 -0
  185. package/dist/lib/resources/aws/storage/index.js +1 -0
  186. package/dist/lib/resources/aws/storage/s3.js +10 -3
  187. package/dist/lib/resources/aws/utilities/customResource.js +18 -9
  188. package/dist/lib/synth_dump.d.ts +1 -0
  189. package/dist/lib/synth_dump.js +42 -0
  190. package/dist/lib/utils/cdkContext.d.ts +2 -0
  191. package/dist/lib/utils/cdkContext.js +4 -2
  192. package/dist/lib/utils/connections.js +6 -0
  193. package/dist/lib/utils/connector.d.ts +12 -0
  194. package/dist/lib/utils/costAllocationTags.d.ts +9 -0
  195. package/dist/lib/utils/costAllocationTags.js +11 -1
  196. package/dist/lib/utils/databaseTypes.d.ts +14 -0
  197. package/dist/lib/utils/getConfig.d.ts +2 -0
  198. package/dist/lib/utils/getConfig.js +2 -0
  199. package/dist/lib/utils/index.d.ts +1 -0
  200. package/dist/lib/utils/index.js +1 -0
  201. package/dist/lib/utils/manifestWriter.d.ts +6 -89
  202. package/dist/lib/utils/manifestWriter.js +36 -23
  203. package/dist/lib/utils/migrationVersionResolvers.d.ts +2 -0
  204. package/dist/lib/utils/migrationVersionResolvers.js +2 -0
  205. package/dist/lib/utils/orgConfigParser.js +2 -1
  206. package/dist/lib/utils/resolveAlertsTopic.d.ts +14 -0
  207. package/dist/lib/utils/resolveAlertsTopic.js +30 -0
  208. package/dist/lib/utils/validationLogger.js +6 -3
  209. package/package.json +22 -19
@@ -0,0 +1,139 @@
1
+ import { Duration } from "aws-cdk-lib";
2
+ import { Alarm, ComparisonOperator, TreatMissingData } from "aws-cdk-lib/aws-cloudwatch";
3
+ import { SnsAction } from "aws-cdk-lib/aws-cloudwatch-actions";
4
+ import { Metric } from "aws-cdk-lib/aws-cloudwatch";
5
+ import { FilterPattern, MetricFilter } from "aws-cdk-lib/aws-logs";
6
+ import { ALARM_DEFAULTS, registerAlarm, buildAlarmDescription } from "./alarmDefaults.js";
7
+ const CLICKHOUSE_METRIC_NAMESPACE = "Fjall/ClickHouse";
8
+ /**
9
+ * Single-node ClickHouse posture alarms. Covers host-level CPU + (optional)
10
+ * memory and disk via the CloudWatch Agent metric namespace `CWAgent`, plus
11
+ * two log-driven alarms:
12
+ *
13
+ * - **Stuck merges** — `client.ts` polls `system.merges` every 5 min and logs
14
+ * `serverLogger.warn("ClickHouse", "Stuck merge detected")` when elapsed
15
+ * exceeds 30 min. The metric filter on the webapp log group emits a count
16
+ * metric per match; the alarm fires on Sum >= 1 over 5 min × 2 evaluations.
17
+ * - **Backup failures** — `AccessDenied` or `S3Exception` from the backup
18
+ * task's BACKUP DATABASE TO S3 statement. Closes the silent-failure mode
19
+ * that masked the original IAM-grant misconfiguration (see
20
+ * `designs/2026-04-27-clickhouse-backup-iam-role.md`).
21
+ */
22
+ export function createClickHouseAlarms(props) {
23
+ const { scope, instanceRole, asgName, alarmTopic, webappLogGroup, backupTaskLogGroup, config = {} } = props;
24
+ const alarms = [];
25
+ const snsAction = new SnsAction(alarmTopic);
26
+ const cpuAlarm = new Alarm(scope, "ClickHouseCpuAlarm", {
27
+ alarmDescription: buildAlarmDescription("ClickHouse host CPU utilisation exceeds threshold", undefined),
28
+ metric: new Metric({
29
+ namespace: "AWS/EC2",
30
+ metricName: "CPUUtilization",
31
+ dimensionsMap: { AutoScalingGroupName: asgName },
32
+ period: ALARM_DEFAULTS.EVALUATION_PERIOD,
33
+ statistic: "Average"
34
+ }),
35
+ threshold: config.cpuThreshold ?? 90,
36
+ evaluationPeriods: 3,
37
+ datapointsToAlarm: 2,
38
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
39
+ treatMissingData: TreatMissingData.NOT_BREACHING
40
+ });
41
+ registerAlarm(cpuAlarm, snsAction, alarms);
42
+ const memoryAlarm = new Alarm(scope, "ClickHouseMemoryAlarm", {
43
+ alarmDescription: buildAlarmDescription("ClickHouse host memory utilisation exceeds threshold (CWAgent)", undefined),
44
+ metric: new Metric({
45
+ namespace: "CWAgent",
46
+ metricName: "mem_used_percent",
47
+ dimensionsMap: { AutoScalingGroupName: asgName },
48
+ period: ALARM_DEFAULTS.EVALUATION_PERIOD,
49
+ statistic: "Average"
50
+ }),
51
+ threshold: config.memoryThreshold ?? 80,
52
+ evaluationPeriods: 3,
53
+ datapointsToAlarm: 2,
54
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
55
+ treatMissingData: TreatMissingData.NOT_BREACHING
56
+ });
57
+ registerAlarm(memoryAlarm, snsAction, alarms);
58
+ const diskWarnAlarm = new Alarm(scope, "ClickHouseDiskWarnAlarm", {
59
+ alarmDescription: buildAlarmDescription("ClickHouse data volume above 70% used — plan growth response", undefined),
60
+ metric: new Metric({
61
+ namespace: "CWAgent",
62
+ metricName: "disk_used_percent",
63
+ dimensionsMap: { AutoScalingGroupName: asgName },
64
+ period: Duration.minutes(15),
65
+ statistic: "Average"
66
+ }),
67
+ threshold: config.diskWarnThreshold ?? 70,
68
+ evaluationPeriods: 2,
69
+ datapointsToAlarm: 2,
70
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
71
+ treatMissingData: TreatMissingData.NOT_BREACHING
72
+ });
73
+ registerAlarm(diskWarnAlarm, snsAction, alarms);
74
+ const diskCriticalAlarm = new Alarm(scope, "ClickHouseDiskCriticalAlarm", {
75
+ alarmDescription: buildAlarmDescription("ClickHouse data volume above 85% used — imminent insert failures", undefined),
76
+ metric: new Metric({
77
+ namespace: "CWAgent",
78
+ metricName: "disk_used_percent",
79
+ dimensionsMap: { AutoScalingGroupName: asgName },
80
+ period: Duration.minutes(5),
81
+ statistic: "Average"
82
+ }),
83
+ threshold: config.diskCriticalThreshold ?? 85,
84
+ evaluationPeriods: 2,
85
+ datapointsToAlarm: 2,
86
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
87
+ treatMissingData: TreatMissingData.NOT_BREACHING
88
+ });
89
+ registerAlarm(diskCriticalAlarm, snsAction, alarms);
90
+ const stuckMergeMetricName = "ClickHouseStuckMergeCount";
91
+ new MetricFilter(scope, "ClickHouseStuckMergeMetricFilter", {
92
+ logGroup: webappLogGroup,
93
+ metricNamespace: CLICKHOUSE_METRIC_NAMESPACE,
94
+ metricName: stuckMergeMetricName,
95
+ filterPattern: FilterPattern.literal('"Stuck merge detected"'),
96
+ metricValue: "1",
97
+ defaultValue: 0
98
+ });
99
+ const stuckMergeAlarm = new Alarm(scope, "ClickHouseStuckMergeAlarm", {
100
+ alarmDescription: buildAlarmDescription("ClickHouse merge stuck > 30 min — investigate parts pressure or replica health", undefined),
101
+ metric: new Metric({
102
+ namespace: CLICKHOUSE_METRIC_NAMESPACE,
103
+ metricName: stuckMergeMetricName,
104
+ period: Duration.minutes(5),
105
+ statistic: "Sum"
106
+ }),
107
+ threshold: 1,
108
+ evaluationPeriods: 2,
109
+ datapointsToAlarm: 2,
110
+ comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
111
+ treatMissingData: TreatMissingData.NOT_BREACHING
112
+ });
113
+ registerAlarm(stuckMergeAlarm, snsAction, alarms);
114
+ const backupFailureMetricName = "ClickHouseBackupFailureCount";
115
+ new MetricFilter(scope, "ClickHouseBackupFailureMetricFilter", {
116
+ logGroup: backupTaskLogGroup,
117
+ metricNamespace: CLICKHOUSE_METRIC_NAMESPACE,
118
+ metricName: backupFailureMetricName,
119
+ filterPattern: FilterPattern.anyTerm("AccessDenied", "S3Exception"),
120
+ metricValue: "1",
121
+ defaultValue: 0
122
+ });
123
+ const backupFailureAlarm = new Alarm(scope, "ClickHouseBackupFailureAlarm", {
124
+ alarmDescription: buildAlarmDescription(`ClickHouse BACKUP TO S3 emitted AccessDenied/S3Exception — verify instance role '${instanceRole.roleName}' grant on backup bucket`, undefined),
125
+ metric: new Metric({
126
+ namespace: CLICKHOUSE_METRIC_NAMESPACE,
127
+ metricName: backupFailureMetricName,
128
+ period: Duration.hours(1),
129
+ statistic: "Sum"
130
+ }),
131
+ threshold: 1,
132
+ evaluationPeriods: 1,
133
+ datapointsToAlarm: 1,
134
+ comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
135
+ treatMissingData: TreatMissingData.NOT_BREACHING
136
+ });
137
+ registerAlarm(backupFailureAlarm, snsAction, alarms);
138
+ return alarms;
139
+ }
@@ -2,3 +2,5 @@ export { ALARM_DEFAULTS, APPLICATION_ID_TAG_KEY, registerAlarm, tagAlarmsWithApp
2
2
  export { createEcsServiceAlarms, type EcsServiceAlarmThresholds, type EcsServiceAlarmsProps } from "./ecsAlarms.js";
3
3
  export { createRdsAlarms, type RdsAlarmThresholds, type RdsAlarmsProps } from "./rdsAlarms.js";
4
4
  export { createLambdaAlarms, type LambdaAlarmThresholds, type LambdaAlarmsProps } from "./lambdaAlarms.js";
5
+ export { createScheduleAlarms, type ScheduleAlarmThresholds, type CreateScheduleAlarmsProps } from "./scheduleAlarms.js";
6
+ export { createClickHouseAlarms, type ClickHouseAlarmThresholds, type ClickHouseAlarmsProps } from "./clickhouseAlarms.js";
@@ -2,3 +2,5 @@ export { ALARM_DEFAULTS, APPLICATION_ID_TAG_KEY, registerAlarm, tagAlarmsWithApp
2
2
  export { createEcsServiceAlarms } from "./ecsAlarms.js";
3
3
  export { createRdsAlarms } from "./rdsAlarms.js";
4
4
  export { createLambdaAlarms } from "./lambdaAlarms.js";
5
+ export { createScheduleAlarms } from "./scheduleAlarms.js";
6
+ export { createClickHouseAlarms } from "./clickhouseAlarms.js";
@@ -0,0 +1,47 @@
1
+ import { Alarm } from "aws-cdk-lib/aws-cloudwatch";
2
+ import type { IRule } from "aws-cdk-lib/aws-events";
3
+ import type { ITopic } from "aws-cdk-lib/aws-sns";
4
+ import type { Construct } from "constructs";
5
+ /**
6
+ * Tunable thresholds for the EventBridge schedule alarms. Lives here (NOT in
7
+ * `messaging/schedule.ts`) so the consumer module — `eventBridgeRule.ts` /
8
+ * `Schedule` — can re-export it without forming a circular import.
9
+ */
10
+ export interface ScheduleAlarmThresholds {
11
+ failedInvocationsThreshold?: number;
12
+ throttledRulesThreshold?: number;
13
+ dlqFailureThreshold?: number;
14
+ /**
15
+ * Multiplier applied to `expectedTicksPerHour` to derive the missed-tick
16
+ * floor. Default 0.8 — fires when observed matches drop below 80% of the
17
+ * expected cadence (silent disablement detector).
18
+ */
19
+ matchedEventsLowFloorRatio?: number;
20
+ }
21
+ export interface CreateScheduleAlarmsProps {
22
+ scope: Construct;
23
+ ruleName: string;
24
+ rule: IRule;
25
+ kind: "schedule" | "subscription";
26
+ /** Populated by `parseRateExpression(...)` — undefined for cron schedules and subscriptions. */
27
+ expectedTicksPerHour?: number;
28
+ config?: ScheduleAlarmThresholds;
29
+ alarmTopic: ITopic;
30
+ applicationId?: string;
31
+ dlqEnabled: boolean;
32
+ }
33
+ /**
34
+ * Provisions D13 alarms for an EventBridge rule. Returns the created alarms
35
+ * (each wired to the SNS topic for both ALARM and OK transitions and tagged
36
+ * with the application id for webhook routing).
37
+ *
38
+ * Alarm coverage:
39
+ * - `FailedInvocations` / `ThrottledRules` always fire.
40
+ * - `InvocationsFailedToBeSentToDlq` fires only when `dlqEnabled === true`.
41
+ * - `MatchedEvents` (missed-tick detector) fires only when
42
+ * `kind === "schedule"` AND `expectedTicksPerHour !== undefined` —
43
+ * i.e. rate expressions, not cron, not subscriptions. Uses
44
+ * `LessThanThreshold` + `treatMissingData: BREACHING` so a fully silent
45
+ * rule trips the alarm.
46
+ */
47
+ export declare function createScheduleAlarms(props: CreateScheduleAlarmsProps): Alarm[];
@@ -0,0 +1,106 @@
1
+ import { Duration } from "aws-cdk-lib";
2
+ import { Alarm, ComparisonOperator, TreatMissingData, Metric } from "aws-cdk-lib/aws-cloudwatch";
3
+ import { SnsAction } from "aws-cdk-lib/aws-cloudwatch-actions";
4
+ import { ALARM_DEFAULTS, registerAlarm, tagAlarmsWithApplicationId, buildAlarmDescription } from "./alarmDefaults.js";
5
+ const DEFAULT_THRESHOLDS = {
6
+ FAILED_INVOCATIONS: 1,
7
+ THROTTLED_RULES: 1,
8
+ DLQ_FAILURE: 1,
9
+ MATCHED_EVENTS_LOW_FLOOR_RATIO: 0.8
10
+ };
11
+ /**
12
+ * Provisions D13 alarms for an EventBridge rule. Returns the created alarms
13
+ * (each wired to the SNS topic for both ALARM and OK transitions and tagged
14
+ * with the application id for webhook routing).
15
+ *
16
+ * Alarm coverage:
17
+ * - `FailedInvocations` / `ThrottledRules` always fire.
18
+ * - `InvocationsFailedToBeSentToDlq` fires only when `dlqEnabled === true`.
19
+ * - `MatchedEvents` (missed-tick detector) fires only when
20
+ * `kind === "schedule"` AND `expectedTicksPerHour !== undefined` —
21
+ * i.e. rate expressions, not cron, not subscriptions. Uses
22
+ * `LessThanThreshold` + `treatMissingData: BREACHING` so a fully silent
23
+ * rule trips the alarm.
24
+ */
25
+ export function createScheduleAlarms(props) {
26
+ const { scope, ruleName, rule, kind, expectedTicksPerHour, config = {}, alarmTopic, applicationId, dlqEnabled } = props;
27
+ const alarms = [];
28
+ const snsAction = new SnsAction(alarmTopic);
29
+ const failedInvocationsThreshold = config.failedInvocationsThreshold ?? DEFAULT_THRESHOLDS.FAILED_INVOCATIONS;
30
+ const throttledRulesThreshold = config.throttledRulesThreshold ?? DEFAULT_THRESHOLDS.THROTTLED_RULES;
31
+ const dlqFailureThreshold = config.dlqFailureThreshold ?? DEFAULT_THRESHOLDS.DLQ_FAILURE;
32
+ const matchedEventsLowFloorRatio = config.matchedEventsLowFloorRatio ??
33
+ DEFAULT_THRESHOLDS.MATCHED_EVENTS_LOW_FLOOR_RATIO;
34
+ const ruleDimensions = { RuleName: rule.ruleName };
35
+ const failedInvocationsAlarm = new Alarm(scope, `${ruleName}FailedInvocationsAlarm`, {
36
+ alarmDescription: buildAlarmDescription(`EventBridge rule ${ruleName} FailedInvocations exceeds ${failedInvocationsThreshold}`, applicationId),
37
+ metric: new Metric({
38
+ namespace: "AWS/Events",
39
+ metricName: "FailedInvocations",
40
+ dimensionsMap: ruleDimensions,
41
+ period: ALARM_DEFAULTS.EVALUATION_PERIOD,
42
+ statistic: "Sum"
43
+ }),
44
+ threshold: failedInvocationsThreshold,
45
+ evaluationPeriods: 2,
46
+ datapointsToAlarm: 2,
47
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
48
+ treatMissingData: TreatMissingData.NOT_BREACHING
49
+ });
50
+ registerAlarm(failedInvocationsAlarm, snsAction, alarms);
51
+ const throttledRulesAlarm = new Alarm(scope, `${ruleName}ThrottledRulesAlarm`, {
52
+ alarmDescription: buildAlarmDescription(`EventBridge rule ${ruleName} ThrottledRules exceeds ${throttledRulesThreshold}`, applicationId),
53
+ metric: new Metric({
54
+ namespace: "AWS/Events",
55
+ metricName: "ThrottledRules",
56
+ dimensionsMap: ruleDimensions,
57
+ period: ALARM_DEFAULTS.EVALUATION_PERIOD,
58
+ statistic: "Sum"
59
+ }),
60
+ threshold: throttledRulesThreshold,
61
+ evaluationPeriods: 2,
62
+ datapointsToAlarm: 2,
63
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
64
+ treatMissingData: TreatMissingData.NOT_BREACHING
65
+ });
66
+ registerAlarm(throttledRulesAlarm, snsAction, alarms);
67
+ if (dlqEnabled) {
68
+ const dlqFailureAlarm = new Alarm(scope, `${ruleName}DlqFailureAlarm`, {
69
+ alarmDescription: buildAlarmDescription(`EventBridge rule ${ruleName} InvocationsFailedToBeSentToDlq exceeds ${dlqFailureThreshold}`, applicationId),
70
+ metric: new Metric({
71
+ namespace: "AWS/Events",
72
+ metricName: "InvocationsFailedToBeSentToDlq",
73
+ dimensionsMap: ruleDimensions,
74
+ period: Duration.minutes(5),
75
+ statistic: "Sum"
76
+ }),
77
+ threshold: dlqFailureThreshold,
78
+ evaluationPeriods: 1,
79
+ datapointsToAlarm: 1,
80
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
81
+ treatMissingData: TreatMissingData.NOT_BREACHING
82
+ });
83
+ registerAlarm(dlqFailureAlarm, snsAction, alarms);
84
+ }
85
+ if (kind === "schedule" && expectedTicksPerHour !== undefined) {
86
+ const floor = Math.max(1, Math.floor(expectedTicksPerHour * matchedEventsLowFloorRatio));
87
+ const matchedEventsAlarm = new Alarm(scope, `${ruleName}MatchedEventsAlarm`, {
88
+ alarmDescription: buildAlarmDescription(`EventBridge rule ${ruleName} expected >=${floor} ticks/hour, observed <=${Math.round(matchedEventsLowFloorRatio * 100)}% threshold = silent disablement detector`, applicationId),
89
+ metric: new Metric({
90
+ namespace: "AWS/Events",
91
+ metricName: "MatchedEvents",
92
+ dimensionsMap: ruleDimensions,
93
+ period: Duration.hours(1),
94
+ statistic: "Sum"
95
+ }),
96
+ threshold: floor,
97
+ evaluationPeriods: 1,
98
+ datapointsToAlarm: 1,
99
+ comparisonOperator: ComparisonOperator.LESS_THAN_THRESHOLD,
100
+ treatMissingData: TreatMissingData.BREACHING
101
+ });
102
+ registerAlarm(matchedEventsAlarm, snsAction, alarms);
103
+ }
104
+ tagAlarmsWithApplicationId(alarms, applicationId);
105
+ return alarms;
106
+ }
@@ -1,7 +1,7 @@
1
1
  import { Construct } from "constructs";
2
2
  import { Tags } from "aws-cdk-lib";
3
3
  import { CrossAccountZoneDelegationRecord as CdkCrossAccountZoneDelegationRecord } from "aws-cdk-lib/aws-route53";
4
- import { DEFAULT_COST_ALLOCATION_ENVIRONMENT } from "../../../utils/costAllocationTags.js";
4
+ import { applyCostAllocationTags } from "../../../utils/costAllocationTags.js";
5
5
  // CDK's CrossAccountZoneDelegationRecord is a custom-resource Lambda: user-level tags
6
6
  // on the record itself may not reach AWS. We tag the wrapping Fjall Construct for
7
7
  // assertion purposes; create-path tags still propagate to child resources (Lambda,
@@ -20,8 +20,10 @@ export class CrossAccountDelegationRecord extends Construct {
20
20
  parentHostedZoneName: props.parentHostedZoneName
21
21
  });
22
22
  Tags.of(this).add("fjall:description", this.description);
23
- Tags.of(this).add("fjall:costAllocation:environment", props.costAllocationEnvironment ?? DEFAULT_COST_ALLOCATION_ENVIRONMENT);
24
- Tags.of(this).add("fjall:costAllocation:service", "crossAccountDelegation");
25
- Tags.of(this).add("fjall:costAllocation:domain", props.costAllocationDomain ?? props.delegatedZoneName);
23
+ applyCostAllocationTags(this, {
24
+ service: "crossAccountDelegation",
25
+ domain: props.costAllocationDomain ?? props.delegatedZoneName,
26
+ environment: props.costAllocationEnvironment
27
+ });
26
28
  }
27
29
  }
@@ -1,5 +1,5 @@
1
1
  import { Construct } from "constructs";
2
- import { Duration } from "aws-cdk-lib";
2
+ import { Duration, Fn } from "aws-cdk-lib";
3
3
  import { Runtime } from "aws-cdk-lib/aws-lambda";
4
4
  import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam";
5
5
  import { CustomResource } from "../utilities/customResource.js";
@@ -35,7 +35,7 @@ export class CrossAccountReturnRoutes extends Construct {
35
35
  properties: {
36
36
  PeerRoleArn: props.peerRoleArn,
37
37
  PeerRegion: props.peerRegion,
38
- RouteTableIds: props.routeTableIds.join(","),
38
+ RouteTableIds: Fn.join(",", props.routeTableIds),
39
39
  LocalVpcCidr: props.localVpcCidr,
40
40
  PeeringConnectionId: props.peeringConnectionId,
41
41
  EnableDns: props.enableDns === false ? "false" : "true"
@@ -107,13 +107,18 @@ exports.handler = async (event) => {
107
107
  if (event.RequestType === "Update") {
108
108
  const oldProps = event.OldResourceProperties || {};
109
109
  const oldRouteTableIds = parseRouteTableIds(oldProps.RouteTableIds);
110
+ const oldCidr = oldProps.LocalVpcCidr || props.LocalVpcCidr;
111
+ const cidrChanged = oldCidr !== props.LocalVpcCidr;
110
112
  const currentSet = new Set(routeTableIds);
111
- const removedIds = oldRouteTableIds.filter((id) => !currentSet.has(id));
112
- for (const routeTableId of removedIds) {
113
+ // CIDR change: stale routes persist on every kept table unless deleted by old CIDR first.
114
+ const idsToDeleteOldCidr = cidrChanged
115
+ ? oldRouteTableIds
116
+ : oldRouteTableIds.filter((id) => !currentSet.has(id));
117
+ for (const routeTableId of idsToDeleteOldCidr) {
113
118
  try {
114
119
  await ec2.send(new DeleteRouteCommand({
115
120
  RouteTableId: routeTableId,
116
- DestinationCidrBlock: oldProps.LocalVpcCidr || props.LocalVpcCidr
121
+ DestinationCidrBlock: oldCidr
117
122
  }));
118
123
  } catch (err) {
119
124
  const name = err && err.name;
@@ -140,14 +145,13 @@ exports.handler = async (event) => {
140
145
  }
141
146
  }
142
147
 
143
- if (props.EnableDns === "true") {
144
- await ec2.send(new ModifyVpcPeeringConnectionOptionsCommand({
145
- VpcPeeringConnectionId: props.PeeringConnectionId,
146
- AccepterPeeringConnectionOptions: {
147
- AllowDnsResolutionFromRemoteVpc: true
148
- }
149
- }));
150
- }
148
+ // Always sent so a flip from true to false actually disables DNS — guarding on === "true" leaves it enabled forever.
149
+ await ec2.send(new ModifyVpcPeeringConnectionOptionsCommand({
150
+ VpcPeeringConnectionId: props.PeeringConnectionId,
151
+ AccepterPeeringConnectionOptions: {
152
+ AllowDnsResolutionFromRemoteVpc: props.EnableDns === "true"
153
+ }
154
+ }));
151
155
 
152
156
  return { PhysicalResourceId: physicalResourceId };
153
157
  };
@@ -1,6 +1,6 @@
1
- import { Duration, Tags } from "aws-cdk-lib";
1
+ import { Duration } from "aws-cdk-lib";
2
2
  import { DNS_APEX } from "@fjall/util";
3
- import { DEFAULT_COST_ALLOCATION_ENVIRONMENT } from "../../../../utils/costAllocationTags.js";
3
+ import { applyCostAllocationTags } from "../../../../utils/costAllocationTags.js";
4
4
  export const DEFAULT_DNS_TTL_SECONDS = 300;
5
5
  export function resolveFqdn(zoneName, recordName) {
6
6
  return recordName === DNS_APEX ? zoneName : `${recordName}.${zoneName}`;
@@ -12,7 +12,9 @@ export function defaultDnsComment(recordType, fqdn) {
12
12
  return `Fjall-managed ${recordType} record for ${fqdn}`;
13
13
  }
14
14
  export function applyDnsRecordTags(construct, props) {
15
- Tags.of(construct).add("fjall:costAllocation:environment", props.costAllocationEnvironment ?? DEFAULT_COST_ALLOCATION_ENVIRONMENT);
16
- Tags.of(construct).add("fjall:costAllocation:service", "dnsRecord");
17
- Tags.of(construct).add("fjall:costAllocation:domain", props.costAllocationDomain ?? props.zoneName);
15
+ applyCostAllocationTags(construct, {
16
+ service: "dnsRecord",
17
+ domain: props.costAllocationDomain ?? props.zoneName,
18
+ environment: props.costAllocationEnvironment
19
+ });
18
20
  }
@@ -1,5 +1,5 @@
1
1
  import { Construct } from "constructs";
2
- import { Certificate } from "aws-cdk-lib/aws-certificatemanager";
2
+ import { type ICertificate } from "aws-cdk-lib/aws-certificatemanager";
3
3
  import { type IHostedZone } from "aws-cdk-lib/aws-route53";
4
4
  export interface DomainCertificateProps {
5
5
  readonly domainName: string;
@@ -11,7 +11,7 @@ export interface DomainCertificateProps {
11
11
  readonly costAllocationDomain?: string;
12
12
  }
13
13
  export declare class DomainCertificate extends Construct {
14
- readonly certificate: Certificate;
14
+ readonly certificate: ICertificate;
15
15
  readonly certificateArn: string;
16
16
  readonly description: string;
17
17
  constructor(scope: Construct, id: string, props: DomainCertificateProps);
@@ -3,7 +3,7 @@ import { CfnOutput, Tags } from "aws-cdk-lib";
3
3
  import { Certificate, CertificateValidation } from "aws-cdk-lib/aws-certificatemanager";
4
4
  import { getDomainExportNames } from "@fjall/util";
5
5
  import { toPascalCase } from "../../../utils/capitaliseString.js";
6
- import { DEFAULT_COST_ALLOCATION_ENVIRONMENT } from "../../../utils/costAllocationTags.js";
6
+ import { applyCostAllocationTags } from "../../../utils/costAllocationTags.js";
7
7
  export class DomainCertificate extends Construct {
8
8
  certificate;
9
9
  certificateArn;
@@ -21,9 +21,11 @@ export class DomainCertificate extends Construct {
21
21
  });
22
22
  this.certificateArn = this.certificate.certificateArn;
23
23
  Tags.of(this).add("fjall:description", this.description);
24
- Tags.of(this).add("fjall:costAllocation:environment", props.costAllocationEnvironment ?? DEFAULT_COST_ALLOCATION_ENVIRONMENT);
25
- Tags.of(this).add("fjall:costAllocation:service", "certificate");
26
- Tags.of(this).add("fjall:costAllocation:domain", props.costAllocationDomain ?? props.domainName);
24
+ applyCostAllocationTags(this, {
25
+ service: "certificate",
26
+ domain: props.costAllocationDomain ?? props.domainName,
27
+ environment: props.costAllocationEnvironment
28
+ });
27
29
  const safeKey = toPascalCase(props.domainName.split(".").join(""));
28
30
  const exports = getDomainExportNames(props.domainName);
29
31
  new CfnOutput(this, `${safeKey}CertificateArn`, {
@@ -4,7 +4,7 @@ import { HostedZone as AWSHostedZone } from "aws-cdk-lib/aws-route53";
4
4
  import { getDomainExportNames } from "@fjall/util";
5
5
  import { toPascalCase, getSafeZoneName } from "../../../utils/capitaliseString.js";
6
6
  import { DelegationRole } from "../iam/delegationRole.js";
7
- import { DEFAULT_COST_ALLOCATION_ENVIRONMENT } from "../../../utils/costAllocationTags.js";
7
+ import { applyCostAllocationTags } from "../../../utils/costAllocationTags.js";
8
8
  export class HostedZoneFactory {
9
9
  static import(stack, hostedZoneId, zoneName, opts) {
10
10
  const safeZone = toPascalCase(getSafeZoneName(zoneName));
@@ -71,10 +71,11 @@ export class HostedZone extends Construct {
71
71
  this.isImported = true;
72
72
  Tags.of(this).add("fjall:description", this.description);
73
73
  }
74
- // Cost allocation tags applied uniformly on both create and import paths.
75
- Tags.of(this).add("fjall:costAllocation:environment", props.costAllocationEnvironment ?? DEFAULT_COST_ALLOCATION_ENVIRONMENT);
76
- Tags.of(this).add("fjall:costAllocation:service", "hostedZone");
77
- Tags.of(this).add("fjall:costAllocation:domain", props.costAllocationDomain ?? props.zoneName);
74
+ applyCostAllocationTags(this, {
75
+ service: "hostedZone",
76
+ domain: props.costAllocationDomain ?? props.zoneName,
77
+ environment: props.costAllocationEnvironment
78
+ });
78
79
  new CfnOutput(this, `${safeZone}HostedZoneId`, {
79
80
  key: `${safeZone}HostedZoneId`,
80
81
  value: this.hostedZoneId,
@@ -0,0 +1,96 @@
1
+ import { Construct } from "constructs";
2
+ import { Duration } from "aws-cdk-lib";
3
+ import { type IVpc } from "aws-cdk-lib/aws-ec2";
4
+ import { type IPrivateDnsNamespace, type IService } from "aws-cdk-lib/aws-servicediscovery";
5
+ /**
6
+ * Plumbing-only base resource wrapping `aws-cdk-lib/aws-servicediscovery`'s
7
+ * `PrivateDnsNamespace`. Per D7 + D29 of the ClickHouse Database Factory
8
+ * promotion design, the only consumer entry point is `App.getNamespace()` —
9
+ * this class is NOT re-exported from `lib/index.ts`.
10
+ *
11
+ * No `static fromXxx(...)` factory is exposed: cross-stack consumption is
12
+ * single-stack-only for v1 per D29. If/when cross-stack arises, a `fromXxx`
13
+ * factory lands then, not now.
14
+ */
15
+ export interface ServiceDiscoveryNamespaceProps {
16
+ vpc: IVpc;
17
+ name: string;
18
+ /** Override the default description (`"ServiceDiscovery <name> — Fjall private DNS namespace"`). */
19
+ description?: string;
20
+ /**
21
+ * Removal policy. Default resolves via the `env()` helper (production →
22
+ * RETAIN; non-prod → DESTROY) — matches the convention codified in D17 of
23
+ * the EventBridge promotion design. NODE_ENV is intentionally NOT consulted
24
+ * because it is unset during CDK synth in Fjall's deployment paths.
25
+ */
26
+ removalPolicy?: "DESTROY" | "RETAIN";
27
+ }
28
+ /**
29
+ * Properties for registering a Cloud Map service inside the namespace via
30
+ * `registerService(...)`. Single source of truth for the second-tier wrapper
31
+ * around `AWS::ServiceDiscovery::Service`. Per D7(d), this method is
32
+ * internal-only — never consumed directly from user infrastructure code.
33
+ */
34
+ export interface ServiceRegistrationProps {
35
+ /** Service name within the namespace (forms the leftmost label of the FQDN). */
36
+ name: string;
37
+ /**
38
+ * Description override. Default: `"Fjall internal service: <name>"`.
39
+ * Operators see this in the Cloud Map console; explicit overrides are
40
+ * preferred when the service name alone is ambiguous (e.g. `"clickhouse"`
41
+ * is fine; `"web"` benefits from a description naming the role).
42
+ */
43
+ description?: string;
44
+ /**
45
+ * DNS TTL applied to the A records returned for this service. Default:
46
+ * 60 seconds — matches CDK's implicit ECS default. Raise for stable
47
+ * services (cuts query volume); lower for rapidly-changing instance sets.
48
+ */
49
+ dnsTtl?: Duration;
50
+ /**
51
+ * DNS record type. Default `"A"` — appropriate for `awsvpc` ECS services
52
+ * where each task has its own ENI IP. Set to `"SRV"` when the consumer is
53
+ * an ECS service running under `host` or `bridge` network mode: SRV records
54
+ * carry both the host IP and the published port, which `A` records cannot.
55
+ * CDK's `Ec2Service.associateCloudMapService(...)` requires SRV (and a
56
+ * `containerName` + `containerPort`) under those modes.
57
+ */
58
+ dnsRecordType?: "A" | "SRV";
59
+ }
60
+ export declare class ServiceDiscoveryNamespace extends Construct {
61
+ #private;
62
+ readonly id: string;
63
+ constructor(scope: Construct, id: string, props: ServiceDiscoveryNamespaceProps);
64
+ /**
65
+ * Get the underlying CDK private DNS namespace as the `IPrivateDnsNamespace`
66
+ * interface. Per D18(a), the public surface is the interface, never the
67
+ * concrete `PrivateDnsNamespace` class.
68
+ */
69
+ getNamespace(): IPrivateDnsNamespace;
70
+ getNamespaceArn(): string;
71
+ getNamespaceName(): string;
72
+ /**
73
+ * Register a Cloud Map service inside this namespace. Creates an
74
+ * `AWS::ServiceDiscovery::Service` resource via the underlying CDK
75
+ * `PrivateDnsNamespace.createService(...)` with Fjall description discipline
76
+ * applied. Internal-only per D7(d) — Fjall resources call this from inside
77
+ * their construct bodies; user code never invokes it directly.
78
+ *
79
+ * Throws on duplicate registration: a single namespace cannot host two
80
+ * services with the same DNS name. Caught at synth time, not deploy time.
81
+ */
82
+ registerService(props: ServiceRegistrationProps): IService;
83
+ /**
84
+ * True if a service with this name has been registered in the namespace.
85
+ * Cheap registry lookup; does NOT consult AWS — only counts services
86
+ * registered via `registerService(...)` on this construct.
87
+ */
88
+ hasService(name: string): boolean;
89
+ /**
90
+ * Build the FQDN for a previously-registered service:
91
+ * `<serviceName>.<namespaceName>` (e.g. `clickhouse.acme.local`). Throws if
92
+ * the service has not been registered — prevents typos that would silently
93
+ * resolve to NXDOMAIN at runtime.
94
+ */
95
+ getEndpoint(serviceName: string): string;
96
+ }