arcvision 0.2.14 → 0.2.15

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 (134) hide show
  1. package/ARCVISION_DIRECTORY_STRUCTURE.md +104 -0
  2. package/CLI_STRUCTURE.md +110 -0
  3. package/CONFIGURATION.md +119 -0
  4. package/IMPLEMENTATION_SUMMARY.md +99 -0
  5. package/README.md +149 -89
  6. package/architecture.authority.ledger.json +46 -0
  7. package/arcvision-0.2.3.tgz +0 -0
  8. package/arcvision-0.2.4.tgz +0 -0
  9. package/arcvision-0.2.5.tgz +0 -0
  10. package/arcvision.context.diff.json +2181 -0
  11. package/arcvision.context.json +1021 -0
  12. package/arcvision.context.v1.json +2163 -0
  13. package/arcvision.context.v2.json +2173 -0
  14. package/arcvision_context/README.md +93 -0
  15. package/arcvision_context/architecture.authority.ledger.json +83 -0
  16. package/arcvision_context/arcvision.context.json +6884 -0
  17. package/debug-cycle-detection.js +56 -0
  18. package/dist/index.js +1626 -25
  19. package/docs/ENHANCED_ACCURACY_SAFETY_PROTOCOL.md +172 -0
  20. package/docs/accuracy-enhancement-artifacts/enhanced-validation-config.json +98 -0
  21. package/docs/acig-robustness-guide.md +164 -0
  22. package/docs/authoritative-gate-implementation.md +168 -0
  23. package/docs/cli-strengthening-summary.md +232 -0
  24. package/docs/invariant-system-summary.md +100 -0
  25. package/docs/invariant-system.md +112 -0
  26. package/generate_large_test.js +42 -0
  27. package/large_test_repo.json +1 -0
  28. package/output1.json +2163 -0
  29. package/output2.json +2163 -0
  30. package/package.json +46 -36
  31. package/scan_calcom_report.txt +0 -0
  32. package/scan_leafmint_report.txt +0 -0
  33. package/scan_output.txt +0 -0
  34. package/scan_trigger_report.txt +0 -0
  35. package/schema/arcvision_context_schema_v1.json +136 -1
  36. package/src/arcvision-guard.js +433 -0
  37. package/src/core/authority-core-detector.js +382 -0
  38. package/src/core/authority-ledger.js +300 -0
  39. package/src/core/blastRadius.js +299 -0
  40. package/src/core/call-resolver.js +196 -0
  41. package/src/core/change-evaluator.js +509 -0
  42. package/src/core/change-evaluator.js.backup +424 -0
  43. package/src/core/change-evaluator.ts +285 -0
  44. package/src/core/chunked-uploader.js +180 -0
  45. package/src/core/circular-dependency-detector.js +404 -0
  46. package/src/core/cli-error-handler.js +458 -0
  47. package/src/core/cli-validator.js +458 -0
  48. package/src/core/compression.js +64 -0
  49. package/src/core/context_builder.js +741 -0
  50. package/src/core/dependency-manager.js +134 -0
  51. package/src/core/di-detector.js +202 -0
  52. package/src/core/diff-analyzer.js +76 -0
  53. package/src/core/example-invariants.js +135 -0
  54. package/src/core/failure-mode-synthesizer.js +341 -0
  55. package/src/core/invariant-analyzer.js +294 -0
  56. package/src/core/invariant-detector.js +548 -0
  57. package/src/core/invariant-enforcer.js +171 -0
  58. package/src/core/invariant-evaluation-utils.js +172 -0
  59. package/src/core/invariant-hooks.js +152 -0
  60. package/src/core/invariant-integration-example.js +186 -0
  61. package/src/core/invariant-registry.js +298 -0
  62. package/src/core/invariant-registry.ts +100 -0
  63. package/src/core/invariant-types.js +66 -0
  64. package/src/core/invariants-index.js +88 -0
  65. package/src/core/method-tracker.js +170 -0
  66. package/src/core/override-handler.js +304 -0
  67. package/src/core/ownership-resolver.js +227 -0
  68. package/src/core/parser-enhanced.js +80 -0
  69. package/src/core/parser.js +610 -0
  70. package/src/core/path-resolver.js +240 -0
  71. package/src/core/pattern-matcher.js +246 -0
  72. package/src/core/progress-tracker.js +71 -0
  73. package/src/core/react-nextjs-detector.js +245 -0
  74. package/src/core/readme-generator.js +167 -0
  75. package/src/core/retry-handler.js +57 -0
  76. package/src/core/scanner.js +289 -0
  77. package/src/core/semantic-analyzer.js +204 -0
  78. package/src/core/structural-context-owner.js +442 -0
  79. package/src/core/symbol-indexer.js +164 -0
  80. package/src/core/tsconfig-utils.js +73 -0
  81. package/src/core/type-analyzer.js +272 -0
  82. package/src/core/watcher.js +18 -0
  83. package/src/core/workspace-scanner.js +88 -0
  84. package/src/engine/context_builder.js +280 -0
  85. package/src/engine/context_sorter.js +59 -0
  86. package/src/engine/context_validator.js +200 -0
  87. package/src/engine/id-generator.js +16 -0
  88. package/src/engine/pass1_facts.js +260 -0
  89. package/src/engine/pass2_semantics.js +333 -0
  90. package/src/engine/pass3_lifter.js +99 -0
  91. package/src/engine/pass4_signals.js +201 -0
  92. package/src/index.js +830 -0
  93. package/src/plugins/express-plugin.js +48 -0
  94. package/src/plugins/plugin-manager.js +58 -0
  95. package/src/plugins/react-plugin.js +54 -0
  96. package/temp_original.js +0 -0
  97. package/test/determinism-test.js +83 -0
  98. package/test-authoritative-context.js +53 -0
  99. package/test-real-authoritative-context.js +118 -0
  100. package/test-upload-enhancements.js +111 -0
  101. package/test_repos/allowed-clean-architecture/.arcvision/invariants.json +57 -0
  102. package/test_repos/allowed-clean-architecture/adapters/controllers/UserController.js +95 -0
  103. package/test_repos/allowed-clean-architecture/adapters/http/HttpServer.js +78 -0
  104. package/test_repos/allowed-clean-architecture/application/dtos/CreateUserRequest.js +37 -0
  105. package/test_repos/allowed-clean-architecture/application/services/UserService.js +61 -0
  106. package/test_repos/allowed-clean-architecture/arcvision_context/README.md +93 -0
  107. package/test_repos/allowed-clean-architecture/arcvision_context/arcvision.context.json +2796 -0
  108. package/test_repos/allowed-clean-architecture/domain/interfaces/UserRepository.js +25 -0
  109. package/test_repos/allowed-clean-architecture/domain/models/User.js +39 -0
  110. package/test_repos/allowed-clean-architecture/index.js +45 -0
  111. package/test_repos/allowed-clean-architecture/infrastructure/database/DatabaseConnection.js +56 -0
  112. package/test_repos/allowed-clean-architecture/infrastructure/repositories/InMemoryUserRepository.js +61 -0
  113. package/test_repos/allowed-clean-architecture/package.json +15 -0
  114. package/test_repos/blocked-legacy-monolith/.arcvision/invariants.json +78 -0
  115. package/test_repos/blocked-legacy-monolith/arcvision_context/README.md +93 -0
  116. package/test_repos/blocked-legacy-monolith/arcvision_context/arcvision.context.json +2882 -0
  117. package/test_repos/blocked-legacy-monolith/database/dbConnection.js +35 -0
  118. package/test_repos/blocked-legacy-monolith/index.js +38 -0
  119. package/test_repos/blocked-legacy-monolith/modules/emailService.js +31 -0
  120. package/test_repos/blocked-legacy-monolith/modules/paymentProcessor.js +37 -0
  121. package/test_repos/blocked-legacy-monolith/package.json +15 -0
  122. package/test_repos/blocked-legacy-monolith/shared/utils.js +19 -0
  123. package/test_repos/blocked-legacy-monolith/utils/helpers.js +23 -0
  124. package/test_repos/risky-microservices-concerns/.arcvision/invariants.json +69 -0
  125. package/test_repos/risky-microservices-concerns/arcvision_context/README.md +93 -0
  126. package/test_repos/risky-microservices-concerns/arcvision_context/arcvision.context.json +3070 -0
  127. package/test_repos/risky-microservices-concerns/common/utils.js +77 -0
  128. package/test_repos/risky-microservices-concerns/gateways/apiGateway.js +84 -0
  129. package/test_repos/risky-microservices-concerns/index.js +20 -0
  130. package/test_repos/risky-microservices-concerns/libs/deprecatedHelper.js +36 -0
  131. package/test_repos/risky-microservices-concerns/package.json +15 -0
  132. package/test_repos/risky-microservices-concerns/services/orderService.js +42 -0
  133. package/test_repos/risky-microservices-concerns/services/userService.js +48 -0
  134. package/verify_engine.js +116 -0
@@ -0,0 +1,424 @@
1
+ /**
2
+ * Change Impact Evaluator
3
+ * Connects existing ArcVision data to invariants
4
+ */
5
+
6
+ /**
7
+ * Evaluates changes against registered invariants
8
+ */
9
+ function evaluateChange(input) {
10
+ // Validate input
11
+ if (!input) {
12
+ return createErrorResult('Invalid input: input object is required');
13
+ }
14
+
15
+ const { changedFiles, dependencyGraph, invariants, context } = input;
16
+
17
+ // Validate required parameters with enhanced checks
18
+ if (!Array.isArray(changedFiles)) {
19
+ return createErrorResult('Invalid input: changedFiles must be an array');
20
+ }
21
+
22
+ if (!dependencyGraph) {
23
+ return createErrorResult('Invalid input: dependencyGraph is required');
24
+ }
25
+
26
+ if (!Array.isArray(invariants)) {
27
+ return createErrorResult('Invalid input: invariants must be an array');
28
+ }
29
+
30
+ try {
31
+ const violations = [];
32
+
33
+ // Check each invariant against the changes
34
+ for (const invariant of invariants) {
35
+ try {
36
+ const matchesScope = matchesInvariantScope(changedFiles, invariant);
37
+
38
+ if (matchesScope) {
39
+ const isViolated = checkInvariantRule(invariant, dependencyGraph, changedFiles);
40
+
41
+ if (isViolated) {
42
+ violations.push(invariant);
43
+ }
44
+ }
45
+ } catch (ruleError) {
46
+ console.warn(`Warning: Error evaluating invariant ${invariant?.id || 'unknown'}:`, ruleError.message);
47
+ // Continue with other invariants instead of failing completely
48
+ }
49
+ }
50
+
51
+ // Determine decision based on violations
52
+ if (violations.some(v => v.severity === 'block')) {
53
+ return {
54
+ decision: 'BLOCKED',
55
+ violations,
56
+ reasons: violations.map(v => v.description || 'Unknown violation'),
57
+ details: calculateImpactDetails(input, violations)
58
+ };
59
+ }
60
+
61
+ if (violations.length > 0) {
62
+ return {
63
+ decision: 'RISKY',
64
+ violations,
65
+ reasons: violations.map(v => v.description || 'Unknown violation'),
66
+ details: calculateImpactDetails(input, violations)
67
+ };
68
+ }
69
+
70
+ return {
71
+ decision: 'ALLOWED',
72
+ violations: [],
73
+ reasons: ['No invariant violations detected'],
74
+ details: calculateImpactDetails(input, violations)
75
+ };
76
+ } catch (error) {
77
+ return createErrorResult(`Evaluation failed: ${error.message}`);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Creates a standardized error result
83
+ */
84
+ function createErrorResult(errorMessage) {
85
+ return {
86
+ decision: 'ERROR',
87
+ violations: [],
88
+ reasons: [errorMessage],
89
+ details: {
90
+ affectedNodes: 0,
91
+ blastRadiusImpact: 0,
92
+ authorityCoreChanges: false
93
+ }
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Checks if changed files match the invariant's scope
99
+ */
100
+ function matchesInvariantScope(changedFiles, invariant) {
101
+ if (!invariant || !invariant.scope) {
102
+ return false; // Invalid invariant, don't match
103
+ }
104
+
105
+ const scope = invariant.scope;
106
+
107
+ // If no scope restrictions, always matches
108
+ if (!scope.files && !scope.components && !scope.flows) {
109
+ return true;
110
+ }
111
+
112
+ // Check file patterns if specified using robust pattern matcher
113
+ if (scope.files && Array.isArray(scope.files) && scope.files.length > 0) {
114
+ try {
115
+ const { patternMatcher } = require('./pattern-matcher');
116
+ return changedFiles.some(file => {
117
+ if (typeof file !== 'string') return false;
118
+ return scope.files.some(pattern => {
119
+ if (typeof pattern !== 'string') return false;
120
+ try {
121
+ return patternMatcher.match(file, pattern, { matchBase: true });
122
+ } catch (error) {
123
+ console.warn(`Warning: Invalid pattern "${pattern}" in invariant ${invariant.id || 'unknown'}:`, error.message);
124
+ return false;
125
+ }
126
+ });
127
+ });
128
+ } catch (error) {
129
+ console.warn(`Warning: Could not process file patterns for invariant ${invariant.id || 'unknown'}:`, error.message);
130
+ return false; // If pattern matching fails, don't match the scope
131
+ }
132
+ }
133
+
134
+ // Check component names if specified
135
+ if (scope.components && Array.isArray(scope.components) && scope.components.length > 0) {
136
+ try {
137
+ return changedFiles.some(file => {
138
+ if (typeof file !== 'string') return false;
139
+ return scope.components.some(component => {
140
+ if (typeof component !== 'string') return false;
141
+ return file.toLowerCase().includes(component.toLowerCase());
142
+ });
143
+ });
144
+ } catch (error) {
145
+ console.warn(`Warning: Could not process component patterns for invariant ${invariant.id || 'unknown'}:`, error.message);
146
+ return false;
147
+ }
148
+ }
149
+
150
+ // Check flow patterns if specified
151
+ if (scope.flows && Array.isArray(scope.flows) && scope.flows.length > 0) {
152
+ // Placeholder for flow matching logic
153
+ // This would require more sophisticated analysis of the dependency graph
154
+ return true;
155
+ }
156
+
157
+ return true;
158
+ }
159
+
160
+ /**
161
+ * Checks if an invariant rule is violated by the changes
162
+ */
163
+ function checkInvariantRule(invariant, dependencyGraph, changedFiles) {
164
+ if (!invariant || !invariant.rule) {
165
+ return false; // Invalid invariant, no violation
166
+ }
167
+
168
+ try {
169
+ switch (invariant.rule.type) {
170
+ case 'dependency':
171
+ return checkDependencyRule(invariant, dependencyGraph, changedFiles);
172
+ case 'flow':
173
+ return checkFlowRule(invariant, dependencyGraph, changedFiles);
174
+ case 'ownership':
175
+ return checkOwnershipRule(invariant, dependencyGraph, changedFiles);
176
+ case 'access_control':
177
+ return checkAccessControlRule(invariant, dependencyGraph, changedFiles);
178
+ case 'data_flow':
179
+ return checkDataFlowRule(invariant, dependencyGraph, changedFiles);
180
+ default:
181
+ // Unknown rule types are treated as non-violating to prevent false positives
182
+ console.warn(`Warning: Unknown rule type "${invariant.rule.type}" in invariant ${invariant.id || 'unknown'}`);
183
+ return false;
184
+ }
185
+ } catch (error) {
186
+ console.warn(`Warning: Error checking rule for invariant ${invariant.id || 'unknown'}:`, error.message);
187
+ return false; // If rule check fails, treat as non-violating to avoid blocking changes unnecessarily
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Generic rule checker that interprets the condition dynamically
193
+ */
194
+ function checkGenericRule(condition, dependencyGraph, changedFiles) {
195
+ if (!condition) return false;
196
+
197
+ try {
198
+ // Handle different condition types dynamically
199
+ if (condition.forbiddenDependency && typeof condition.forbiddenDependency === 'object') {
200
+ return checkForbiddenDependency(condition.forbiddenDependency, dependencyGraph, changedFiles);
201
+ }
202
+
203
+ if (condition.mustExist) {
204
+ return checkRequirement(condition.mustExist, dependencyGraph, changedFiles);
205
+ }
206
+
207
+ if (condition.mustNotExist) {
208
+ return checkProhibition(condition.mustNotExist, dependencyGraph, changedFiles);
209
+ }
210
+
211
+ if (condition.pattern) {
212
+ return checkPatternMatch(condition.pattern, dependencyGraph, changedFiles);
213
+ }
214
+
215
+ // Default: if condition exists and has properties, consider it potentially violated
216
+ return typeof condition === 'object' && Object.keys(condition).length > 0;
217
+ } catch (error) {
218
+ console.warn(`Warning: Error in generic rule check:`, error.message);
219
+ return false; // If generic check fails, don't treat as violation
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Check dependency rule violations
225
+ */
226
+ function checkDependencyRule(invariant, dependencyGraph, changedFiles) {
227
+ // Use the generic rule checker to interpret the condition
228
+ return checkGenericRule(invariant.rule.condition, dependencyGraph, changedFiles);
229
+ }
230
+
231
+ /**
232
+ * Check forbidden dependency specifically - more generic approach
233
+ */
234
+ function checkForbiddenDependency(forbiddenDep, dependencyGraph, changedFiles) {
235
+ if (!forbiddenDep || typeof forbiddenDep !== 'object') {
236
+ return false;
237
+ }
238
+
239
+ if (!forbiddenDep.from || !forbiddenDep.to) {
240
+ return false;
241
+ }
242
+
243
+ try {
244
+ // Check if any changed file matches the 'from' pattern and creates dependency to 'to'
245
+ const { patternMatcher } = require('./pattern-matcher');
246
+
247
+ return changedFiles.some(file => {
248
+ if (typeof file !== 'string') return false;
249
+
250
+ // Check if file matches the 'from' scope using robust pattern matching
251
+ const matchesFrom = patternMatcher.match(file, forbiddenDep.from, { matchBase: true }) ||
252
+ patternMatcher.match(file, `**/${forbiddenDep.from}/**`, { matchBase: false }) ||
253
+ file.toLowerCase().includes(forbiddenDep.from.toLowerCase());
254
+
255
+ if (matchesFrom) {
256
+ // Check if this file now depends on the 'to' component
257
+ return hasDependencyTo(dependencyGraph, file, forbiddenDep.to);
258
+ }
259
+ return false;
260
+ });
261
+ } catch (error) {
262
+ console.warn(`Warning: Error checking forbidden dependency:`, error.message);
263
+ return false; // If dependency check fails, don't block
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Check general requirement
269
+ */
270
+ function checkRequirement(requirement, dependencyGraph, changedFiles) {
271
+ // Placeholder for requirement checking logic
272
+ // For now, return false to avoid false positives
273
+ return false;
274
+ }
275
+
276
+ /**
277
+ * Check prohibition
278
+ */
279
+ function checkProhibition(prohibition, dependencyGraph, changedFiles) {
280
+ // Placeholder for prohibition checking logic
281
+ // For now, return false to avoid false positives
282
+ return false;
283
+ }
284
+
285
+ /**
286
+ * Check pattern matching
287
+ */
288
+ function checkPatternMatch(pattern, dependencyGraph, changedFiles) {
289
+ // Placeholder for pattern matching logic
290
+ // For now, return false to avoid false positives
291
+ return false;
292
+ }
293
+
294
+ /**
295
+ * Check flow rule violations
296
+ */
297
+ function checkFlowRule(invariant, dependencyGraph, changedFiles) {
298
+ // Use the generic rule checker to interpret the condition
299
+ return checkGenericRule(invariant.rule.condition, dependencyGraph, changedFiles);
300
+ }
301
+
302
+ /**
303
+ * Check ownership rule violations
304
+ */
305
+ function checkOwnershipRule(invariant, dependencyGraph, changedFiles) {
306
+ try {
307
+ // Check if changed files fall under this invariant's scope and if ownership is violated
308
+ if (invariant.rule.condition && invariant.scope.files) {
309
+ const { authorizedOwners } = invariant.rule.condition;
310
+
311
+ if (authorizedOwners && Array.isArray(authorizedOwners)) {
312
+ return changedFiles.some(file => {
313
+ if (typeof file !== 'string') return false;
314
+
315
+ // Check if file matches the scope
316
+ const matchesScope = invariant.scope.files.some(pattern => {
317
+ if (typeof pattern !== 'string') return false;
318
+ try {
319
+ const { patternMatcher } = require('./pattern-matcher');
320
+ return patternMatcher.match(file, pattern, { matchBase: true });
321
+ } catch (error) {
322
+ console.warn(`Warning: Invalid pattern "${pattern}" in ownership check:`, error.message);
323
+ return false;
324
+ }
325
+ });
326
+
327
+ if (matchesScope) {
328
+ // Check if current owner is authorized (this would require additional context about who made the change)
329
+ // For now, we'll just check if the invariant has an owner specified and it's different
330
+ return invariant.owner && !authorizedOwners.includes(invariant.owner);
331
+ }
332
+ return false;
333
+ });
334
+ }
335
+ }
336
+ } catch (error) {
337
+ console.warn(`Warning: Error in ownership rule check:`, error.message);
338
+ return false;
339
+ }
340
+
341
+ return false;
342
+ }
343
+
344
+ /**
345
+ * Check access control rule violations
346
+ */
347
+ function checkAccessControlRule(invariant, dependencyGraph, changedFiles) {
348
+ // Use the generic rule checker to interpret the condition
349
+ return checkGenericRule(invariant.rule.condition, dependencyGraph, changedFiles);
350
+ }
351
+
352
+ /**
353
+ * Check data flow rule violations
354
+ */
355
+ function checkDataFlowRule(invariant, dependencyGraph, changedFiles) {
356
+ // Use the generic rule checker to interpret the condition
357
+ return checkGenericRule(invariant.rule.condition, dependencyGraph, changedFiles);
358
+ }
359
+
360
+ /**
361
+ * Helper function to check if a file has dependency to another component
362
+ */
363
+ function hasDependencyTo(dependencyGraph, fromFile, toComponent) {
364
+ if (typeof fromFile !== 'string' || typeof toComponent !== 'string') {
365
+ return false;
366
+ }
367
+
368
+ try {
369
+ // Simplified implementation - would need to analyze actual dependency graph
370
+ if (dependencyGraph && dependencyGraph.edges && Array.isArray(dependencyGraph.edges)) {
371
+ return dependencyGraph.edges.some((edge) => {
372
+ if (!edge || !edge.source || !edge.target) return false;
373
+
374
+ return edge.source === fromFile &&
375
+ (edge.target.includes(toComponent) ||
376
+ edge.target.toLowerCase().includes(toComponent.toLowerCase()));
377
+ });
378
+ }
379
+ } catch (error) {
380
+ console.warn(`Warning: Error checking dependency graph:`, error.message);
381
+ }
382
+
383
+ return false;
384
+ }
385
+
386
+ /**
387
+ * Calculate impact details for the evaluation result
388
+ */
389
+ function calculateImpactDetails(input, violations) {
390
+ const { changedFiles, dependencyGraph, context } = input || {};
391
+
392
+ // Calculate affected nodes from dependency graph
393
+ let affectedNodes = 0;
394
+ if (dependencyGraph && dependencyGraph.nodes && Array.isArray(dependencyGraph.nodes)) {
395
+ affectedNodes = dependencyGraph.nodes.filter((node) =>
396
+ changedFiles && Array.isArray(changedFiles) &&
397
+ changedFiles.includes(node.id || node.path)
398
+ ).length;
399
+ }
400
+
401
+ // Calculate blast radius impact if available in context
402
+ let blastRadiusImpact = 0;
403
+ if (context && context.blastRadiusAnalysis) {
404
+ blastRadiusImpact = context.blastRadiusAnalysis.totalAffected || 0;
405
+ }
406
+
407
+ // Check for authority core changes
408
+ let authorityCoreChanges = false;
409
+ if (context && Array.isArray(context.authorityCores)) {
410
+ authorityCoreChanges = changedFiles && Array.isArray(changedFiles) &&
411
+ changedFiles.some(file =>
412
+ context.authorityCores.some((core) => core.path === file)
413
+ );
414
+ }
415
+
416
+ return {
417
+ affectedNodes,
418
+ blastRadiusImpact,
419
+ authorityCoreChanges
420
+ };
421
+ }
422
+
423
+ module.exports = { evaluateChange };function isValidInvariant(invariant) { if (!invariant || typeof invariant !== 'object') return false; if (!invariant.id || typeof invariant.id !== 'string' || invariant.id.trim().length === 0) return false; if (!invariant.system || typeof invariant.system !== 'string' || invariant.system.trim().length === 0) return false; if (!invariant.description || typeof invariant.description !== 'string' || invariant.description.trim().length === 0) return false; if (!invariant.severity || typeof invariant.severity !== 'string' || !['block', 'risk', 'BLOCK', 'RISK'].includes(invariant.severity)) return false; if (!invariant.scope || typeof invariant.scope !== 'object') return false; if (!invariant.rule || typeof invariant.rule !== 'object') return false; return true; }
424
+
@@ -0,0 +1,285 @@
1
+ import { Invariant } from './invariant-registry';
2
+
3
+ /**
4
+ * Change Impact Evaluator
5
+ * Connects existing ArcVision data to invariants
6
+ */
7
+ export interface ChangeEvaluationInput {
8
+ changedFiles: string[];
9
+ dependencyGraph: any; // ArcVision's existing graph structure
10
+ invariants: Invariant[];
11
+ context?: any; // Additional context from ArcVision analysis
12
+ }
13
+
14
+ export interface ChangeEvaluationResult {
15
+ decision: 'ALLOWED' | 'RISKY' | 'BLOCKED';
16
+ violations: Invariant[];
17
+ reasons: string[];
18
+ details: {
19
+ affectedNodes: number;
20
+ blastRadiusImpact: number;
21
+ authorityCoreChanges: boolean;
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Evaluates changes against registered invariants
27
+ */
28
+ export function evaluateChange(input: ChangeEvaluationInput): ChangeEvaluationResult {
29
+ const { changedFiles, dependencyGraph, invariants } = input;
30
+ const violations: Invariant[] = [];
31
+
32
+ // Check each invariant against the changes
33
+ for (const invariant of invariants) {
34
+ const matchesScope = matchesInvariantScope(changedFiles, invariant);
35
+
36
+ if (matchesScope) {
37
+ const isViolated = checkInvariantRule(invariant, dependencyGraph, changedFiles);
38
+
39
+ if (isViolated) {
40
+ violations.push(invariant);
41
+ }
42
+ }
43
+ }
44
+
45
+ // Determine decision based on violations
46
+ if (violations.some(v => v.severity === 'block')) {
47
+ return {
48
+ decision: 'BLOCKED',
49
+ violations,
50
+ reasons: violations.map(v => v.description),
51
+ details: calculateImpactDetails(input, violations)
52
+ };
53
+ }
54
+
55
+ if (violations.length > 0) {
56
+ return {
57
+ decision: 'RISKY',
58
+ violations,
59
+ reasons: violations.map(v => v.description),
60
+ details: calculateImpactDetails(input, violations)
61
+ };
62
+ }
63
+
64
+ return {
65
+ decision: 'ALLOWED',
66
+ violations: [],
67
+ reasons: ['No invariant violations detected'],
68
+ details: calculateImpactDetails(input, violations)
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Checks if changed files match the invariant's scope
74
+ */
75
+ function matchesInvariantScope(changedFiles: string[], invariant: Invariant): boolean {
76
+ const scope = invariant.scope;
77
+
78
+ // If no scope restrictions, always matches
79
+ if (!scope.files && !scope.components && !scope.flows) {
80
+ return true;
81
+ }
82
+
83
+ // Check file patterns if specified
84
+ if (scope.files && scope.files.length > 0) {
85
+ const minimatch = require('minimatch');
86
+ return changedFiles.some(file =>
87
+ scope.files!.some(pattern =>
88
+ minimatch(file, pattern, { matchBase: true })
89
+ )
90
+ );
91
+ }
92
+
93
+ // Check component names if specified
94
+ if (scope.components && scope.components.length > 0) {
95
+ // For now, check if any changed file contains a component name
96
+ return changedFiles.some(file =>
97
+ scope.components!.some(component =>
98
+ file.toLowerCase().includes(component.toLowerCase())
99
+ )
100
+ );
101
+ }
102
+
103
+ // Check flow patterns if specified
104
+ if (scope.flows && scope.flows.length > 0) {
105
+ // Placeholder for flow matching logic
106
+ // This would require more sophisticated analysis of the dependency graph
107
+ return true;
108
+ }
109
+
110
+ return true;
111
+ }
112
+
113
+ /**
114
+ * Checks if an invariant rule is violated by the changes
115
+ */
116
+ function checkInvariantRule(
117
+ invariant: Invariant,
118
+ dependencyGraph: any,
119
+ changedFiles: string[]
120
+ ): boolean {
121
+ switch (invariant.rule.type) {
122
+ case 'dependency':
123
+ return checkDependencyRule(invariant, dependencyGraph, changedFiles);
124
+ case 'flow':
125
+ return checkFlowRule(invariant, dependencyGraph, changedFiles);
126
+ case 'ownership':
127
+ return checkOwnershipRule(invariant, dependencyGraph, changedFiles);
128
+ case 'access_control':
129
+ return checkAccessControlRule(invariant, dependencyGraph, changedFiles);
130
+ case 'data_flow':
131
+ return checkDataFlowRule(invariant, dependencyGraph, changedFiles);
132
+ default:
133
+ console.warn(`Unknown rule type: ${invariant.rule.type}`);
134
+ return false;
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Check dependency rule violations
140
+ */
141
+ function checkDependencyRule(
142
+ invariant: Invariant,
143
+ dependencyGraph: any,
144
+ changedFiles: string[]
145
+ ): boolean {
146
+ // Example: "Auth service cannot depend on user service"
147
+ // Check if the dependency graph now contains a dependency that violates the rule
148
+ if (invariant.rule.condition && typeof invariant.rule.condition === 'object') {
149
+ const { forbiddenDependency } = invariant.rule.condition;
150
+
151
+ if (forbiddenDependency && typeof forbiddenDependency === 'object') {
152
+ const { from, to } = forbiddenDependency;
153
+
154
+ // Check if 'from' component now depends on 'to' component
155
+ // This is a simplified check - real implementation would traverse the dependency graph
156
+ if (from && to) {
157
+ // Check if any changed file is in the 'from' scope and creates dependency to 'to'
158
+ return changedFiles.some(file => {
159
+ // Simplified logic - in reality would need to analyze the dependency graph
160
+ return file.includes(from) && hasDependencyTo(dependencyGraph, file, to);
161
+ });
162
+ }
163
+ }
164
+ }
165
+
166
+ return false;
167
+ }
168
+
169
+ /**
170
+ * Check flow rule violations
171
+ */
172
+ function checkFlowRule(
173
+ invariant: Invariant,
174
+ dependencyGraph: any,
175
+ changedFiles: string[]
176
+ ): boolean {
177
+ // Example: "Payments flow must never bypass fraud check"
178
+ // This would require flow analysis of the code changes
179
+ return false; // Placeholder
180
+ }
181
+
182
+ /**
183
+ * Check ownership rule violations
184
+ */
185
+ function checkOwnershipRule(
186
+ invariant: Invariant,
187
+ dependencyGraph: any,
188
+ changedFiles: string[]
189
+ ): boolean {
190
+ // Example: "Only billing team can modify these files"
191
+ // Check if changed files are owned by unauthorized team
192
+ if (invariant.rule.condition && invariant.scope.files) {
193
+ const { authorizedOwners } = invariant.rule.condition;
194
+
195
+ if (authorizedOwners && Array.isArray(authorizedOwners)) {
196
+ // Check if any changed file is restricted to specific owners
197
+ return changedFiles.some(file => {
198
+ // If file matches invariant scope and owner is not authorized
199
+ return invariant.scope.files?.some(pattern => {
200
+ const minimatch = require('minimatch');
201
+ return minimatch(file, pattern, { matchBase: true });
202
+ }) && !authorizedOwners.includes(invariant.owner || '');
203
+ });
204
+ }
205
+ }
206
+
207
+ return false;
208
+ }
209
+
210
+ /**
211
+ * Check access control rule violations
212
+ */
213
+ function checkAccessControlRule(
214
+ invariant: Invariant,
215
+ dependencyGraph: any,
216
+ changedFiles: string[]
217
+ ): boolean {
218
+ // Example: "Direct database access not allowed from controllers"
219
+ // Check if changes create unauthorized access patterns
220
+ return false; // Placeholder
221
+ }
222
+
223
+ /**
224
+ * Check data flow rule violations
225
+ */
226
+ function checkDataFlowRule(
227
+ invariant: Invariant,
228
+ dependencyGraph: any,
229
+ changedFiles: string[]
230
+ ): boolean {
231
+ // Example: "PII data must not flow to logging systems"
232
+ // Check data flow patterns in the changes
233
+ return false; // Placeholder
234
+ }
235
+
236
+ /**
237
+ * Helper function to check if a file has dependency to another component
238
+ */
239
+ function hasDependencyTo(dependencyGraph: any, fromFile: string, toComponent: string): boolean {
240
+ // Simplified implementation - would need to analyze actual dependency graph
241
+ if (dependencyGraph && dependencyGraph.edges) {
242
+ return dependencyGraph.edges.some((edge: any) => {
243
+ return edge.source === fromFile && edge.target.includes(toComponent);
244
+ });
245
+ }
246
+ return false;
247
+ }
248
+
249
+ /**
250
+ * Calculate impact details for the evaluation result
251
+ */
252
+ function calculateImpactDetails(
253
+ input: ChangeEvaluationInput,
254
+ violations: Invariant[]
255
+ ): ChangeEvaluationResult['details'] {
256
+ const { changedFiles, dependencyGraph, context } = input;
257
+
258
+ // Calculate affected nodes from dependency graph
259
+ let affectedNodes = 0;
260
+ if (dependencyGraph && dependencyGraph.nodes) {
261
+ affectedNodes = dependencyGraph.nodes.filter((node: any) =>
262
+ changedFiles.includes(node.id || node.path)
263
+ ).length;
264
+ }
265
+
266
+ // Calculate blast radius impact if available in context
267
+ let blastRadiusImpact = 0;
268
+ if (context && context.blastRadiusAnalysis) {
269
+ blastRadiusImpact = context.blastRadiusAnalysis.totalAffected || 0;
270
+ }
271
+
272
+ // Check for authority core changes
273
+ let authorityCoreChanges = false;
274
+ if (context && context.authorityCores) {
275
+ authorityCoreChanges = changedFiles.some(file =>
276
+ context.authorityCores.some((core: any) => core.path === file)
277
+ );
278
+ }
279
+
280
+ return {
281
+ affectedNodes,
282
+ blastRadiusImpact,
283
+ authorityCoreChanges
284
+ };
285
+ }