@friggframework/devtools 2.0.0--canary.474.86c5119.0 → 2.0.0--canary.474.6a0bba7.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/domains/database/migration-builder.js +199 -1
  2. package/infrastructure/domains/database/migration-builder.test.js +73 -0
  3. package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
  4. package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
  5. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +6 -0
  6. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +307 -1
  7. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +38 -5
  8. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +3 -3
  9. package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
  10. package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
  11. package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
  12. package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
  13. package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
  14. package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
  15. package/infrastructure/domains/health/domain/entities/issue.js +50 -1
  16. package/infrastructure/domains/health/domain/entities/issue.test.js +111 -0
  17. package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
  18. package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
  19. package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
  20. package/infrastructure/domains/health/domain/services/health-score-calculator.js +174 -91
  21. package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +332 -228
  22. package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
  23. package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
  24. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
  25. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
  26. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
  27. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +407 -20
  28. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +698 -26
  29. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +108 -14
  30. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +69 -12
  31. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +392 -1
  32. package/package.json +6 -6
@@ -1,57 +1,71 @@
1
1
  /**
2
2
  * HealthScoreCalculator Domain Service
3
3
  *
4
- * Calculates health scores (0-100) based on detected issues and resource states.
4
+ * Calculates health scores (0-100) based on percentage-based penalties
5
+ * weighted by resource criticality.
5
6
  *
6
- * Penalty System:
7
- * - Critical issues (orphaned, missing, immutable drift): 20-30 points each
8
- * - Warning issues (mutable drift): 10 points each
9
- * - Info issues (missing tags): 5 points each
7
+ * Resource Criticality:
8
+ * - Critical: Lambda, RDS, DynamoDB (affect application functionality)
9
+ * - Infrastructure: VPC, Subnet, SecurityGroup, KMS, etc.
10
10
  *
11
- * Score is capped at 0 (cannot go negative).
11
+ * Percentage-Based Penalties (max 100 points):
12
+ * - Critical issues (orphaned, missing): up to 50 points (% of total resources)
13
+ * - Functional drift: up to 30 points (% of critical resources drifted)
14
+ * - Infrastructure drift: up to 20 points (% of infrastructure resources drifted)
15
+ *
16
+ * Example: 16/16 Lambdas drifted = 100% functional drift = 30 penalty → 70/100 score
12
17
  */
13
18
 
14
19
  const HealthScore = require('../value-objects/health-score');
15
20
 
16
21
  class HealthScoreCalculator {
17
22
  /**
18
- * Default penalty values for different issue severities
23
+ * Critical resource types that affect application functionality
24
+ * @private
25
+ */
26
+ static CRITICAL_RESOURCE_TYPES = [
27
+ 'AWS::Lambda::Function',
28
+ 'AWS::RDS::DBCluster',
29
+ 'AWS::RDS::DBInstance',
30
+ 'AWS::DynamoDB::Table',
31
+ ];
32
+
33
+ /**
34
+ * Maximum penalties for each category (sum = 100)
19
35
  * @private
20
36
  */
21
- static DEFAULT_PENALTIES = {
22
- critical: 30, // Orphaned resources, missing resources
23
- warning: 10, // Mutable property mismatches
24
- info: 5, // Missing tags, minor issues
25
- immutablePropertyMismatch: 20, // Immutable property changes (requires replacement)
37
+ static MAX_PENALTIES = {
38
+ criticalIssues: 50, // Orphaned resources, missing resources
39
+ functionalDrift: 30, // Drift on critical resources (Lambda, RDS, DynamoDB)
40
+ infrastructureDrift: 20, // Drift on infrastructure resources (VPC, networking, KMS)
26
41
  };
27
42
 
28
43
  /**
29
44
  * Create a new HealthScoreCalculator
30
45
  *
31
46
  * @param {Object} [config={}]
32
- * @param {Object} [config.penalties] - Custom penalty configuration
33
- * @param {number} [config.penalties.critical] - Penalty for critical issues (default: 30)
34
- * @param {number} [config.penalties.warning] - Penalty for warning issues (default: 10)
35
- * @param {number} [config.penalties.info] - Penalty for info issues (default: 5)
36
- * @param {number} [config.penalties.immutablePropertyMismatch] - Penalty for immutable property changes (default: 20)
47
+ * @param {Object} [config.maxPenalties] - Custom max penalty configuration
48
+ * @param {number} [config.maxPenalties.criticalIssues] - Max penalty for critical issues (default: 50)
49
+ * @param {number} [config.maxPenalties.functionalDrift] - Max penalty for functional drift (default: 30)
50
+ * @param {number} [config.maxPenalties.infrastructureDrift] - Max penalty for infra drift (default: 20)
37
51
  */
38
52
  constructor(config = {}) {
39
- this.penalties = {
40
- ...HealthScoreCalculator.DEFAULT_PENALTIES,
41
- ...(config.penalties || {}),
53
+ this.maxPenalties = {
54
+ ...HealthScoreCalculator.MAX_PENALTIES,
55
+ ...(config.maxPenalties || {}),
42
56
  };
43
57
  }
44
58
 
45
59
  /**
46
- * Get default penalty configuration
47
- * @returns {Object}
60
+ * Check if resource type is critical (affects functionality)
61
+ * @private
48
62
  */
49
- static getDefaultPenalties() {
50
- return { ...HealthScoreCalculator.DEFAULT_PENALTIES };
63
+ _isCriticalResourceType(resourceType) {
64
+ return HealthScoreCalculator.CRITICAL_RESOURCE_TYPES.includes(resourceType);
51
65
  }
52
66
 
53
67
  /**
54
- * Calculate health score based on resources and issues
68
+ * Calculate health score based on percentage-based penalties
55
69
  *
56
70
  * @param {Object} params
57
71
  * @param {Resource[]} params.resources - Resources in the stack
@@ -60,55 +74,73 @@ class HealthScoreCalculator {
60
74
  */
61
75
  calculate({ resources, issues }) {
62
76
  const startingScore = 100;
63
- let totalPenalty = 0;
64
77
 
65
- // Calculate penalties for each issue
66
- for (const issue of issues) {
67
- totalPenalty += this._calculateIssuePenalty(issue);
78
+ // Handle empty stack edge case
79
+ if (resources.length === 0) {
80
+ return new HealthScore(startingScore);
68
81
  }
69
82
 
70
- // Calculate final score (capped at 0)
71
- const finalScore = Math.max(0, startingScore - totalPenalty);
72
-
73
- return new HealthScore(finalScore);
74
- }
83
+ // Categorize resources by criticality
84
+ const criticalResources = resources.filter((r) =>
85
+ this._isCriticalResourceType(r.resourceType)
86
+ );
87
+ const infraResources = resources.filter(
88
+ (r) => !this._isCriticalResourceType(r.resourceType)
89
+ );
90
+
91
+ // Count issues by category
92
+ const criticalIssues = issues.filter(
93
+ (issue) => issue.type === 'ORPHANED_RESOURCE' || issue.type === 'MISSING_RESOURCE'
94
+ );
95
+ const functionalDriftIssues = issues.filter(
96
+ (issue) =>
97
+ issue.type === 'PROPERTY_MISMATCH' &&
98
+ this._isCriticalResourceType(issue.resourceType)
99
+ );
100
+ const infraDriftIssues = issues.filter(
101
+ (issue) =>
102
+ issue.type === 'PROPERTY_MISMATCH' &&
103
+ !this._isCriticalResourceType(issue.resourceType)
104
+ );
105
+
106
+ // Calculate percentage-based penalties
107
+ let totalPenalty = 0;
75
108
 
76
- /**
77
- * Calculate penalty for a single issue
78
- *
79
- * @private
80
- * @param {Issue} issue
81
- * @returns {number}
82
- */
83
- _calculateIssuePenalty(issue) {
84
- // Special case: immutable property mismatch has higher penalty than regular critical
85
- if (
86
- issue.isPropertyMismatch() &&
87
- issue.propertyMismatch &&
88
- issue.propertyMismatch.requiresReplacement()
89
- ) {
90
- return this.penalties.immutablePropertyMismatch;
109
+ // 1. Critical issues penalty (up to 50 points)
110
+ if (criticalIssues.length > 0) {
111
+ const criticalImpactPercent = criticalIssues.length / resources.length;
112
+ totalPenalty += criticalImpactPercent * this.maxPenalties.criticalIssues;
91
113
  }
92
114
 
93
- // Standard severity-based penalties
94
- if (issue.isCritical()) {
95
- return this.penalties.critical;
115
+ // 2. Functional drift penalty (up to 30 points)
116
+ if (functionalDriftIssues.length > 0 && criticalResources.length > 0) {
117
+ // Get unique drifted critical resources
118
+ const driftedCriticalResourceIds = new Set(
119
+ functionalDriftIssues.map((issue) => issue.resourceId)
120
+ );
121
+ const functionalDriftPercent =
122
+ driftedCriticalResourceIds.size / criticalResources.length;
123
+ totalPenalty += functionalDriftPercent * this.maxPenalties.functionalDrift;
96
124
  }
97
125
 
98
- if (issue.isWarning()) {
99
- return this.penalties.warning;
126
+ // 3. Infrastructure drift penalty (up to 20 points)
127
+ if (infraDriftIssues.length > 0 && infraResources.length > 0) {
128
+ // Get unique drifted infrastructure resources
129
+ const driftedInfraResourceIds = new Set(
130
+ infraDriftIssues.map((issue) => issue.resourceId)
131
+ );
132
+ const infraDriftPercent = driftedInfraResourceIds.size / infraResources.length;
133
+ totalPenalty += infraDriftPercent * this.maxPenalties.infrastructureDrift;
100
134
  }
101
135
 
102
- if (issue.isInfo()) {
103
- return this.penalties.info;
104
- }
136
+ // Calculate final score (capped at 0)
137
+ const finalScore = Math.max(0, Math.round(startingScore - totalPenalty));
105
138
 
106
- // Fallback (should never reach here if Issue validation is correct)
107
- return 0;
139
+ return new HealthScore(finalScore);
108
140
  }
109
141
 
110
142
  /**
111
- * Explain the score calculation with detailed breakdown
143
+ * Explain the score calculation with detailed percentage-based breakdown
112
144
  *
113
145
  * @param {Object} params
114
146
  * @param {Resource[]} params.resources - Resources in the stack
@@ -117,47 +149,98 @@ class HealthScoreCalculator {
117
149
  */
118
150
  explainScore({ resources, issues }) {
119
151
  const startingScore = 100;
120
- let totalPenalty = 0;
121
152
 
122
- // Count issues by severity
153
+ if (resources.length === 0) {
154
+ return {
155
+ finalScore: startingScore,
156
+ startingScore,
157
+ totalPenalty: 0,
158
+ breakdown: {
159
+ criticalIssues: { count: 0, impactPercent: 0, penalty: 0 },
160
+ functionalDrift: { count: 0, impactPercent: 0, penalty: 0 },
161
+ infrastructureDrift: { count: 0, impactPercent: 0, penalty: 0 },
162
+ },
163
+ };
164
+ }
165
+
166
+ // Categorize resources
167
+ const criticalResources = resources.filter((r) =>
168
+ this._isCriticalResourceType(r.resourceType)
169
+ );
170
+ const infraResources = resources.filter(
171
+ (r) => !this._isCriticalResourceType(r.resourceType)
172
+ );
173
+
174
+ // Count issues by category
175
+ const criticalIssues = issues.filter(
176
+ (issue) => issue.type === 'ORPHANED_RESOURCE' || issue.type === 'MISSING_RESOURCE'
177
+ );
178
+ const functionalDriftIssues = issues.filter(
179
+ (issue) =>
180
+ issue.type === 'PROPERTY_MISMATCH' &&
181
+ this._isCriticalResourceType(issue.resourceType)
182
+ );
183
+ const infraDriftIssues = issues.filter(
184
+ (issue) =>
185
+ issue.type === 'PROPERTY_MISMATCH' &&
186
+ !this._isCriticalResourceType(issue.resourceType)
187
+ );
188
+
189
+ // Calculate penalties
123
190
  const breakdown = {
124
- critical: { count: 0, penalty: 0 },
125
- warning: { count: 0, penalty: 0 },
126
- info: { count: 0, penalty: 0 },
191
+ criticalIssues: {
192
+ count: criticalIssues.length,
193
+ impactPercent: criticalIssues.length / resources.length,
194
+ penalty:
195
+ (criticalIssues.length / resources.length) * this.maxPenalties.criticalIssues,
196
+ },
197
+ functionalDrift: {
198
+ count: functionalDriftIssues.length,
199
+ impactPercent:
200
+ criticalResources.length > 0
201
+ ? new Set(functionalDriftIssues.map((i) => i.resourceId)).size /
202
+ criticalResources.length
203
+ : 0,
204
+ penalty:
205
+ criticalResources.length > 0
206
+ ? (new Set(functionalDriftIssues.map((i) => i.resourceId)).size /
207
+ criticalResources.length) *
208
+ this.maxPenalties.functionalDrift
209
+ : 0,
210
+ },
211
+ infrastructureDrift: {
212
+ count: infraDriftIssues.length,
213
+ impactPercent:
214
+ infraResources.length > 0
215
+ ? new Set(infraDriftIssues.map((i) => i.resourceId)).size /
216
+ infraResources.length
217
+ : 0,
218
+ penalty:
219
+ infraResources.length > 0
220
+ ? (new Set(infraDriftIssues.map((i) => i.resourceId)).size /
221
+ infraResources.length) *
222
+ this.maxPenalties.infrastructureDrift
223
+ : 0,
224
+ },
127
225
  };
128
226
 
129
- // Count issues by type
130
- const issueTypes = {};
131
-
132
- // Process each issue
133
- for (const issue of issues) {
134
- const penalty = this._calculateIssuePenalty(issue);
135
- totalPenalty += penalty;
136
-
137
- // Track by severity
138
- if (issue.isCritical()) {
139
- breakdown.critical.count++;
140
- breakdown.critical.penalty += penalty;
141
- } else if (issue.isWarning()) {
142
- breakdown.warning.count++;
143
- breakdown.warning.penalty += penalty;
144
- } else if (issue.isInfo()) {
145
- breakdown.info.count++;
146
- breakdown.info.penalty += penalty;
147
- }
148
-
149
- // Track by type
150
- issueTypes[issue.type] = (issueTypes[issue.type] || 0) + 1;
151
- }
227
+ const totalPenalty =
228
+ breakdown.criticalIssues.penalty +
229
+ breakdown.functionalDrift.penalty +
230
+ breakdown.infrastructureDrift.penalty;
152
231
 
153
- const finalScore = Math.max(0, startingScore - totalPenalty);
232
+ const finalScore = Math.max(0, Math.round(startingScore - totalPenalty));
154
233
 
155
234
  return {
156
235
  finalScore,
157
236
  startingScore,
158
- totalPenalty,
237
+ totalPenalty: Math.round(totalPenalty * 100) / 100, // Round to 2 decimal places
159
238
  breakdown,
160
- issueTypes,
239
+ resourceCounts: {
240
+ total: resources.length,
241
+ critical: criticalResources.length,
242
+ infrastructure: infraResources.length,
243
+ },
161
244
  };
162
245
  }
163
246
  }