@fjall/components-infrastructure 28.4.0 → 30.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.
package/dist/lib/app.d.ts CHANGED
@@ -55,7 +55,10 @@ export interface IAppOptions {
55
55
  /**
56
56
  * Backup configuration for automatic AWS Backup enrolment.
57
57
  * - Object with tier: Tags all resources with `fjall:disasterRecovery:tier`
58
- * - false: Explicitly disabled (no backup tag)
58
+ * and sets the app backup tier that resource defaults derive from — S3
59
+ * buckets version by default at `resilient`/`enterprise`; a per-bucket
60
+ * `backupVaultTier` overrides the tier for that bucket.
61
+ * - false: Explicitly disabled (no backup tag, no tier-derived defaults)
59
62
  */
60
63
  backup?: {
61
64
  tier: BackupTier;
@@ -107,14 +110,24 @@ export declare class App extends CdkApp {
107
110
  private bastion?;
108
111
  private networkDisabled;
109
112
  private globalTags;
113
+ private backupTier?;
114
+ private backupTierObserved;
110
115
  private aspectApplied;
111
116
  private resourceInventory?;
112
117
  private manifestCollector;
113
118
  private constructor();
114
- private applyBackupTag;
119
+ private applyBackupTier;
115
120
  private initialiseTunnel;
116
121
  private initialiseNetwork;
117
122
  getName(): string;
123
+ /**
124
+ * The app-level backup tier from `backup: { tier }`, or undefined when no
125
+ * backup is declared. Factories thread it to resources whose defaults derive
126
+ * from it (S3 versioning); a per-resource `backupVaultTier` overrides it.
127
+ * Reading it pins the tier: a `backup` applied on a later `getApp` call
128
+ * throws, so no resource can carry the tier's tag without its defaults.
129
+ */
130
+ getBackupTier(): BackupTier | undefined;
118
131
  static getApp(name?: string, options?: IAppOptions): App;
119
132
  static getInstance(name?: string, options?: IAppOptions): App;
120
133
  /**
@@ -345,10 +358,10 @@ export declare class App extends CdkApp {
345
358
  * Add a storage resource (S3) to the default storage stack using the factory pattern.
346
359
  *
347
360
  * @example
348
- * // Private bucket (default)
349
- * const assets = app.addStorage(StorageFactory.build("Assets", {
350
- * versioned: true
351
- * }));
361
+ * // Private bucket (default). Versioning follows the app backup tier
362
+ * // (`backup: { tier }` — on at resilient/enterprise); `versioned` or a
363
+ * // per-bucket `backupVaultTier` overrides it.
364
+ * const assets = app.addStorage(StorageFactory.build("Assets"));
352
365
  *
353
366
  * @example
354
367
  * // Website bucket
package/dist/lib/app.js CHANGED
@@ -48,6 +48,8 @@ export class App extends CdkApp {
48
48
  bastion;
49
49
  networkDisabled = false;
50
50
  globalTags = {};
51
+ backupTier;
52
+ backupTierObserved = false;
51
53
  aspectApplied = false;
52
54
  resourceInventory;
53
55
  manifestCollector;
@@ -64,9 +66,9 @@ export class App extends CdkApp {
64
66
  // Initialise manifest collector for build-time service discovery
65
67
  this.manifestCollector = getManifestCollector(this.name);
66
68
  this.initialiseStandardTags();
67
- // Apply backup tier tag if configured
69
+ // Apply backup tier if configured
68
70
  if (options?.backup && typeof options.backup === "object") {
69
- this.applyBackupTag(options.backup.tier);
71
+ this.applyBackupTier(options.backup.tier);
70
72
  }
71
73
  // Initialise network immediately if configured
72
74
  if (options?.network === false) {
@@ -83,7 +85,14 @@ export class App extends CdkApp {
83
85
  this.eventBusOverride = options.eventBus;
84
86
  }
85
87
  }
86
- applyBackupTag(tier) {
88
+ applyBackupTier(tier) {
89
+ if (this.backupTierObserved) {
90
+ throw new Error(`Backup tier "${tier}" applied after a resource already resolved the app's tier: ` +
91
+ `that resource would carry the tier's disaster-recovery tag without its versioning default. ` +
92
+ `Pass \`backup\` on the first App.getApp("${this.name}") call, before addStorage(), ` +
93
+ `addBuildkite() or a Payload pattern.`);
94
+ }
95
+ this.backupTier = tier;
87
96
  this.globalTags[BACKUP_TIER_TAG_KEY] = BACKUP_TIER_TAG_MAP[tier];
88
97
  }
89
98
  initialiseTunnel(config) {
@@ -109,6 +118,17 @@ export class App extends CdkApp {
109
118
  getName() {
110
119
  return this.name;
111
120
  }
121
+ /**
122
+ * The app-level backup tier from `backup: { tier }`, or undefined when no
123
+ * backup is declared. Factories thread it to resources whose defaults derive
124
+ * from it (S3 versioning); a per-resource `backupVaultTier` overrides it.
125
+ * Reading it pins the tier: a `backup` applied on a later `getApp` call
126
+ * throws, so no resource can carry the tier's tag without its defaults.
127
+ */
128
+ getBackupTier() {
129
+ this.backupTierObserved = true;
130
+ return this.backupTier;
131
+ }
112
132
  static getApp(name, options) {
113
133
  return App.getInstance(name, options);
114
134
  }
@@ -145,11 +165,11 @@ export class App extends CdkApp {
145
165
  !App.instance.networkDisabled) {
146
166
  App.instance.initialiseNetwork(options.network);
147
167
  }
148
- // Apply backup tag if configured and not already set
168
+ // Apply backup tier if configured and not already set
149
169
  if (options?.backup &&
150
170
  typeof options.backup === "object" &&
151
- !App.instance.globalTags[BACKUP_TIER_TAG_KEY]) {
152
- App.instance.applyBackupTag(options.backup.tier);
171
+ App.instance.backupTier === undefined) {
172
+ App.instance.applyBackupTier(options.backup.tier);
153
173
  }
154
174
  // Initialise tunnel if configured and not already created
155
175
  if (options?.tunnel && !App.instance.bastion) {
@@ -557,10 +577,10 @@ export class App extends CdkApp {
557
577
  * Add a storage resource (S3) to the default storage stack using the factory pattern.
558
578
  *
559
579
  * @example
560
- * // Private bucket (default)
561
- * const assets = app.addStorage(StorageFactory.build("Assets", {
562
- * versioned: true
563
- * }));
580
+ * // Private bucket (default). Versioning follows the app backup tier
581
+ * // (`backup: { tier }` — on at resilient/enterprise); `versioned` or a
582
+ * // per-bucket `backupVaultTier` overrides it.
583
+ * const assets = app.addStorage(StorageFactory.build("Assets"));
564
584
  *
565
585
  * @example
566
586
  * // Website bucket
@@ -1,8 +1,14 @@
1
1
  import { type IVpc } from "aws-cdk-lib/aws-ec2";
2
2
  import { Construct } from "constructs";
3
+ import { type BackupTier } from "../../../utils/backupTierMapping.js";
3
4
  import { type BuildkitePropsInput } from "./schema.js";
4
5
  export type BuildkiteConstructProps = BuildkitePropsInput & {
5
6
  readonly vpc: IVpc;
7
+ /**
8
+ * The owning app's backup tier — `BuildkiteFactory` fills it from the App
9
+ * so the artefact bucket's versioning default follows the tier.
10
+ */
11
+ readonly appBackupTier?: BackupTier;
6
12
  };
7
13
  /**
8
14
  * Self-hosted Buildkite agent fleet on the pinned Elastic CI Stack AMIs —
@@ -37,7 +37,7 @@ export class Buildkite extends Construct {
37
37
  autoScalingGroupName;
38
38
  constructor(scope, id, props) {
39
39
  super(scope, id);
40
- const { vpc, ...plainProps } = props;
40
+ const { vpc, appBackupTier, ...plainProps } = props;
41
41
  const config = validateBuildkiteProps(plainProps);
42
42
  // Deliberate asymmetry: the agent token is load-bearing (boot + scaler),
43
43
  // so underivable throws; the api-key hook and per-pipeline job secrets
@@ -88,10 +88,14 @@ export class Buildkite extends Construct {
88
88
  owner: config.costAllocationOwner
89
89
  })
90
90
  });
91
- const artifactBucket = new S3Bucket(this, `${id}ArtifactBucket`);
91
+ // Artefacts are write-once keys, so versioning follows the app backup tier
92
+ // at negligible cost; the bucket is otherwise scratch output and keeps the
93
+ // wrapper's env-aware default (DESTROY + pre-empty off prod).
94
+ const artifactBucket = new S3Bucket(this, `${id}ArtifactBucket`, {
95
+ appBackupTier
96
+ });
92
97
  // Secrets (SSH deploy keys, git credentials) must survive stack teardown
93
- // and accidental overwrite; the artifact bucket is scratch output and
94
- // keeps the wrapper's env-aware default (DESTROY + pre-empty off prod).
98
+ // and accidental overwrite.
95
99
  const managedSecretsBucket = new S3Bucket(this, `${id}ManagedSecretsBucket`, { versioned: true, removalPolicy: RemovalPolicy.RETAIN });
96
100
  if (fjallApiKeyParameterName !== undefined ||
97
101
  pipelineSecretsPrefix !== undefined) {
@@ -10,10 +10,10 @@ import type { BuildkitePropsInput } from "./schema.js";
10
10
  */
11
11
  export type BuildkiteFactoryFn = (app: App, scope: Construct) => Buildkite;
12
12
  /**
13
- * Resolve construct props from plain-data input: VPC injection plus the three
13
+ * Resolve construct props from plain-data input: VPC injection plus the
14
14
  * app-level defaults (cost-allocation environment, shared alarm topic,
15
- * applicationId). Single source for both `App.addBuildkite` entry paths
16
- * (design § D2) — explicit props always win.
15
+ * applicationId, backup tier). Single source for both `App.addBuildkite`
16
+ * entry paths (design § D2) — explicit props always win.
17
17
  */
18
18
  export declare function resolveBuildkiteConstructProps(app: App, props: BuildkitePropsInput): BuildkiteConstructProps;
19
19
  /**
@@ -3,10 +3,10 @@ import { UNKNOWN_ENVIRONMENT } from "../../../utils/env.js";
3
3
  import { getConfig } from "../../../utils/getConfig.js";
4
4
  import { Buildkite } from "./buildkite.js";
5
5
  /**
6
- * Resolve construct props from plain-data input: VPC injection plus the three
6
+ * Resolve construct props from plain-data input: VPC injection plus the
7
7
  * app-level defaults (cost-allocation environment, shared alarm topic,
8
- * applicationId). Single source for both `App.addBuildkite` entry paths
9
- * (design § D2) — explicit props always win.
8
+ * applicationId, backup tier). Single source for both `App.addBuildkite`
9
+ * entry paths (design § D2) — explicit props always win.
10
10
  */
11
11
  export function resolveBuildkiteConstructProps(app, props) {
12
12
  const configEnvironment = getConfig().environment;
@@ -24,7 +24,10 @@ export function resolveBuildkiteConstructProps(app, props) {
24
24
  if (constructProps.applicationId === undefined) {
25
25
  constructProps.applicationId = app.getName();
26
26
  }
27
- return constructProps;
27
+ const appBackupTier = app.getBackupTier();
28
+ return appBackupTier === undefined
29
+ ? constructProps
30
+ : { ...constructProps, appBackupTier };
28
31
  }
29
32
  /**
30
33
  * Factory for the self-hosted Buildkite agent fleet — the codemod add path
@@ -201,7 +201,12 @@ export interface PayloadComputeConfig {
201
201
  * S3 bucket configuration for pattern sub-buckets.
202
202
  */
203
203
  export interface PatternStorageBucketConfig {
204
- /** Enable versioning. Default: false */
204
+ /**
205
+ * Bucket versioning. Absent, the pattern decides: the media bucket follows
206
+ * the app backup tier (on at `resilient`/`enterprise`), while assets (build
207
+ * output, pruned per deploy) and cache (ISR, regenerated at runtime) stay
208
+ * off at every tier. An explicit value always wins.
209
+ */
205
210
  versioned?: boolean;
206
211
  }
207
212
  /**
@@ -210,9 +215,7 @@ export interface PatternStorageBucketConfig {
210
215
  *
211
216
  * @example
212
217
  * storage: {
213
- * assets: { versioned: true },
214
- * cache: { versioned: false },
215
- * media: { versioned: true }
218
+ * media: { versioned: true } // force on below the resilient tier
216
219
  * }
217
220
  */
218
221
  export interface PayloadStorageConfig {
@@ -219,6 +219,7 @@ export class Payload extends Construct {
219
219
  createAssetsBucket() {
220
220
  const assetsConfig = this.props.storage?.assets ?? {};
221
221
  const assetsProps = {
222
+ // Build output, pruned on every deploy — versioning would only bill for churn.
222
223
  versioned: assetsConfig.versioned ?? false,
223
224
  deployment: {
224
225
  source: `${PAYLOAD_DEFAULTS.SOURCE}/${PAYLOAD_DEFAULTS.OPENNEXT.ASSETS}`,
@@ -234,6 +235,7 @@ export class Payload extends Construct {
234
235
  createCacheBucket() {
235
236
  const cacheConfig = this.props.storage?.cache ?? {};
236
237
  const cacheProps = {
238
+ // ISR cache, regenerated at runtime — versioning would only bill for churn.
237
239
  versioned: cacheConfig.versioned ?? false,
238
240
  deployment: {
239
241
  source: `${PAYLOAD_DEFAULTS.SOURCE}/${PAYLOAD_DEFAULTS.OPENNEXT.CACHE}`,
@@ -244,8 +246,11 @@ export class Payload extends Construct {
244
246
  }
245
247
  createMediaBucket() {
246
248
  const mediaConfig = this.props.storage?.media ?? {};
249
+ // Customer uploads: versioning follows the app backup tier unless set here.
247
250
  const mediaProps = {
248
- versioned: mediaConfig.versioned ?? false
251
+ ...(mediaConfig.versioned !== undefined && {
252
+ versioned: mediaConfig.versioned
253
+ })
249
254
  };
250
255
  return this.app.addStorage(StorageFactory.build(`${this.props.name}-media`, mediaProps));
251
256
  }
@@ -33,6 +33,11 @@ export interface S3Props {
33
33
  readonly bucketName?: string;
34
34
  readonly publicReadAccess?: boolean;
35
35
  readonly websiteHosting?: WebsiteHostingConfig;
36
+ /**
37
+ * Bucket versioning. Defaults from the effective backup tier — the
38
+ * per-bucket `backupVaultTier`, else the app's `backup: { tier }` — on at
39
+ * `resilient`/`enterprise`, off otherwise. An explicit value always wins.
40
+ */
36
41
  readonly versioned?: boolean;
37
42
  readonly encryption?: "AES256" | "KMS";
38
43
  readonly kmsKeyArn?: string;
@@ -42,6 +47,10 @@ export interface S3Props {
42
47
  * false to opt out. Ignored for non-KMS encryption.
43
48
  */
44
49
  readonly bucketKeyEnabled?: boolean;
50
+ /**
51
+ * Per-bucket backup tier override. Beats the app-level tier for both the
52
+ * `fjall:disasterRecovery:tier` tag and the versioning default.
53
+ */
45
54
  readonly backupVaultTier?: BackupTier;
46
55
  readonly cors?: CorsRule[];
47
56
  readonly deployment?: S3DeploymentConfig;
@@ -64,6 +73,12 @@ export interface S3Props {
64
73
  * construction.
65
74
  */
66
75
  readonly appName?: string;
76
+ /**
77
+ * The owning app's backup tier (`App.getApp({ backup: { tier } })`), which
78
+ * the versioning default follows. `StorageFactory` fills it from the App as
79
+ * it does `appName`; pass it only for direct `new Storage(...)` construction.
80
+ */
81
+ readonly appBackupTier?: BackupTier;
67
82
  }
68
83
  export interface StorageBuildProps extends S3Props {
69
84
  readonly stackPlacement?: "storage" | "cdn" | "compute";
@@ -84,6 +84,7 @@ export class Storage extends Construct {
84
84
  bucketKeyEnabled: props.bucketKeyEnabled
85
85
  }),
86
86
  backupVaultTier: props.backupVaultTier,
87
+ appBackupTier: props.appBackupTier,
87
88
  publicReadAccess: props.publicReadAccess,
88
89
  websiteHosting: props.websiteHosting,
89
90
  resourcePolicyStatements: props.resourcePolicyStatements,
@@ -218,12 +219,14 @@ export class StorageFactory {
218
219
  const { stackPlacement, ...s3Props } = props;
219
220
  const fn = (app, scope) => {
220
221
  validateStorageProps(s3Props);
221
- // The App is the only place the app name is known here; website-hosting
222
- // buckets need it for their (app, bucket)-keyed exports — the same fill
223
- // CdnFactory performs for Cdn.
222
+ // The App is the only place the app name and backup tier are known
223
+ // here: website-hosting buckets need the name for their (app,
224
+ // bucket)-keyed exports (the same fill CdnFactory performs for Cdn), and
225
+ // the bucket's versioning default follows the tier.
224
226
  return new Storage(scope, id, {
225
227
  ...s3Props,
226
- appName: s3Props.appName ?? app.getName()
228
+ appName: s3Props.appName ?? app.getName(),
229
+ appBackupTier: s3Props.appBackupTier ?? app.getBackupTier()
227
230
  });
228
231
  };
229
232
  if (stackPlacement) {
@@ -46,7 +46,19 @@ export interface ResourcePolicyStatement {
46
46
  * `bucketKeyEnabled: true` (pass `false` to opt out).
47
47
  */
48
48
  export interface S3BucketProps extends BucketProps {
49
+ /**
50
+ * Per-bucket backup tier override. Beats `appBackupTier` for both the
51
+ * `fjall:disasterRecovery:tier` tag and the versioning default.
52
+ */
49
53
  backupVaultTier?: BackupTier;
54
+ /**
55
+ * The owning app's backup tier (`App.getApp({ backup: { tier } })`). The
56
+ * versioning default follows the effective tier — this, unless
57
+ * `backupVaultTier` overrides it — on at `resilient`/`enterprise`. The
58
+ * `StorageFactory` and `BuildkiteFactory` paths fill it from the App; only
59
+ * direct `new S3Bucket` construction needs to pass it.
60
+ */
61
+ appBackupTier?: BackupTier;
50
62
  publicReadAccess?: boolean;
51
63
  websiteHosting?: WebsiteHostingConfig;
52
64
  /**
@@ -80,5 +92,7 @@ export interface S3BucketProps extends BucketProps {
80
92
  }
81
93
  export declare class S3Bucket extends Bucket {
82
94
  readonly backupVaultTier?: BackupTier;
95
+ /** The tier the bucket runs under: `backupVaultTier`, else `appBackupTier`. */
96
+ readonly effectiveBackupTier?: BackupTier;
83
97
  constructor(scope: Construct, id: string, props?: S3BucketProps);
84
98
  }
@@ -3,6 +3,7 @@ import { Annotations, CfnOutput, Duration, RemovalPolicy, Tags } from "aws-cdk-l
3
3
  import { BlockPublicAccess, Bucket, BucketEncryption } from "aws-cdk-lib/aws-s3";
4
4
  import { ArnPrincipal, Effect, PolicyStatement, StarPrincipal } from "aws-cdk-lib/aws-iam";
5
5
  import { RegionInfo } from "aws-cdk-lib/region-info";
6
+ import { resolveEffectiveBackupTier, shouldAutoVersion } from "../../../utils/backupTierMapping.js";
6
7
  import { toPascalCase } from "../../../utils/capitaliseString.js";
7
8
  import { bucketWebsiteEndpointExportName, bucketWebsiteHostedZoneIdExportName } from "@fjall/util";
8
9
  import { envAwareRemovalPolicyDefault, toRemovalPolicy } from "../../../utils/removalPolicy.js";
@@ -15,9 +16,6 @@ export { SDK_PRE_EMPTY_TAG_KEY } from "@fjall/util/aws";
15
16
  * every site so the retention window cannot drift.
16
17
  */
17
18
  export const NONCURRENT_VERSION_EXPIRY_DAYS = 30;
18
- function shouldAutoVersion(tier) {
19
- return tier === "resilient" || tier === "enterprise";
20
- }
21
19
  function toResourcePolicyPrincipal(identifier) {
22
20
  return identifier === "*"
23
21
  ? new StarPrincipal()
@@ -25,10 +23,13 @@ function toResourcePolicyPrincipal(identifier) {
25
23
  }
26
24
  export class S3Bucket extends Bucket {
27
25
  backupVaultTier;
26
+ /** The tier the bucket runs under: `backupVaultTier`, else `appBackupTier`. */
27
+ effectiveBackupTier;
28
28
  constructor(scope, id, props = {}) {
29
- const { websiteHosting, backupVaultTier, resourcePolicyStatements, aliasTargetName, appName, ...cdkProps } = props;
29
+ const { websiteHosting, backupVaultTier, appBackupTier, resourcePolicyStatements, aliasTargetName, appName, ...cdkProps } = props;
30
30
  const isPublic = props.publicReadAccess === true || websiteHosting !== undefined;
31
- const versioned = props.versioned ?? shouldAutoVersion(backupVaultTier);
31
+ const effectiveBackupTier = resolveEffectiveBackupTier(backupVaultTier, appBackupTier);
32
+ const versioned = props.versioned ?? shouldAutoVersion(effectiveBackupTier);
32
33
  const removalPolicy = props.removalPolicy ?? toRemovalPolicy(envAwareRemovalPolicyDefault());
33
34
  // Mirrors the CDK Bucket's own resolution: an encryptionKey with no
34
35
  // explicit encryption infers KMS. DSSE is excluded deliberately — S3
@@ -86,6 +87,7 @@ export class S3Bucket extends Bucket {
86
87
  ]
87
88
  });
88
89
  this.backupVaultTier = backupVaultTier;
90
+ this.effectiveBackupTier = effectiveBackupTier;
89
91
  for (const statement of resourcePolicyStatements ?? []) {
90
92
  this.addToResourcePolicy(new PolicyStatement({
91
93
  ...(statement.sid !== undefined && { sid: statement.sid }),
@@ -5,7 +5,16 @@ export type BackupTier = "standard" | "resilient" | "enterprise";
5
5
  /**
6
6
  * Shared mapping from application backup tier names to AWS Backup plan tag values.
7
7
  *
8
- * Used by both App.applyBackupTag() (app-level tag) and StandardTagsAspect
9
- * (per-resource override tag) to ensure consistent tier → tag translation.
8
+ * Used by both App.applyBackupTier() (app-level tag) and StandardTagsAspect
9
+ * (per-resource tag) to ensure consistent tier → tag translation.
10
10
  */
11
11
  export declare const BACKUP_TIER_TAG_MAP: Readonly<Record<BackupTier, string>>;
12
+ /**
13
+ * The tier a resource actually runs under: its own override, else the
14
+ * app-level tier `App.getApp({ backup: { tier } })` declared. The single
15
+ * resolution every tier-derived default reads (S3 versioning, the DR tag),
16
+ * so the derivations cannot drift.
17
+ */
18
+ export declare function resolveEffectiveBackupTier(resourceTier: BackupTier | undefined, appTier: BackupTier | undefined): BackupTier | undefined;
19
+ /** Tiers whose S3 buckets version by default. */
20
+ export declare function shouldAutoVersion(tier: BackupTier | undefined): boolean;
@@ -3,11 +3,24 @@ export const BACKUP_TIER_TAG_KEY = "fjall:disasterRecovery:tier";
3
3
  /**
4
4
  * Shared mapping from application backup tier names to AWS Backup plan tag values.
5
5
  *
6
- * Used by both App.applyBackupTag() (app-level tag) and StandardTagsAspect
7
- * (per-resource override tag) to ensure consistent tier → tag translation.
6
+ * Used by both App.applyBackupTier() (app-level tag) and StandardTagsAspect
7
+ * (per-resource tag) to ensure consistent tier → tag translation.
8
8
  */
9
9
  export const BACKUP_TIER_TAG_MAP = Object.freeze({
10
10
  standard: "default",
11
11
  resilient: "resilient",
12
12
  enterprise: "enterprise"
13
13
  });
14
+ /**
15
+ * The tier a resource actually runs under: its own override, else the
16
+ * app-level tier `App.getApp({ backup: { tier } })` declared. The single
17
+ * resolution every tier-derived default reads (S3 versioning, the DR tag),
18
+ * so the derivations cannot drift.
19
+ */
20
+ export function resolveEffectiveBackupTier(resourceTier, appTier) {
21
+ return resourceTier ?? appTier;
22
+ }
23
+ /** Tiers whose S3 buckets version by default. */
24
+ export function shouldAutoVersion(tier) {
25
+ return tier === "resilient" || tier === "enterprise";
26
+ }
@@ -22,10 +22,13 @@ export declare class StandardTagsAspect implements IAspect {
22
22
  */
23
23
  private addIpamPoolTag;
24
24
  /**
25
- * Add disaster recovery tier tag to S3 buckets with backupVaultTier property.
26
- * This serves as a per-resource override when `backupVaultTier` is set on a
27
- * specific S3 bucket. CDK child tags override parent tags, so this takes
28
- * precedence over the app-level backup tag set via `App.getApp({ backup })`.
25
+ * Add the disaster recovery tier tag to S3 buckets from the bucket's
26
+ * effective tier the per-bucket `backupVaultTier` override, else the
27
+ * app-level tier the factory threaded in. CDK child tags override parent
28
+ * tags, so an override wins over the app-level tag set via
29
+ * `App.getApp({ backup })`; an inherited app tier writes the value that tag
30
+ * already carries. The same resolution decides the bucket's versioning
31
+ * default (`S3Bucket`).
29
32
  *
30
33
  * Tag format: fjall:disasterRecovery:tier = "{tier}"
31
34
  *
@@ -54,10 +54,13 @@ export class StandardTagsAspect {
54
54
  }
55
55
  }
56
56
  /**
57
- * Add disaster recovery tier tag to S3 buckets with backupVaultTier property.
58
- * This serves as a per-resource override when `backupVaultTier` is set on a
59
- * specific S3 bucket. CDK child tags override parent tags, so this takes
60
- * precedence over the app-level backup tag set via `App.getApp({ backup })`.
57
+ * Add the disaster recovery tier tag to S3 buckets from the bucket's
58
+ * effective tier the per-bucket `backupVaultTier` override, else the
59
+ * app-level tier the factory threaded in. CDK child tags override parent
60
+ * tags, so an override wins over the app-level tag set via
61
+ * `App.getApp({ backup })`; an inherited app tier writes the value that tag
62
+ * already carries. The same resolution decides the bucket's versioning
63
+ * default (`S3Bucket`).
61
64
  *
62
65
  * Tag format: fjall:disasterRecovery:tier = "{tier}"
63
66
  *
@@ -70,8 +73,8 @@ export class StandardTagsAspect {
70
73
  let construct = resource;
71
74
  while (construct) {
72
75
  const backupConstruct = construct;
73
- if (backupConstruct.backupVaultTier) {
74
- const tier = backupConstruct.backupVaultTier;
76
+ if (backupConstruct.effectiveBackupTier) {
77
+ const tier = backupConstruct.effectiveBackupTier;
75
78
  const tagValue = BACKUP_TIER_TAG_MAP[tier];
76
79
  Tags.of(resource).add(BACKUP_TIER_TAG_KEY, tagValue);
77
80
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/components-infrastructure",
3
- "version": "28.4.0",
3
+ "version": "30.0.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/fjall-tech/fjall.git",
@@ -78,8 +78,8 @@
78
78
  },
79
79
  "dependencies": {
80
80
  "@aws-sdk/client-organizations": "^3.1098.0",
81
- "@fjall/generator": "^28.4.0",
82
- "@fjall/util": "^28.4.0",
81
+ "@fjall/generator": "^30.0.0",
82
+ "@fjall/util": "^30.0.0",
83
83
  "constructs": "^10.7.2",
84
84
  "zod": "^4.4.3"
85
85
  },