@fjall/components-infrastructure 34.0.0 → 35.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.js CHANGED
@@ -12,6 +12,7 @@ import { ServiceDiscoveryNamespace } from "./resources/aws/networking/serviceDis
12
12
  import { BuildkiteFactory } from "./patterns/aws/buildkite.js";
13
13
  import { DevSubstrate } from "./patterns/aws/devSubstrate.js";
14
14
  import { StandardTagsAspect } from "./utils/standardTagsAspect.js";
15
+ import { DeployTimeWriterMarkerAspect } from "./utils/deployTimeWriterMarkerAspect.js";
15
16
  import { emitAppLevelAlbAliasExports } from "./utils/albAliasTargetRegistry.js";
16
17
  import { emitAppLevelCdnAliasExports } from "./utils/cdnAliasTargetRegistry.js";
17
18
  import { ensureFunctionLogGroups } from "./resources/aws/logging/logGroupHygiene.js";
@@ -61,6 +62,7 @@ export class App extends CdkApp {
61
62
  context: { [RDS_PREVENT_RENDERING_DEPRECATED_CREDENTIALS]: true }
62
63
  });
63
64
  App.instance = this;
65
+ Aspects.of(this).add(new DeployTimeWriterMarkerAspect());
64
66
  this.name = name ?? "FjallApp";
65
67
  this.stackPrefix = toPascalCase(this.name);
66
68
  // Initialise manifest collector for build-time service discovery
@@ -102,6 +102,12 @@ export declare const SECURITY_TIER_DIGESTS: {
102
102
  * Five is IAM's per-policy version ceiling: at five, the oldest non-default
103
103
  * version is dropped before the new default is published.
104
104
  * `GetPolicyVersion` returns the document URL-encoded, hence the decode.
105
+ *
106
+ * TODO 2026-09-05, owner: paul — tag on adopt (a `TagPolicy` after the live
107
+ * read when the marker is absent, so an ADOPTED policy carries the marker the
108
+ * create path applies via `Tags: props.PolicyTags`); blocked on the
109
+ * inline-code budget, tracked in the deploy-boundary raises decisions design
110
+ * note (2026-09-05, § 6 "Train 8").
105
111
  */
106
112
  export declare const DEPLOY_BOUNDARY_HANDLER_CORE: string;
107
113
  /**
@@ -1,4 +1,4 @@
1
- import { SECURITY_TIER_TO_BOUNDARY, deployBoundaryName } from "@fjall/generator";
1
+ import { SECURITY_TIER_TO_BOUNDARY, deployBoundaryName, deployBoundaryPolicyArn } from "@fjall/generator";
2
2
  import { Aws, Duration } from "aws-cdk-lib";
3
3
  import * as iam from "aws-cdk-lib/aws-iam";
4
4
  import { Runtime } from "aws-cdk-lib/aws-lambda";
@@ -147,6 +147,12 @@ const ALLOWED_DIGEST_LITERALS = Object.values(SECURITY_TIER_DIGESTS)
147
147
  * Five is IAM's per-policy version ceiling: at five, the oldest non-default
148
148
  * version is dropped before the new default is published.
149
149
  * `GetPolicyVersion` returns the document URL-encoded, hence the decode.
150
+ *
151
+ * TODO 2026-09-05, owner: paul — tag on adopt (a `TagPolicy` after the live
152
+ * read when the marker is absent, so an ADOPTED policy carries the marker the
153
+ * create path applies via `Tags: props.PolicyTags`); blocked on the
154
+ * inline-code budget, tracked in the deploy-boundary raises decisions design
155
+ * note (2026-09-05, § 6 "Train 8").
150
156
  */
151
157
  export const DEPLOY_BOUNDARY_HANDLER_CORE = `const { IAMClient, GetPolicyCommand, GetPolicyVersionCommand, CreatePolicyCommand, CreatePolicyVersionCommand, ListPolicyVersionsCommand, DeletePolicyVersionCommand } = require('@aws-sdk/client-iam');
152
158
  const { createHash } = require('crypto');
@@ -225,7 +231,9 @@ export class DeployBoundarySingleton extends Construct {
225
231
  constructor(scope, id, props) {
226
232
  super(scope, id);
227
233
  this.policyName = deployBoundaryName(props.fjallOrgId);
228
- const policyArn = `arn:${Aws.PARTITION}:iam::${Aws.ACCOUNT_ID}:policy/${this.policyName}`;
234
+ // Same SSoT builder the seam-2 probe and the destroy sweep address the
235
+ // policy with — three renderers, one derivation.
236
+ const policyArn = deployBoundaryPolicyArn(Aws.PARTITION, Aws.ACCOUNT_ID, props.fjallOrgId);
229
237
  const writer = new CustomResource(this, "Policy", {
230
238
  runtime: Runtime.NODEJS_24_X,
231
239
  timeout: Duration.seconds(DEPLOY_BOUNDARY_WRITER_TIMEOUT_SECONDS),
@@ -5,6 +5,7 @@ import { Annotations, CfnOutput, NestedStack } from "aws-cdk-lib";
5
5
  import { AwsCustomResource } from "../../resources/aws/utilities/awsCustomResource.js";
6
6
  import { Group, GroupMembership, PermissionSet, Assignment, IdentityStoreUser, IdentityStoreUserProvider, identityStoreUserArnPattern } from "../../resources/aws/iam/identityCenter/index.js";
7
7
  import { ManagedPolicy, Role } from "../../resources/aws/iam/index.js";
8
+ import { markDeployTimeWriter } from "../../utils/resourceTypeTags.js";
8
9
  import { allGroupNames, assignmentConstructId, boundGroupsForSet, effectivePermissionSets, implicitGroupNames, resolveTargetAccountNames, validateIdentityCentreConfig } from "./identityCentreConfig.js";
9
10
  // CreateAccountAssignment has a 15-outstanding-async limit. Batching at 8
10
11
  // keeps headroom for CFN's internal retries within the same backoff window.
@@ -74,6 +75,7 @@ export class IdentityCenter extends NestedStack {
74
75
  })
75
76
  }
76
77
  });
78
+ markDeployTimeWriter(this.listInstancesRole);
77
79
  }
78
80
  listIdentityCenterInstance() {
79
81
  const listInstancesCall = {
@@ -109,6 +109,11 @@ export interface LambdaFunctionProps {
109
109
  /**
110
110
  * Framework custom-resource handlers are tier-agnostic: the tier-owned
111
111
  * knobs are excluded at the type rather than silently ignored.
112
+ *
113
+ * Every singleton in this library backs a custom-resource provider, which is
114
+ * why the constructor marks the hoisted subtree as a deploy-time writer. A
115
+ * singleton fronting a runtime lambda would need that call moved to the two
116
+ * writer call sites (`utilities/customResource.ts`, `iam/identityCenter/user.ts`).
112
117
  */
113
118
  export interface SingletonFunctionProps extends Omit<LambdaFunctionProps, "tracing" | "appBackupTier"> {
114
119
  uuid?: string;
@@ -18,6 +18,7 @@ import { resolveImportedSecret } from "../secrets/index.js";
18
18
  import { validateSsmPathComponent } from "./ecsValidation.js";
19
19
  import { toPascalCase } from "../../../utils/capitaliseString.js";
20
20
  import { resolvePrivateSubnetType } from "../../../utils/vpcUtils.js";
21
+ import { markDeployTimeWriter } from "../../../utils/resourceTypeTags.js";
21
22
  import { createLambdaAlarms } from "../monitoring/index.js";
22
23
  function addPoliciesToRole(target, statements) {
23
24
  for (const statement of statements) {
@@ -155,6 +156,13 @@ export class SingletonFunction extends singletonFunction {
155
156
  addPoliciesToRole(this, props.inlinePolicy);
156
157
  applyRoleDescription(this, props.roleDescription);
157
158
  applyRolePathAndName(this, props.rolePath, props.roleName);
159
+ // aws-cdk-lib hoists the singleton's Function — and with it the execution
160
+ // role — to the stack scope, so a tag applied to this construct or to the
161
+ // enclosing writer never reaches either. Found the way CDK's own
162
+ // `ensureLambda` finds it, by the public `constructName`; `findChild`
163
+ // throws if the hoist ever moves, so a CDK upgrade fails at synth rather
164
+ // than shipping an unmarked role.
165
+ markDeployTimeWriter(Stack.of(this).node.findChild(this.constructName));
158
166
  }
159
167
  /**
160
168
  * The Lambda's execution role (auto-generated by CDK)
@@ -7,6 +7,7 @@ import { Provider } from "aws-cdk-lib/custom-resources";
7
7
  import { Construct } from "constructs";
8
8
  import { SingletonFunction } from "../../compute/lambda.js";
9
9
  import { DEFAULT_FRAMEWORK_LOG_RETENTION, LogGroup } from "../../logging/logGroup.js";
10
+ import { markDeployTimeWriter } from "../../../../utils/resourceTypeTags.js";
10
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
11
12
  /**
12
13
  * Resolves relative to the compiled location too — the build step copies the
@@ -58,6 +59,7 @@ export class IdentityStoreUserProvider extends Construct {
58
59
  })
59
60
  });
60
61
  this.serviceToken = provider.serviceToken;
62
+ markDeployTimeWriter(this);
61
63
  }
62
64
  }
63
65
  /**
@@ -1,7 +1,8 @@
1
1
  import { Construct } from "constructs";
2
- import { ArnFormat, Stack, Tags } from "aws-cdk-lib";
2
+ import { ArnFormat, Stack } from "aws-cdk-lib";
3
3
  import { AccountPrincipal, CompositePrincipal, PolicyDocument, PolicyStatement, Role } from "aws-cdk-lib/aws-iam";
4
4
  import { applyCostAllocationTags } from "../../../utils/costAllocationTags.js";
5
+ import { applyResourceTypeTag, FJALL_RESOURCE_TYPES } from "../../../utils/resourceTypeTags.js";
5
6
  export class VpcPeeringAccepterRole extends Construct {
6
7
  role;
7
8
  roleArn;
@@ -53,7 +54,7 @@ export class VpcPeeringAccepterRole extends Construct {
53
54
  }
54
55
  });
55
56
  this.roleArn = this.role.roleArn;
56
- Tags.of(this).add("fjall:resource:type", "vpc-peering-accepter");
57
+ applyResourceTypeTag(this, FJALL_RESOURCE_TYPES.vpcPeeringAccepter);
57
58
  applyCostAllocationTags(this, {
58
59
  service: "vpcPeeringAccepter",
59
60
  domain: props.costAllocationDomain ?? props.localVpc.node.id,
@@ -4,6 +4,7 @@ import { CfnRoute, CfnVPCPeeringConnection, SubnetType } from "aws-cdk-lib/aws-e
4
4
  import { PolicyStatement } from "aws-cdk-lib/aws-iam";
5
5
  import { AwsCustomResourcePolicy, PhysicalResourceId } from "aws-cdk-lib/custom-resources";
6
6
  import { applyCostAllocationTags } from "../../../utils/costAllocationTags.js";
7
+ import { applyResourceTypeTag, FJALL_RESOURCE_TYPES } from "../../../utils/resourceTypeTags.js";
7
8
  import { AwsCustomResource } from "../utilities/awsCustomResource.js";
8
9
  import { CrossAccountReturnRoutes } from "./crossAccountReturnRoutes.js";
9
10
  export class VpcPeeringConnection extends Construct {
@@ -92,7 +93,7 @@ export class VpcPeeringConnection extends Construct {
92
93
  enableDns: enableDnsResolution
93
94
  });
94
95
  }
95
- Tags.of(this).add("fjall:resource:type", "vpc-peering");
96
+ applyResourceTypeTag(this, FJALL_RESOURCE_TYPES.vpcPeering);
96
97
  Tags.of(this).add("fjall:peering:peer-vpc", props.peerVpcId);
97
98
  if (props.peerAccountId) {
98
99
  Tags.of(this).add("fjall:peering:peer-account", props.peerAccountId);
@@ -8,6 +8,11 @@ import { type Construct } from "constructs";
8
8
  * service-auto-created. Per the Wrapper Routing Discipline rule (generator
9
9
  * standards) and the infrastructure wrapper-routing pattern (§ A3), `lib/{config,
10
10
  * patterns}/` MUST instantiate this class rather than the raw CDK construct.
11
+ *
12
+ * The handler is a deploy-time writer: aws-cdk-lib hoists its function and
13
+ * execution role to the stack scope under an id that embeds a per-variant
14
+ * uuid (memory, VPC, ephemeral storage), so the role is marked through the
15
+ * public `handlerRole` seam rather than by construct id.
11
16
  */
12
17
  export declare class BucketDeployment extends CdkBucketDeployment {
13
18
  constructor(scope: Construct, id: string, props: BucketDeploymentProps);
@@ -1,4 +1,5 @@
1
1
  import { BucketDeployment as CdkBucketDeployment } from "aws-cdk-lib/aws-s3-deployment";
2
+ import { markDeployTimeWriter } from "../../../utils/resourceTypeTags.js";
2
3
  import { stackSingletonLogGroup } from "../logging/singletonLogGroup.js";
3
4
  /**
4
5
  * Routing-point wrapper for `aws-cdk-lib/aws-s3-deployment.BucketDeployment`.
@@ -8,6 +9,11 @@ import { stackSingletonLogGroup } from "../logging/singletonLogGroup.js";
8
9
  * service-auto-created. Per the Wrapper Routing Discipline rule (generator
9
10
  * standards) and the infrastructure wrapper-routing pattern (§ A3), `lib/{config,
10
11
  * patterns}/` MUST instantiate this class rather than the raw CDK construct.
12
+ *
13
+ * The handler is a deploy-time writer: aws-cdk-lib hoists its function and
14
+ * execution role to the stack scope under an id that embeds a per-variant
15
+ * uuid (memory, VPC, ephemeral storage), so the role is marked through the
16
+ * public `handlerRole` seam rather than by construct id.
11
17
  */
12
18
  export class BucketDeployment extends CdkBucketDeployment {
13
19
  constructor(scope, id, props) {
@@ -19,5 +25,6 @@ export class BucketDeployment extends CdkBucketDeployment {
19
25
  // site instantiates first.
20
26
  logGroup: props.logGroup ?? stackSingletonLogGroup(scope, "BucketDeployment")
21
27
  });
28
+ markDeployTimeWriter(this.handlerRole);
22
29
  }
23
30
  }
@@ -1,5 +1,14 @@
1
1
  import * as customResource from "aws-cdk-lib/custom-resources";
2
2
  import { type Construct } from "constructs";
3
+ /**
4
+ * The stack-scope construct id under which aws-cdk-lib hoists the one
5
+ * `AwsCustomResource` provider every site in a stack shares: the singleton's
6
+ * `lambdaPurpose` ("AWS") followed by its uuid with the hyphens stripped —
7
+ * `SingletonFunction.constructName`'s derivation, from the public
8
+ * `PROVIDER_FUNCTION_UUID`. Test fixtures address the hoisted function and
9
+ * its execution role by this prefix.
10
+ */
11
+ export declare const AWS_CUSTOM_RESOURCE_PROVIDER_ID: string;
3
12
  export declare class AwsCustomResource extends customResource.AwsCustomResource {
4
13
  readonly response: string;
5
14
  constructor(scope: Construct, id: string, props: customResource.AwsCustomResourceProps);
@@ -1,5 +1,16 @@
1
+ import { Stack } from "aws-cdk-lib";
1
2
  import * as customResource from "aws-cdk-lib/custom-resources";
3
+ import { markDeployTimeWriter } from "../../../utils/resourceTypeTags.js";
2
4
  import { stackSingletonLogGroup } from "../logging/singletonLogGroup.js";
5
+ /**
6
+ * The stack-scope construct id under which aws-cdk-lib hoists the one
7
+ * `AwsCustomResource` provider every site in a stack shares: the singleton's
8
+ * `lambdaPurpose` ("AWS") followed by its uuid with the hyphens stripped —
9
+ * `SingletonFunction.constructName`'s derivation, from the public
10
+ * `PROVIDER_FUNCTION_UUID`. Test fixtures address the hoisted function and
11
+ * its execution role by this prefix.
12
+ */
13
+ export const AWS_CUSTOM_RESOURCE_PROVIDER_ID = `AWS${customResource.AwsCustomResource.PROVIDER_FUNCTION_UUID.replace(/[^a-zA-Z0-9]/g, "")}`;
3
14
  export class AwsCustomResource extends customResource.AwsCustomResource {
4
15
  response;
5
16
  constructor(scope, id, props) {
@@ -14,5 +25,10 @@ export class AwsCustomResource extends customResource.AwsCustomResource {
14
25
  resources: customResource.AwsCustomResourcePolicy.ANY_RESOURCE
15
26
  })
16
27
  });
28
+ // aws-cdk-lib hoists the shared provider — and, unless the site passed
29
+ // `role`, its execution role — to the stack scope, out of reach of any
30
+ // tag on this construct. `findChild` throws if the hoist ever moves, so a
31
+ // CDK upgrade that would silently un-mark the role fails at synth.
32
+ markDeployTimeWriter(Stack.of(this).node.findChild(AWS_CUSTOM_RESOURCE_PROVIDER_ID));
17
33
  }
18
34
  }
@@ -5,6 +5,7 @@ import { Construct } from "constructs";
5
5
  import { SingletonFunction, LambdaFunction } from "../compute/lambda.js";
6
6
  import { DEFAULT_CUSTOM_RESOURCE_TIMEOUT_SECONDS } from "../compute/ecsConstants.js";
7
7
  import { DEFAULT_FRAMEWORK_LOG_RETENTION, LogGroup } from "../logging/logGroup.js";
8
+ import { markDeployTimeWriter } from "../../../utils/resourceTypeTags.js";
8
9
  export class CustomResource extends Construct {
9
10
  response;
10
11
  resource;
@@ -71,5 +72,6 @@ export class CustomResource extends Construct {
71
72
  })
72
73
  });
73
74
  this.response = this.resource.getAtt("Response").toString();
75
+ markDeployTimeWriter(this);
74
76
  }
75
77
  }
@@ -1,5 +1,11 @@
1
1
  import { Provider, type ProviderProps } from "aws-cdk-lib/custom-resources";
2
2
  import { type Construct } from "constructs";
3
+ /**
4
+ * Routing-point wrapper for the custom-resource provider framework. The
5
+ * framework's onEvent/isComplete functions and their roles are deploy-time
6
+ * writers; the caller's own handler (`props.onEventHandler`) lives outside
7
+ * this scope and is the caller's to mark.
8
+ */
3
9
  export declare class CustomResourceProvider extends Provider {
4
10
  constructor(scope: Construct, id: string, props: ProviderProps);
5
11
  }
@@ -1,6 +1,14 @@
1
1
  import { Provider } from "aws-cdk-lib/custom-resources";
2
+ import { markDeployTimeWriter } from "../../../utils/resourceTypeTags.js";
3
+ /**
4
+ * Routing-point wrapper for the custom-resource provider framework. The
5
+ * framework's onEvent/isComplete functions and their roles are deploy-time
6
+ * writers; the caller's own handler (`props.onEventHandler`) lives outside
7
+ * this scope and is the caller's to mark.
8
+ */
2
9
  export class CustomResourceProvider extends Provider {
3
10
  constructor(scope, id, props) {
4
11
  super(scope, id, props);
12
+ markDeployTimeWriter(this);
5
13
  }
6
14
  }
@@ -0,0 +1,23 @@
1
+ import { type IAspect } from "aws-cdk-lib";
2
+ import type { IConstruct } from "constructs";
3
+ /**
4
+ * Marks the execution roles aws-cdk-lib's lightweight custom-resource
5
+ * provider framework (`CustomResourceProviderBase`) renders as raw
6
+ * `AWS::IAM::Role` CfnResources: `Custom::VpcRestrictDefaultSG` under the
7
+ * `@aws-cdk/aws-ec2:restrictDefaultSecurityGroup` flag every scaffolded
8
+ * `cdk.json` sets, `Custom::CrossAccountZoneDelegation`, and whichever the
9
+ * next CDK release adds. Those roles have no tag manager, so `Tags.of()` —
10
+ * the wrappers' mechanism — never reaches them; this aspect writes the marker
11
+ * as a property override instead. Registered once per `App`, so every stack
12
+ * gets it without a per-construct site to forget.
13
+ *
14
+ * Recognised structurally, not by construct id: a `Role` CfnResource whose
15
+ * parent exposes the framework's `serviceToken` + `roleArn` pair. Prototype
16
+ * probing rather than `instanceof`, for the duplicate-aws-cdk-lib reason
17
+ * `logGroupHygiene` records. The full `Provider` framework exposes no
18
+ * `roleArn` and builds L2 roles, so it is not matched here — its wrappers tag
19
+ * it directly.
20
+ */
21
+ export declare class DeployTimeWriterMarkerAspect implements IAspect {
22
+ visit(node: IConstruct): void;
23
+ }
@@ -0,0 +1,35 @@
1
+ import { CfnResource } from "aws-cdk-lib";
2
+ import { markDeployTimeWriterCfn } from "./resourceTypeTags.js";
3
+ /**
4
+ * Marks the execution roles aws-cdk-lib's lightweight custom-resource
5
+ * provider framework (`CustomResourceProviderBase`) renders as raw
6
+ * `AWS::IAM::Role` CfnResources: `Custom::VpcRestrictDefaultSG` under the
7
+ * `@aws-cdk/aws-ec2:restrictDefaultSecurityGroup` flag every scaffolded
8
+ * `cdk.json` sets, `Custom::CrossAccountZoneDelegation`, and whichever the
9
+ * next CDK release adds. Those roles have no tag manager, so `Tags.of()` —
10
+ * the wrappers' mechanism — never reaches them; this aspect writes the marker
11
+ * as a property override instead. Registered once per `App`, so every stack
12
+ * gets it without a per-construct site to forget.
13
+ *
14
+ * Recognised structurally, not by construct id: a `Role` CfnResource whose
15
+ * parent exposes the framework's `serviceToken` + `roleArn` pair. Prototype
16
+ * probing rather than `instanceof`, for the duplicate-aws-cdk-lib reason
17
+ * `logGroupHygiene` records. The full `Provider` framework exposes no
18
+ * `roleArn` and builds L2 roles, so it is not matched here — its wrappers tag
19
+ * it directly.
20
+ */
21
+ export class DeployTimeWriterMarkerAspect {
22
+ visit(node) {
23
+ if (!CfnResource.isCfnResource(node) ||
24
+ node.cfnResourceType !== "AWS::IAM::Role") {
25
+ return;
26
+ }
27
+ const parent = node.node.scope;
28
+ if (parent === undefined || !isLightweightProvider(parent))
29
+ return;
30
+ markDeployTimeWriterCfn(node);
31
+ }
32
+ }
33
+ function isLightweightProvider(construct) {
34
+ return "serviceToken" in construct && "roleArn" in construct;
35
+ }
@@ -0,0 +1,41 @@
1
+ import { FJALL_RESOURCE_TYPE_TAG_KEY, FJALL_RESOURCE_TYPES, type FjallResourceType } from "@fjall/util/aws";
2
+ import { type CfnResource } from "aws-cdk-lib";
3
+ import type { IConstruct } from "constructs";
4
+ export { FJALL_RESOURCE_TYPE_TAG_KEY, FJALL_RESOURCE_TYPES, type FjallResourceType };
5
+ /**
6
+ * Stamp the `fjall:resource:type` marker on a construct subtree. The marker
7
+ * answers "what IS this resource" for resources whose physical name is
8
+ * CloudFormation-generated, so a consumer never has to pattern-match a name.
9
+ */
10
+ export declare function applyResourceTypeTag(scope: IConstruct, resourceType: FjallResourceType): void;
11
+ /**
12
+ * Mark a deploy-time-only writer subtree — the execution role CloudFormation
13
+ * assumes while driving a custom resource, plus the handler, the provider
14
+ * framework function and their log groups.
15
+ *
16
+ * Every writer producer in the library routes through one of two mechanisms,
17
+ * nothing stamping the key by hand:
18
+ *
19
+ * - this helper, called by each wrapper that constructs a writer: the shared
20
+ * `CustomResource` utility and the `CustomResourceProvider` wrapper (their
21
+ * own subtree), the `SingletonFunction`, `AwsCustomResource` and
22
+ * `BucketDeployment` wrappers (the function and role aws-cdk-lib hoists to
23
+ * the stack scope, out of reach of any tag on the wrapper itself), the
24
+ * identity-store user provider, and the Identity Centre ListInstances role;
25
+ * - `DeployTimeWriterMarkerAspect` (registered by `App`), for the execution
26
+ * roles aws-cdk-lib's lightweight provider framework renders as raw
27
+ * CfnResources — `Tags.of()` cannot reach those, so the marker goes on as a
28
+ * property override via `markDeployTimeWriterCfn`.
29
+ *
30
+ * The producer set is enumerated from source by
31
+ * `__tests__/deployTimeWriterCensus.test.ts`: a new construction of a CDK
32
+ * writer class fails that test until it names the mechanism that marks it.
33
+ */
34
+ export declare function markDeployTimeWriter(scope: IConstruct): void;
35
+ /**
36
+ * The raw-CfnResource twin of `markDeployTimeWriter`, for a role the
37
+ * lightweight `CustomResourceProviderBase` framework renders without a tag
38
+ * manager: the marker is written straight into the resource's `Tags`
39
+ * property. Called only by `DeployTimeWriterMarkerAspect`.
40
+ */
41
+ export declare function markDeployTimeWriterCfn(role: CfnResource): void;
@@ -0,0 +1,54 @@
1
+ import { FJALL_RESOURCE_TYPE_TAG_KEY, FJALL_RESOURCE_TYPES } from "@fjall/util/aws";
2
+ import { Tags } from "aws-cdk-lib";
3
+ // Canonical home: @fjall/util (`util/src/aws/infraTags.ts`) — shared with the
4
+ // Quick-Create template's hand-written twin and the webapp posture exclusion.
5
+ // Re-exported here so construct-package consumers keep one import path.
6
+ export { FJALL_RESOURCE_TYPE_TAG_KEY, FJALL_RESOURCE_TYPES };
7
+ /**
8
+ * Stamp the `fjall:resource:type` marker on a construct subtree. The marker
9
+ * answers "what IS this resource" for resources whose physical name is
10
+ * CloudFormation-generated, so a consumer never has to pattern-match a name.
11
+ */
12
+ export function applyResourceTypeTag(scope, resourceType) {
13
+ Tags.of(scope).add(FJALL_RESOURCE_TYPE_TAG_KEY, resourceType);
14
+ }
15
+ /**
16
+ * Mark a deploy-time-only writer subtree — the execution role CloudFormation
17
+ * assumes while driving a custom resource, plus the handler, the provider
18
+ * framework function and their log groups.
19
+ *
20
+ * Every writer producer in the library routes through one of two mechanisms,
21
+ * nothing stamping the key by hand:
22
+ *
23
+ * - this helper, called by each wrapper that constructs a writer: the shared
24
+ * `CustomResource` utility and the `CustomResourceProvider` wrapper (their
25
+ * own subtree), the `SingletonFunction`, `AwsCustomResource` and
26
+ * `BucketDeployment` wrappers (the function and role aws-cdk-lib hoists to
27
+ * the stack scope, out of reach of any tag on the wrapper itself), the
28
+ * identity-store user provider, and the Identity Centre ListInstances role;
29
+ * - `DeployTimeWriterMarkerAspect` (registered by `App`), for the execution
30
+ * roles aws-cdk-lib's lightweight provider framework renders as raw
31
+ * CfnResources — `Tags.of()` cannot reach those, so the marker goes on as a
32
+ * property override via `markDeployTimeWriterCfn`.
33
+ *
34
+ * The producer set is enumerated from source by
35
+ * `__tests__/deployTimeWriterCensus.test.ts`: a new construction of a CDK
36
+ * writer class fails that test until it names the mechanism that marks it.
37
+ */
38
+ export function markDeployTimeWriter(scope) {
39
+ applyResourceTypeTag(scope, FJALL_RESOURCE_TYPES.deployTimeWriter);
40
+ }
41
+ /**
42
+ * The raw-CfnResource twin of `markDeployTimeWriter`, for a role the
43
+ * lightweight `CustomResourceProviderBase` framework renders without a tag
44
+ * manager: the marker is written straight into the resource's `Tags`
45
+ * property. Called only by `DeployTimeWriterMarkerAspect`.
46
+ */
47
+ export function markDeployTimeWriterCfn(role) {
48
+ role.addPropertyOverride("Tags", [
49
+ {
50
+ Key: FJALL_RESOURCE_TYPE_TAG_KEY,
51
+ Value: FJALL_RESOURCE_TYPES.deployTimeWriter
52
+ }
53
+ ]);
54
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/components-infrastructure",
3
- "version": "34.0.0",
3
+ "version": "35.0.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/fjall-tech/fjall.git",
@@ -80,8 +80,8 @@
80
80
  },
81
81
  "dependencies": {
82
82
  "@aws-sdk/client-organizations": "^3.1098.0",
83
- "@fjall/generator": "^34.0.0",
84
- "@fjall/util": "^34.0.0",
83
+ "@fjall/generator": "^35.0.0",
84
+ "@fjall/util": "^35.0.0",
85
85
  "constructs": "^10.7.2",
86
86
  "zod": "^4.4.3"
87
87
  },