@fjall/components-infrastructure 4.4.0 → 6.0.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 (46) hide show
  1. package/dist/lib/config/aws/ebsDefaultEncryption.js +1 -1
  2. package/dist/lib/config/aws/inspectorEnablement.js +1 -1
  3. package/dist/lib/config/aws/oidcConnector.d.ts +6 -6
  4. package/dist/lib/config/aws/oidcConnector.js +1 -1
  5. package/dist/lib/config/aws/s3BlockPublicAccess.js +1 -1
  6. package/dist/lib/config/aws/securityServicesAdmin.js +2 -2
  7. package/dist/lib/lambda-assets/cert-generator/asset/index.js +31 -31
  8. package/dist/lib/patterns/aws/account.d.ts +11 -10
  9. package/dist/lib/patterns/aws/account.js +11 -10
  10. package/dist/lib/patterns/aws/clickhouseDatabase.d.ts +16 -3
  11. package/dist/lib/patterns/aws/clickhouseDatabase.js +23 -2
  12. package/dist/lib/patterns/aws/compute.js +1 -1
  13. package/dist/lib/patterns/aws/computeEcs.js +5 -0
  14. package/dist/lib/patterns/aws/computeEcsTypes.d.ts +12 -2
  15. package/dist/lib/patterns/aws/computeLambda.d.ts +1 -1
  16. package/dist/lib/patterns/aws/database.js +5 -5
  17. package/dist/lib/patterns/aws/devSubstrate.js +1 -1
  18. package/dist/lib/patterns/aws/payload.js +3 -3
  19. package/dist/lib/patterns/aws/staticSite.js +1 -1
  20. package/dist/lib/resources/aws/compute/ec2GracefulTerminationHandler.js +1 -1
  21. package/dist/lib/resources/aws/compute/ecs.js +1 -0
  22. package/dist/lib/resources/aws/compute/ecsCapacityConfig.d.ts +43 -0
  23. package/dist/lib/resources/aws/compute/ecsCapacityConfig.js +161 -0
  24. package/dist/lib/resources/aws/compute/ecsConstants.d.ts +18 -0
  25. package/dist/lib/resources/aws/compute/ecsConstants.js +32 -0
  26. package/dist/lib/resources/aws/compute/ecsLifecycleHookMigration.js +1 -1
  27. package/dist/lib/resources/aws/compute/ecsServiceFactory.d.ts +9 -6
  28. package/dist/lib/resources/aws/compute/ecsServiceFactory.js +17 -41
  29. package/dist/lib/resources/aws/compute/ecsTaskDefinition.js +2 -6
  30. package/dist/lib/resources/aws/compute/ecsValidation.js +15 -2
  31. package/dist/lib/resources/aws/compute/persistentDataVolume.js +1 -1
  32. package/dist/lib/resources/aws/database/clickhouseConstants.d.ts +58 -25
  33. package/dist/lib/resources/aws/database/clickhouseConstants.js +89 -25
  34. package/dist/lib/resources/aws/database/clickhouseUserData.js +77 -4
  35. package/dist/lib/resources/aws/database/rdsAurora.js +1 -1
  36. package/dist/lib/resources/aws/database/rdsInstance.js +2 -2
  37. package/dist/lib/resources/aws/iam/identityCenter/user.js +1 -1
  38. package/dist/lib/resources/aws/monitoring/clickhouseAlarms.d.ts +9 -5
  39. package/dist/lib/resources/aws/monitoring/clickhouseAlarms.js +21 -12
  40. package/dist/lib/resources/aws/networking/crossAccountReturnRoutes.js +1 -1
  41. package/dist/lib/resources/aws/networking/ipamPool.js +1 -1
  42. package/dist/lib/resources/aws/organisation/costAllocationTagActivator.js +1 -1
  43. package/dist/lib/resources/aws/utilities/tlsCertGenerator.js +1 -1
  44. package/dist/lib/utils/engineCompat.d.ts +42 -13
  45. package/dist/lib/utils/engineCompat.js +111 -25
  46. package/package.json +30 -23
@@ -11,7 +11,7 @@ export class EbsDefaultEncryption extends Construct {
11
11
  constructor(scope, id) {
12
12
  super(scope, id);
13
13
  new CustomResource(this, "EbsDefaultEncryption", {
14
- runtime: Runtime.NODEJS_22_X,
14
+ runtime: Runtime.NODEJS_24_X,
15
15
  timeout: Duration.minutes(5),
16
16
  lambdaDescription: "Enables EBS default encryption at account level",
17
17
  inlinePolicy: [
@@ -12,7 +12,7 @@ export class InspectorEnablement extends Construct {
12
12
  constructor(scope, id) {
13
13
  super(scope, id);
14
14
  new CustomResource(this, "InspectorEnable", {
15
- runtime: Runtime.NODEJS_22_X,
15
+ runtime: Runtime.NODEJS_24_X,
16
16
  timeout: Duration.minutes(5),
17
17
  lambdaDescription: "Enables/disables Inspector v2 for the account",
18
18
  inlinePolicy: [
@@ -24,12 +24,12 @@ export interface OidcConnectorProps {
24
24
  * Account TIER of the connected account (organisation | platform | account) —
25
25
  * the structural TIER axis, from the account stack's polymorphic
26
26
  * `deployRoleAccountTier()`; NEVER the wire `environment` / stage axis (that is
27
- * `accountStage`). Drives the §4.4a deploy-trust governance expand: a
28
- * governance tier (organisation/platform) plus the synth account id widens the
29
- * `FjallDeploy` machine subject to ALSO accept the account-scoped literal
30
- * `org:{orgId}:acct:{awsAccountId}` (StringEquals array = OR of exact
31
- * literals), so per-account deploy issuance can later flip. App-tier
32
- * ("account") or absent generic-only scalar (unchanged, fail-safe).
27
+ * `accountStage`). Since the §4.4b Step-1 expand, trust WIDTH no longer
28
+ * keys on this tier: every synth passes the account id, so the `FjallDeploy`
29
+ * machine subject accepts `[generic, acct]` (StringEquals array = OR of
30
+ * exact literals) for EVERY tier, letting per-account deploy issuance flip.
31
+ * The tier now gates only the `machineTrustMode: "account-scoped"` contract
32
+ * (governance-only until §4.4b's probe gate) and the boundary preset.
33
33
  */
34
34
  accountTier?: AccountTier;
35
35
  /**
@@ -74,7 +74,7 @@ export class OidcConnector extends Construct {
74
74
  const issuerDomain = FJALL_OIDC_ISSUER_DOMAIN;
75
75
  const issuerUrl = `https://${issuerDomain}`;
76
76
  const providerResource = new CustomResource(this, "OidcProvider", {
77
- runtime: Runtime.NODEJS_22_X,
77
+ runtime: Runtime.NODEJS_24_X,
78
78
  timeout: Duration.minutes(5),
79
79
  lambdaDescription: "Adopt-or-create the account-global Fjall OIDC provider",
80
80
  resourceType: "Custom::FjallOidcProvider",
@@ -25,7 +25,7 @@ export class S3BlockPublicAccess extends Construct {
25
25
  const account = getConfig().providerAccounts.find((pa) => pa.id === props.accountId);
26
26
  const enforce = (account?.s3BlockPublicAccess ?? "off") === "enforced";
27
27
  new CustomResource(this, "S3BlockPublicAccess", {
28
- runtime: Runtime.NODEJS_22_X,
28
+ runtime: Runtime.NODEJS_24_X,
29
29
  timeout: Duration.minutes(5),
30
30
  lambdaDescription: "Manages S3 Block Public Access at account level",
31
31
  rolePath: "/fjall/",
@@ -35,7 +35,7 @@ export class SecurityServicesAdmin extends Construct {
35
35
  }
36
36
  createGuardDutyAdmin() {
37
37
  new CustomResource(this, "GuardDutyOrgConfig", {
38
- runtime: Runtime.NODEJS_22_X,
38
+ runtime: Runtime.NODEJS_24_X,
39
39
  timeout: Duration.minutes(5),
40
40
  lambdaDescription: "Configures GuardDuty organisation-wide auto-enablement",
41
41
  inlinePolicy: [
@@ -70,7 +70,7 @@ exports.handler = async (event) => {
70
70
  }
71
71
  createSecurityHubAdmin() {
72
72
  new CustomResource(this, "SecurityHubOrgConfig", {
73
- runtime: Runtime.NODEJS_22_X,
73
+ runtime: Runtime.NODEJS_24_X,
74
74
  timeout: Duration.minutes(5),
75
75
  lambdaDescription: "Configures SecurityHub organisation-wide auto-enablement",
76
76
  inlinePolicy: [
@@ -6549,15 +6549,15 @@ var require_parser = __commonJS({
6549
6549
  var helper_1 = require_helper();
6550
6550
  var storage_1 = require_storage();
6551
6551
  var AsnParser = class {
6552
- static parse(data, target) {
6553
- const asn1Parsed = asn1js.fromBER((0, bytes_1.toArrayBuffer)(data));
6552
+ static parse(data, target, options) {
6553
+ const asn1Parsed = asn1js.fromBER((0, bytes_1.toArrayBuffer)(data), options?.berOptions);
6554
6554
  if (asn1Parsed.result.error) {
6555
6555
  throw new Error(asn1Parsed.result.error);
6556
6556
  }
6557
- const res = this.fromASN(asn1Parsed.result, target);
6557
+ const res = this.fromASN(asn1Parsed.result, target, options);
6558
6558
  return res;
6559
6559
  }
6560
- static fromASN(asn1Schema, target) {
6560
+ static fromASN(asn1Schema, target, options) {
6561
6561
  try {
6562
6562
  if ((0, helper_1.isConvertible)(target)) {
6563
6563
  const value = new target();
@@ -6566,7 +6566,7 @@ var require_parser = __commonJS({
6566
6566
  const schema = storage_1.schemaStorage.get(target);
6567
6567
  storage_1.schemaStorage.cache(target);
6568
6568
  let targetSchema = schema.schema;
6569
- const choiceResult = this.handleChoiceTypes(asn1Schema, schema, target, targetSchema);
6569
+ const choiceResult = this.handleChoiceTypes(asn1Schema, schema, target, targetSchema, options);
6570
6570
  if (choiceResult?.result) {
6571
6571
  return choiceResult.result;
6572
6572
  }
@@ -6576,9 +6576,9 @@ var require_parser = __commonJS({
6576
6576
  const sequenceResult = this.handleSequenceTypes(asn1Schema, schema, target, targetSchema);
6577
6577
  const res = new target();
6578
6578
  if ((0, helper_1.isTypeOfArray)(target)) {
6579
- return this.handleArrayTypes(asn1Schema, schema, target);
6579
+ return this.handleArrayTypes(asn1Schema, schema, target, options);
6580
6580
  }
6581
- this.processSchemaItems(schema, sequenceResult, res);
6581
+ this.processSchemaItems(schema, sequenceResult, res, options);
6582
6582
  return res;
6583
6583
  } catch (error) {
6584
6584
  if (error instanceof errors_1.AsnSchemaValidationError) {
@@ -6587,7 +6587,7 @@ var require_parser = __commonJS({
6587
6587
  throw error;
6588
6588
  }
6589
6589
  }
6590
- static handleChoiceTypes(asn1Schema, schema, target, targetSchema) {
6590
+ static handleChoiceTypes(asn1Schema, schema, target, targetSchema, options) {
6591
6591
  if (asn1Schema.constructor === asn1js.Constructed && schema.type === enums_1.AsnTypeTypes.Choice && asn1Schema.idBlock.tagClass === 3) {
6592
6592
  for (const key in schema.items) {
6593
6593
  const schemaItem = schema.items[key];
@@ -6598,7 +6598,7 @@ var require_parser = __commonJS({
6598
6598
  const newSeq = new asn1js.Sequence();
6599
6599
  if ("value" in asn1Schema.valueBlock && Array.isArray(asn1Schema.valueBlock.value) && "value" in newSeq.valueBlock) {
6600
6600
  newSeq.valueBlock.value = asn1Schema.valueBlock.value;
6601
- const fieldValue = this.fromASN(newSeq, schemaItem.type);
6601
+ const fieldValue = this.fromASN(newSeq, schemaItem.type, options);
6602
6602
  const res = new target();
6603
6603
  res[key] = fieldValue;
6604
6604
  return { result: res };
@@ -6689,7 +6689,7 @@ var require_parser = __commonJS({
6689
6689
  throw err;
6690
6690
  }
6691
6691
  }
6692
- static handleArrayTypes(asn1Schema, schema, target) {
6692
+ static handleArrayTypes(asn1Schema, schema, target, options) {
6693
6693
  if (!("value" in asn1Schema.valueBlock && Array.isArray(asn1Schema.valueBlock.value))) {
6694
6694
  throw new Error("Cannot get items from the ASN.1 parsed value. ASN.1 object is not constructed.");
6695
6695
  }
@@ -6701,10 +6701,10 @@ var require_parser = __commonJS({
6701
6701
  }
6702
6702
  return target.from(asn1Schema.valueBlock.value, (element) => converter.fromASN(element));
6703
6703
  } else {
6704
- return target.from(asn1Schema.valueBlock.value, (element) => this.fromASN(element, itemType));
6704
+ return target.from(asn1Schema.valueBlock.value, (element) => this.fromASN(element, itemType, options));
6705
6705
  }
6706
6706
  }
6707
- static processSchemaItems(schema, asn1ComparedSchema, res) {
6707
+ static processSchemaItems(schema, asn1ComparedSchema, res, options) {
6708
6708
  for (const key in schema.items) {
6709
6709
  const asn1SchemaValue = asn1ComparedSchema.result[key];
6710
6710
  if (!asn1SchemaValue) {
@@ -6714,9 +6714,9 @@ var require_parser = __commonJS({
6714
6714
  const schemaItemType = schemaItem.type;
6715
6715
  let parsedValue;
6716
6716
  if (typeof schemaItemType === "number" || (0, helper_1.isConvertible)(schemaItemType)) {
6717
- parsedValue = this.processPrimitiveSchemaItem(asn1SchemaValue, schemaItem, schemaItemType);
6717
+ parsedValue = this.processPrimitiveSchemaItem(asn1SchemaValue, schemaItem, schemaItemType, options);
6718
6718
  } else {
6719
- parsedValue = this.processComplexSchemaItem(asn1SchemaValue, schemaItem, schemaItemType);
6719
+ parsedValue = this.processComplexSchemaItem(asn1SchemaValue, schemaItem, schemaItemType, options);
6720
6720
  }
6721
6721
  if (parsedValue && typeof parsedValue === "object" && "value" in parsedValue && "raw" in parsedValue) {
6722
6722
  res[key] = parsedValue.value;
@@ -6726,23 +6726,23 @@ var require_parser = __commonJS({
6726
6726
  }
6727
6727
  }
6728
6728
  }
6729
- static processPrimitiveSchemaItem(asn1SchemaValue, schemaItem, schemaItemType) {
6729
+ static processPrimitiveSchemaItem(asn1SchemaValue, schemaItem, schemaItemType, options) {
6730
6730
  const converter = schemaItem.converter ?? ((0, helper_1.isConvertible)(schemaItemType) ? new schemaItemType() : null);
6731
6731
  if (!converter) {
6732
6732
  throw new Error("Converter is empty");
6733
6733
  }
6734
6734
  if (schemaItem.repeated) {
6735
- return this.processRepeatedPrimitiveItem(asn1SchemaValue, schemaItem, converter);
6735
+ return this.processRepeatedPrimitiveItem(asn1SchemaValue, schemaItem, converter, options);
6736
6736
  } else {
6737
- return this.processSinglePrimitiveItem(asn1SchemaValue, schemaItem, schemaItemType, converter);
6737
+ return this.processSinglePrimitiveItem(asn1SchemaValue, schemaItem, schemaItemType, converter, options);
6738
6738
  }
6739
6739
  }
6740
- static processRepeatedPrimitiveItem(asn1SchemaValue, schemaItem, converter) {
6740
+ static processRepeatedPrimitiveItem(asn1SchemaValue, schemaItem, converter, options) {
6741
6741
  if (schemaItem.implicit) {
6742
6742
  const Container = schemaItem.repeated === "sequence" ? asn1js.Sequence : asn1js.Set;
6743
6743
  const newItem = new Container();
6744
6744
  newItem.valueBlock = asn1SchemaValue.valueBlock;
6745
- const newItemAsn = asn1js.fromBER(newItem.toBER(false));
6745
+ const newItemAsn = asn1js.fromBER(newItem.toBER(false), options?.berOptions);
6746
6746
  if (newItemAsn.offset === -1) {
6747
6747
  throw new Error(`Cannot parse the child item. ${newItemAsn.result.error}`);
6748
6748
  }
@@ -6755,7 +6755,7 @@ var require_parser = __commonJS({
6755
6755
  return Array.from(asn1SchemaValue, (element) => converter.fromASN(element));
6756
6756
  }
6757
6757
  }
6758
- static processSinglePrimitiveItem(asn1SchemaValue, schemaItem, schemaItemType, converter) {
6758
+ static processSinglePrimitiveItem(asn1SchemaValue, schemaItem, schemaItemType, converter, options) {
6759
6759
  let value = asn1SchemaValue;
6760
6760
  if (schemaItem.implicit) {
6761
6761
  let newItem;
@@ -6770,21 +6770,21 @@ var require_parser = __commonJS({
6770
6770
  newItem = new Asn1Type();
6771
6771
  }
6772
6772
  newItem.valueBlock = value.valueBlock;
6773
- value = asn1js.fromBER(newItem.toBER(false)).result;
6773
+ value = asn1js.fromBER(newItem.toBER(false), options?.berOptions).result;
6774
6774
  }
6775
6775
  return converter.fromASN(value);
6776
6776
  }
6777
- static processComplexSchemaItem(asn1SchemaValue, schemaItem, schemaItemType) {
6777
+ static processComplexSchemaItem(asn1SchemaValue, schemaItem, schemaItemType, options) {
6778
6778
  if (schemaItem.repeated) {
6779
6779
  if (!Array.isArray(asn1SchemaValue)) {
6780
6780
  throw new Error("Cannot get list of items from the ASN.1 parsed value. ASN.1 value should be iterable.");
6781
6781
  }
6782
- return Array.from(asn1SchemaValue, (element) => this.fromASN(element, schemaItemType));
6782
+ return Array.from(asn1SchemaValue, (element) => this.fromASN(element, schemaItemType, options));
6783
6783
  } else {
6784
6784
  const valueToProcess = this.handleImplicitTagging(asn1SchemaValue, schemaItem, schemaItemType);
6785
6785
  if (this.isOptionalChoiceField(schemaItem)) {
6786
6786
  try {
6787
- return this.fromASN(valueToProcess, schemaItemType);
6787
+ return this.fromASN(valueToProcess, schemaItemType, options);
6788
6788
  } catch (err) {
6789
6789
  if (err instanceof errors_1.AsnSchemaValidationError && /Wrong values for Choice type/.test(err.message)) {
6790
6790
  return void 0;
@@ -6792,7 +6792,7 @@ var require_parser = __commonJS({
6792
6792
  throw err;
6793
6793
  }
6794
6794
  } else {
6795
- const parsedValue = this.fromASN(valueToProcess, schemaItemType);
6795
+ const parsedValue = this.fromASN(valueToProcess, schemaItemType, options);
6796
6796
  if (schemaItem.raw) {
6797
6797
  return {
6798
6798
  value: parsedValue,
@@ -7009,12 +7009,12 @@ var require_convert = __commonJS({
7009
7009
  static serialize(obj) {
7010
7010
  return serializer_1.AsnSerializer.serialize(obj);
7011
7011
  }
7012
- static parse(data, target) {
7013
- return parser_1.AsnParser.parse(data, target);
7012
+ static parse(data, target, options) {
7013
+ return parser_1.AsnParser.parse(data, target, options);
7014
7014
  }
7015
- static toString(data) {
7015
+ static toString(data, options) {
7016
7016
  const buf = (0, bytes_1.isBufferSource)(data) ? (0, bytes_1.toArrayBuffer)(data) : _AsnConvert.serialize(data);
7017
- const asn = asn1js.fromBER(buf);
7017
+ const asn = asn1js.fromBER(buf, options?.berOptions);
7018
7018
  if (asn.offset === -1) {
7019
7019
  throw new Error(`Cannot decode ASN.1 data. ${asn.result.error}`);
7020
7020
  }
@@ -14678,9 +14678,9 @@ var require_cjs11 = __commonJS({
14678
14678
  }
14679
14679
  });
14680
14680
 
14681
- // node_modules/@peculiar/x509/build/x509.cjs.js
14681
+ // ../../node_modules/@peculiar/x509/build/x509.cjs.js
14682
14682
  var require_x509_cjs = __commonJS({
14683
- "node_modules/@peculiar/x509/build/x509.cjs.js"(exports2) {
14683
+ "../../node_modules/@peculiar/x509/build/x509.cjs.js"(exports2) {
14684
14684
  "use strict";
14685
14685
  var asn1Schema = require_cjs();
14686
14686
  var asn1X509 = require_cjs2();
@@ -61,16 +61,17 @@ export declare class Account extends Stack {
61
61
  */
62
62
  protected receivesDeployRole(): boolean;
63
63
  /**
64
- * Account TIER passed to this account's `OidcConnector` for the §4.4a
65
- * deploy-trust governance expand. A polymorphic METHOD, not a read of the
66
- * `organisationType` field: the connector is instantiated in THIS base
67
- * constructor, and subclass field initialisers run AFTER `super()`, so
68
- * `this.organisationType` would read "account" for every subclass here (the
69
- * same construction-order reason `receivesDeployRole()` is a method). Base ⇒
70
- * "account" (app-tier, generic-only trust); `Platform` "platform" and
71
- * `Organisation` "organisation" override. Governance tiers widen the deploy
72
- * machine subject; kept in lockstep with `organisationType` by the
73
- * Platform-synth deploy-trust parity test.
64
+ * Account TIER passed to this account's `OidcConnector`. A polymorphic
65
+ * METHOD, not a read of the `organisationType` field: the connector is
66
+ * instantiated in THIS base constructor, and subclass field initialisers run
67
+ * AFTER `super()`, so `this.organisationType` would read "account" for every
68
+ * subclass here (the same construction-order reason `receivesDeployRole()`
69
+ * is a method). Base ⇒ "account"; `Platform` ⇒ "platform" and `Organisation`
70
+ * "organisation" override. Since the §4.4b Step-1 expand every tier
71
+ * renders the widened `[generic, acct]` machine subject the tier now gates
72
+ * only the governance-only "account-scoped" contract mode (the builder
73
+ * throws for tier "account"); kept in lockstep with `organisationType` by
74
+ * the Platform-synth deploy-trust parity test.
74
75
  */
75
76
  protected deployRoleAccountTier(): AccountTier;
76
77
  /**
@@ -151,16 +151,17 @@ export class Account extends Stack {
151
151
  return this.constructor === Account;
152
152
  }
153
153
  /**
154
- * Account TIER passed to this account's `OidcConnector` for the §4.4a
155
- * deploy-trust governance expand. A polymorphic METHOD, not a read of the
156
- * `organisationType` field: the connector is instantiated in THIS base
157
- * constructor, and subclass field initialisers run AFTER `super()`, so
158
- * `this.organisationType` would read "account" for every subclass here (the
159
- * same construction-order reason `receivesDeployRole()` is a method). Base ⇒
160
- * "account" (app-tier, generic-only trust); `Platform` "platform" and
161
- * `Organisation` "organisation" override. Governance tiers widen the deploy
162
- * machine subject; kept in lockstep with `organisationType` by the
163
- * Platform-synth deploy-trust parity test.
154
+ * Account TIER passed to this account's `OidcConnector`. A polymorphic
155
+ * METHOD, not a read of the `organisationType` field: the connector is
156
+ * instantiated in THIS base constructor, and subclass field initialisers run
157
+ * AFTER `super()`, so `this.organisationType` would read "account" for every
158
+ * subclass here (the same construction-order reason `receivesDeployRole()`
159
+ * is a method). Base ⇒ "account"; `Platform` ⇒ "platform" and `Organisation`
160
+ * "organisation" override. Since the §4.4b Step-1 expand every tier
161
+ * renders the widened `[generic, acct]` machine subject the tier now gates
162
+ * only the governance-only "account-scoped" contract mode (the builder
163
+ * throws for tier "account"); kept in lockstep with `organisationType` by
164
+ * the Platform-synth deploy-trust parity test.
164
165
  */
165
166
  deployRoleAccountTier() {
166
167
  return "account";
@@ -17,9 +17,22 @@ export interface ClickHouseDatabaseProps {
17
17
  type: "ClickHouse";
18
18
  /** VPC to deploy into. Falls back to `app.getVpc()` via the factory. */
19
19
  vpc?: IVpc;
20
- /** EC2 instance type. Default: `m7g.medium` (1 vCPU sustained, 4 GiB).
21
- * CH user-data is tuned for 1 vCPU; raise per-user `max_threads` and
22
- * `max_concurrent_queries` if bumping to a >=2 vCPU host. */
20
+ /** EC2 instance type. Default: `m7g.medium` (1 vCPU sustained, 4 GiB);
21
+ * recommended step-up `r8g.medium` (8 GiB see
22
+ * `DEFAULT_CLICKHOUSE_INSTANCE_TYPE`). Must appear in
23
+ * `CLICKHOUSE_INSTANCE_MEMORY_GIB` — the container memory limit derives
24
+ * from it via `clickHouseTaskMemoryMiB()` and unknown types throw at
25
+ * synth. CH user-data is tuned for 1 vCPU; raise per-user `max_threads`
26
+ * and `max_concurrent_queries` if bumping to a >=2 vCPU host.
27
+ *
28
+ * WARNING — changing this on a DEPLOYED stack REPLACES the persistent
29
+ * data volume, not just the instance. The instance type is embedded in
30
+ * the compute construct's logical-ID key (`getEc2ConfigKey`), so the
31
+ * `AWS::EC2::Volume` beneath it gets a new logical ID: CloudFormation
32
+ * snapshots-then-deletes the old volume (`RemovalPolicy.SNAPSHOT`) and
33
+ * creates an EMPTY one, and the volume's `ownerLogicalId` re-attach tag
34
+ * chain resets. Plan a snapshot-restore migration for live data — this
35
+ * is NOT a config-only change on a stack with data. */
23
36
  instanceType?: string;
24
37
  /**
25
38
  * ECS service desired task count. Defaults to 1 (single-node, single-task
@@ -22,7 +22,7 @@ import { resolveAlertsTopic } from "../../utils/resolveAlertsTopic.js";
22
22
  import { createClickHouseAlarms } from "../../resources/aws/monitoring/index.js";
23
23
  import { ClickHouseSchemaAdminSchema, ManagedPasswordNameSchema, ProfileSpecSchema, ClickHouseDefaultProfiles, PROFILE_NAME_PATTERN } from "../../resources/aws/database/clickhouseSchemas.js";
24
24
  import { inferAmiHardwareType } from "../../resources/aws/compute/ecsConstants.js";
25
- import { CLICKHOUSE_DATABASE_NAME, DEFAULT_CLICKHOUSE_INSTANCE_TYPE, CLICKHOUSE_IMAGE, CLICKHOUSE_EBS_VOLUME_SIZE_GB, CLICKHOUSE_EBS_IOPS, CLICKHOUSE_EBS_THROUGHPUT_MBPS, CLICKHOUSE_TASK_MEMORY_MIB, CLICKHOUSE_HTTP_PORT, CLICKHOUSE_HTTPS_PORT, CLICKHOUSE_NATIVE_PORT, CLICKHOUSE_TCP_SECURE_PORT, CLICKHOUSE_TLS_CERT_MOUNT_PATH, CLICKHOUSE_PROMETHEUS_PORT, CLICKHOUSE_DATA_MOUNT_PATH, CLICKHOUSE_SECRET_OPTIONS, CLICKHOUSE_SERVER_ROLE_TAG, clickHouseUserSecretName, CLICKHOUSE_HEALTH_CHECK, CLICKHOUSE_STOP_TIMEOUT_SECONDS, CLICKHOUSE_EBS_DEVICE_NAME, CLICKHOUSE_CONFIG_SUBDIR, CLICKHOUSE_USERS_SUBDIR, userPasswordEnvName, OPTIMISE_FINAL_SCHEDULE, REPLACING_MERGE_TREE_TABLES, OPTIMISE_MV_TABLES, CLICKHOUSE_CLOUDMAP_SERVICE_NAME, CLICKHOUSE_SERVER_CONTAINER_NAME, OPTIMISE_TASK_MEMORY_MIB, OPTIMISE_TASK_CPU_UNITS, BACKUP_SCHEDULE, BACKUP_TASK_MEMORY_MIB, BACKUP_TASK_CPU_UNITS, BACKUP_RETENTION_DAYS } from "../../resources/aws/database/clickhouseConstants.js";
25
+ import { CLICKHOUSE_DATABASE_NAME, DEFAULT_CLICKHOUSE_INSTANCE_TYPE, CLICKHOUSE_IMAGE, CLICKHOUSE_EBS_VOLUME_SIZE_GB, CLICKHOUSE_EBS_IOPS, CLICKHOUSE_EBS_THROUGHPUT_MBPS, clickHouseTaskMemoryMiB, CLICKHOUSE_HTTP_PORT, CLICKHOUSE_HTTPS_PORT, CLICKHOUSE_NATIVE_PORT, CLICKHOUSE_TCP_SECURE_PORT, CLICKHOUSE_TLS_CERT_MOUNT_PATH, CLICKHOUSE_PROMETHEUS_PORT, CLICKHOUSE_DATA_MOUNT_PATH, CLICKHOUSE_SECRET_OPTIONS, CLICKHOUSE_SERVER_ROLE_TAG, CLICKHOUSE_HOST_METRICS, clickHouseUserSecretName, CLICKHOUSE_HEALTH_CHECK, CLICKHOUSE_STOP_TIMEOUT_SECONDS, CLICKHOUSE_EBS_DEVICE_NAME, CLICKHOUSE_CONFIG_SUBDIR, CLICKHOUSE_USERS_SUBDIR, userPasswordEnvName, OPTIMISE_FINAL_SCHEDULE, REPLACING_MERGE_TREE_TABLES, OPTIMISE_MV_TABLES, CLICKHOUSE_CLOUDMAP_SERVICE_NAME, CLICKHOUSE_SERVER_CONTAINER_NAME, OPTIMISE_TASK_MEMORY_MIB, OPTIMISE_TASK_CPU_UNITS, BACKUP_SCHEDULE, BACKUP_TASK_MEMORY_MIB, BACKUP_TASK_CPU_UNITS, BACKUP_RETENTION_DAYS } from "../../resources/aws/database/clickhouseConstants.js";
26
26
  import { TlsCertGenerator } from "../../resources/aws/utilities/tlsCertGenerator.js";
27
27
  import { EcsCompute } from "./computeEcs.js";
28
28
  /**
@@ -375,6 +375,10 @@ export class ClickHouseDatabase extends Construct {
375
375
  name: "ClickHouseService",
376
376
  capacityProvider: "EC2",
377
377
  desiredCount,
378
+ // Omitting `scaling` attaches default CPU target tracking. A scaled-out
379
+ // second task is an EMPTY second database behind the same Cloud Map name
380
+ // (plain MergeTree does not replicate) — desired-count scaling stays off.
381
+ scaling: false,
378
382
  serviceDiscovery: {
379
383
  name: CLICKHOUSE_CLOUDMAP_SERVICE_NAME,
380
384
  // getaddrinfo clients require A; AWS rejects A under HOST/BRIDGE → AWS_VPC.
@@ -403,7 +407,7 @@ export class ClickHouseDatabase extends Construct {
403
407
  desiredCapacity: 1,
404
408
  minCapacity: 1,
405
409
  maxCapacity: 1,
406
- memoryLimitMiB: CLICKHOUSE_TASK_MEMORY_MIB,
410
+ memoryLimitMiB: clickHouseTaskMemoryMiB(instanceType),
407
411
  tags: {
408
412
  [CLICKHOUSE_SERVER_ROLE_TAG.key]: CLICKHOUSE_SERVER_ROLE_TAG.value
409
413
  }
@@ -493,6 +497,23 @@ export class ClickHouseDatabase extends Construct {
493
497
  tlsCaSecret.grantRead(instanceRole);
494
498
  if (tlsServerSecret !== undefined)
495
499
  tlsServerSecret.grantRead(instanceRole);
500
+ // For the user-data host-metrics timer (see buildClickHouseUserData).
501
+ // PutMetricData is not resource-scopable; the namespace condition bounds
502
+ // the grant to the CWAgent metrics the host alarms consume. The describe
503
+ // grant backs the IMDS-tag fallback path for ASG-name resolution.
504
+ instanceRole.addToPrincipalPolicy(new PolicyStatement({
505
+ actions: ["cloudwatch:PutMetricData"],
506
+ resources: ["*"],
507
+ conditions: {
508
+ StringEquals: {
509
+ "cloudwatch:namespace": CLICKHOUSE_HOST_METRICS.namespace
510
+ }
511
+ }
512
+ }));
513
+ instanceRole.addToPrincipalPolicy(new PolicyStatement({
514
+ actions: ["autoscaling:DescribeAutoScalingInstances"],
515
+ resources: ["*"]
516
+ }));
496
517
  // asgName is the CloudWatch dimension the host metrics key off, so without
497
518
  // it the alarms cannot be built; no topic is the default, so skip silently
498
519
  // rather than throw.
@@ -78,7 +78,7 @@ export const COMPUTE_DEFAULTS = {
78
78
  },
79
79
  LAMBDA: {
80
80
  HANDLER: "index.handler",
81
- RUNTIME: Runtime.NODEJS_22_X,
81
+ RUNTIME: Runtime.NODEJS_24_X,
82
82
  ARCHITECTURE: Architecture.ARM_64
83
83
  }
84
84
  };
@@ -26,6 +26,7 @@ import { VALIDATION_PATTERNS } from "@fjall/generator";
26
26
  import { evaluateBakeGuard } from "@fjall/util/docker";
27
27
  import { toKebab, buildParameterPath } from "@fjall/util";
28
28
  import { validateEc2ServiceSizing, validateEcsDomainConfig, validateSecretName } from "../../resources/aws/compute/ecsValidation.js";
29
+ import { validateSharedEc2CapacityConfig } from "../../resources/aws/compute/ecsCapacityConfig.js";
29
30
  import { COMPUTE_DEFAULTS, collectImportedSecretNames } from "./compute.js";
30
31
  import { isHookMigrations } from "./computeEcsTypes.js";
31
32
  export { ScalingType } from "./computeEcsTypes.js";
@@ -174,6 +175,10 @@ export function validateEcsProps(props) {
174
175
  validateSeparateTaskDef(service.name, service.migrations.separateTaskDef);
175
176
  }
176
177
  }
178
+ // Mirror of the resources-layer shared-ASG capacity check (drift on a reused
179
+ // ASG, shared persistentDataVolume singleton) — defence-in-depth per
180
+ // generator-standards § "Validate at the Lowest Layer the Field Belongs To".
181
+ validateSharedEc2CapacityConfig(props.services);
177
182
  // Validate awaitMigrationsFrom cross-service migration ordering.
178
183
  const serviceByName = new Map(props.services.map((s) => [s.name, s]));
179
184
  for (const service of props.services) {
@@ -622,9 +622,19 @@ export interface EcsServiceConfig {
622
622
  * ]
623
623
  */
624
624
  routing?: EcsRoutingConfig | EcsRoutingConfig[];
625
- /** CPU units for this service's tasks (256-4096) */
625
+ /**
626
+ * CPU units for this service's tasks (256-4096). Fargate only — EC2 task
627
+ * definitions carry no task-level CPU reservation (CPU is bounded by the
628
+ * instance type), so a value here is rejected at synth when `capacityProvider`
629
+ * is 'EC2' (see `validateEc2ServiceSizing`).
630
+ */
626
631
  cpu?: number;
627
- /** Memory in MiB for this service's tasks (512-30720) */
632
+ /**
633
+ * Memory in MiB for this service's tasks (512-30720). Fargate only — for EC2
634
+ * capacity the container memory comes from `ec2Config.memoryLimitMiB`; a
635
+ * service-level value here is rejected at synth unless it exactly mirrors
636
+ * that field (see `validateEc2ServiceSizing`).
637
+ */
628
638
  memoryLimitMiB?: number;
629
639
  /** Desired number of tasks. Default: 2 */
630
640
  desiredCount?: number;
@@ -201,7 +201,7 @@ export interface CodeLambdaProps extends BaseLambdaProps {
201
201
  code: Code;
202
202
  /** Handler function. Default: "index.handler" */
203
203
  handler?: string;
204
- /** Lambda runtime. Default: NODEJS_22_X */
204
+ /** Lambda runtime. Default: NODEJS_24_X */
205
205
  runtime?: Runtime;
206
206
  }
207
207
  /**
@@ -16,15 +16,15 @@ import { Duration } from "aws-cdk-lib";
16
16
  export { isAwsManagedKey, isCMKRequested, AWS_MANAGED, USE_CMK } from "../../utils/databaseTypes.js";
17
17
  export const DATABASE_ENGINE_CONFIG = Object.freeze({
18
18
  postgresql: {
19
- aurora: { version: "16.10", majorVersion: "16" },
20
- instance: { version: "17.5", majorVersion: "17" },
19
+ aurora: { version: "18.4", majorVersion: "18" },
20
+ instance: { version: "17.9", majorVersion: "17" },
21
21
  defaultUsername: "postgres",
22
22
  sslParameters: { "rds.force_ssl": "1" }
23
23
  },
24
24
  mysql: {
25
- // majorVersion must be Aurora version (3.08.0), NOT MySQL version (8.0)
26
- aurora: { version: "8.0.mysql_aurora.3.08.0", majorVersion: "3.08.0" },
27
- instance: { version: "8.4.4", majorVersion: "8.4" },
25
+ // majorVersion must be Aurora version (3.12.0), NOT MySQL version (8.0)
26
+ aurora: { version: "8.0.mysql_aurora.3.12.0", majorVersion: "3.12.0" },
27
+ instance: { version: "8.4.9", majorVersion: "8.4" },
28
28
  defaultUsername: "admin",
29
29
  sslParameters: { require_secure_transport: "ON" }
30
30
  }
@@ -317,7 +317,7 @@ export class DevSubstrate extends Construct {
317
317
  // control plane (ECS / Aurora / SSM), never over the VPC, and the ALB invokes
318
318
  // it through the Lambda API — not the network path.
319
319
  const waker = new LambdaFunction(this, DEV_SUBSTRATE_WAKER_ID, {
320
- runtime: Runtime.NODEJS_22_X,
320
+ runtime: Runtime.NODEJS_24_X,
321
321
  handler: "index.handler",
322
322
  code: Code.fromInline(source),
323
323
  lambdaDescription: "Dev substrate slot waker",
@@ -308,7 +308,7 @@ export class Payload extends Construct {
308
308
  deployment: "code",
309
309
  code: Code.fromAsset(`${PAYLOAD_DEFAULTS.SOURCE}/${PAYLOAD_DEFAULTS.OPENNEXT.SERVER}`),
310
310
  handler: "index.handler",
311
- runtime: Runtime.NODEJS_22_X,
311
+ runtime: Runtime.NODEJS_24_X,
312
312
  architecture: Architecture.ARM_64,
313
313
  memorySize,
314
314
  timeout,
@@ -370,7 +370,7 @@ export class Payload extends Construct {
370
370
  deployment: "code",
371
371
  code: Code.fromAsset(`${PAYLOAD_DEFAULTS.SOURCE}/${PAYLOAD_DEFAULTS.OPENNEXT.IMAGE}`),
372
372
  handler: "index.handler",
373
- runtime: Runtime.NODEJS_22_X,
373
+ runtime: Runtime.NODEJS_24_X,
374
374
  architecture: Architecture.ARM_64,
375
375
  memorySize,
376
376
  timeout,
@@ -396,7 +396,7 @@ export class Payload extends Construct {
396
396
  deployment: "code",
397
397
  code: Code.fromAsset(`${PAYLOAD_DEFAULTS.SOURCE}/${PAYLOAD_DEFAULTS.OPENNEXT.REVALIDATION}`),
398
398
  handler: "index.handler",
399
- runtime: Runtime.NODEJS_22_X,
399
+ runtime: Runtime.NODEJS_24_X,
400
400
  architecture: Architecture.ARM_64,
401
401
  memorySize,
402
402
  timeout,
@@ -42,7 +42,7 @@ import { toPascalCase } from "../../utils/capitaliseString.js";
42
42
  const __dirname = dirname(fileURLToPath(import.meta.url));
43
43
  const FORMS_ASSET_DIR = resolve(__dirname, "../../lambda-assets/static-site-forms/asset");
44
44
  const FORMS_DEFAULTS = {
45
- RUNTIME: Runtime.NODEJS_22_X,
45
+ RUNTIME: Runtime.NODEJS_24_X,
46
46
  ARCHITECTURE: Architecture.ARM_64,
47
47
  TIMEOUT_SECONDS: 10,
48
48
  MEMORY_MB: 256,
@@ -144,7 +144,7 @@ export class Ec2GracefulTerminationHandler extends Construct {
144
144
  ]
145
145
  : [];
146
146
  this.lambda = new LambdaFunction(this, `${id}Fn`, {
147
- runtime: Runtime.NODEJS_22_X,
147
+ runtime: Runtime.NODEJS_24_X,
148
148
  handler: "index.handler",
149
149
  code: Code.fromInline(source),
150
150
  lambdaDescription: `${id} ${EC2_GRACEFUL_TERMINATION_HANDLER_DESCRIPTION}`,
@@ -66,6 +66,7 @@ export default class EcsCluster extends Construct {
66
66
  // EC2-specific (mutable state shared with ecsServiceFactory)
67
67
  asgState = {
68
68
  providers: new Map(),
69
+ asgOrigins: new Map(),
69
70
  autoScalingGroup: undefined,
70
71
  asgSecurityGroup: undefined
71
72
  };
@@ -0,0 +1,43 @@
1
+ import type { Ec2CapacityConfig, EcsCapacityProvider } from "./ecsTypes.js";
2
+ /** The service + ec2Config that first claimed an EC2 capacity key. */
3
+ export interface Ec2AsgOrigin {
4
+ serviceName: string;
5
+ ec2Config: Ec2CapacityConfig;
6
+ }
7
+ /**
8
+ * Rejects at synth when a service reusing a shared ASG is incompatible with
9
+ * it: either the origin's `persistentDataVolume` makes the ASG a singleton
10
+ * (two tasks would race to attach the same EBS volume), or the reusing
11
+ * service asks for ASG settings that differ from the ones the ASG was built
12
+ * with — those settings would be silently ignored (same silent-ignore class
13
+ * as `validateEc2ServiceSizing`).
14
+ */
15
+ export declare function assertSharedAsgConfigMatches(serviceName: string, ec2Config: Ec2CapacityConfig, key: string, origin: Ec2AsgOrigin): void;
16
+ /**
17
+ * Generates a unique key for EC2 config so services with matching
18
+ * configurations share an ASG.
19
+ */
20
+ export declare function getEc2ConfigKey(ec2Config: Ec2CapacityConfig): string;
21
+ /**
22
+ * Minimal structural input for {@link validateSharedEc2CapacityConfig} —
23
+ * exactly the fields the check reads. Both the resources-layer
24
+ * `EcsServiceProps` and the patterns-layer `EcsServiceConfig` structurally
25
+ * satisfy it (typescript-standards § "Helpers consume resolved state, not the
26
+ * dispatch enum").
27
+ */
28
+ export interface SharedEc2CapacityServiceInput {
29
+ name: string;
30
+ capacityProvider?: EcsCapacityProvider;
31
+ ec2Config?: Ec2CapacityConfig;
32
+ }
33
+ /**
34
+ * Cross-service shared-ASG compatibility: groups EC2 services by their
35
+ * capacity key and rejects a reuse that is incompatible with the ASG's origin
36
+ * — config drift the shared ASG would silently discard, or a
37
+ * `persistentDataVolume` singleton being shared. Pure; called from BOTH
38
+ * validation layers — resources `validateEcsClusterProps` and patterns
39
+ * `validateEcsProps` (generator-standards § "Validate at the Lowest Layer the
40
+ * Field Belongs To") — with the same check at the construction site
41
+ * (`getOrCreateAsgCapacityProvider`) as defence-in-depth.
42
+ */
43
+ export declare function validateSharedEc2CapacityConfig(services: readonly SharedEc2CapacityServiceInput[]): void;