@friggframework/devtools 2.0.0--canary.461.8cf93ae.0 → 2.0.0--canary.474.213c7d9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/infrastructure/ARCHITECTURE.md +487 -0
  2. package/infrastructure/domains/database/aurora-builder.js +234 -57
  3. package/infrastructure/domains/database/aurora-builder.test.js +7 -2
  4. package/infrastructure/domains/database/aurora-resolver.js +210 -0
  5. package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
  6. package/infrastructure/domains/database/migration-builder.js +256 -215
  7. package/infrastructure/domains/database/migration-builder.test.js +5 -111
  8. package/infrastructure/domains/database/migration-resolver.js +163 -0
  9. package/infrastructure/domains/database/migration-resolver.test.js +337 -0
  10. package/infrastructure/domains/integration/integration-builder.js +258 -84
  11. package/infrastructure/domains/integration/integration-resolver.js +170 -0
  12. package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
  13. package/infrastructure/domains/networking/vpc-builder.js +856 -135
  14. package/infrastructure/domains/networking/vpc-builder.test.js +10 -6
  15. package/infrastructure/domains/networking/vpc-resolver.js +324 -0
  16. package/infrastructure/domains/networking/vpc-resolver.test.js +501 -0
  17. package/infrastructure/domains/security/kms-builder.js +179 -22
  18. package/infrastructure/domains/security/kms-resolver.js +96 -0
  19. package/infrastructure/domains/security/kms-resolver.test.js +216 -0
  20. package/infrastructure/domains/shared/base-resolver.js +186 -0
  21. package/infrastructure/domains/shared/base-resolver.test.js +305 -0
  22. package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
  23. package/infrastructure/domains/shared/cloudformation-discovery.test.js +26 -1
  24. package/infrastructure/domains/shared/types/app-definition.js +205 -0
  25. package/infrastructure/domains/shared/types/discovery-result.js +106 -0
  26. package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
  27. package/infrastructure/domains/shared/types/index.js +46 -0
  28. package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
  29. package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
  30. package/package.json +6 -6
  31. package/infrastructure/REFACTOR.md +0 -532
  32. package/infrastructure/TRANSFORMATION-VISUAL.md +0 -239
@@ -11,6 +11,8 @@
11
11
  */
12
12
 
13
13
  const { InfrastructureBuilder, ValidationResult } = require('../shared/base-builder');
14
+ const { KmsResourceResolver } = require('./kms-resolver');
15
+ const { createEmptyDiscoveryResult, ResourceOwnership } = require('../shared/types');
14
16
 
15
17
  class KmsBuilder extends InfrastructureBuilder {
16
18
  constructor() {
@@ -53,11 +55,14 @@ class KmsBuilder extends InfrastructureBuilder {
53
55
  }
54
56
 
55
57
  /**
56
- * Build KMS infrastructure
58
+ * Build KMS infrastructure using ownership-based architecture
57
59
  */
58
60
  async build(appDefinition, discoveredResources) {
59
61
  console.log(`\n[${this.name}] Configuring KMS encryption...`);
60
62
 
63
+ // Backwards compatibility: Translate old schema to new ownership schema
64
+ appDefinition = this.translateLegacyConfig(appDefinition, discoveredResources);
65
+
61
66
  const result = {
62
67
  resources: {},
63
68
  iamStatements: [],
@@ -66,28 +71,186 @@ class KmsBuilder extends InfrastructureBuilder {
66
71
  plugins: [],
67
72
  };
68
73
 
69
- // Normalize top-level managementMode
74
+ // Get structured discovery result
75
+ const discovery = discoveredResources._structured || this.convertFlatDiscoveryToStructured(discoveredResources, appDefinition);
76
+
77
+ // Use KmsResourceResolver to make ownership decisions
78
+ const resolver = new KmsResourceResolver();
79
+ const decisions = resolver.resolveAll(appDefinition, discovery);
80
+
81
+ console.log('\n 📋 Resource Ownership Decisions:');
82
+ console.log(` Key: ${decisions.key.ownership} - ${decisions.key.reason}`);
83
+
84
+ // Build resources based on ownership decisions
85
+ await this.buildFromDecisions(decisions, appDefinition, discoveredResources, result);
86
+
87
+ // Add IAM permissions for Lambda role
88
+ result.iamStatements.push({
89
+ Effect: 'Allow',
90
+ Action: ['kms:GenerateDataKey', 'kms:Decrypt', 'kms:Encrypt', 'kms:DescribeKey'],
91
+ Resource: result.environment.KMS_KEY_ARN,
92
+ });
93
+
94
+ console.log(`[${this.name}] ✅ KMS configuration completed`);
95
+ return result;
96
+ }
97
+
98
+ /**
99
+ * Convert flat discovery to structured discovery
100
+ * Provides backwards compatibility for tests
101
+ */
102
+ convertFlatDiscoveryToStructured(flatDiscovery, appDefinition = {}) {
103
+ const discovery = createEmptyDiscoveryResult();
104
+
105
+ if (!flatDiscovery) {
106
+ return discovery;
107
+ }
108
+
109
+ // Check if resources are from CloudFormation stack
110
+ const isManagedIsolated = appDefinition.managementMode === 'managed' &&
111
+ (appDefinition.vpcIsolation === 'isolated' || !appDefinition.vpcIsolation);
112
+ const hasExistingStackResources = isManagedIsolated && flatDiscovery.defaultKmsKeyId &&
113
+ typeof flatDiscovery.defaultKmsKeyId === 'string';
114
+
115
+ if (flatDiscovery.fromCloudFormationStack || hasExistingStackResources) {
116
+ discovery.fromCloudFormation = true;
117
+ discovery.stackName = flatDiscovery.stackName || 'assumed-stack';
118
+
119
+ // Add stack-managed resources
120
+ let existingLogicalIds = flatDiscovery.existingLogicalIds || [];
121
+
122
+ // Infer logical IDs from physical IDs if needed
123
+ if (hasExistingStackResources && existingLogicalIds.length === 0) {
124
+ if (flatDiscovery.defaultKmsKeyId) existingLogicalIds.push('FriggKMSKey');
125
+ }
126
+
127
+ existingLogicalIds.forEach(logicalId => {
128
+ let resourceType = '';
129
+ let physicalId = '';
130
+
131
+ if (logicalId === 'FriggKMSKey') {
132
+ resourceType = 'AWS::KMS::Key';
133
+ physicalId = flatDiscovery.defaultKmsKeyId;
134
+ }
135
+
136
+ if (physicalId && typeof physicalId === 'string') {
137
+ discovery.stackManaged.push({
138
+ logicalId,
139
+ physicalId,
140
+ resourceType
141
+ });
142
+ }
143
+ });
144
+ } else {
145
+ // Resources discovered from AWS API (external)
146
+ if (flatDiscovery.defaultKmsKeyId && typeof flatDiscovery.defaultKmsKeyId === 'string') {
147
+ discovery.external.push({
148
+ physicalId: flatDiscovery.defaultKmsKeyId,
149
+ resourceType: 'AWS::KMS::Key',
150
+ source: 'aws-discovery'
151
+ });
152
+ }
153
+ }
154
+
155
+ return discovery;
156
+ }
157
+
158
+ /**
159
+ * Translate legacy configuration to ownership-based configuration
160
+ * Provides backwards compatibility
161
+ */
162
+ translateLegacyConfig(appDefinition, discoveredResources) {
163
+ // If already using ownership schema, return as-is
164
+ if (appDefinition.encryption?.ownership) {
165
+ return appDefinition;
166
+ }
167
+
168
+ const translated = JSON.parse(JSON.stringify(appDefinition));
169
+
170
+ // Initialize ownership sections
171
+ if (!translated.encryption) translated.encryption = {};
172
+ if (!translated.encryption.ownership) {
173
+ translated.encryption.ownership = {};
174
+ }
175
+
176
+ // Handle top-level managementMode
70
177
  const globalMode = appDefinition.managementMode || 'discover';
71
- let createIfNoneFound = appDefinition.encryption.createResourceIfNoneFound;
178
+ const vpcIsolation = appDefinition.vpcIsolation || 'shared';
72
179
 
73
180
  if (globalMode === 'managed') {
74
- // In managed mode, always create KMS if not found
75
- createIfNoneFound = true;
76
- if (appDefinition.encryption.createResourceIfNoneFound !== undefined) {
181
+ if (appDefinition.encryption?.createResourceIfNoneFound !== undefined) {
77
182
  console.log(` ⚠️ managementMode='managed' ignoring: encryption.createResourceIfNoneFound`);
78
183
  }
184
+
185
+ if (vpcIsolation === 'isolated') {
186
+ const hasStackKms = discoveredResources?.defaultKmsKeyId &&
187
+ typeof discoveredResources.defaultKmsKeyId === 'string';
188
+
189
+ if (hasStackKms) {
190
+ translated.encryption.ownership.key = 'auto';
191
+ console.log(` managementMode='managed' + vpcIsolation='isolated' → stack has KMS, reusing`);
192
+ } else {
193
+ translated.encryption.ownership.key = 'stack';
194
+ console.log(` managementMode='managed' + vpcIsolation='isolated' → no stack KMS, creating new`);
195
+ }
196
+ } else {
197
+ translated.encryption.ownership.key = 'auto';
198
+ console.log(` managementMode='managed' + vpcIsolation='shared' → discovering KMS`);
199
+ }
200
+ } else {
201
+ // Handle legacy createResourceIfNoneFound
202
+ const createIfNoneFound = appDefinition.encryption?.createResourceIfNoneFound;
203
+ if (createIfNoneFound === true) {
204
+ translated.encryption.ownership.key = 'stack';
205
+ } else if (createIfNoneFound === false || createIfNoneFound === undefined) {
206
+ // When createResourceIfNoneFound is false or not specified:
207
+ // - If KMS found → use it (auto)
208
+ // - If not found → use environment variable (external)
209
+ // We use 'auto' here; the resolver will decide based on discovery
210
+ // But we need special handling in buildFromDecisions for the env var fallback
211
+ translated.encryption.ownership.key = 'auto';
212
+ translated.encryption._useEnvVarFallback = true; // Flag for env var fallback
213
+ }
79
214
  }
80
215
 
81
- // Check if we should create a new KMS key
82
- if (!discoveredResources.defaultKmsKeyId && createIfNoneFound === true) {
83
- console.log(' Creating new KMS key...');
216
+ return translated;
217
+ }
218
+
219
+ /**
220
+ * Build all KMS resources based on ownership decisions
221
+ */
222
+ async buildFromDecisions(decisions, appDefinition, discoveredResources, result) {
223
+ // Check for environment variable fallback flag (legacy behavior)
224
+ const useEnvVarFallback = appDefinition.encryption?._useEnvVarFallback;
225
+
226
+ if (decisions.key.ownership === ResourceOwnership.STACK && decisions.key.physicalId) {
227
+ // Key exists in stack - add definitions (CloudFormation idempotency)
228
+ console.log(' → Adding KMS definitions to template (existing in stack)');
84
229
  result.resources = this.createKmsKey(appDefinition);
85
230
  result.environment.KMS_KEY_ARN = { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] };
86
231
  console.log(' ✅ KMS key resources created');
87
- } else {
232
+ } else if (decisions.key.ownership === ResourceOwnership.STACK && !decisions.key.physicalId && !useEnvVarFallback) {
233
+ // Create new KMS key (only if not using env var fallback)
234
+ console.log(' → Creating new KMS key in stack');
235
+ result.resources = this.createKmsKey(appDefinition);
236
+ result.environment.KMS_KEY_ARN = { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] };
237
+ console.log(' ✅ KMS key resources created');
238
+ } else if (decisions.key.ownership === ResourceOwnership.STACK && !decisions.key.physicalId && useEnvVarFallback) {
239
+ // Legacy behavior: fallback to environment variable when createResourceIfNoneFound=false/undefined
240
+ const createIfNoneFound = discoveredResources.defaultKmsKeyId ? true : appDefinition.encryption?.createResourceIfNoneFound;
241
+ const formatAsArn = createIfNoneFound === undefined; // Format as ARN when not specified
242
+
243
+ if (formatAsArn) {
244
+ console.log(' → Using environment variable for KMS key (formatted as ARN)');
245
+ result.environment.KMS_KEY_ARN = 'arn:aws:kms:${self:provider.region}:${aws:accountId}:key/${env:AWS_DISCOVERY_KMS_KEY_ID}';
246
+ } else {
247
+ console.log(' → Using environment variable for KMS key');
248
+ result.environment.KMS_KEY_ARN = '${env:AWS_DISCOVERY_KMS_KEY_ID}';
249
+ }
250
+ } else if (decisions.key.ownership === ResourceOwnership.EXTERNAL) {
88
251
  // Use discovered KMS key
89
- const kmsKeyId = discoveredResources.defaultKmsKeyId || '${env:AWS_DISCOVERY_KMS_KEY_ID}';
90
- console.log(` Using ${discoveredResources.defaultKmsKeyId ? 'discovered' : 'environment variable'} KMS key`);
252
+ const kmsKeyId = decisions.key.physicalId || '${env:AWS_DISCOVERY_KMS_KEY_ID}';
253
+ console.log(` Using ${decisions.key.physicalId ? 'discovered' : 'environment variable'} KMS key`);
91
254
 
92
255
  // Format as ARN if it's just a key ID (for IAM policies)
93
256
  const kmsArn = kmsKeyId.startsWith('arn:')
@@ -95,17 +258,11 @@ class KmsBuilder extends InfrastructureBuilder {
95
258
  : `arn:aws:kms:\${self:provider.region}:\${aws:accountId}:key/${kmsKeyId}`;
96
259
 
97
260
  result.environment.KMS_KEY_ARN = kmsArn;
261
+ } else {
262
+ // Fallback
263
+ console.log(' → Using environment variable for KMS key');
264
+ result.environment.KMS_KEY_ARN = 'arn:aws:kms:${self:provider.region}:${aws:accountId}:key/${env:AWS_DISCOVERY_KMS_KEY_ID}';
98
265
  }
99
-
100
- // Add IAM permissions for Lambda role
101
- result.iamStatements.push({
102
- Effect: 'Allow',
103
- Action: ['kms:GenerateDataKey', 'kms:Decrypt', 'kms:Encrypt', 'kms:DescribeKey'],
104
- Resource: result.environment.KMS_KEY_ARN,
105
- });
106
-
107
- console.log(`[${this.name}] ✅ KMS configuration completed`);
108
- return result;
109
266
  }
110
267
 
111
268
  /**
@@ -0,0 +1,96 @@
1
+ /**
2
+ * KMS (Key Management Service) Resource Resolver
3
+ *
4
+ * Resolves KMS key ownership based on user intent and discovered resources.
5
+ *
6
+ * Ownership Resolution Logic:
7
+ * - User sets 'stack' → Create KMS key in CloudFormation stack
8
+ * - User sets 'external' → Use existing KMS key (discovered or env var)
9
+ * - User sets 'auto' (or unspecified):
10
+ * - If KMS key found in stack → Use stack resource (STACK)
11
+ * - If KMS key found externally → Use external resource (EXTERNAL)
12
+ * - If nothing found → Create in stack (STACK)
13
+ */
14
+
15
+ const BaseResourceResolver = require('../shared/base-resolver');
16
+ const { ResourceOwnership } = require('../shared/types');
17
+
18
+ class KmsResourceResolver extends BaseResourceResolver {
19
+ constructor() {
20
+ super();
21
+ }
22
+
23
+ /**
24
+ * Resolve KMS key ownership
25
+ * @param {Object} appDefinition - Application definition
26
+ * @param {Object} discovery - Structured discovery result
27
+ * @returns {Object} Ownership decision with metadata
28
+ */
29
+ resolveKey(appDefinition, discovery) {
30
+ // Get user intent from app definition
31
+ const userIntent = appDefinition.encryption?.ownership?.key || ResourceOwnership.AUTO;
32
+
33
+ // Check if KMS key exists in CloudFormation stack
34
+ const inStack = this.isInStack('FriggKMSKey', discovery);
35
+
36
+ if (userIntent === ResourceOwnership.STACK) {
37
+ // Explicit: Create/manage in stack
38
+ const stackResource = inStack ? this.findInStack('FriggKMSKey', discovery) : null;
39
+ return this.createStackDecision(
40
+ stackResource?.physicalId || null,
41
+ inStack ? 'Found FriggKMSKey in CloudFormation stack' : 'Will create FriggKMSKey in stack'
42
+ );
43
+ }
44
+
45
+ if (userIntent === ResourceOwnership.EXTERNAL) {
46
+ // Explicit: Use external key
47
+ const external = this.findExternal('AWS::KMS::Key', discovery);
48
+ if (!external) {
49
+ throw new Error(
50
+ 'ownership.key=external but no KMS key discovered. ' +
51
+ 'Provide defaultKmsKeyId in discoveredResources or set ownership.key=stack'
52
+ );
53
+ }
54
+ return this.createExternalDecision(
55
+ external.physicalId,
56
+ 'Using external KMS key per ownership.key=external'
57
+ );
58
+ }
59
+
60
+ // AUTO resolution
61
+ if (inStack) {
62
+ const stackResource = this.findInStack('FriggKMSKey', discovery);
63
+ return this.createStackDecision(
64
+ stackResource.physicalId,
65
+ 'Found FriggKMSKey in CloudFormation stack'
66
+ );
67
+ }
68
+
69
+ // Check for external KMS key
70
+ const external = this.findExternal('AWS::KMS::Key', discovery);
71
+ if (external) {
72
+ return this.createExternalDecision(
73
+ external.physicalId,
74
+ 'Found external KMS key via discovery'
75
+ );
76
+ }
77
+
78
+ // No KMS key found - create in stack
79
+ return this.createStackDecision(
80
+ null,
81
+ 'No existing KMS key - will create in stack'
82
+ );
83
+ }
84
+
85
+ /**
86
+ * Resolve all KMS resources
87
+ * Convenience method for resolving all KMS resource ownership
88
+ */
89
+ resolveAll(appDefinition, discovery) {
90
+ return {
91
+ key: this.resolveKey(appDefinition, discovery),
92
+ };
93
+ }
94
+ }
95
+
96
+ module.exports = { KmsResourceResolver };
@@ -0,0 +1,216 @@
1
+ const { KmsResourceResolver } = require('./kms-resolver');
2
+ const { ResourceOwnership, createEmptyDiscoveryResult } = require('../shared/types');
3
+
4
+ describe('KmsResourceResolver', () => {
5
+ let resolver;
6
+
7
+ beforeEach(() => {
8
+ resolver = new KmsResourceResolver();
9
+ });
10
+
11
+ describe('resolveKey', () => {
12
+ describe('Explicit ownership intent', () => {
13
+ it('should respect ownership.key=stack when specified', () => {
14
+ const appDefinition = {
15
+ encryption: {
16
+ ownership: { key: 'stack' }
17
+ }
18
+ };
19
+ const discovery = createEmptyDiscoveryResult();
20
+
21
+ const decision = resolver.resolveKey(appDefinition, discovery);
22
+
23
+ expect(decision.ownership).toBe(ResourceOwnership.STACK);
24
+ expect(decision.physicalId).toBeNull();
25
+ expect(decision.reason).toContain('Will create FriggKMSKey in stack');
26
+ });
27
+
28
+ it('should respect ownership.key=external when KMS key discovered', () => {
29
+ const appDefinition = {
30
+ encryption: {
31
+ ownership: { key: 'external' }
32
+ }
33
+ };
34
+ const discovery = createEmptyDiscoveryResult();
35
+ discovery.external.push({
36
+ physicalId: 'arn:aws:kms:us-east-1:123456789012:key/abcd-1234',
37
+ resourceType: 'AWS::KMS::Key',
38
+ source: 'aws-discovery'
39
+ });
40
+
41
+ const decision = resolver.resolveKey(appDefinition, discovery);
42
+
43
+ expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
44
+ expect(decision.physicalId).toBe('arn:aws:kms:us-east-1:123456789012:key/abcd-1234');
45
+ expect(decision.reason).toContain('external');
46
+ });
47
+
48
+ it('should error when ownership.key=external but no KMS key discovered', () => {
49
+ const appDefinition = {
50
+ encryption: {
51
+ ownership: { key: 'external' }
52
+ }
53
+ };
54
+ const discovery = createEmptyDiscoveryResult();
55
+
56
+ expect(() => resolver.resolveKey(appDefinition, discovery))
57
+ .toThrow('ownership.key=external but no KMS key discovered');
58
+ });
59
+ });
60
+
61
+ describe('Auto resolution (ownership.key=auto)', () => {
62
+ it('should use stack KMS key when found in CloudFormation', () => {
63
+ const appDefinition = {
64
+ encryption: {
65
+ ownership: { key: 'auto' }
66
+ }
67
+ };
68
+ const discovery = createEmptyDiscoveryResult();
69
+ discovery.fromCloudFormation = true;
70
+ discovery.stackManaged.push({
71
+ logicalId: 'FriggKMSKey',
72
+ physicalId: 'arn:aws:kms:us-east-1:123456789012:key/stack-key',
73
+ resourceType: 'AWS::KMS::Key'
74
+ });
75
+
76
+ const decision = resolver.resolveKey(appDefinition, discovery);
77
+
78
+ expect(decision.ownership).toBe(ResourceOwnership.STACK);
79
+ expect(decision.physicalId).toBe('arn:aws:kms:us-east-1:123456789012:key/stack-key');
80
+ expect(decision.reason).toContain('Found FriggKMSKey in CloudFormation stack');
81
+ });
82
+
83
+ it('should use external KMS key when found via discovery', () => {
84
+ const appDefinition = {
85
+ encryption: {
86
+ ownership: { key: 'auto' }
87
+ }
88
+ };
89
+ const discovery = createEmptyDiscoveryResult();
90
+ discovery.external.push({
91
+ physicalId: 'arn:aws:kms:us-east-1:123456789012:key/external-key',
92
+ resourceType: 'AWS::KMS::Key',
93
+ source: 'aws-discovery'
94
+ });
95
+
96
+ const decision = resolver.resolveKey(appDefinition, discovery);
97
+
98
+ expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
99
+ expect(decision.physicalId).toBe('arn:aws:kms:us-east-1:123456789012:key/external-key');
100
+ expect(decision.reason).toContain('Found external KMS key via discovery');
101
+ });
102
+
103
+ it('should create new KMS key when none found', () => {
104
+ const appDefinition = {
105
+ encryption: {
106
+ ownership: { key: 'auto' }
107
+ }
108
+ };
109
+ const discovery = createEmptyDiscoveryResult();
110
+
111
+ const decision = resolver.resolveKey(appDefinition, discovery);
112
+
113
+ expect(decision.ownership).toBe(ResourceOwnership.STACK);
114
+ expect(decision.physicalId).toBeNull();
115
+ expect(decision.reason).toContain('No existing KMS key - will create in stack');
116
+ });
117
+ });
118
+
119
+ describe('Default behavior (no ownership specified)', () => {
120
+ it('should default to auto resolution', () => {
121
+ const appDefinition = {
122
+ encryption: {
123
+ fieldLevelEncryptionMethod: 'kms'
124
+ // No ownership specified
125
+ }
126
+ };
127
+ const discovery = createEmptyDiscoveryResult();
128
+
129
+ const decision = resolver.resolveKey(appDefinition, discovery);
130
+
131
+ expect(decision.ownership).toBe(ResourceOwnership.STACK);
132
+ expect(decision.reason).toContain('No existing KMS key - will create in stack');
133
+ });
134
+ });
135
+ });
136
+
137
+ describe('resolveAll', () => {
138
+ it('should return decisions for all KMS resources', () => {
139
+ const appDefinition = {
140
+ encryption: {
141
+ fieldLevelEncryptionMethod: 'kms'
142
+ }
143
+ };
144
+ const discovery = createEmptyDiscoveryResult();
145
+
146
+ const decisions = resolver.resolveAll(appDefinition, discovery);
147
+
148
+ expect(decisions).toHaveProperty('key');
149
+ expect(decisions.key.ownership).toBe(ResourceOwnership.STACK);
150
+ });
151
+ });
152
+
153
+ describe('Real-world scenarios', () => {
154
+ it('should handle managementMode=managed scenario (create KMS)', () => {
155
+ // In managed mode, we want to create KMS in stack
156
+ const appDefinition = {
157
+ managementMode: 'managed',
158
+ encryption: {
159
+ fieldLevelEncryptionMethod: 'kms',
160
+ ownership: { key: 'stack' }
161
+ }
162
+ };
163
+ const discovery = createEmptyDiscoveryResult();
164
+
165
+ const decision = resolver.resolveKey(appDefinition, discovery);
166
+
167
+ expect(decision.ownership).toBe(ResourceOwnership.STACK);
168
+ expect(decision.physicalId).toBeNull();
169
+ });
170
+
171
+ it('should handle existing stack KMS key (reuse)', () => {
172
+ // Stack already has KMS from previous deployment
173
+ const appDefinition = {
174
+ encryption: {
175
+ fieldLevelEncryptionMethod: 'kms',
176
+ ownership: { key: 'auto' }
177
+ }
178
+ };
179
+ const discovery = createEmptyDiscoveryResult();
180
+ discovery.fromCloudFormation = true;
181
+ discovery.stackManaged.push({
182
+ logicalId: 'FriggKMSKey',
183
+ physicalId: 'arn:aws:kms:us-east-1:123456789012:key/existing-stack-key',
184
+ resourceType: 'AWS::KMS::Key'
185
+ });
186
+
187
+ const decision = resolver.resolveKey(appDefinition, discovery);
188
+
189
+ expect(decision.ownership).toBe(ResourceOwnership.STACK);
190
+ expect(decision.physicalId).toBe('arn:aws:kms:us-east-1:123456789012:key/existing-stack-key');
191
+ });
192
+
193
+ it('should handle shared KMS key scenario (vpcIsolation=shared)', () => {
194
+ // Using shared infrastructure KMS key
195
+ const appDefinition = {
196
+ managementMode: 'managed',
197
+ vpcIsolation: 'shared',
198
+ encryption: {
199
+ fieldLevelEncryptionMethod: 'kms',
200
+ ownership: { key: 'auto' }
201
+ }
202
+ };
203
+ const discovery = createEmptyDiscoveryResult();
204
+ discovery.external.push({
205
+ physicalId: 'arn:aws:kms:us-east-1:123456789012:key/shared-key',
206
+ resourceType: 'AWS::KMS::Key',
207
+ source: 'aws-discovery'
208
+ });
209
+
210
+ const decision = resolver.resolveKey(appDefinition, discovery);
211
+
212
+ expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
213
+ expect(decision.physicalId).toBe('arn:aws:kms:us-east-1:123456789012:key/shared-key');
214
+ });
215
+ });
216
+ });