@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.
- package/infrastructure/domains/database/migration-builder.js +199 -1
- package/infrastructure/domains/database/migration-builder.test.js +73 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +6 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +307 -1
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +38 -5
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +3 -3
- package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
- package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
- package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
- package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
- package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
- package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
- package/infrastructure/domains/health/domain/entities/issue.js +50 -1
- package/infrastructure/domains/health/domain/entities/issue.test.js +111 -0
- package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
- package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
- package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.js +174 -91
- package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +332 -228
- package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
- package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +407 -20
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +698 -26
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +108 -14
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +69 -12
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +392 -1
- 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
|
|
4
|
+
* Calculates health scores (0-100) based on percentage-based penalties
|
|
5
|
+
* weighted by resource criticality.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
* - Critical
|
|
8
|
-
* -
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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.
|
|
33
|
-
* @param {number} [config.
|
|
34
|
-
* @param {number} [config.
|
|
35
|
-
* @param {number} [config.
|
|
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.
|
|
40
|
-
...HealthScoreCalculator.
|
|
41
|
-
...(config.
|
|
53
|
+
this.maxPenalties = {
|
|
54
|
+
...HealthScoreCalculator.MAX_PENALTIES,
|
|
55
|
+
...(config.maxPenalties || {}),
|
|
42
56
|
};
|
|
43
57
|
}
|
|
44
58
|
|
|
45
59
|
/**
|
|
46
|
-
*
|
|
47
|
-
* @
|
|
60
|
+
* Check if resource type is critical (affects functionality)
|
|
61
|
+
* @private
|
|
48
62
|
*/
|
|
49
|
-
|
|
50
|
-
return
|
|
63
|
+
_isCriticalResourceType(resourceType) {
|
|
64
|
+
return HealthScoreCalculator.CRITICAL_RESOURCE_TYPES.includes(resourceType);
|
|
51
65
|
}
|
|
52
66
|
|
|
53
67
|
/**
|
|
54
|
-
* Calculate health score based on
|
|
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
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
78
|
+
// Handle empty stack edge case
|
|
79
|
+
if (resources.length === 0) {
|
|
80
|
+
return new HealthScore(startingScore);
|
|
68
81
|
}
|
|
69
82
|
|
|
70
|
-
//
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
//
|
|
94
|
-
if (
|
|
95
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
136
|
+
// Calculate final score (capped at 0)
|
|
137
|
+
const finalScore = Math.max(0, Math.round(startingScore - totalPenalty));
|
|
105
138
|
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
239
|
+
resourceCounts: {
|
|
240
|
+
total: resources.length,
|
|
241
|
+
critical: criticalResources.length,
|
|
242
|
+
infrastructure: infraResources.length,
|
|
243
|
+
},
|
|
161
244
|
};
|
|
162
245
|
}
|
|
163
246
|
}
|