Repository: awslabs/aws-ddk (language: python)

Issue:
typescript: ddk config support global security settings to replace `BaseStack` funtionality
- [x] Implement `BaseStack ` with synthesizer only
- [x] Support Default Permissions Boundary Creation in Bootstrap stack of user's choosing (custom or legacy `ddk bootstrap`)
- [x] Add Config support for legacy ddk bootstrap roles & custom roles
  - [x] Tests for all bootstrap configuration cases (none, global, environment, etc)

*P.S. Please Don't attach files. Add code snippets directly in the message body instead.*


Discussion / hints:

Fix patch:
diff --git a/.projen/deps.json b/.projen/deps.json
index f1c79f4c..16583bb5 100644
--- a/.projen/deps.json
+++ b/.projen/deps.json
@@ -101,10 +101,6 @@
       "name": "jsii-pacmak",
       "type": "build"
     },
-    {
-      "name": "json-schema",
-      "type": "build"
-    },
     {
       "name": "npm-check-updates",
       "version": "^16",
@@ -136,6 +132,10 @@
       "name": "ts-node",
       "type": "bundled"
     },
+    {
+      "name": "yaml",
+      "type": "bundled"
+    },
     {
       "name": "@aws-cdk/aws-glue-alpha",
       "version": "2.62.2-alpha.0",
diff --git a/.projenrc.js b/.projenrc.js
index 4adaedea..62043cb4 100644
--- a/.projenrc.js
+++ b/.projenrc.js
@@ -42,7 +42,7 @@ const project = new awscdk.AwsCdkConstructLibrary({
 });
 
 // Other Bundled dependencies
-["deepmerge@4.0.0", "ts-node"].forEach((dep) => {
+["deepmerge@4.0.0", "ts-node", "yaml"].forEach((dep) => {
   project.addBundledDeps(dep);
 });
 
diff --git a/API.md b/API.md
index cf54f176..3b852e74 100644
--- a/API.md
+++ b/API.md
@@ -414,6 +414,866 @@ public readonly stateMachine: StateMachine;
 ---
 
 
+### BaseStack <a name="BaseStack" id="aws-ddk-core.BaseStack"></a>
+
+#### Initializers <a name="Initializers" id="aws-ddk-core.BaseStack.Initializer"></a>
+
+```typescript
+import { BaseStack } from 'aws-ddk-core'
+
+new BaseStack(scope: Construct, id: string, props: BaseStackProps)
+```
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| <code><a href="#aws-ddk-core.BaseStack.Initializer.parameter.scope">scope</a></code> | <code>constructs.Construct</code> | *No description.* |
+| <code><a href="#aws-ddk-core.BaseStack.Initializer.parameter.id">id</a></code> | <code>string</code> | *No description.* |
+| <code><a href="#aws-ddk-core.BaseStack.Initializer.parameter.props">props</a></code> | <code><a href="#aws-ddk-core.BaseStackProps">BaseStackProps</a></code> | *No description.* |
+
+---
+
+##### `scope`<sup>Required</sup> <a name="scope" id="aws-ddk-core.BaseStack.Initializer.parameter.scope"></a>
+
+- *Type:* constructs.Construct
+
+---
+
+##### `id`<sup>Required</sup> <a name="id" id="aws-ddk-core.BaseStack.Initializer.parameter.id"></a>
+
+- *Type:* string
+
+---
+
+##### `props`<sup>Required</sup> <a name="props" id="aws-ddk-core.BaseStack.Initializer.parameter.props"></a>
+
+- *Type:* <a href="#aws-ddk-core.BaseStackProps">BaseStackProps</a>
+
+---
+
+#### Methods <a name="Methods" id="Methods"></a>
+
+| **Name** | **Description** |
+| --- | --- |
+| <code><a href="#aws-ddk-core.BaseStack.toString">toString</a></code> | Returns a string representation of this construct. |
+| <code><a href="#aws-ddk-core.BaseStack.addDependency">addDependency</a></code> | Add a dependency between this stack and another stack. |
+| <code><a href="#aws-ddk-core.BaseStack.addMetadata">addMetadata</a></code> | Adds an arbitary key-value pair, with information you want to record about the stack. |
+| <code><a href="#aws-ddk-core.BaseStack.addTransform">addTransform</a></code> | Add a Transform to this stack. A Transform is a macro that AWS CloudFormation uses to process your template. |
+| <code><a href="#aws-ddk-core.BaseStack.exportStringListValue">exportStringListValue</a></code> | Create a CloudFormation Export for a string list value. |
+| <code><a href="#aws-ddk-core.BaseStack.exportValue">exportValue</a></code> | Create a CloudFormation Export for a string value. |
+| <code><a href="#aws-ddk-core.BaseStack.formatArn">formatArn</a></code> | Creates an ARN from components. |
+| <code><a href="#aws-ddk-core.BaseStack.getLogicalId">getLogicalId</a></code> | Allocates a stack-unique CloudFormation-compatible logical identity for a specific resource. |
+| <code><a href="#aws-ddk-core.BaseStack.regionalFact">regionalFact</a></code> | Look up a fact value for the given fact for the region of this stack. |
+| <code><a href="#aws-ddk-core.BaseStack.renameLogicalId">renameLogicalId</a></code> | Rename a generated logical identities. |
+| <code><a href="#aws-ddk-core.BaseStack.reportMissingContextKey">reportMissingContextKey</a></code> | Indicate that a context key was expected. |
+| <code><a href="#aws-ddk-core.BaseStack.resolve">resolve</a></code> | Resolve a tokenized value in the context of the current stack. |
+| <code><a href="#aws-ddk-core.BaseStack.splitArn">splitArn</a></code> | Splits the provided ARN into its components. |
+| <code><a href="#aws-ddk-core.BaseStack.toJsonString">toJsonString</a></code> | Convert an object, potentially containing tokens, to a JSON string. |
+
+---
+
+##### `toString` <a name="toString" id="aws-ddk-core.BaseStack.toString"></a>
+
+```typescript
+public toString(): string
+```
+
+Returns a string representation of this construct.
+
+##### `addDependency` <a name="addDependency" id="aws-ddk-core.BaseStack.addDependency"></a>
+
+```typescript
+public addDependency(target: Stack, reason?: string): void
+```
+
+Add a dependency between this stack and another stack.
+
+This can be used to define dependencies between any two stacks within an
+app, and also supports nested stacks.
+
+###### `target`<sup>Required</sup> <a name="target" id="aws-ddk-core.BaseStack.addDependency.parameter.target"></a>
+
+- *Type:* aws-cdk-lib.Stack
+
+---
+
+###### `reason`<sup>Optional</sup> <a name="reason" id="aws-ddk-core.BaseStack.addDependency.parameter.reason"></a>
+
+- *Type:* string
+
+---
+
+##### `addMetadata` <a name="addMetadata" id="aws-ddk-core.BaseStack.addMetadata"></a>
+
+```typescript
+public addMetadata(key: string, value: any): void
+```
+
+Adds an arbitary key-value pair, with information you want to record about the stack.
+
+These get translated to the Metadata section of the generated template.
+
+> [https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/metadata-section-structure.html](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/metadata-section-structure.html)
+
+###### `key`<sup>Required</sup> <a name="key" id="aws-ddk-core.BaseStack.addMetadata.parameter.key"></a>
+
+- *Type:* string
+
+---
+
+###### `value`<sup>Required</sup> <a name="value" id="aws-ddk-core.BaseStack.addMetadata.parameter.value"></a>
+
+- *Type:* any
+
+---
+
+##### `addTransform` <a name="addTransform" id="aws-ddk-core.BaseStack.addTransform"></a>
+
+```typescript
+public addTransform(transform: string): void
+```
+
+Add a Transform to this stack. A Transform is a macro that AWS CloudFormation uses to process your template.
+
+Duplicate values are removed when stack is synthesized.
+
+> [https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html)
+
+*Example*
+
+```typescript
+declare const stack: Stack;
+
+stack.addTransform('AWS::Serverless-2016-10-31')
+```
+
+
+###### `transform`<sup>Required</sup> <a name="transform" id="aws-ddk-core.BaseStack.addTransform.parameter.transform"></a>
+
+- *Type:* string
+
+The transform to add.
+
+---
+
+##### `exportStringListValue` <a name="exportStringListValue" id="aws-ddk-core.BaseStack.exportStringListValue"></a>
+
+```typescript
+public exportStringListValue(exportedValue: any, options?: ExportValueOptions): string[]
+```
+
+Create a CloudFormation Export for a string list value.
+
+Returns a string list representing the corresponding `Fn.importValue()`
+expression for this Export. The export expression is automatically wrapped with an
+`Fn::Join` and the import value with an `Fn::Split`, since CloudFormation can only
+export strings. You can control the name for the export by passing the `name` option.
+
+If you don't supply a value for `name`, the value you're exporting must be
+a Resource attribute (for example: `bucket.bucketName`) and it will be
+given the same name as the automatic cross-stack reference that would be created
+if you used the attribute in another Stack.
+
+One of the uses for this method is to *remove* the relationship between
+two Stacks established by automatic cross-stack references. It will
+temporarily ensure that the CloudFormation Export still exists while you
+remove the reference from the consuming stack. After that, you can remove
+the resource and the manual export.
+
+See `exportValue` for an example of this process.
+
+###### `exportedValue`<sup>Required</sup> <a name="exportedValue" id="aws-ddk-core.BaseStack.exportStringListValue.parameter.exportedValue"></a>
+
+- *Type:* any
+
+---
+
+###### `options`<sup>Optional</sup> <a name="options" id="aws-ddk-core.BaseStack.exportStringListValue.parameter.options"></a>
+
+- *Type:* aws-cdk-lib.ExportValueOptions
+
+---
+
+##### `exportValue` <a name="exportValue" id="aws-ddk-core.BaseStack.exportValue"></a>
+
+```typescript
+public exportValue(exportedValue: any, options?: ExportValueOptions): string
+```
+
+Create a CloudFormation Export for a string value.
+
+Returns a string representing the corresponding `Fn.importValue()`
+expression for this Export. You can control the name for the export by
+passing the `name` option.
+
+If you don't supply a value for `name`, the value you're exporting must be
+a Resource attribute (for example: `bucket.bucketName`) and it will be
+given the same name as the automatic cross-stack reference that would be created
+if you used the attribute in another Stack.
+
+One of the uses for this method is to *remove* the relationship between
+two Stacks established by automatic cross-stack references. It will
+temporarily ensure that the CloudFormation Export still exists while you
+remove the reference from the consuming stack. After that, you can remove
+the resource and the manual export.
+
+## Example
+
+Here is how the process works. Let's say there are two stacks,
+`producerStack` and `consumerStack`, and `producerStack` has a bucket
+called `bucket`, which is referenced by `consumerStack` (perhaps because
+an AWS Lambda Function writes into it, or something like that).
+
+It is not safe to remove `producerStack.bucket` because as the bucket is being
+deleted, `consumerStack` might still be using it.
+
+Instead, the process takes two deployments:
+
+### Deployment 1: break the relationship
+
+- Make sure `consumerStack` no longer references `bucket.bucketName` (maybe the consumer
+   stack now uses its own bucket, or it writes to an AWS DynamoDB table, or maybe you just
+   remove the Lambda Function altogether).
+- In the `ProducerStack` class, call `this.exportValue(this.bucket.bucketName)`. This
+   will make sure the CloudFormation Export continues to exist while the relationship
+   between the two stacks is being broken.
+- Deploy (this will effectively only change the `consumerStack`, but it's safe to deploy both).
+
+### Deployment 2: remove the bucket resource
+
+- You are now free to remove the `bucket` resource from `producerStack`.
+- Don't forget to remove the `exportValue()` call as well.
+- Deploy again (this time only the `producerStack` will be changed -- the bucket will be deleted).
+
+###### `exportedValue`<sup>Required</sup> <a name="exportedValue" id="aws-ddk-core.BaseStack.exportValue.parameter.exportedValue"></a>
+
+- *Type:* any
+
+---
+
+###### `options`<sup>Optional</sup> <a name="options" id="aws-ddk-core.BaseStack.exportValue.parameter.options"></a>
+
+- *Type:* aws-cdk-lib.ExportValueOptions
+
+---
+
+##### `formatArn` <a name="formatArn" id="aws-ddk-core.BaseStack.formatArn"></a>
+
+```typescript
+public formatArn(components: ArnComponents): string
+```
+
+Creates an ARN from components.
+
+If `partition`, `region` or `account` are not specified, the stack's
+partition, region and account will be used.
+
+If any component is the empty string, an empty string will be inserted
+into the generated ARN at the location that component corresponds to.
+
+The ARN will be formatted as follows:
+
+   arn:{partition}:{service}:{region}:{account}:{resource}{sep}{resource-name}
+
+The required ARN pieces that are omitted will be taken from the stack that
+the 'scope' is attached to. If all ARN pieces are supplied, the supplied scope
+can be 'undefined'.
+
+###### `components`<sup>Required</sup> <a name="components" id="aws-ddk-core.BaseStack.formatArn.parameter.components"></a>
+
+- *Type:* aws-cdk-lib.ArnComponents
+
+---
+
+##### `getLogicalId` <a name="getLogicalId" id="aws-ddk-core.BaseStack.getLogicalId"></a>
+
+```typescript
+public getLogicalId(element: CfnElement): string
+```
+
+Allocates a stack-unique CloudFormation-compatible logical identity for a specific resource.
+
+This method is called when a `CfnElement` is created and used to render the
+initial logical identity of resources. Logical ID renames are applied at
+this stage.
+
+This method uses the protected method `allocateLogicalId` to render the
+logical ID for an element. To modify the naming scheme, extend the `Stack`
+class and override this method.
+
+###### `element`<sup>Required</sup> <a name="element" id="aws-ddk-core.BaseStack.getLogicalId.parameter.element"></a>
+
+- *Type:* aws-cdk-lib.CfnElement
+
+The CloudFormation element for which a logical identity is needed.
+
+---
+
+##### `regionalFact` <a name="regionalFact" id="aws-ddk-core.BaseStack.regionalFact"></a>
+
+```typescript
+public regionalFact(factName: string, defaultValue?: string): string
+```
+
+Look up a fact value for the given fact for the region of this stack.
+
+Will return a definite value only if the region of the current stack is resolved.
+If not, a lookup map will be added to the stack and the lookup will be done at
+CDK deployment time.
+
+What regions will be included in the lookup map is controlled by the
+`@aws-cdk/core:target-partitions` context value: it must be set to a list
+of partitions, and only regions from the given partitions will be included.
+If no such context key is set, all regions will be included.
+
+This function is intended to be used by construct library authors. Application
+builders can rely on the abstractions offered by construct libraries and do
+not have to worry about regional facts.
+
+If `defaultValue` is not given, it is an error if the fact is unknown for
+the given region.
+
+###### `factName`<sup>Required</sup> <a name="factName" id="aws-ddk-core.BaseStack.regionalFact.parameter.factName"></a>
+
+- *Type:* string
+
+---
+
+###### `defaultValue`<sup>Optional</sup> <a name="defaultValue" id="aws-ddk-core.BaseStack.regionalFact.parameter.defaultValue"></a>
+
+- *Type:* string
+
+---
+
+##### `renameLogicalId` <a name="renameLogicalId" id="aws-ddk-core.BaseStack.renameLogicalId"></a>
+
+```typescript
+public renameLogicalId(oldId: string, newId: string): void
+```
+
+Rename a generated logical identities.
+
+To modify the naming scheme strategy, extend the `Stack` class and
+override the `allocateLogicalId` method.
+
+###### `oldId`<sup>Required</sup> <a name="oldId" id="aws-ddk-core.BaseStack.renameLogicalId.parameter.oldId"></a>
+
+- *Type:* string
+
+---
+
+###### `newId`<sup>Required</sup> <a name="newId" id="aws-ddk-core.BaseStack.renameLogicalId.parameter.newId"></a>
+
+- *Type:* string
+
+---
+
+##### `reportMissingContextKey` <a name="reportMissingContextKey" id="aws-ddk-core.BaseStack.reportMissingContextKey"></a>
+
+```typescript
+public reportMissingContextKey(report: MissingContext): void
+```
+
+Indicate that a context key was expected.
+
+Contains instructions which will be emitted into the cloud assembly on how
+the key should be supplied.
+
+###### `report`<sup>Required</sup> <a name="report" id="aws-ddk-core.BaseStack.reportMissingContextKey.parameter.report"></a>
+
+- *Type:* aws-cdk-lib.cloud_assembly_schema.MissingContext
+
+The set of parameters needed to obtain the context.
+
+---
+
+##### `resolve` <a name="resolve" id="aws-ddk-core.BaseStack.resolve"></a>
+
+```typescript
+public resolve(obj: any): any
+```
+
+Resolve a tokenized value in the context of the current stack.
+
+###### `obj`<sup>Required</sup> <a name="obj" id="aws-ddk-core.BaseStack.resolve.parameter.obj"></a>
+
+- *Type:* any
+
+---
+
+##### `splitArn` <a name="splitArn" id="aws-ddk-core.BaseStack.splitArn"></a>
+
+```typescript
+public splitArn(arn: string, arnFormat: ArnFormat): ArnComponents
+```
+
+Splits the provided ARN into its components.
+
+Works both if 'arn' is a string like 'arn:aws:s3:::bucket',
+and a Token representing a dynamic CloudFormation expression
+(in which case the returned components will also be dynamic CloudFormation expressions,
+encoded as Tokens).
+
+###### `arn`<sup>Required</sup> <a name="arn" id="aws-ddk-core.BaseStack.splitArn.parameter.arn"></a>
+
+- *Type:* string
+
+the ARN to split into its components.
+
+---
+
+###### `arnFormat`<sup>Required</sup> <a name="arnFormat" id="aws-ddk-core.BaseStack.splitArn.parameter.arnFormat"></a>
+
+- *Type:* aws-cdk-lib.ArnFormat
+
+the expected format of 'arn' - depends on what format the service 'arn' represents uses.
+
+---
+
+##### `toJsonString` <a name="toJsonString" id="aws-ddk-core.BaseStack.toJsonString"></a>
+
+```typescript
+public toJsonString(obj: any, space?: number): string
+```
+
+Convert an object, potentially containing tokens, to a JSON string.
+
+###### `obj`<sup>Required</sup> <a name="obj" id="aws-ddk-core.BaseStack.toJsonString.parameter.obj"></a>
+
+- *Type:* any
+
+---
+
+###### `space`<sup>Optional</sup> <a name="space" id="aws-ddk-core.BaseStack.toJsonString.parameter.space"></a>
+
+- *Type:* number
+
+---
+
+#### Static Functions <a name="Static Functions" id="Static Functions"></a>
+
+| **Name** | **Description** |
+| --- | --- |
+| <code><a href="#aws-ddk-core.BaseStack.isConstruct">isConstruct</a></code> | Checks if `x` is a construct. |
+| <code><a href="#aws-ddk-core.BaseStack.isStack">isStack</a></code> | Return whether the given object is a Stack. |
+| <code><a href="#aws-ddk-core.BaseStack.of">of</a></code> | Looks up the first stack scope in which `construct` is defined. |
+
+---
+
+##### ~~`isConstruct`~~ <a name="isConstruct" id="aws-ddk-core.BaseStack.isConstruct"></a>
+
+```typescript
+import { BaseStack } from 'aws-ddk-core'
+
+BaseStack.isConstruct(x: any)
+```
+
+Checks if `x` is a construct.
+
+###### `x`<sup>Required</sup> <a name="x" id="aws-ddk-core.BaseStack.isConstruct.parameter.x"></a>
+
+- *Type:* any
+
+Any object.
+
+---
+
+##### `isStack` <a name="isStack" id="aws-ddk-core.BaseStack.isStack"></a>
+
+```typescript
+import { BaseStack } from 'aws-ddk-core'
+
+BaseStack.isStack(x: any)
+```
+
+Return whether the given object is a Stack.
+
+We do attribute detection since we can't reliably use 'instanceof'.
+
+###### `x`<sup>Required</sup> <a name="x" id="aws-ddk-core.BaseStack.isStack.parameter.x"></a>
+
+- *Type:* any
+
+---
+
+##### `of` <a name="of" id="aws-ddk-core.BaseStack.of"></a>
+
+```typescript
+import { BaseStack } from 'aws-ddk-core'
+
+BaseStack.of(construct: IConstruct)
+```
+
+Looks up the first stack scope in which `construct` is defined.
+
+Fails if there is no stack up the tree.
+
+###### `construct`<sup>Required</sup> <a name="construct" id="aws-ddk-core.BaseStack.of.parameter.construct"></a>
+
+- *Type:* constructs.IConstruct
+
+The construct to start the search from.
+
+---
+
+#### Properties <a name="Properties" id="Properties"></a>
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| <code><a href="#aws-ddk-core.BaseStack.property.node">node</a></code> | <code>constructs.Node</code> | The tree node. |
+| <code><a href="#aws-ddk-core.BaseStack.property.account">account</a></code> | <code>string</code> | The AWS account into which this stack will be deployed. |
+| <code><a href="#aws-ddk-core.BaseStack.property.artifactId">artifactId</a></code> | <code>string</code> | The ID of the cloud assembly artifact for this stack. |
+| <code><a href="#aws-ddk-core.BaseStack.property.availabilityZones">availabilityZones</a></code> | <code>string[]</code> | Returns the list of AZs that are available in the AWS environment (account/region) associated with this stack. |
+| <code><a href="#aws-ddk-core.BaseStack.property.bundlingRequired">bundlingRequired</a></code> | <code>boolean</code> | Indicates whether the stack requires bundling or not. |
+| <code><a href="#aws-ddk-core.BaseStack.property.dependencies">dependencies</a></code> | <code>aws-cdk-lib.Stack[]</code> | Return the stacks this stack depends on. |
+| <code><a href="#aws-ddk-core.BaseStack.property.environment">environment</a></code> | <code>string</code> | The environment coordinates in which this stack is deployed. |
+| <code><a href="#aws-ddk-core.BaseStack.property.nested">nested</a></code> | <code>boolean</code> | Indicates if this is a nested stack, in which case `parentStack` will include a reference to it's parent. |
+| <code><a href="#aws-ddk-core.BaseStack.property.notificationArns">notificationArns</a></code> | <code>string[]</code> | Returns the list of notification Amazon Resource Names (ARNs) for the current stack. |
+| <code><a href="#aws-ddk-core.BaseStack.property.partition">partition</a></code> | <code>string</code> | The partition in which this stack is defined. |
+| <code><a href="#aws-ddk-core.BaseStack.property.region">region</a></code> | <code>string</code> | The AWS region into which this stack will be deployed (e.g. `us-west-2`). |
+| <code><a href="#aws-ddk-core.BaseStack.property.stackId">stackId</a></code> | <code>string</code> | The ID of the stack. |
+| <code><a href="#aws-ddk-core.BaseStack.property.stackName">stackName</a></code> | <code>string</code> | The concrete CloudFormation physical stack name. |
+| <code><a href="#aws-ddk-core.BaseStack.property.synthesizer">synthesizer</a></code> | <code>aws-cdk-lib.IStackSynthesizer</code> | Synthesis method for this stack. |
+| <code><a href="#aws-ddk-core.BaseStack.property.tags">tags</a></code> | <code>aws-cdk-lib.TagManager</code> | Tags to be applied to the stack. |
+| <code><a href="#aws-ddk-core.BaseStack.property.templateFile">templateFile</a></code> | <code>string</code> | The name of the CloudFormation template file emitted to the output directory during synthesis. |
+| <code><a href="#aws-ddk-core.BaseStack.property.templateOptions">templateOptions</a></code> | <code>aws-cdk-lib.ITemplateOptions</code> | Options for CloudFormation template (like version, transform, description). |
+| <code><a href="#aws-ddk-core.BaseStack.property.urlSuffix">urlSuffix</a></code> | <code>string</code> | The Amazon domain suffix for the region in which this stack is defined. |
+| <code><a href="#aws-ddk-core.BaseStack.property.nestedStackParent">nestedStackParent</a></code> | <code>aws-cdk-lib.Stack</code> | If this is a nested stack, returns it's parent stack. |
+| <code><a href="#aws-ddk-core.BaseStack.property.nestedStackResource">nestedStackResource</a></code> | <code>aws-cdk-lib.CfnResource</code> | If this is a nested stack, this represents its `AWS::CloudFormation::Stack` resource. |
+| <code><a href="#aws-ddk-core.BaseStack.property.terminationProtection">terminationProtection</a></code> | <code>boolean</code> | Whether termination protection is enabled for this stack. |
+
+---
+
+##### `node`<sup>Required</sup> <a name="node" id="aws-ddk-core.BaseStack.property.node"></a>
+
+```typescript
+public readonly node: Node;
+```
+
+- *Type:* constructs.Node
+
+The tree node.
+
+---
+
+##### `account`<sup>Required</sup> <a name="account" id="aws-ddk-core.BaseStack.property.account"></a>
+
+```typescript
+public readonly account: string;
+```
+
+- *Type:* string
+
+The AWS account into which this stack will be deployed.
+
+This value is resolved according to the following rules:
+
+1. The value provided to `env.account` when the stack is defined. This can
+    either be a concrete account (e.g. `585695031111`) or the
+    `Aws.ACCOUNT_ID` token.
+3. `Aws.ACCOUNT_ID`, which represents the CloudFormation intrinsic reference
+    `{ "Ref": "AWS::AccountId" }` encoded as a string token.
+
+Preferably, you should use the return value as an opaque string and not
+attempt to parse it to implement your logic. If you do, you must first
+check that it is a concrete value an not an unresolved token. If this
+value is an unresolved token (`Token.isUnresolved(stack.account)` returns
+`true`), this implies that the user wishes that this stack will synthesize
+into a **account-agnostic template**. In this case, your code should either
+fail (throw an error, emit a synth error using `Annotations.of(construct).addError()`) or
+implement some other region-agnostic behavior.
+
+---
+
+##### `artifactId`<sup>Required</sup> <a name="artifactId" id="aws-ddk-core.BaseStack.property.artifactId"></a>
+
+```typescript
+public readonly artifactId: string;
+```
+
+- *Type:* string
+
+The ID of the cloud assembly artifact for this stack.
+
+---
+
+##### `availabilityZones`<sup>Required</sup> <a name="availabilityZones" id="aws-ddk-core.BaseStack.property.availabilityZones"></a>
+
+```typescript
+public readonly availabilityZones: string[];
+```
+
+- *Type:* string[]
+
+Returns the list of AZs that are available in the AWS environment (account/region) associated with this stack.
+
+If the stack is environment-agnostic (either account and/or region are
+tokens), this property will return an array with 2 tokens that will resolve
+at deploy-time to the first two availability zones returned from CloudFormation's
+`Fn::GetAZs` intrinsic function.
+
+If they are not available in the context, returns a set of dummy values and
+reports them as missing, and let the CLI resolve them by calling EC2
+`DescribeAvailabilityZones` on the target environment.
+
+To specify a different strategy for selecting availability zones override this method.
+
+---
+
+##### `bundlingRequired`<sup>Required</sup> <a name="bundlingRequired" id="aws-ddk-core.BaseStack.property.bundlingRequired"></a>
+
+```typescript
+public readonly bundlingRequired: boolean;
+```
+
+- *Type:* boolean
+
+Indicates whether the stack requires bundling or not.
+
+---
+
+##### `dependencies`<sup>Required</sup> <a name="dependencies" id="aws-ddk-core.BaseStack.property.dependencies"></a>
+
+```typescript
+public readonly dependencies: Stack[];
+```
+
+- *Type:* aws-cdk-lib.Stack[]
+
+Return the stacks this stack depends on.
+
+---
+
+##### `environment`<sup>Required</sup> <a name="environment" id="aws-ddk-core.BaseStack.property.environment"></a>
+
+```typescript
+public readonly environment: string;
+```
+
+- *Type:* string
+
+The environment coordinates in which this stack is deployed.
+
+In the form
+`aws://account/region`. Use `stack.account` and `stack.region` to obtain
+the specific values, no need to parse.
+
+You can use this value to determine if two stacks are targeting the same
+environment.
+
+If either `stack.account` or `stack.region` are not concrete values (e.g.
+`Aws.ACCOUNT_ID` or `Aws.REGION`) the special strings `unknown-account` and/or
+`unknown-region` will be used respectively to indicate this stack is
+region/account-agnostic.
+
+---
+
+##### `nested`<sup>Required</sup> <a name="nested" id="aws-ddk-core.BaseStack.property.nested"></a>
+
+```typescript
+public readonly nested: boolean;
+```
+
+- *Type:* boolean
+
+Indicates if this is a nested stack, in which case `parentStack` will include a reference to it's parent.
+
+---
+
+##### `notificationArns`<sup>Required</sup> <a name="notificationArns" id="aws-ddk-core.BaseStack.property.notificationArns"></a>
+
+```typescript
+public readonly notificationArns: string[];
+```
+
+- *Type:* string[]
+
+Returns the list of notification Amazon Resource Names (ARNs) for the current stack.
+
+---
+
+##### `partition`<sup>Required</sup> <a name="partition" id="aws-ddk-core.BaseStack.property.partition"></a>
+
+```typescript
+public readonly partition: string;
+```
+
+- *Type:* string
+
+The partition in which this stack is defined.
+
+---
+
+##### `region`<sup>Required</sup> <a name="region" id="aws-ddk-core.BaseStack.property.region"></a>
+
+```typescript
+public readonly region: string;
+```
+
+- *Type:* string
+
+The AWS region into which this stack will be deployed (e.g. `us-west-2`).
+
+This value is resolved according to the following rules:
+
+1. The value provided to `env.region` when the stack is defined. This can
+    either be a concrete region (e.g. `us-west-2`) or the `Aws.REGION`
+    token.
+3. `Aws.REGION`, which is represents the CloudFormation intrinsic reference
+    `{ "Ref": "AWS::Region" }` encoded as a string token.
+
+Preferably, you should use the return value as an opaque string and not
+attempt to parse it to implement your logic. If you do, you must first
+check that it is a concrete value an not an unresolved token. If this
+value is an unresolved token (`Token.isUnresolved(stack.region)` returns
+`true`), this implies that the user wishes that this stack will synthesize
+into a **region-agnostic template**. In this case, your code should either
+fail (throw an error, emit a synth error using `Annotations.of(construct).addError()`) or
+implement some other region-agnostic behavior.
+
+---
+
+##### `stackId`<sup>Required</sup> <a name="stackId" id="aws-ddk-core.BaseStack.property.stackId"></a>
+
+```typescript
+public readonly stackId: string;
+```
+
+- *Type:* string
+
+The ID of the stack.
+
+---
+
+*Example*
+
+```typescript
+// After resolving, looks like
+'arn:aws:cloudformation:us-west-2:123456789012:stack/teststack/51af3dc0-da77-11e4-872e-1234567db123'
+```
+
+
+##### `stackName`<sup>Required</sup> <a name="stackName" id="aws-ddk-core.BaseStack.property.stackName"></a>
+
+```typescript
+public readonly stackName: string;
+```
+
+- *Type:* string
+
+The concrete CloudFormation physical stack name.
+
+This is either the name defined explicitly in the `stackName` prop or
+allocated based on the stack's location in the construct tree. Stacks that
+are directly defined under the app use their construct `id` as their stack
+name. Stacks that are defined deeper within the tree will use a hashed naming
+scheme based on the construct path to ensure uniqueness.
+
+If you wish to obtain the deploy-time AWS::StackName intrinsic,
+you can use `Aws.STACK_NAME` directly.
+
+---
+
+##### `synthesizer`<sup>Required</sup> <a name="synthesizer" id="aws-ddk-core.BaseStack.property.synthesizer"></a>
+
+```typescript
+public readonly synthesizer: IStackSynthesizer;
+```
+
+- *Type:* aws-cdk-lib.IStackSynthesizer
+
+Synthesis method for this stack.
+
+---
+
+##### `tags`<sup>Required</sup> <a name="tags" id="aws-ddk-core.BaseStack.property.tags"></a>
+
+```typescript
+public readonly tags: TagManager;
+```
+
+- *Type:* aws-cdk-lib.TagManager
+
+Tags to be applied to the stack.
+
+---
+
+##### `templateFile`<sup>Required</sup> <a name="templateFile" id="aws-ddk-core.BaseStack.property.templateFile"></a>
+
+```typescript
+public readonly templateFile: string;
+```
+
+- *Type:* string
+
+The name of the CloudFormation template file emitted to the output directory during synthesis.
+
+Example value: `MyStack.template.json`
+
+---
+
+##### `templateOptions`<sup>Required</sup> <a name="templateOptions" id="aws-ddk-core.BaseStack.property.templateOptions"></a>
+
+```typescript
+public readonly templateOptions: ITemplateOptions;
+```
+
+- *Type:* aws-cdk-lib.ITemplateOptions
+
+Options for CloudFormation template (like version, transform, description).
+
+---
+
+##### `urlSuffix`<sup>Required</sup> <a name="urlSuffix" id="aws-ddk-core.BaseStack.property.urlSuffix"></a>
+
+```typescript
+public readonly urlSuffix: string;
+```
+
+- *Type:* string
+
+The Amazon domain suffix for the region in which this stack is defined.
+
+---
+
+##### `nestedStackParent`<sup>Optional</sup> <a name="nestedStackParent" id="aws-ddk-core.BaseStack.property.nestedStackParent"></a>
+
+```typescript
+public readonly nestedStackParent: Stack;
+```
+
+- *Type:* aws-cdk-lib.Stack
+
+If this is a nested stack, returns it's parent stack.
+
+---
+
+##### `nestedStackResource`<sup>Optional</sup> <a name="nestedStackResource" id="aws-ddk-core.BaseStack.property.nestedStackResource"></a>
+
+```typescript
+public readonly nestedStackResource: CfnResource;
+```
+
+- *Type:* aws-cdk-lib.CfnResource
+
+If this is a nested stack, this represents its `AWS::CloudFormation::Stack` resource.
+
+`undefined` for top-level (non-nested) stacks.
+
+---
+
+##### `terminationProtection`<sup>Optional</sup> <a name="terminationProtection" id="aws-ddk-core.BaseStack.property.terminationProtection"></a>
+
+```typescript
+public readonly terminationProtection: boolean;
+```
+
+- *Type:* boolean
+
+Whether termination protection is enabled for this stack.
+
+---
+
+
 ### CICDPipelineStack <a name="CICDPipelineStack" id="aws-ddk-core.CICDPipelineStack"></a>
 
 #### Initializers <a name="Initializers" id="aws-ddk-core.CICDPipelineStack.Initializer"></a>
@@ -468,6 +1328,7 @@ new CICDPipelineStack(scope: Construct, id: string, props: CICDPipelineStackProp
 | <code><a href="#aws-ddk-core.CICDPipelineStack.resolve">resolve</a></code> | Resolve a tokenized value in the context of the current stack. |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.splitArn">splitArn</a></code> | Splits the provided ARN into its components. |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.toJsonString">toJsonString</a></code> | Convert an object, potentially containing tokens, to a JSON string. |
+| <code><a href="#aws-ddk-core.CICDPipelineStack.addChecks">addChecks</a></code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.addCustomStage">addCustomStage</a></code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.addNotifications">addNotifications</a></code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.addSecurityLintStage">addSecurityLintStage</a></code> | *No description.* |
@@ -859,6 +1720,12 @@ Convert an object, potentially containing tokens, to a JSON string.
 
 ---
 
+##### `addChecks` <a name="addChecks" id="aws-ddk-core.CICDPipelineStack.addChecks"></a>
+
+```typescript
+public addChecks(): CICDPipelineStack
+```
+
 ##### `addCustomStage` <a name="addCustomStage" id="aws-ddk-core.CICDPipelineStack.addCustomStage"></a>
 
 ```typescript
@@ -1064,6 +1931,7 @@ The construct to start the search from.
 | <code><a href="#aws-ddk-core.CICDPipelineStack.property.nestedStackParent">nestedStackParent</a></code> | <code>aws-cdk-lib.Stack</code> | If this is a nested stack, returns it's parent stack. |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.property.nestedStackResource">nestedStackResource</a></code> | <code>aws-cdk-lib.CfnResource</code> | If this is a nested stack, this represents its `AWS::CloudFormation::Stack` resource. |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.property.terminationProtection">terminationProtection</a></code> | <code>boolean</code> | Whether termination protection is enabled for this stack. |
+| <code><a href="#aws-ddk-core.CICDPipelineStack.property.config">config</a></code> | <code><a href="#aws-ddk-core.Configurator">Configurator</a></code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.property.environmentId">environmentId</a></code> | <code>string</code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.property.pipelineId">pipelineId</a></code> | <code>string</code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStack.property.pipelineName">pipelineName</a></code> | <code>string</code> | *No description.* |
@@ -1405,6 +2273,16 @@ Whether termination protection is enabled for this stack.
 
 ---
 
+##### `config`<sup>Required</sup> <a name="config" id="aws-ddk-core.CICDPipelineStack.property.config"></a>
+
+```typescript
+public readonly config: Configurator;
+```
+
+- *Type:* <a href="#aws-ddk-core.Configurator">Configurator</a>
+
+---
+
 ##### `environmentId`<sup>Optional</sup> <a name="environmentId" id="aws-ddk-core.CICDPipelineStack.property.environmentId"></a>
 
 ```typescript
@@ -4515,6 +5393,78 @@ public readonly workGroup: string;
 
 ---
 
+### BaseStackProps <a name="BaseStackProps" id="aws-ddk-core.BaseStackProps"></a>
+
+#### Initializer <a name="Initializer" id="aws-ddk-core.BaseStackProps.Initializer"></a>
+
+```typescript
+import { BaseStackProps } from 'aws-ddk-core'
+
+const baseStackProps: BaseStackProps = { ... }
+```
+
+#### Properties <a name="Properties" id="Properties"></a>
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| <code><a href="#aws-ddk-core.BaseStackProps.property.config">config</a></code> | <code>string \| object</code> | *No description.* |
+| <code><a href="#aws-ddk-core.BaseStackProps.property.environmentId">environmentId</a></code> | <code>string</code> | *No description.* |
+| <code><a href="#aws-ddk-core.BaseStackProps.property.permissionsBoundaryArn">permissionsBoundaryArn</a></code> | <code>string</code> | *No description.* |
+| <code><a href="#aws-ddk-core.BaseStackProps.property.synthesizer">synthesizer</a></code> | <code>aws-cdk-lib.IStackSynthesizer</code> | *No description.* |
+| <code><a href="#aws-ddk-core.BaseStackProps.property.terminationProtection">terminationProtection</a></code> | <code>boolean</code> | *No description.* |
+
+---
+
+##### `config`<sup>Optional</sup> <a name="config" id="aws-ddk-core.BaseStackProps.property.config"></a>
+
+```typescript
+public readonly config: string | object;
+```
+
+- *Type:* string | object
+
+---
+
+##### `environmentId`<sup>Optional</sup> <a name="environmentId" id="aws-ddk-core.BaseStackProps.property.environmentId"></a>
+
+```typescript
+public readonly environmentId: string;
+```
+
+- *Type:* string
+
+---
+
+##### `permissionsBoundaryArn`<sup>Optional</sup> <a name="permissionsBoundaryArn" id="aws-ddk-core.BaseStackProps.property.permissionsBoundaryArn"></a>
+
+```typescript
+public readonly permissionsBoundaryArn: string;
+```
+
+- *Type:* string
+
+---
+
+##### `synthesizer`<sup>Optional</sup> <a name="synthesizer" id="aws-ddk-core.BaseStackProps.property.synthesizer"></a>
+
+```typescript
+public readonly synthesizer: IStackSynthesizer;
+```
+
+- *Type:* aws-cdk-lib.IStackSynthesizer
+
+---
+
+##### `terminationProtection`<sup>Optional</sup> <a name="terminationProtection" id="aws-ddk-core.BaseStackProps.property.terminationProtection"></a>
+
+```typescript
+public readonly terminationProtection: boolean;
+```
+
+- *Type:* boolean
+
+---
+
 ### CICDPipelineStackProps <a name="CICDPipelineStackProps" id="aws-ddk-core.CICDPipelineStackProps"></a>
 
 #### Initializer <a name="Initializer" id="aws-ddk-core.CICDPipelineStackProps.Initializer"></a>
@@ -4538,6 +5488,8 @@ const cICDPipelineStackProps: CICDPipelineStackProps = { ... }
 | <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.synthesizer">synthesizer</a></code> | <code>aws-cdk-lib.IStackSynthesizer</code> | Synthesis method to use while deploying this stack. |
 | <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.tags">tags</a></code> | <code>{[ key: string ]: string}</code> | Stack tags that will be applied to all the taggable resources and the stack itself. |
 | <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.terminationProtection">terminationProtection</a></code> | <code>boolean</code> | Whether to enable termination protection for this stack. |
+| <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.config">config</a></code> | <code>object</code> | *No description.* |
+| <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.configPath">configPath</a></code> | <code>string</code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.environmentId">environmentId</a></code> | <code>string</code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.pipelineName">pipelineName</a></code> | <code>string</code> | *No description.* |
 
@@ -4736,6 +5688,26 @@ Whether to enable termination protection for this stack.
 
 ---
 
+##### `config`<sup>Optional</sup> <a name="config" id="aws-ddk-core.CICDPipelineStackProps.property.config"></a>
+
+```typescript
+public readonly config: object;
+```
+
+- *Type:* object
+
+---
+
+##### `configPath`<sup>Optional</sup> <a name="configPath" id="aws-ddk-core.CICDPipelineStackProps.property.configPath"></a>
+
+```typescript
+public readonly configPath: string;
+```
+
+- *Type:* string
+
+---
+
 ##### `environmentId`<sup>Optional</sup> <a name="environmentId" id="aws-ddk-core.CICDPipelineStackProps.property.environmentId"></a>
 
 ```typescript
@@ -5696,6 +6668,56 @@ public readonly jobRunArgs: {[ key: string ]: any};
 
 ---
 
+### PermissionsBoundaryProps <a name="PermissionsBoundaryProps" id="aws-ddk-core.PermissionsBoundaryProps"></a>
+
+#### Initializer <a name="Initializer" id="aws-ddk-core.PermissionsBoundaryProps.Initializer"></a>
+
+```typescript
+import { PermissionsBoundaryProps } from 'aws-ddk-core'
+
+const permissionsBoundaryProps: PermissionsBoundaryProps = { ... }
+```
+
+#### Properties <a name="Properties" id="Properties"></a>
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| <code><a href="#aws-ddk-core.PermissionsBoundaryProps.property.environmentId">environmentId</a></code> | <code>string</code> | *No description.* |
+| <code><a href="#aws-ddk-core.PermissionsBoundaryProps.property.prefix">prefix</a></code> | <code>string</code> | *No description.* |
+| <code><a href="#aws-ddk-core.PermissionsBoundaryProps.property.qualifier">qualifier</a></code> | <code>string</code> | *No description.* |
+
+---
+
+##### `environmentId`<sup>Optional</sup> <a name="environmentId" id="aws-ddk-core.PermissionsBoundaryProps.property.environmentId"></a>
+
+```typescript
+public readonly environmentId: string;
+```
+
+- *Type:* string
+
+---
+
+##### `prefix`<sup>Optional</sup> <a name="prefix" id="aws-ddk-core.PermissionsBoundaryProps.property.prefix"></a>
+
+```typescript
+public readonly prefix: string;
+```
+
+- *Type:* string
+
+---
+
+##### `qualifier`<sup>Optional</sup> <a name="qualifier" id="aws-ddk-core.PermissionsBoundaryProps.property.qualifier"></a>
+
+```typescript
+public readonly qualifier: string;
+```
+
+- *Type:* string
+
+---
+
 ### S3EventStageProps <a name="S3EventStageProps" id="aws-ddk-core.S3EventStageProps"></a>
 
 #### Initializer <a name="Initializer" id="aws-ddk-core.S3EventStageProps.Initializer"></a>
@@ -7109,5 +8131,112 @@ public readonly synthAction: CodeBuildStep;
 
 ---
 
+## Classes <a name="Classes" id="Classes"></a>
+
+### Configurator <a name="Configurator" id="aws-ddk-core.Configurator"></a>
+
+#### Initializers <a name="Initializers" id="aws-ddk-core.Configurator.Initializer"></a>
+
+```typescript
+import { Configurator } from 'aws-ddk-core'
+
+new Configurator(scope: Construct, config: string | object, environmentId?: string)
+```
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| <code><a href="#aws-ddk-core.Configurator.Initializer.parameter.scope">scope</a></code> | <code>constructs.Construct</code> | *No description.* |
+| <code><a href="#aws-ddk-core.Configurator.Initializer.parameter.config">config</a></code> | <code>string \| object</code> | *No description.* |
+| <code><a href="#aws-ddk-core.Configurator.Initializer.parameter.environmentId">environmentId</a></code> | <code>string</code> | *No description.* |
+
+---
+
+##### `scope`<sup>Required</sup> <a name="scope" id="aws-ddk-core.Configurator.Initializer.parameter.scope"></a>
+
+- *Type:* constructs.Construct
+
+---
+
+##### `config`<sup>Required</sup> <a name="config" id="aws-ddk-core.Configurator.Initializer.parameter.config"></a>
+
+- *Type:* string | object
+
+---
+
+##### `environmentId`<sup>Optional</sup> <a name="environmentId" id="aws-ddk-core.Configurator.Initializer.parameter.environmentId"></a>
+
+- *Type:* string
+
+---
+
+#### Methods <a name="Methods" id="Methods"></a>
+
+| **Name** | **Description** |
+| --- | --- |
+| <code><a href="#aws-ddk-core.Configurator.getEnvConfig">getEnvConfig</a></code> | *No description.* |
+| <code><a href="#aws-ddk-core.Configurator.tagConstruct">tagConstruct</a></code> | *No description.* |
+
+---
+
+##### `getEnvConfig` <a name="getEnvConfig" id="aws-ddk-core.Configurator.getEnvConfig"></a>
+
+```typescript
+public getEnvConfig(attribute: string): any
+```
+
+###### `attribute`<sup>Required</sup> <a name="attribute" id="aws-ddk-core.Configurator.getEnvConfig.parameter.attribute"></a>
+
+- *Type:* string
+
+---
+
+##### `tagConstruct` <a name="tagConstruct" id="aws-ddk-core.Configurator.tagConstruct"></a>
+
+```typescript
+public tagConstruct(scope: Construct, tags: {[ key: string ]: string}): void
+```
+
+###### `scope`<sup>Required</sup> <a name="scope" id="aws-ddk-core.Configurator.tagConstruct.parameter.scope"></a>
+
+- *Type:* constructs.Construct
+
+---
+
+###### `tags`<sup>Required</sup> <a name="tags" id="aws-ddk-core.Configurator.tagConstruct.parameter.tags"></a>
+
+- *Type:* {[ key: string ]: string}
+
+---
+
+
+#### Properties <a name="Properties" id="Properties"></a>
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| <code><a href="#aws-ddk-core.Configurator.property.config">config</a></code> | <code>any</code> | *No description.* |
+| <code><a href="#aws-ddk-core.Configurator.property.environmentId">environmentId</a></code> | <code>string</code> | *No description.* |
+
+---
+
+##### `config`<sup>Required</sup> <a name="config" id="aws-ddk-core.Configurator.property.config"></a>
+
+```typescript
+public readonly config: any;
+```
+
+- *Type:* any
+
+---
+
+##### `environmentId`<sup>Optional</sup> <a name="environmentId" id="aws-ddk-core.Configurator.property.environmentId"></a>
+
+```typescript
+public readonly environmentId: string;
+```
+
+- *Type:* string
+
+---
+
 
 
diff --git a/package-lock.json b/package-lock.json
index 1138f33c..fa32ede6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,12 +8,15 @@
       "name": "aws-ddk-core",
       "version": "0.0.0",
       "bundleDependencies": [
-        "deepmerge"
+        "deepmerge",
+        "ts-node",
+        "yaml"
       ],
       "license": "Apache-2.0",
       "dependencies": {
-        "aws-cdk": "^2.62.2",
-        "deepmerge": "4.0.0"
+        "deepmerge": "4.0.0",
+        "ts-node": "^10.9.1",
+        "yaml": "^2.2.1"
       },
       "devDependencies": {
         "@aws-cdk/aws-glue-alpha": "2.62.2-alpha.0",
@@ -38,10 +41,9 @@
         "jsii-diff": "^1.73.0",
         "jsii-docgen": "^7.0.216",
         "jsii-pacmak": "^1.73.0",
-        "json-schema": "^0.4.0",
         "npm-check-updates": "^16",
         "prettier": "^2.8.3",
-        "projen": "^0.67.19",
+        "projen": "^0.67.64",
         "ts-jest": "^27",
         "typescript": "^4.9.4"
       },
@@ -716,6 +718,28 @@
       "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
       "dev": true
     },
+    "node_modules/@cspotcode/source-map-support": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+      "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+      "inBundle": true,
+      "dependencies": {
+        "@jridgewell/trace-mapping": "0.3.9"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.9",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+      "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+      "inBundle": true,
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.0.3",
+        "@jridgewell/sourcemap-codec": "^1.4.10"
+      }
+    },
     "node_modules/@eslint/eslintrc": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz",
@@ -1143,7 +1167,7 @@
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
       "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
-      "dev": true,
+      "inBundle": true,
       "engines": {
         "node": ">=6.0.0"
       }
@@ -1161,7 +1185,7 @@
       "version": "1.4.14",
       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
       "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
-      "dev": true
+      "inBundle": true
     },
     "node_modules/@jridgewell/trace-mapping": {
       "version": "0.3.17",
@@ -1504,6 +1528,30 @@
         "node": ">= 6"
       }
     },
+    "node_modules/@tsconfig/node10": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
+      "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
+      "inBundle": true
+    },
+    "node_modules/@tsconfig/node12": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+      "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+      "inBundle": true
+    },
+    "node_modules/@tsconfig/node14": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+      "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+      "inBundle": true
+    },
+    "node_modules/@tsconfig/node16": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
+      "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
+      "inBundle": true
+    },
     "node_modules/@types/babel__core": {
       "version": "7.20.0",
       "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz",
@@ -1626,7 +1674,7 @@
       "version": "14.18.36",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.36.tgz",
       "integrity": "sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==",
-      "dev": true
+      "inBundle": true
     },
     "node_modules/@types/prettier": {
       "version": "2.7.2",
@@ -1873,7 +1921,7 @@
       "version": "8.8.2",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
       "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
-      "dev": true,
+      "inBundle": true,
       "bin": {
         "acorn": "bin/acorn"
       },
@@ -2068,6 +2116,12 @@
         "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
       }
     },
+    "node_modules/arg": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+      "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+      "inBundle": true
+    },
     "node_modules/argparse": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -2156,20 +2210,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/aws-cdk": {
-      "version": "2.62.2",
-      "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.62.2.tgz",
-      "integrity": "sha512-fACUQEdc43AfiqKKXUZLuHB2VADLU965hl50Fn6BhMemFmUsc+iS/GFfOwvfCUO3/iaRtsn/gk+dQ3zZ+snKRw==",
-      "bin": {
-        "cdk": "bin/cdk"
-      },
-      "engines": {
-        "node": ">= 14.15.0"
-      },
-      "optionalDependencies": {
-        "fsevents": "2.3.2"
-      }
-    },
     "node_modules/aws-cdk-lib": {
       "version": "2.62.2",
       "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.62.2.tgz",
@@ -2791,14 +2831,14 @@
       }
     },
     "node_modules/cacheable-request": {
-      "version": "10.2.5",
-      "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.5.tgz",
-      "integrity": "sha512-5RwYYCfzjNPsyJxb/QpaM0bfzx+kw5/YpDhZPm9oMIDntHFQ9YXeyV47ZvzlTE0XrrrbyO2UITJH4GF9eRLdXQ==",
+      "version": "10.2.7",
+      "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.7.tgz",
+      "integrity": "sha512-I4SA6mKgDxcxVbSt/UmIkb9Ny8qSkg6ReBHtAAXnZHk7KOSx5g3DTiAOaYzcHCs6oOdHn+bip9T48E6tMvK9hw==",
       "dev": true,
       "dependencies": {
         "@types/http-cache-semantics": "^4.0.1",
         "get-stream": "^6.0.1",
-        "http-cache-semantics": "^4.1.0",
+        "http-cache-semantics": "^4.1.1",
         "keyv": "^4.5.2",
         "mimic-response": "^4.0.0",
         "normalize-url": "^8.0.0",
@@ -3168,6 +3208,12 @@
       "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
       "dev": true
     },
+    "node_modules/create-require": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+      "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+      "inBundle": true
+    },
     "node_modules/cross-spawn": {
       "version": "7.0.3",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -3424,6 +3470,15 @@
         "node": ">=8"
       }
     },
+    "node_modules/diff": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+      "inBundle": true,
+      "engines": {
+        "node": ">=0.3.1"
+      }
+    },
     "node_modules/diff-sequences": {
       "version": "27.5.1",
       "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz",
@@ -4448,6 +4503,7 @@
       "version": "2.3.2",
       "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
       "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+      "dev": true,
       "hasInstallScript": true,
       "optional": true,
       "os": [
@@ -6525,12 +6581,6 @@
         "jju": "^1.1.0"
       }
     },
-    "node_modules/json-schema": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
-      "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
-      "dev": true
-    },
     "node_modules/json-schema-traverse": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -6741,7 +6791,7 @@
       "version": "1.3.6",
       "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
       "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
-      "dev": true
+      "inBundle": true
     },
     "node_modules/make-fetch-happen": {
       "version": "10.2.1",
@@ -8229,9 +8279,9 @@
       }
     },
     "node_modules/projen": {
-      "version": "0.67.19",
-      "resolved": "https://registry.npmjs.org/projen/-/projen-0.67.19.tgz",
-      "integrity": "sha512-qRfFwzZlGjSuaELUNw1+WKDA/VT0Ee7J2hJnSEAIvNYkNH+ktRkkOql6IkIYrnqa8JDwIu+PgZD+Tei5Gus6qg==",
+      "version": "0.67.64",
+      "resolved": "https://registry.npmjs.org/projen/-/projen-0.67.64.tgz",
+      "integrity": "sha512-hUJkMiS+6Cae7qyoAxWwm24EkXqpqZj5Kdkd7dkW9dR4Og7xSwJaex+6MmW6dDeLiSSFKReNhj0AoSFEJcoEEw==",
       "bundleDependencies": [
         "@iarna/toml",
         "case",
@@ -8502,6 +8552,7 @@
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
       "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
       "dev": true,
+      "inBundle": true,
       "license": "MIT"
     },
     "node_modules/projen/node_modules/conventional-changelog-config-spec": {
@@ -8627,21 +8678,8 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
-    "node_modules/projen/node_modules/glob/node_modules/brace-expansion": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
-      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
-      "dev": true,
-      "inBundle": true,
-      "license": "MIT",
-      "dependencies": {
-        "balanced-match": "^1.0.0"
-      }
-    },
     "node_modules/projen/node_modules/glob/node_modules/minimatch": {
       "version": "5.1.6",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
-      "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
       "dev": true,
       "inBundle": true,
       "license": "ISC",
@@ -8778,23 +8816,31 @@
       }
     },
     "node_modules/projen/node_modules/minimatch": {
-      "version": "5.1.6",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
-      "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
       "dev": true,
       "inBundle": true,
       "license": "ISC",
       "dependencies": {
-        "brace-expansion": "^2.0.1"
+        "brace-expansion": "^1.1.7"
       },
       "engines": {
-        "node": ">=10"
+        "node": "*"
+      }
+    },
+    "node_modules/projen/node_modules/minimatch/node_modules/brace-expansion": {
+      "version": "1.1.11",
+      "dev": true,
+      "inBundle": true,
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
       }
     },
     "node_modules/projen/node_modules/minimist": {
-      "version": "1.2.7",
-      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
-      "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
+      "version": "1.2.8",
       "dev": true,
       "inBundle": true,
       "license": "MIT",
@@ -8905,8 +8951,6 @@
     },
     "node_modules/projen/node_modules/semver/node_modules/lru-cache": {
       "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
       "dev": true,
       "inBundle": true,
       "license": "ISC",
@@ -8938,8 +8982,6 @@
     },
     "node_modules/projen/node_modules/shelljs/node_modules/glob": {
       "version": "7.2.3",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
-      "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
       "dev": true,
       "inBundle": true,
       "license": "ISC",
@@ -9099,17 +9141,13 @@
       }
     },
     "node_modules/projen/node_modules/xmlbuilder2/node_modules/@types/node": {
-      "version": "18.11.18",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
-      "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==",
+      "version": "18.14.0",
       "dev": true,
       "inBundle": true,
       "license": "MIT"
     },
     "node_modules/projen/node_modules/xmlbuilder2/node_modules/js-yaml": {
       "version": "3.14.0",
-      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
-      "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
       "dev": true,
       "inBundle": true,
       "license": "MIT",
@@ -10489,6 +10527,58 @@
         }
       }
     },
+    "node_modules/ts-node": {
+      "version": "10.9.1",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
+      "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
+      "inBundle": true,
+      "dependencies": {
+        "@cspotcode/source-map-support": "^0.8.0",
+        "@tsconfig/node10": "^1.0.7",
+        "@tsconfig/node12": "^1.0.7",
+        "@tsconfig/node14": "^1.0.0",
+        "@tsconfig/node16": "^1.0.2",
+        "acorn": "^8.4.1",
+        "acorn-walk": "^8.1.1",
+        "arg": "^4.1.0",
+        "create-require": "^1.1.0",
+        "diff": "^4.0.1",
+        "make-error": "^1.1.1",
+        "v8-compile-cache-lib": "^3.0.1",
+        "yn": "3.1.1"
+      },
+      "bin": {
+        "ts-node": "dist/bin.js",
+        "ts-node-cwd": "dist/bin-cwd.js",
+        "ts-node-esm": "dist/bin-esm.js",
+        "ts-node-script": "dist/bin-script.js",
+        "ts-node-transpile-only": "dist/bin-transpile.js",
+        "ts-script": "dist/bin-script-deprecated.js"
+      },
+      "peerDependencies": {
+        "@swc/core": ">=1.2.50",
+        "@swc/wasm": ">=1.2.50",
+        "@types/node": "*",
+        "typescript": ">=2.7"
+      },
+      "peerDependenciesMeta": {
+        "@swc/core": {
+          "optional": true
+        },
+        "@swc/wasm": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/ts-node/node_modules/acorn-walk": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
+      "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
+      "inBundle": true,
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/tsconfig-paths": {
       "version": "3.14.1",
       "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
@@ -10609,7 +10699,7 @@
       "version": "4.9.4",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
       "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
-      "dev": true,
+      "inBundle": true,
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
@@ -10790,6 +10880,12 @@
         "uuid": "dist/bin/uuid"
       }
     },
+    "node_modules/v8-compile-cache-lib": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+      "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+      "inBundle": true
+    },
     "node_modules/v8-to-istanbul": {
       "version": "8.1.1",
       "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz",
@@ -11232,7 +11328,7 @@
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz",
       "integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==",
-      "dev": true,
+      "inBundle": true,
       "engines": {
         "node": ">= 14"
       }
@@ -11264,6 +11360,15 @@
         "node": ">=10"
       }
     },
+    "node_modules/yn": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+      "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+      "inBundle": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/yocto-queue": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/package.json b/package.json
index 59aa36fc..54a680b7 100644
--- a/package.json
+++ b/package.json
@@ -55,10 +55,9 @@
     "jsii-diff": "^1.73.0",
     "jsii-docgen": "^7.0.216",
     "jsii-pacmak": "^1.73.0",
-    "json-schema": "^0.4.0",
     "npm-check-updates": "^16",
     "prettier": "^2.8.3",
-    "projen": "^0.67.19",
+    "projen": "^0.67.64",
     "ts-jest": "^27",
     "typescript": "^4.9.4"
   },
@@ -72,11 +71,13 @@
   },
   "dependencies": {
     "deepmerge": "4.0.0",
-    "ts-node": "^10.9.1"
+    "ts-node": "^10.9.1",
+    "yaml": "^2.2.1"
   },
   "bundledDependencies": [
     "deepmerge",
-    "ts-node"
+    "ts-node",
+    "yaml"
   ],
   "keywords": [
     "cdk"
diff --git a/src/base/index.ts b/src/base/index.ts
new file mode 100644
index 00000000..dc9eefda
--- /dev/null
+++ b/src/base/index.ts
@@ -0,0 +1,1 @@
+export * from "./stack";
diff --git a/src/base/stack.ts b/src/base/stack.ts
new file mode 100644
index 00000000..7eebbd1e
--- /dev/null
+++ b/src/base/stack.ts
@@ -0,0 +1,107 @@
+import * as cdk from "aws-cdk-lib";
+import * as iam from "aws-cdk-lib/aws-iam";
+import { Construct } from "constructs";
+import { getStackSynthesizer } from "../config";
+
+export interface BaseStackProps {
+  readonly terminationProtection?: boolean | undefined;
+  readonly permissionsBoundaryArn?: string;
+  readonly synthesizer?: cdk.IStackSynthesizer;
+  readonly environmentId?: string;
+  readonly config?: string | object;
+}
+
+export class BaseStack extends cdk.Stack {
+  readonly terminationProtection?: boolean | undefined;
+
+  constructor(scope: Construct, id: string, props: BaseStackProps) {
+    const environmentId = props.environmentId ? props.environmentId : "dev";
+    const synthesizer = props.synthesizer
+      ? props.synthesizer
+      : getStackSynthesizer({ environmentId: environmentId, config: props.config });
+
+    super(scope, id, { synthesizer: synthesizer, terminationProtection: props.terminationProtection });
+
+    if (props.permissionsBoundaryArn) {
+      iam.PermissionsBoundary.of(scope).apply(
+        iam.ManagedPolicy.fromManagedPolicyArn(this, "Permissions Boundary", props.permissionsBoundaryArn),
+      );
+    }
+  }
+}
+
+export interface PermissionsBoundaryProps {
+  readonly environmentId?: string;
+  readonly prefix?: string;
+  readonly qualifier?: string;
+}
+
+export function createDefaultPermissionsBoundary(scope: Construct, id: string, props: PermissionsBoundaryProps) {
+  const prefix = props.prefix ?? "ddk";
+  const environmentId = props.environmentId ?? "dev";
+  const qualifier = props.environmentId ?? "hnb659fds";
+
+  const policyStatements = [
+    new iam.PolicyStatement({
+      effect: iam.Effect.DENY,
+      actions: ["s3:PutAccountPublicAccessBlock"],
+      resources: ["*"],
+    }),
+    new iam.PolicyStatement({
+      effect: iam.Effect.DENY,
+      actions: [
+        "iam:CreatePolicyVersion",
+        "iam:DeletePolicy",
+        "iam:DeletePolicyVersion",
+        "iam:SetDefaultPolicyVersion",
+      ],
+      resources: [
+        `arn:${cdk.Stack.of(scope).partition}:iam::${
+          cdk.Stack.of(scope).account
+        }:policy/${prefix}-${environmentId}-${qualifier}-permissions-boundary-${cdk.Stack.of(scope).account}-${
+          cdk.Stack.of(scope).region
+        }`,
+      ],
+    }),
+    new iam.PolicyStatement({
+      effect: iam.Effect.DENY,
+      actions: ["iam:DeleteRolePermissionsBoundary"],
+      resources: [`arn:${cdk.Stack.of(scope).partition}:iam::${cdk.Stack.of(scope).account}:role/*`],
+      conditions: {
+        "ForAnyValue:StringEquals": {
+          "iam:PermissionsBoundary": `arn:${cdk.Stack.of(scope).partition}:iam::${
+            cdk.Stack.of(scope).account
+          }:policy/${prefix}-${environmentId}-${qualifier}-permissions-boundary-${cdk.Stack.of(scope).account}-${
+            cdk.Stack.of(scope).region
+          }`,
+        },
+      },
+    }),
+    new iam.PolicyStatement({
+      effect: iam.Effect.DENY,
+      actions: ["iam:PutRolePermissionsBoundary"],
+      resources: [`arn:${cdk.Stack.of(scope).partition}:iam::${cdk.Stack.of(scope).account}:role/*`],
+      conditions: {
+        "ForAnyValue:StringNotEquals": {
+          "iam:PermissionsBoundary": `arn:${cdk.Stack.of(scope).partition}:iam::${
+            cdk.Stack.of(scope).account
+          }:policy/${prefix}-${environmentId}-${qualifier}-permissions-boundary-${cdk.Stack.of(scope).account}-${
+            cdk.Stack.of(scope).region
+          }`,
+        },
+      },
+    }),
+    new iam.PolicyStatement({
+      effect: iam.Effect.ALLOW,
+      actions: ["*"],
+      resources: ["*"],
+    }),
+  ];
+  return new iam.ManagedPolicy(scope, id, {
+    statements: policyStatements,
+    managedPolicyName: `${prefix}-${environmentId}-${qualifier}-permissions-boundary-${cdk.Stack.of(scope).account}-${
+      cdk.Stack.of(scope).region
+    }`,
+    description: "AWS-DDK: Deny dangerous actions that could escalate privilege or cause security incident",
+  });
+}
diff --git a/src/cicd/pipelines.ts b/src/cicd/pipelines.ts
index 6f4285a6..c6e2a4d7 100644
--- a/src/cicd/pipelines.ts
+++ b/src/cicd/pipelines.ts
@@ -7,6 +7,8 @@ import * as pipelines from "aws-cdk-lib/pipelines";
 import { Construct, IConstruct } from "constructs";
 import { getBanditAction, getCfnNagAction, getCodeCommitSourceAction, getSynthAction, getTestsAction } from "./actions";
 import { toTitleCase } from "./utils";
+import { BaseStack } from "../base";
+import { Configurator } from "../config";
 
 export interface SourceActionProps {
   readonly sourceAction?: pipelines.CodePipelineSource;
@@ -59,6 +61,8 @@ export interface AddCustomStageProps {
 export interface CICDPipelineStackProps extends cdk.StackProps {
   readonly environmentId?: string;
   readonly pipelineName?: string;
+  readonly configPath?: string;
+  readonly config?: object;
 }
 
 export interface AdditionalPipelineProps {
@@ -76,10 +80,11 @@ export interface AdditionalPipelineProps {
   readonly synthCodeBuildDefaults?: pipelines.CodeBuildOptions;
 }
 
-export class CICDPipelineStack extends cdk.Stack {
+export class CICDPipelineStack extends BaseStack {
   readonly environmentId?: string;
   readonly pipelineName?: string;
   readonly pipelineId?: string;
+  readonly config: Configurator;
   public notificationRule?: codestarnotifications.NotificationRule;
   public pipeline?: pipelines.CodePipeline;
   public pipelineKey?: IConstruct;
@@ -92,6 +97,8 @@ export class CICDPipelineStack extends cdk.Stack {
     this.environmentId = props.environmentId;
     this.pipelineName = props.pipelineName;
     this.pipelineId = id;
+    const config = props.configPath ?? props.config ?? "./ddk.json";
+    this.config = new Configurator(this, config, this.environmentId);
   }
 
   addSourceAction(props: SourceActionProps) {
@@ -163,7 +170,7 @@ export class CICDPipelineStack extends cdk.Stack {
     if (this.pipeline === undefined) {
       throw new Error("`.buildPipeline()` needs to be called first before adding application stages to the pipeline.");
     }
-    var manualApprovals = props.manualApprovals ?? false;
+    const manualApprovals = props.manualApprovals ?? this.config.getEnvConfig("manual_approvals") ?? false;
 
     if (manualApprovals) {
       this.pipeline?.addStage(props.stage, {
@@ -195,7 +202,7 @@ export class CICDPipelineStack extends cdk.Stack {
     if (this.pipeline === undefined) {
       throw new Error("`.buildPipeline()` needs to be called first before adding application stages to the pipeline.");
     }
-    const manualApprovals = props.manualApprovals ?? false; // || this._config.get_env_config(stage_id).get('manual_approvals');
+    const manualApprovals = props.manualApprovals ?? this.config.getEnvConfig("manual_approvals") ?? false;
 
     var wave = new pipelines.Wave(props.stageId);
     if (manualApprovals) {
@@ -286,38 +293,40 @@ export class CICDPipelineStack extends cdk.Stack {
     CICD pipeline
     */
     if (this.pipeline === undefined) {
-      throw new Error("`.buildPipeline()` needs to be called first before adding application stages to the pipeline.");
+      throw new Error("`.buildPipeline()` needs to be called first before adding notifications to the pipeline.");
     }
 
+    const topic =
+      this.environmentId && this.config.getEnvConfig("notifications_topic_arn")
+        ? sns.Topic.fromTopicArn(
+            this,
+            "ExecutionFailedNotifications",
+            this.config.getEnvConfig("notifications_topic_arn"),
+          )
+        : new sns.Topic(this, "ExecutionFailedNotifications");
     this.notificationRule =
       props.notificationRule ??
       new codestarnotifications.NotificationRule(this, "Notification", {
         detailType: codestarnotifications.DetailType.BASIC,
         events: ["codepipeline-pipeline-pipeline-execution-failed"],
         source: this.pipeline?.pipeline,
-        targets: [new sns.Topic(this, "ExecutionFailedNotifications")], // Implement config defined topic later on
+        targets: [topic],
       });
     return this;
   }
 
-  // addChecks() {
-  //   /*
-  //   Add checks to the pipeline (e.g. linting, security, tests...).
-  //     Returns
-  //   -------
-  //   pipeline : CICDPipelineStack
-  //   CICD pipeline
-  //   */
-  //   if (this._config.get_env_config(this.environment_id).get('execute_security_lint')) {
-  //     this.add_security_lint_stage();
-  //   }
-
-  //   if (this._config.get_env_config(this.environment_id).get('execute_tests')) {
-  //     this.add_test_stage();
-  //   }
-
-  //   return this;
-  // }
+  addChecks() {
+    /*
+    Add checks to the pipeline (e.g. linting, security, tests...).
+      Returns
+    -------
+    pipeline : CICDPipelineStack
+    CICD pipeline
+    */
+    this.addSecurityLintStage({});
+    this.addTestStage({});
+    return this;
+  }
 
   addCustomStage(props: AddCustomStageProps) {
     /*
diff --git a/src/config/configurator.ts b/src/config/configurator.ts
new file mode 100644
index 00000000..a8edb687
--- /dev/null
+++ b/src/config/configurator.ts
@@ -0,0 +1,183 @@
+import { readFileSync, existsSync } from "fs";
+import * as cdk from "aws-cdk-lib";
+import * as constructs from "constructs";
+import { parse } from "yaml";
+
+const ddkBootstrapConfigKey = "bootstrap";
+const bootstrapPrefix = "ddk";
+const bootstrapQualifier = "hnb659fds";
+const accountId = process.env.CDK_DEFAULT_ACCOUNT;
+const region = process.env.CDK_DEFAULT_REGION;
+
+function readJson(path: string): object {
+  try {
+    const rawdata = readFileSync(path, "utf-8");
+    return JSON.parse(rawdata);
+  } catch (err) {
+    return {};
+  }
+}
+function readYaml(path: string): object {
+  try {
+    const rawdata = readFileSync(path, "utf-8");
+    return parse(rawdata);
+  } catch (err) {
+    return {};
+  }
+}
+function readConfigFile(path: string): object {
+  if (path.includes(".json")) {
+    return readJson(path);
+  } else if (path.includes(".yaml") || path.includes(".yml")) {
+    return readYaml(path);
+  } else {
+    throw TypeError("Config file must be in YAML or JSON format");
+  }
+}
+interface getConfigProps {
+  readonly config?: string | object;
+}
+export function getConfig(props: getConfigProps): any {
+  const configData: any = props.config
+    ? typeof props.config == "object"
+      ? props.config
+      : readConfigFile(props.config)
+    : existsSync("./ddk.json")
+    ? readConfigFile("./ddk.json")
+    : undefined;
+  return configData;
+}
+
+interface getEnvConfigProps {
+  readonly config?: string | object;
+  readonly environmentId: string;
+}
+export function getEnvConfig(props: getEnvConfigProps): any {
+  const config = getConfig({ config: props.config });
+  return config[props.environmentId];
+}
+
+interface getStackSynthesizerProps {
+  readonly config?: string | object;
+  readonly environmentId: string;
+}
+export function getStackSynthesizer(props: getStackSynthesizerProps): cdk.IStackSynthesizer {
+  const configData = getConfig({ config: props.config });
+
+  const bootstrapConfig: any = configData
+    ? configData.ddkBootstrapConfigKey
+      ? configData.ddkBootstrapConfigKey
+      : configData.environments[props.environmentId][ddkBootstrapConfigKey]
+    : undefined;
+  if (bootstrapConfig) {
+    const qualifier = bootstrapConfig.qualifier ? bootstrapConfig.qualifier : bootstrapQualifier;
+    const prefix = bootstrapConfig.prefix ? bootstrapConfig.prefix : bootstrapPrefix;
+    return new cdk.DefaultStackSynthesizer({
+      qualifier: qualifier,
+      fileAssetsBucketName: bootstrapConfig.file_assets_bucket_name
+        ? bootstrapConfig.file_assets_bucket_name
+        : undefined,
+      bootstrapStackVersionSsmParameter: bootstrapConfig.stack_version_ssm_parameter
+        ? bootstrapConfig.stack_version_ssm_parameter
+        : undefined,
+      deployRoleArn: bootstrapConfig.deploy_role
+        ? bootstrapConfig.deploy_role
+        : `arn:aws:iam::${accountId}:role/${prefix}-${props.environmentId}-${qualifier}-deploy-${accountId}-${region}`,
+      fileAssetPublishingRoleArn: bootstrapConfig.file_publish_role
+        ? bootstrapConfig.file_publish_role
+        : `arn:aws:iam::${accountId}:role/${prefix}-${props.environmentId}-${qualifier}-file-publish-${accountId}-${region}`,
+      cloudFormationExecutionRole: bootstrapConfig.cfn_execution_role
+        ? bootstrapConfig.cfn_execution_role
+        : `arn:aws:iam::${accountId}:role/${prefix}-${props.environmentId}-${qualifier}-cfn-exec-${accountId}-${region}`,
+      lookupRoleArn: bootstrapConfig.lookup_role
+        ? bootstrapConfig.lookup_role
+        : `arn:aws:iam::${accountId}:role/${prefix}-${props.environmentId}-${qualifier}-lookup-${accountId}-${region}`,
+    });
+  } else {
+    return new cdk.DefaultStackSynthesizer();
+  }
+}
+
+interface ConfiguratorAspectProps {
+  readonly propertyName: string;
+  readonly propertyValue: any;
+  readonly resourceType?: string;
+  readonly resourceId?: string;
+}
+
+class ConfiguratorAspect implements cdk.IAspect {
+  private readonly propertyName: string;
+  private readonly propertyValue: any;
+  private readonly resourceType: any;
+  private readonly resourceId: any;
+
+  constructor(props: ConfiguratorAspectProps) {
+    this.resourceType = props.resourceType;
+    this.propertyName = props.propertyName;
+    this.propertyValue = props.propertyValue;
+    this.resourceId = props.resourceId;
+  }
+  public visit(node: constructs.IConstruct): void {
+    if (this.resourceType && cdk.CfnResource.isCfnResource(node) && node.cfnResourceType == this.resourceType) {
+      node.addPropertyOverride(this.propertyName, this.propertyValue);
+    }
+
+    if (this.resourceId && cdk.CfnResource.isCfnResource(node)) {
+      node.addPropertyOverride(this.propertyName, this.propertyValue);
+    }
+  }
+}
+
+export class Configurator {
+  public readonly config: any;
+  public readonly environmentId?: string;
+  constructor(scope: constructs.Construct, config: string | object, environmentId?: string) {
+    this.config = getConfig({ config: config });
+    this.environmentId = environmentId;
+
+    if (environmentId && this.config.environments) {
+      // Tags
+      const tags = { ...this.config.tags, ...this.config.environments[environmentId].tags };
+      this.tagConstruct(scope, tags);
+
+      // Environment Based
+      const environment = this.config.environments[environmentId];
+      for (const attribute in environment) {
+        if (attribute == "resources") {
+          for (const resourceIdentifier in environment.resources) {
+            const regexp = new RegExp("^AWS::.*::.*$");
+            var resourceIdentifierArgument;
+            if (regexp.test(resourceIdentifier)) {
+              resourceIdentifierArgument = {
+                resourceType: resourceIdentifier,
+              };
+            } else {
+              resourceIdentifierArgument = {
+                resourceId: resourceIdentifier,
+              };
+            }
+            for (const property in environment.resources[resourceIdentifier]) {
+              cdk.Aspects.of(scope).add(
+                new ConfiguratorAspect({
+                  propertyName: property,
+                  propertyValue: environment.resources[resourceIdentifier][property],
+                  ...resourceIdentifierArgument,
+                }),
+              );
+            }
+          }
+        }
+      }
+    }
+  }
+  tagConstruct(scope: constructs.Construct, tags: { [key: string]: string }): void {
+    if (tags) {
+      Object.entries(tags).forEach(([key, value]) => cdk.Tags.of(scope).add(key, value));
+    }
+  }
+  getEnvConfig(attribute: string): any {
+    return this.environmentId && this.config[this.environmentId] && this.config[this.environmentId][attribute]
+      ? this.config[this.environmentId][attribute]
+      : undefined;
+  }
+}
diff --git a/src/config/index.ts b/src/config/index.ts
new file mode 100644
index 00000000..2a508ef5
--- /dev/null
+++ b/src/config/index.ts
@@ -0,0 +1,1 @@
+export * from "./configurator";
diff --git a/src/index.ts b/src/index.ts
index 863a1adf..75634336 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,3 +1,5 @@
+export * from "./base";
 export * from "./cicd";
+export * from "./config";
 export * from "./pipelines";
 export * from "./stages";
diff --git a/tsconfig.dev.json b/tsconfig.dev.json
index 2e29dbaa..3bd36716 100644
--- a/tsconfig.dev.json
+++ b/tsconfig.dev.json
@@ -1,3 +1,4 @@
+// ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen".
 {
   "compilerOptions": {
     "alwaysStrict": true,
@@ -31,6 +32,5 @@
   ],
   "exclude": [
     "node_modules"
-  ],
-  "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"."
+  ]
 }
diff --git a/yarn.lock b/yarn.lock
index c67e5cf5..02fe9de6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -333,7 +333,7 @@
 
 "@cspotcode/source-map-support@^0.8.0":
   version "0.8.1"
-  resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
+  resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz"
   integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
   dependencies:
     "@jridgewell/trace-mapping" "0.3.9"
@@ -601,7 +601,7 @@
 
 "@jridgewell/trace-mapping@0.3.9":
   version "0.3.9"
-  resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
+  resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz"
   integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
   dependencies:
     "@jridgewell/resolve-uri" "^3.0.3"
@@ -814,22 +814,22 @@
 
 "@tsconfig/node10@^1.0.7":
   version "1.0.9"
-  resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
+  resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz"
   integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==
 
 "@tsconfig/node12@^1.0.7":
   version "1.0.11"
-  resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d"
+  resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz"
   integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==
 
 "@tsconfig/node14@^1.0.0":
   version "1.0.3"
-  resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1"
+  resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz"
   integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==
 
 "@tsconfig/node16@^1.0.2":
   version "1.0.3"
-  resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e"
+  resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz"
   integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==
 
 "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14":
@@ -1077,7 +1077,7 @@ acorn-walk@^7.1.1:
 
 acorn-walk@^8.1.1:
   version "8.2.0"
-  resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
+  resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz"
   integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
 
 acorn@^7.1.1:
@@ -1205,7 +1205,7 @@ are-we-there-yet@^3.0.0:
 
 arg@^4.1.0:
   version "4.1.3"
-  resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
+  resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz"
   integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
 
 argparse@^1.0.7:
@@ -1486,13 +1486,13 @@ cacheable-lookup@^7.0.0:
   integrity sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==
 
 cacheable-request@^10.2.1:
-  version "10.2.5"
-  resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.5.tgz"
-  integrity sha512-5RwYYCfzjNPsyJxb/QpaM0bfzx+kw5/YpDhZPm9oMIDntHFQ9YXeyV47ZvzlTE0XrrrbyO2UITJH4GF9eRLdXQ==
+  version "10.2.7"
+  resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.7.tgz"
+  integrity sha512-I4SA6mKgDxcxVbSt/UmIkb9Ny8qSkg6ReBHtAAXnZHk7KOSx5g3DTiAOaYzcHCs6oOdHn+bip9T48E6tMvK9hw==
   dependencies:
     "@types/http-cache-semantics" "^4.0.1"
     get-stream "^6.0.1"
-    http-cache-semantics "^4.1.0"
+    http-cache-semantics "^4.1.1"
     keyv "^4.5.2"
     mimic-response "^4.0.0"
     normalize-url "^8.0.0"
@@ -1746,7 +1746,7 @@ core-util-is@^1.0.3:
 
 create-require@^1.1.0:
   version "1.1.1"
-  resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
+  resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz"
   integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
 
 cross-spawn@^7.0.2, cross-spawn@^7.0.3:
@@ -1907,7 +1907,7 @@ diff-sequences@^27.5.1:
 
 diff@^4.0.1:
   version "4.0.2"
-  resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
+  resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz"
   integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
 
 dir-glob@^3.0.1:
@@ -2802,7 +2802,7 @@ html-escaper@^2.0.0:
   resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz"
   integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
 
-http-cache-semantics@^4.1.0:
+http-cache-semantics@^4.1.0, http-cache-semantics@^4.1.1:
   version "4.1.1"
   resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz"
   integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==
@@ -3642,7 +3642,7 @@ js-tokens@^4.0.0:
 
 js-yaml@3.14.0:
   version "3.14.0"
-  resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
   integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
   dependencies:
     argparse "^1.0.7"
@@ -3827,11 +3827,6 @@ json-schema-traverse@^1.0.0:
   resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz"
   integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
 
-json-schema@^0.4.0:
-  version "0.4.0"
-  resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz"
-  integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==
-
 json-stable-stringify-without-jsonify@^1.0.1:
   version "1.0.1"
   resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz"
@@ -4121,11 +4116,16 @@ minimatch@^5.0.1, minimatch@^5.1.2:
   dependencies:
     brace-expansion "^2.0.1"
 
-minimist@>=1.2.2, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6:
+minimist@>=1.2.2, minimist@^1.2.0, minimist@^1.2.6:
   version "1.2.7"
   resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz"
   integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
 
+minimist@^1.2.3:
+  version "1.2.8"
+  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
+  integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
+
 minipass-collect@^1.0.2:
   version "1.0.2"
   resolved "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz"
@@ -4674,10 +4674,10 @@ progress@^2.0.3:
   resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz"
   integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
 
-projen@^0.67.19:
-  version "0.67.19"
-  resolved "https://registry.npmjs.org/projen/-/projen-0.67.19.tgz"
-  integrity sha512-qRfFwzZlGjSuaELUNw1+WKDA/VT0Ee7J2hJnSEAIvNYkNH+ktRkkOql6IkIYrnqa8JDwIu+PgZD+Tei5Gus6qg==
+projen@^0.67.64:
+  version "0.67.64"
+  resolved "https://registry.npmjs.org/projen/-/projen-0.67.64.tgz"
+  integrity sha512-hUJkMiS+6Cae7qyoAxWwm24EkXqpqZj5Kdkd7dkW9dR4Og7xSwJaex+6MmW6dDeLiSSFKReNhj0AoSFEJcoEEw==
   dependencies:
     "@iarna/toml" "^2.2.5"
     case "^1.6.3"
@@ -5436,7 +5436,7 @@ ts-jest@^27:
 
 ts-node@^10.9.1:
   version "10.9.1"
-  resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b"
+  resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz"
   integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==
   dependencies:
     "@cspotcode/source-map-support" "^0.8.0"
@@ -5665,7 +5665,7 @@ uuid@^8.3.2:
 
 v8-compile-cache-lib@^3.0.1:
   version "3.0.1"
-  resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
+  resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz"
   integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
 
 v8-to-istanbul@^8.1.0:
@@ -5904,7 +5904,7 @@ yaml@2.0.0:
   resolved "https://registry.npmjs.org/yaml/-/yaml-2.0.0.tgz"
   integrity sha512-JbfdlHKGP2Ik9IHylzWlGd4pPK++EU46/IxMykphS2ZKw7a7h+dHNmcXObLgpRDriBY+rpWslldikckX8oruWQ==
 
-yaml@^2.2.0:
+yaml@^2.2.0, yaml@^2.2.1:
   version "2.2.1"
   resolved "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz"
   integrity sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==
@@ -5929,7 +5929,7 @@ yargs@^16.2.0:
 
 yn@3.1.1:
   version "3.1.1"
-  resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
+  resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz"
   integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
 
 yocto-queue@^0.1.0:


Test patch:
diff --git a/test/base-stack.test.ts b/test/base-stack.test.ts
new file mode 100644
index 00000000..97d67de8
--- /dev/null
+++ b/test/base-stack.test.ts
@@ -0,0 +1,391 @@
+import { assert } from "console";
+import path from "path";
+import * as cdk from "aws-cdk-lib";
+import { Template } from "aws-cdk-lib/assertions";
+import * as lambda from "aws-cdk-lib/aws-lambda";
+
+import { BaseStack, createDefaultPermissionsBoundary, SqsToLambdaStage } from "../src";
+
+test("Base Stack No Bootstrap Config", () => {
+  const sampleConfig = {
+    environments: {
+      dev: {},
+    },
+  };
+  const app = new cdk.App();
+  const stack = new BaseStack(app, "my-stack", { config: sampleConfig });
+
+  new SqsToLambdaStage(stack, "Stage", {
+    lambdaFunctionProps: {
+      code: lambda.Code.fromAsset(path.join(__dirname, "/../src/")),
+      handler: "commons.handlers.lambda_handler",
+      memorySize: 512,
+      runtime: lambda.Runtime.PYTHON_3_9,
+    },
+  });
+
+  const expectedValues = {
+    qualifier: "hnb659fds",
+    bucketName: "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
+    repositoryName: "cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}",
+    _deployRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
+    _cloudFormationExecutionRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
+    fileAssetPublishingRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}",
+    imageAssetPublishingRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}",
+    lookupRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}",
+  };
+  for (const attribute in expectedValues) {
+    assert(
+      stack.synthesizer[attribute as keyof typeof stack.synthesizer] ===
+        expectedValues[attribute as keyof typeof expectedValues],
+    );
+  }
+});
+
+test("Base Stack Global Bootstrap Minimal Config", () => {
+  const sampleConfig = {
+    bootstrap: {
+      qualifier: "abcdefgh",
+    },
+    environments: {
+      dev: {},
+    },
+  };
+  const app = new cdk.App();
+  const stack = new BaseStack(app, "my-stack", { config: sampleConfig });
+
+  new SqsToLambdaStage(stack, "Stage", {
+    lambdaFunctionProps: {
+      code: lambda.Code.fromAsset(path.join(__dirname, "/../src/")),
+      handler: "commons.handlers.lambda_handler",
+      memorySize: 512,
+      runtime: lambda.Runtime.PYTHON_3_9,
+    },
+  });
+  const expectedValues = {
+    qualifier: "hnb659fds",
+    bucketName: "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
+    repositoryName: "cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}",
+    _deployRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
+    _cloudFormationExecutionRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
+    fileAssetPublishingRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}",
+    imageAssetPublishingRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}",
+    lookupRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}",
+    bucketPrefix: "",
+    dockerTagPrefix: "",
+    bootstrapStackVersionSsmParameter: "/cdk-bootstrap/hnb659fds/version",
+  };
+  for (const attribute in expectedValues) {
+    assert(
+      stack.synthesizer[attribute as keyof typeof stack.synthesizer] ===
+        expectedValues[attribute as keyof typeof expectedValues],
+    );
+  }
+});
+
+test("Base Stack Global Bootstrap Full Config", () => {
+  const sampleConfig = {
+    bootstrap: {
+      qualifier: "abcdefgh",
+      bucket_name: "ddk-abcdefgh-assets-000000000000-us-west-2",
+      repositoryName: "ddk-abcdefgh--container-assets-000000000000-us-west-2",
+      _deployRoleArn: "arn:aws:iam::000000000000:role/ddk-abcdefgh--deploy-role-000000000000-us-west-2",
+      _cloudFormationExecutionRoleArn:
+        "arn:aws:iam::000000000000:role/ddk-abcdefgh-cfn-exec-role-000000000000-us-west-2",
+      fileAssetPublishingRoleArn:
+        "arn:aws:iam::000000000000:role/ddk-abcdefgh-file-publishing-role-000000000000-us-west-2",
+      imageAssetPublishingRoleArn:
+        "arn:aws:iam::000000000000:role/ddk-abcdefgh-image-publishing-role-000000000000-us-west-2",
+      lookupRoleArn: "arn:aws:iam::000000000000:role/ddk-abcdefgh-lookup-role-000000000000-us-west-2",
+    },
+    environments: {
+      dev: {},
+    },
+  };
+  const app = new cdk.App();
+  const stack = new BaseStack(app, "my-stack", { config: sampleConfig });
+
+  new SqsToLambdaStage(stack, "Stage", {
+    lambdaFunctionProps: {
+      code: lambda.Code.fromAsset(path.join(__dirname, "/../src/")),
+      handler: "commons.handlers.lambda_handler",
+      memorySize: 512,
+      runtime: lambda.Runtime.PYTHON_3_9,
+    },
+  });
+  const expectedValues = {
+    qualifier: "hnb659fds",
+    bucketName: "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
+    repositoryName: "cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}",
+    _deployRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
+    _cloudFormationExecutionRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
+    fileAssetPublishingRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}",
+    imageAssetPublishingRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}",
+    lookupRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}",
+    bucketPrefix: "",
+    dockerTagPrefix: "",
+    bootstrapStackVersionSsmParameter: "/cdk-bootstrap/hnb659fds/version",
+  };
+  for (const attribute in expectedValues) {
+    assert(
+      stack.synthesizer[attribute as keyof typeof stack.synthesizer] ===
+        expectedValues[attribute as keyof typeof expectedValues],
+    );
+  }
+});
+
+test("Base Stack Environment Bootstrap Minimal Config", () => {
+  const sampleConfig = {
+    environments: {
+      dev: {
+        bootstrap: {
+          qualifier: "abcdefgh",
+        },
+      },
+    },
+  };
+  const app = new cdk.App();
+  const stack = new BaseStack(app, "my-stack", { config: sampleConfig, environmentId: "dev" });
+
+  new SqsToLambdaStage(stack, "Stage", {
+    lambdaFunctionProps: {
+      code: lambda.Code.fromAsset(path.join(__dirname, "/../src/")),
+      handler: "commons.handlers.lambda_handler",
+      memorySize: 512,
+      runtime: lambda.Runtime.PYTHON_3_9,
+    },
+  });
+  const expectedValues = {
+    qualifier: "abcdefgh",
+    bucketName: "cdk-abcdefgh-assets-${AWS::AccountId}-${AWS::Region}",
+    repositoryName: "cdk-abcdefgh-container-assets-${AWS::AccountId}-${AWS::Region}",
+    _deployRoleArn: "arn:aws:iam::undefined:role/ddk-dev-abcdefgh-deploy-undefined-undefined",
+    _cloudFormationExecutionRoleArn: "arn:aws:iam::undefined:role/ddk-dev-abcdefgh-cfn-exec-undefined-undefined",
+    fileAssetPublishingRoleArn: "arn:aws:iam::undefined:role/ddk-dev-abcdefgh-file-publish-undefined-undefined",
+    imageAssetPublishingRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-abcdefgh-image-publishing-role-${AWS::AccountId}-${AWS::Region}",
+    lookupRoleArn: "arn:aws:iam::undefined:role/ddk-dev-abcdefgh-lookup-undefined-undefined",
+    bucketPrefix: "",
+    dockerTagPrefix: "",
+    bootstrapStackVersionSsmParameter: "/cdk-bootstrap/abcdefgh/version",
+  };
+  for (const attribute in expectedValues) {
+    assert(
+      stack.synthesizer[attribute as keyof typeof stack.synthesizer] ===
+        expectedValues[attribute as keyof typeof expectedValues],
+    );
+  }
+});
+
+test("Base Stack Environment Bootstrap Full Config", () => {
+  const sampleConfig = {
+    environments: {
+      dev: {
+        bootstrap: {
+          qualifier: "abcdefgh",
+          bucket_name: "ddk-abcdefgh-assets-000000000000-us-west-2",
+          repositoryName: "ddk-abcdefgh--container-assets-000000000000-us-west-2",
+          _deployRoleArn: "arn:aws:iam::000000000000:role/ddk-abcdefgh--deploy-role-000000000000-us-west-2",
+          _cloudFormationExecutionRoleArn:
+            "arn:aws:iam::000000000000:role/ddk-abcdefgh-cfn-exec-role-000000000000-us-west-2",
+          fileAssetPublishingRoleArn:
+            "arn:aws:iam::000000000000:role/ddk-abcdefgh-file-publishing-role-000000000000-us-west-2",
+          imageAssetPublishingRoleArn:
+            "arn:aws:iam::000000000000:role/ddk-abcdefgh-image-publishing-role-000000000000-us-west-2",
+          lookupRoleArn: "arn:aws:iam::000000000000:role/ddk-abcdefgh-lookup-role-000000000000-us-west-2",
+        },
+      },
+    },
+  };
+  const app = new cdk.App();
+  const stack = new BaseStack(app, "my-stack", { config: sampleConfig, environmentId: "dev" });
+
+  new SqsToLambdaStage(stack, "Stage", {
+    lambdaFunctionProps: {
+      code: lambda.Code.fromAsset(path.join(__dirname, "/../src/")),
+      handler: "commons.handlers.lambda_handler",
+      memorySize: 512,
+      runtime: lambda.Runtime.PYTHON_3_9,
+    },
+  });
+  const expectedValues = {
+    qualifier: "abcdefgh",
+    bucketName: "cdk-abcdefgh-assets-${AWS::AccountId}-${AWS::Region}",
+    repositoryName: "cdk-abcdefgh-container-assets-${AWS::AccountId}-${AWS::Region}",
+    _deployRoleArn: "arn:aws:iam::undefined:role/ddk-dev-abcdefgh-deploy-undefined-undefined",
+    _cloudFormationExecutionRoleArn: "arn:aws:iam::undefined:role/ddk-dev-abcdefgh-cfn-exec-undefined-undefined",
+    fileAssetPublishingRoleArn: "arn:aws:iam::undefined:role/ddk-dev-abcdefgh-file-publish-undefined-undefined",
+    imageAssetPublishingRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-abcdefgh-image-publishing-role-${AWS::AccountId}-${AWS::Region}",
+    lookupRoleArn: "arn:aws:iam::undefined:role/ddk-dev-abcdefgh-lookup-undefined-undefined",
+    bucketPrefix: "",
+    dockerTagPrefix: "",
+    bootstrapStackVersionSsmParameter: "/cdk-bootstrap/abcdefgh/version",
+  };
+  for (const attribute in expectedValues) {
+    assert(
+      stack.synthesizer[attribute as keyof typeof stack.synthesizer] ===
+        expectedValues[attribute as keyof typeof expectedValues],
+    );
+  }
+});
+
+test("Base Stack Bootstrap Empty Config", () => {
+  const sampleConfig = {
+    bootstrap: {},
+    environments: {
+      dev: {},
+    },
+  };
+  const app = new cdk.App();
+  const stack = new BaseStack(app, "my-stack", { config: sampleConfig });
+
+  new SqsToLambdaStage(stack, "Stage", {
+    lambdaFunctionProps: {
+      code: lambda.Code.fromAsset(path.join(__dirname, "/../src/")),
+      handler: "commons.handlers.lambda_handler",
+      memorySize: 512,
+      runtime: lambda.Runtime.PYTHON_3_9,
+    },
+  });
+  const expectedValues = {
+    qualifier: "hnb659fds",
+    bucketName: "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
+    repositoryName: "cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}",
+    _deployRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
+    _cloudFormationExecutionRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
+    fileAssetPublishingRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}",
+    imageAssetPublishingRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}",
+    lookupRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}",
+    bucketPrefix: "",
+    dockerTagPrefix: "",
+    bootstrapStackVersionSsmParameter: "/cdk-bootstrap/hnb659fds/version",
+  };
+  for (const attribute in expectedValues) {
+    assert(
+      stack.synthesizer[attribute as keyof typeof stack.synthesizer] ===
+        expectedValues[attribute as keyof typeof expectedValues],
+    );
+  }
+});
+
+test("Base Stack Permissions Boundary", () => {
+  const app = new cdk.App();
+
+  const stack = new BaseStack(app, "my-stack", {
+    environmentId: "dev",
+    permissionsBoundaryArn: "arn:aws:iam:000000000000:role/foobar",
+  });
+
+  new SqsToLambdaStage(stack, "Stage", {
+    lambdaFunctionProps: {
+      code: lambda.Code.fromAsset(path.join(__dirname, "/../src/")),
+      handler: "commons.handlers.lambda_handler",
+      memorySize: 512,
+      runtime: lambda.Runtime.PYTHON_3_9,
+    },
+  });
+});
+
+test("Test Permissions Boundary Creation and Usage in BaseStack", () => {
+  const app = new cdk.App();
+  const bootstrapStack = new cdk.Stack(app, "my-bootstrap-stack");
+  const permissionsBoundary = createDefaultPermissionsBoundary(bootstrapStack, "DDK Default Permissions Boundary", {});
+  const stack = new BaseStack(app, "my-stack", {
+    environmentId: "dev",
+    permissionsBoundaryArn: permissionsBoundary.managedPolicyArn,
+  });
+
+  new SqsToLambdaStage(stack, "Stage", {
+    lambdaFunctionProps: {
+      code: lambda.Code.fromAsset(path.join(__dirname, "/../src/")),
+      handler: "commons.handlers.lambda_handler",
+      memorySize: 512,
+      runtime: lambda.Runtime.PYTHON_3_9,
+    },
+  });
+  const bootstrapTemplate = Template.fromStack(bootstrapStack);
+  bootstrapTemplate.resourceCountIs("AWS::IAM::ManagedPolicy", 1);
+  const stackTemplate = Template.fromStack(stack);
+  stackTemplate.hasResourceProperties("AWS::IAM::Role", {
+    PermissionsBoundary: {
+      Ref: "DDKDefaultPermissionsBoundary6721F63A",
+    },
+  });
+});
+
+test("Permissions Boundary Creation Full", () => {
+  const app = new cdk.App();
+  const bootstrapStack = new cdk.Stack(app, "my-bootstrap-stack");
+  createDefaultPermissionsBoundary(bootstrapStack, "DDK Default Permissions Boundary", {
+    prefix: "custom",
+    environmentId: "stage",
+    qualifier: "abcdefgh",
+  });
+
+  const bootstrapTemplate = Template.fromStack(bootstrapStack);
+  bootstrapTemplate.resourceCountIs("AWS::IAM::ManagedPolicy", 1);
+});
+
+test("Bring Your Own Stack Synthesizer", () => {
+  const app = new cdk.App();
+
+  const stack = new BaseStack(app, "my-stack", {
+    environmentId: "dev",
+    permissionsBoundaryArn: "arn:aws:iam:000000000000:role/foobar",
+    synthesizer: new cdk.DefaultStackSynthesizer(),
+  });
+
+  new SqsToLambdaStage(stack, "Stage", {
+    lambdaFunctionProps: {
+      code: lambda.Code.fromAsset(path.join(__dirname, "/../src/")),
+      handler: "commons.handlers.lambda_handler",
+      memorySize: 512,
+      runtime: lambda.Runtime.PYTHON_3_9,
+    },
+  });
+  const expectedValues = {
+    qualifier: "hnb659fds",
+    bucketName: "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
+    repositoryName: "cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}",
+    _deployRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
+    _cloudFormationExecutionRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
+    fileAssetPublishingRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}",
+    imageAssetPublishingRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}",
+    lookupRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}",
+    bucketPrefix: "",
+    dockerTagPrefix: "",
+    bootstrapStackVersionSsmParameter: "/cdk-bootstrap/hnb659fds/version",
+  };
+  for (const attribute in expectedValues) {
+    assert(
+      stack.synthesizer[attribute as keyof typeof stack.synthesizer] ===
+        expectedValues[attribute as keyof typeof expectedValues],
+    );
+  }
+});
diff --git a/test/cicd-pipeline-stack.test.ts b/test/cicd-pipeline-stack.test.ts
index f9873040..53c4eb5e 100644
--- a/test/cicd-pipeline-stack.test.ts
+++ b/test/cicd-pipeline-stack.test.ts
@@ -347,6 +347,93 @@ test("Test Pipeline with Artifact Upload", () => {
   });
 });
 
+test("Manual Approvals Stage", () => {
+  const app = new cdk.App();
+  const stage = new cdk.Stage(app, "app-stage");
+  new cdk.Stack(stage, "app-stack");
+  const stack = new CICDPipelineStack(app, "dummy-pipeline", { environmentId: "dev", pipelineName: "dummy-pipeline" })
+    .addSourceAction({ repositoryName: "dummy-repository" })
+    .addSynthAction()
+    .buildPipeline()
+    .addStage({ stageId: "dev", stage: stage, manualApprovals: true })
+    .synth();
+  const template = Template.fromStack(stack);
+  template.hasResourceProperties("AWS::CodePipeline::Pipeline", {
+    Stages: Match.arrayWith([
+      Match.objectLike({
+        Name: "app-stage",
+        Actions: Match.arrayWith([
+          Match.objectLike({
+            Name: "PromoteToDev",
+            ActionTypeId: {
+              Category: "Approval",
+              Provider: "Manual",
+              Owner: "AWS",
+            },
+          }),
+        ]),
+      }),
+    ]),
+  });
+});
+
+test("Manual Approvals Wave", () => {
+  const app = new cdk.App();
+  const stage = new cdk.Stage(app, "app-stage");
+  new cdk.Stack(stage, "app-stack");
+  const stack = new CICDPipelineStack(app, "dummy-pipeline", { environmentId: "dev", pipelineName: "dummy-pipeline" })
+    .addSourceAction({ repositoryName: "dummy-repository" })
+    .addSynthAction()
+    .buildPipeline()
+    .addWave({ stageId: "dev", stages: [stage], manualApprovals: true })
+    .synth();
+  const template = Template.fromStack(stack);
+  template.hasResourceProperties("AWS::CodePipeline::Pipeline", {
+    Stages: Match.arrayWith([
+      Match.objectLike({
+        Name: "dev",
+        Actions: Match.arrayWith([
+          Match.objectLike({
+            Name: "PromoteToDev",
+            ActionTypeId: {
+              Category: "Approval",
+              Provider: "Manual",
+              Owner: "AWS",
+            },
+          }),
+        ]),
+      }),
+    ]),
+  });
+});
+
+test("Pipeline With Checks", () => {
+  const app = new cdk.App();
+  const stage = new cdk.Stage(app, "app-stage");
+  new cdk.Stack(stage, "app-stack");
+  const stack = new CICDPipelineStack(app, "dummy-pipeline", { environmentId: "dev", pipelineName: "dummy-pipeline" })
+    .addSourceAction({ repositoryName: "dummy-repository" })
+    .addSynthAction()
+    .buildPipeline()
+    .addChecks()
+    .synth();
+  const template = Template.fromStack(stack);
+  template.hasResourceProperties("AWS::CodePipeline::Pipeline", {
+    Stages: Match.arrayWith([
+      Match.objectLike({
+        Name: "SecurityLint",
+      }),
+    ]),
+  });
+  template.hasResourceProperties("AWS::CodePipeline::Pipeline", {
+    Stages: Match.arrayWith([
+      Match.objectLike({
+        Name: "Tests",
+      }),
+    ]),
+  });
+});
+
 test("Build Pipeline with no Synth Action", () => {
   const app = new cdk.App();
   const stack = () =>
@@ -393,6 +480,18 @@ test("Add stage without building pipeline", () => {
   );
 });
 
+test("Add notifications without building pipeline", () => {
+  const app = new cdk.App();
+  const stack = () =>
+    new CICDPipelineStack(app, "dummy-pipeline", { environmentId: "dev", pipelineName: "dummy-pipeline" })
+      .addSynthAction()
+      .addNotifications()
+      .synth();
+  expect(stack).toThrow(
+    Error("`.buildPipeline()` needs to be called first before adding notifications to the pipeline."),
+  );
+});
+
 test("Add notifications without building pipeline", () => {
   const app = new cdk.App();
   const stack = () =>
@@ -434,6 +533,42 @@ test("Error when running build pipeline before adding synth action", () => {
   expect(stack).toThrow(Error("Pipeline cannot be built without a synth action."));
 });
 
+test("Error: Adding stages before 'buildPipeline()'", () => {
+  const app = new cdk.App();
+  const appStage = new cdk.Stage(app, "app", { env: { account: "000000000000" } });
+  new cdk.Stack(appStage, "AppStack");
+
+  const stack = () =>
+    new CICDPipelineStack(app, "dummy-pipeline", {
+      pipelineName: "dummy-pipeline",
+    })
+      .addSourceAction({ repositoryName: "dummy-repository" })
+      .addSynthAction()
+      .addStage({ stageId: "app", stage: appStage })
+      .buildPipeline();
+  expect(stack).toThrow(
+    Error("`.buildPipeline()` needs to be called first before adding application stages to the pipeline."),
+  );
+});
+
+test("Error: Adding waves before 'buildPipeline()'", () => {
+  const app = new cdk.App();
+  const appStage = new cdk.Stage(app, "app", { env: { account: "000000000000" } });
+  new cdk.Stack(appStage, "AppStack");
+
+  const stack = () =>
+    new CICDPipelineStack(app, "dummy-pipeline", {
+      pipelineName: "dummy-pipeline",
+    })
+      .addSourceAction({ repositoryName: "dummy-repository" })
+      .addSynthAction()
+      .addWave({ stageId: "app", stages: [appStage] })
+      .buildPipeline();
+  expect(stack).toThrow(
+    Error("`.buildPipeline()` needs to be called first before adding application stages to the pipeline."),
+  );
+});
+
 test("Test Pipeline with Artifact Upload", () => {
   const app = new cdk.App();
   const stack = new CICDPipelineStack(app, "dummy-pipeline", { environmentId: "dev", pipelineName: "dummy-pipeline" })
diff --git a/test/config.test.ts b/test/config.test.ts
new file mode 100644
index 00000000..302ca2a0
--- /dev/null
+++ b/test/config.test.ts
@@ -0,0 +1,384 @@
+import { assert } from "console";
+import path from "path";
+import * as cdk from "aws-cdk-lib";
+import { Template } from "aws-cdk-lib/assertions";
+import * as lambda from "aws-cdk-lib/aws-lambda";
+import * as s3 from "aws-cdk-lib/aws-s3";
+import * as sqs from "aws-cdk-lib/aws-sqs";
+
+import {
+  Configurator,
+  CICDPipelineStack,
+  DataPipeline,
+  FirehoseToS3Stage,
+  SqsToLambdaStage,
+  getStackSynthesizer,
+  getEnvConfig,
+} from "../src";
+
+test("Config Simple Override", () => {
+  const sampleConfig = {
+    environments: {
+      dev: {
+        account: "222222222222",
+        region: "us-east-1",
+        resources: {
+          "AWS::Lambda::Function": {
+            MemorySize: 128,
+            Runtime: lambda.Runtime.PYTHON_3_8,
+          },
+        },
+      },
+    },
+  };
+  const stack = new cdk.Stack();
+
+  new SqsToLambdaStage(stack, "Stage", {
+    lambdaFunctionProps: {
+      code: lambda.Code.fromAsset(path.join(__dirname, "/../src/")),
+      handler: "commons.handlers.lambda_handler",
+      memorySize: 512,
+      runtime: lambda.Runtime.PYTHON_3_9,
+    },
+  });
+
+  new Configurator(stack, sampleConfig, "dev");
+  const template = Template.fromStack(stack);
+  template.hasResourceProperties("AWS::Lambda::Function", {
+    MemorySize: 128,
+    Runtime: {
+      bundlingDockerImage: { image: "public.ecr.aws/sam/build-python3.8" },
+      bundlingImage: { image: "public.ecr.aws/sam/build-python3.8" },
+      family: 2,
+      name: "python3.8",
+      supportsCodeGuruProfiling: true,
+      supportsInlineCode: true,
+    },
+  });
+});
+
+test("Config Override By Id", () => {
+  const sampleConfig = {
+    environments: {
+      dev: {
+        resources: {
+          MyBucket: {
+            BucketName: "my-exact-bucket-name-for-this-resource",
+          },
+          MyQueue: {
+            KmsMasterKeyId: "alias/aws/sqs",
+          },
+        },
+      },
+    },
+  };
+  const stack = new cdk.Stack();
+  new s3.Bucket(stack, "MyBucket");
+  new sqs.Queue(stack, "MyQueue");
+
+  new Configurator(stack, sampleConfig, "dev");
+
+  const template = Template.fromStack(stack);
+  template.hasResourceProperties("AWS::S3::Bucket", {
+    BucketName: "my-exact-bucket-name-for-this-resource",
+  });
+  template.hasResourceProperties("AWS::SQS::Queue", {
+    KmsMasterKeyId: "alias/aws/sqs",
+  });
+});
+
+test("Different values per environment", () => {
+  const sampleConfig = {
+    environments: {
+      dev: {
+        resources: {
+          "AWS::Lambda::Function": {
+            MemorySize: 128,
+          },
+          "AWS::SQS::Queue": {
+            MessageRetentionPeriod: 300,
+          },
+        },
+        tags: { CostCenter: "2020" },
+      },
+      stage: {
+        resources: {
+          "AWS::Lambda::Function": {
+            MemorySize: 512,
+          },
+          "AWS::SQS::Queue": {
+            MessageRetentionPeriod: 3600,
+          },
+        },
+        tags: { CostCenter: "3030" },
+      },
+      prod: {
+        resources: {
+          "AWS::Lambda::Function": {
+            MemorySize: 1024,
+          },
+          "AWS::SQS::Queue": {
+            MessageRetentionPeriod: 7200,
+          },
+        },
+        tags: { CostCenter: "4040" },
+      },
+    },
+  };
+
+  const stacks: { [environment: string]: cdk.Stack } = {
+    dev: new cdk.Stack(),
+    stage: new cdk.Stack(),
+    prod: new cdk.Stack(),
+  };
+
+  for (const env in stacks) {
+    new SqsToLambdaStage(stacks[env], "Stage", {
+      lambdaFunctionProps: {
+        code: lambda.Code.fromAsset(path.join(__dirname, "/../src/")),
+        handler: "commons.handlers.lambda_handler",
+        memorySize: 256,
+        runtime: lambda.Runtime.PYTHON_3_9,
+      },
+    });
+    new Configurator(stacks[env], sampleConfig, env);
+  }
+
+  const devTemplate = Template.fromStack(stacks.dev);
+  devTemplate.hasResourceProperties("AWS::Lambda::Function", {
+    MemorySize: 128,
+    Tags: [
+      {
+        Key: "CostCenter",
+        Value: "2020",
+      },
+    ],
+  });
+  devTemplate.hasResourceProperties("AWS::SQS::Queue", {
+    MessageRetentionPeriod: 300,
+    Tags: [
+      {
+        Key: "CostCenter",
+        Value: "2020",
+      },
+    ],
+  });
+
+  const stageTemplate = Template.fromStack(stacks.stage);
+  stageTemplate.hasResourceProperties("AWS::Lambda::Function", {
+    MemorySize: 512,
+    Tags: [
+      {
+        Key: "CostCenter",
+        Value: "3030",
+      },
+    ],
+  });
+  stageTemplate.hasResourceProperties("AWS::SQS::Queue", {
+    MessageRetentionPeriod: 3600,
+    Tags: [
+      {
+        Key: "CostCenter",
+        Value: "3030",
+      },
+    ],
+  });
+
+  const prodTemplate = Template.fromStack(stacks.prod);
+  prodTemplate.hasResourceProperties("AWS::Lambda::Function", {
+    MemorySize: 1024,
+    Tags: [
+      {
+        Key: "CostCenter",
+        Value: "4040",
+      },
+    ],
+  });
+  prodTemplate.hasResourceProperties("AWS::SQS::Queue", {
+    MessageRetentionPeriod: 7200,
+    Tags: [
+      {
+        Key: "CostCenter",
+        Value: "4040",
+      },
+    ],
+  });
+});
+
+test("File Based Config: JSON", () => {
+  const stack = new cdk.Stack();
+
+  new SqsToLambdaStage(stack, "Stage", {
+    lambdaFunctionProps: {
+      code: lambda.Code.fromAsset(path.join(__dirname, "/../src/")),
+      handler: "commons.handlers.lambda_handler",
+      memorySize: 512,
+      runtime: lambda.Runtime.PYTHON_3_9,
+    },
+  });
+
+  new Configurator(stack, "./test/test-config.json", "dev");
+  const template = Template.fromStack(stack);
+  template.hasResourceProperties("AWS::Lambda::Function", {
+    MemorySize: 128,
+    Runtime: "python3.8",
+    Tags: [
+      {
+        Key: "CostCenter",
+        Value: "2014",
+      },
+      {
+        Key: "global:foo",
+        Value: "bar",
+      },
+    ],
+  });
+});
+
+test("File Based Config: YAML", () => {
+  const stack = new cdk.Stack();
+
+  new SqsToLambdaStage(stack, "Stage", {
+    lambdaFunctionProps: {
+      code: lambda.Code.fromAsset(path.join(__dirname, "/../src/")),
+      handler: "commons.handlers.lambda_handler",
+      memorySize: 512,
+      runtime: lambda.Runtime.PYTHON_3_9,
+    },
+  });
+
+  new Configurator(stack, "./test/test-config.yaml", "dev");
+  const template = Template.fromStack(stack);
+  template.hasResourceProperties("AWS::Lambda::Function", {
+    MemorySize: 128,
+    Runtime: "python3.8",
+    Tags: [
+      {
+        Key: "CostCenter",
+        Value: "2014",
+      },
+      {
+        Key: "global:foo",
+        Value: "bar",
+      },
+    ],
+  });
+});
+
+test("File based config: Bad Format", () => {
+  const stack = new cdk.Stack();
+  expect(() => {
+    new Configurator(stack, "./test/test-config.jso", "dev");
+  }).toThrowError("Config file must be in YAML or JSON format");
+});
+
+test("File based config: None specified, default does not exist", () => {
+  const synthesizer = getStackSynthesizer({ environmentId: "dev" });
+  const expectedValues = {
+    qualifier: "hnb659fds",
+    bucketName: "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
+    repositoryName: "cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}",
+    _deployRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
+    _cloudFormationExecutionRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
+    fileAssetPublishingRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}",
+    imageAssetPublishingRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}",
+    lookupRoleArn:
+      "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}",
+    bucketPrefix: "",
+    dockerTagPrefix: "",
+    bootstrapStackVersionSsmParameter: "/cdk-bootstrap/hnb659fds/version",
+  };
+  for (const attribute in expectedValues) {
+    assert(
+      synthesizer[attribute as keyof typeof synthesizer] === expectedValues[attribute as keyof typeof expectedValues],
+    );
+  }
+});
+
+test("CICDPipeline with Config", () => {
+  const app = new cdk.App();
+
+  // Dev
+  const devStage = new cdk.Stage(app, "dev", { env: { account: "000000000000" } });
+  const devStack = new cdk.Stack(devStage, "dev-application-stack");
+  new Configurator(devStage, "./test/test-config.json", "dev");
+  const devBucket = new s3.Bucket(devStack, "Bucket");
+  const devFirehoseToS3Stage = new FirehoseToS3Stage(devStack, "Firehose To S3 Stage", { s3Bucket: devBucket });
+
+  const devSqsToLambdaStage = new SqsToLambdaStage(devStack, "SQS To Lambda Stage", {
+    lambdaFunctionProps: {
+      code: lambda.Code.fromAsset(path.join(__dirname, "/../src/")),
+      handler: "commons.handlers.lambda_handler",
+      memorySize: 512,
+      runtime: lambda.Runtime.PYTHON_3_9,
+    },
+  });
+
+  const devDataPipeline = new DataPipeline(devStack, "Pipeline", {});
+
+  devDataPipeline.addStage({ stage: devFirehoseToS3Stage }).addStage({ stage: devSqsToLambdaStage });
+
+  // Prod
+  const prodStage = new cdk.Stage(app, "prod", { env: { account: "000000000000" } });
+  const prodStack = new cdk.Stack(prodStage, "prod-application-stack");
+  new Configurator(prodStage, "./test/test-config.json", "prod");
+  const prodBucket = new s3.Bucket(prodStack, "Bucket");
+  const prodFirehoseToS3Stage = new FirehoseToS3Stage(prodStack, "Firehose To S3 Stage", { s3Bucket: prodBucket });
+
+  const prodSqsToLambdaStage = new SqsToLambdaStage(prodStack, "SQS To Lambda Stage", {
+    lambdaFunctionProps: {
+      code: lambda.Code.fromAsset(path.join(__dirname, "/../src/")),
+      handler: "commons.handlers.lambda_handler",
+      memorySize: 512,
+      runtime: lambda.Runtime.PYTHON_3_9,
+    },
+  });
+  const prodDataPipeline = new DataPipeline(prodStack, "Pipeline", {});
+  prodDataPipeline.addStage({ stage: prodFirehoseToS3Stage }).addStage({ stage: prodSqsToLambdaStage });
+
+  // CI/CD Stack
+  new CICDPipelineStack(app, "dummy-pipeline", { environmentId: "dev", pipelineName: "dummy-pipeline" })
+    .addSourceAction({ repositoryName: "dummy-repository" })
+    .addSynthAction()
+    .buildPipeline()
+    .addStage({ stageId: "dev", stage: devStage })
+    .addStage({ stageId: "prod", stage: prodStage })
+    .synth();
+
+  const devStageTemplate = Template.fromStack(devStack);
+  devStageTemplate.hasResourceProperties("AWS::Lambda::Function", {
+    MemorySize: 128,
+    Runtime: "python3.8",
+  });
+  const prodStageTemplate = Template.fromStack(prodStack);
+  prodStageTemplate.hasResourceProperties("AWS::Lambda::Function", {
+    MemorySize: 1024,
+    Runtime: "python3.8",
+  });
+});
+
+test("Get Env Config Method", () => {
+  const devConfig = getEnvConfig({ config: "./test/test-config.json", environmentId: "dev" });
+  const expectedDevConfig = {
+    account: "222222222222",
+    region: "us-east-1",
+    resources: {
+      "AWS::Lambda::Function": {
+        MemorySize: 128,
+        Runtime: "python3.8",
+      },
+    },
+    tags: { CostCenter: "2014" },
+  };
+  assert(devConfig === expectedDevConfig);
+});
+
+test("Get Env Config Method: Non-Existent File", () => {
+  const devConfig = getEnvConfig({ config: "./test/not-real.yaml", environmentId: "dev" });
+  const expectedDevConfig = {};
+  assert(devConfig === expectedDevConfig);
+});
diff --git a/test/test-config.jso b/test/test-config.jso
new file mode 100644
index 00000000..68d50167
--- /dev/null
+++ b/test/test-config.jso
@@ -0,0 +1,29 @@
+{
+  "tags": {
+    "global:foo": "bar"
+  },
+  "environments": {
+    "dev": {
+      "account": "222222222222",
+      "region": "us-east-1",
+      "resources": {
+        "AWS::Lambda::Function": {
+          "MemorySize": 128,
+          "Runtime": "python3.8"
+        }
+      },
+      "tags": {"CostCenter": "2014"}
+    },
+    "prod": {
+      "account": "222222222222",
+      "region": "us-east-1",
+      "resources": {
+        "AWS::Lambda::Function": {
+          "MemorySize": 1024,
+          "Runtime": "python3.8"
+        }
+      },
+      "tags": {"CostCenter": "2015"}
+    }
+  }
+}
\ No newline at end of file
diff --git a/test/test-config.json b/test/test-config.json
new file mode 100644
index 00000000..68d50167
--- /dev/null
+++ b/test/test-config.json
@@ -0,0 +1,29 @@
+{
+  "tags": {
+    "global:foo": "bar"
+  },
+  "environments": {
+    "dev": {
+      "account": "222222222222",
+      "region": "us-east-1",
+      "resources": {
+        "AWS::Lambda::Function": {
+          "MemorySize": 128,
+          "Runtime": "python3.8"
+        }
+      },
+      "tags": {"CostCenter": "2014"}
+    },
+    "prod": {
+      "account": "222222222222",
+      "region": "us-east-1",
+      "resources": {
+        "AWS::Lambda::Function": {
+          "MemorySize": 1024,
+          "Runtime": "python3.8"
+        }
+      },
+      "tags": {"CostCenter": "2015"}
+    }
+  }
+}
\ No newline at end of file
diff --git a/test/test-config.yaml b/test/test-config.yaml
new file mode 100644
index 00000000..20c93fbc
--- /dev/null
+++ b/test/test-config.yaml
@@ -0,0 +1,21 @@
+tags:
+  global:foo: bar
+environments:
+  dev:
+    account: '222222222222'
+    region: us-east-1
+    resources:
+      AWS::Lambda::Function:
+        MemorySize: 128
+        Runtime: python3.8
+    tags:
+      CostCenter: '2014'
+  prod:
+    account: '222222222222'
+    region: us-east-1
+    resources:
+      AWS::Lambda::Function:
+        MemorySize: 1024
+        Runtime: python3.8
+    tags:
+      CostCenter: '2015'
\ No newline at end of file