arcvision 0.2.12 → 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,170 @@
1
+ /**
2
+ * Method Call Tracker
3
+ *
4
+ * Tracks method and function calls to build a call graph.
5
+ * This helps identify which files actually USE functions from other files,
6
+ * not just import them.
7
+ */
8
+
9
+ const traverse = require('@babel/traverse').default;
10
+
11
+ /**
12
+ * Track all method and function calls in an AST
13
+ * @param {Object} ast - Babel AST
14
+ * @param {Array} imports - Import metadata from parser
15
+ * @returns {Object} Method call metadata
16
+ */
17
+ function trackMethodCalls(ast, imports = []) {
18
+ const functionCalls = [];
19
+ const methodCalls = [];
20
+ const constructorCalls = [];
21
+
22
+ if (!ast) {
23
+ return { functionCalls, methodCalls, constructorCalls };
24
+ }
25
+
26
+ // Build a map of imported symbols for quick lookup
27
+ const importedSymbols = new Set();
28
+ imports.forEach(imp => {
29
+ if (imp.specifiers && Array.isArray(imp.specifiers)) {
30
+ imp.specifiers.forEach(spec => {
31
+ const localName = spec.local || spec.imported || spec;
32
+ if (typeof localName === 'string') {
33
+ importedSymbols.add(localName);
34
+ }
35
+ });
36
+ }
37
+ });
38
+
39
+ traverse(ast, {
40
+ CallExpression(path) {
41
+ const callee = path.node.callee;
42
+
43
+ // Simple function call: functionName()
44
+ if (callee.type === 'Identifier') {
45
+ const functionName = callee.name;
46
+
47
+ // Track all identifier calls - let CallResolver handle resolution
48
+ functionCalls.push({
49
+ name: functionName,
50
+ type: 'function_call',
51
+ loc: path.node.loc
52
+ });
53
+ }
54
+
55
+ // Method call: object.method()
56
+ else if (callee.type === 'MemberExpression') {
57
+ const objectName = extractObjectName(callee.object);
58
+ const methodName = callee.property.name || callee.property.value;
59
+
60
+ if (objectName && methodName) {
61
+ methodCalls.push({
62
+ object: objectName,
63
+ method: methodName,
64
+ type: 'method_call',
65
+ loc: path.node.loc
66
+ });
67
+ }
68
+ }
69
+ },
70
+
71
+ // Track constructor calls: new ClassName()
72
+ NewExpression(path) {
73
+ const callee = path.node.callee;
74
+
75
+ if (callee.type === 'Identifier') {
76
+ const className = callee.name;
77
+
78
+ // Track all constructor calls
79
+ constructorCalls.push({
80
+ className,
81
+ type: 'constructor_call',
82
+ loc: path.node.loc
83
+ });
84
+ }
85
+ }
86
+ });
87
+
88
+ return {
89
+ functionCalls,
90
+ methodCalls,
91
+ constructorCalls
92
+ };
93
+ }
94
+
95
+ /**
96
+ * Build a call graph from all nodes
97
+ * @param {Array} nodes - All scanned file nodes with metadata
98
+ * @returns {Array} Call graph edges
99
+ */
100
+ function buildCallGraph(nodes) {
101
+ const callEdges = [];
102
+
103
+ nodes.forEach(node => {
104
+ const filePath = node.id;
105
+ const metadata = node.metadata;
106
+
107
+ if (!metadata) return;
108
+
109
+ // Create edges for function calls
110
+ if (metadata.functionCalls && Array.isArray(metadata.functionCalls)) {
111
+ metadata.functionCalls.forEach(call => {
112
+ // We'll need to resolve which file this function comes from
113
+ // This is done in the semantic analyzer
114
+ callEdges.push({
115
+ source: filePath,
116
+ callType: 'function',
117
+ calledSymbol: call.name,
118
+ loc: call.loc
119
+ });
120
+ });
121
+ }
122
+
123
+ // Create edges for constructor calls
124
+ if (metadata.constructorCalls && Array.isArray(metadata.constructorCalls)) {
125
+ metadata.constructorCalls.forEach(call => {
126
+ callEdges.push({
127
+ source: filePath,
128
+ callType: 'constructor',
129
+ calledSymbol: call.className,
130
+ loc: call.loc
131
+ });
132
+ });
133
+ }
134
+
135
+ // Create edges for method calls
136
+ if (metadata.methodCalls && Array.isArray(metadata.methodCalls)) {
137
+ metadata.methodCalls.forEach(call => {
138
+ callEdges.push({
139
+ source: filePath,
140
+ callType: 'method',
141
+ calledSymbol: `${call.object}.${call.method}`,
142
+ loc: call.loc
143
+ });
144
+ });
145
+ }
146
+ });
147
+
148
+ return callEdges;
149
+ }
150
+
151
+ /**
152
+ * Helper: Extract object name from various node types
153
+ */
154
+ function extractObjectName(node) {
155
+ if (node.type === 'Identifier') {
156
+ return node.name;
157
+ } else if (node.type === 'ThisExpression') {
158
+ return 'this';
159
+ } else if (node.type === 'MemberExpression') {
160
+ const object = extractObjectName(node.object);
161
+ const property = node.property.name || node.property.value;
162
+ return object && property ? `${object}.${property}` : object;
163
+ }
164
+ return null;
165
+ }
166
+
167
+ module.exports = {
168
+ trackMethodCalls,
169
+ buildCallGraph
170
+ };
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Override Handler - Controlled bypass mechanism for Authoritative Gate
3
+ * Implements explicit override with permanent trace requirements
4
+ */
5
+
6
+ const { authorityLedger } = require('./authority-ledger');
7
+ const { invariantRegistry } = require('./invariant-registry');
8
+ const chalk = require('chalk');
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ class OverrideHandler {
13
+ constructor() {
14
+ this.overrideHistory = new Map(); // In-memory cache of recent overrides
15
+ }
16
+
17
+ /**
18
+ * Process override request for a blocked evaluation
19
+ */
20
+ async processOverride(options) {
21
+ const { eventId, reason, owner, commit, branch } = options;
22
+
23
+ // Validate required parameters
24
+ if (!eventId) {
25
+ throw new Error('Event ID is required for override');
26
+ }
27
+
28
+ const minReasonLength = parseInt(process.env.OVERRIDE_MIN_REASON_LENGTH) || 10;
29
+ if (!reason || reason.trim().length < minReasonLength) {
30
+ throw new Error(`Reason must be at least ${minReasonLength} characters long`);
31
+ }
32
+
33
+ if (!owner || owner.trim().length === 0) {
34
+ throw new Error('Owner is required for override');
35
+ }
36
+
37
+ try {
38
+ // Verify the event exists and was BLOCKED
39
+ const ledger = authorityLedger.readLedger();
40
+ const blockedEvent = ledger.ledger.find(e =>
41
+ e.event_id === eventId && e.decision === 'BLOCKED'
42
+ );
43
+
44
+ if (!blockedEvent) {
45
+ throw new Error(`No BLOCKED event found with ID: ${eventId}`);
46
+ }
47
+
48
+ // Check if already overridden
49
+ const isAlreadyOverridden = ledger.ledger.some(e =>
50
+ e.decision === 'OVERRIDDEN' &&
51
+ e.original_blocked_event === eventId
52
+ );
53
+
54
+ if (isAlreadyOverridden) {
55
+ throw new Error(`Event ${eventId} has already been overridden`);
56
+ }
57
+
58
+ // Record the override in authority ledger
59
+ const overrideEventId = authorityLedger.recordOverride(
60
+ eventId,
61
+ reason.trim(),
62
+ owner.trim(),
63
+ { commit, branch, author: owner.trim() }
64
+ );
65
+
66
+ // Store in memory cache for immediate access
67
+ this.overrideHistory.set(eventId, {
68
+ overrideEventId,
69
+ reason: reason.trim(),
70
+ owner: owner.trim(),
71
+ timestamp: new Date().toISOString(),
72
+ commit,
73
+ branch
74
+ });
75
+
76
+ console.log(chalk.green('✅ Override recorded successfully'));
77
+ console.log(chalk.green(`📝 Override ID: ${overrideEventId}`));
78
+ console.log(chalk.yellow(`⚠️ Permanent scar created for commit: ${commit || 'unknown'}`));
79
+
80
+ return {
81
+ success: true,
82
+ overrideEventId,
83
+ blockedEventId: eventId,
84
+ reason: reason.trim(),
85
+ owner: owner.trim()
86
+ };
87
+
88
+ } catch (error) {
89
+ console.error(chalk.red('❌ Override failed:'), error.message);
90
+ throw error;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Check if an evaluation can be overridden
96
+ */
97
+ canOverride(evaluation, context = {}) {
98
+ // Only BLOCKED decisions can be overridden
99
+ if (evaluation.decision !== 'BLOCKED') {
100
+ return {
101
+ allowed: false,
102
+ reason: `Cannot override ${evaluation.decision} decisions`
103
+ };
104
+ }
105
+
106
+ // Must have violations to override
107
+ if (!evaluation.violations || evaluation.violations.length === 0) {
108
+ return {
109
+ allowed: false,
110
+ reason: 'No violations to override'
111
+ };
112
+ }
113
+
114
+ return {
115
+ allowed: true,
116
+ violations: evaluation.violations
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Generate override instructions for BLOCKED evaluation
122
+ */
123
+ generateOverrideInstructions(evaluation, eventId, context = {}) {
124
+ const instructions = [
125
+ chalk.red.bold('\n🚨 CHANGE BLOCKED - Architectural Risk Detected'),
126
+ chalk.red('=' .repeat(60)),
127
+ '',
128
+ chalk.red('The following invariants were violated:'),
129
+ ...evaluation.violations.map((violation, index) =>
130
+ chalk.red(`${index + 1}. ${violation.description}`)
131
+ ),
132
+ '',
133
+ chalk.yellow('To override this BLOCKED decision, run:'),
134
+ chalk.cyan(process.env.OVERRIDE_COMMAND_FORMAT || `arcvision override --event-id ${eventId}`),
135
+ chalk.cyan(process.env.OVERRIDE_REASON_PLACEHOLDER || ' --reason "<detailed explanation>"'),
136
+ chalk.cyan(process.env.OVERRIDE_OWNER_PLACEHOLDER || ' --owner "<responsible person>"'),
137
+ '',
138
+ chalk.yellow(process.env.OVERRIDE_REQUIREMENTS_HEADER || 'Requirements:'),
139
+ chalk.yellow(process.env.OVERRIDE_REASON_REQUIREMENT || `• Reason must be detailed (minimum ${minReasonLength} characters)`),
140
+ chalk.yellow(process.env.OVERRIDE_OWNER_REQUIREMENT || '• Owner must be identified'),
141
+ chalk.yellow(process.env.OVERRIDE_RECORDING_REQUIREMENT || '• Override is permanently recorded'),
142
+ chalk.yellow(process.env.OVERRIDE_FUTURE_SCRUTINY || '• Future violations will face increased scrutiny'),
143
+ '',
144
+ chalk.red('⚠️ WARNING: This creates a permanent architectural scar')
145
+ ];
146
+
147
+ return instructions.join('\n');
148
+ }
149
+
150
+ /**
151
+ * Apply override to convert BLOCKED to ALLOWED
152
+ */
153
+ applyOverride(evaluation, overrideInfo) {
154
+ if (evaluation.decision !== 'BLOCKED') {
155
+ return evaluation;
156
+ }
157
+
158
+ // Convert to ALLOWED with override annotation
159
+ return {
160
+ ...evaluation,
161
+ decision: 'ALLOWED',
162
+ overrideApplied: true,
163
+ overrideInfo: {
164
+ eventId: overrideInfo.blockedEventId,
165
+ overrideEventId: overrideInfo.overrideEventId,
166
+ reason: overrideInfo.reason,
167
+ owner: overrideInfo.owner,
168
+ timestamp: new Date().toISOString()
169
+ },
170
+ reasons: [
171
+ ...evaluation.reasons,
172
+ `OVERRIDE APPLIED: ${overrideInfo.reason}`,
173
+ `Override owner: ${overrideInfo.owner}`
174
+ ]
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Get override history for a commit
180
+ */
181
+ getOverrideHistory(commitHash) {
182
+ try {
183
+ const events = authorityLedger.getEventsForCommit(commitHash);
184
+ const overrides = events.filter(e => e.decision === 'OVERRIDDEN');
185
+
186
+ return overrides.map(override => ({
187
+ eventId: override.event_id,
188
+ timestamp: override.timestamp,
189
+ owner: override.override.owner,
190
+ reason: override.override.reason,
191
+ originalEventId: override.original_blocked_event
192
+ }));
193
+ } catch (error) {
194
+ console.warn('Could not retrieve override history:', error.message);
195
+ return [];
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Check if current context has recent overrides (increased scrutiny)
201
+ */
202
+ hasRecentOverrides(limitDays = 30) {
203
+ try {
204
+ const ledger = authorityLedger.readLedger();
205
+ const cutoffDate = new Date();
206
+ cutoffDate.setDate(cutoffDate.getDate() - limitDays);
207
+
208
+ const recentOverrides = ledger.ledger.filter(event => {
209
+ if (event.decision !== 'OVERRIDDEN') return false;
210
+ const eventDate = new Date(event.timestamp);
211
+ return eventDate >= cutoffDate;
212
+ });
213
+
214
+ return recentOverrides.length > 0;
215
+ } catch (error) {
216
+ console.warn('Could not check recent overrides:', error.message);
217
+ return false;
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Generate override report
223
+ */
224
+ generateOverrideReport(format = 'summary') {
225
+ try {
226
+ const ledger = authorityLedger.readLedger();
227
+ const overrides = ledger.ledger.filter(e => e.decision === 'OVERRIDDEN');
228
+
229
+ switch (format) {
230
+ case 'summary':
231
+ return this.generateSummaryReport(overrides, ledger);
232
+ case 'detailed':
233
+ return this.generateDetailedReport(overrides, ledger);
234
+ default:
235
+ throw new Error(`Unsupported report format: ${format}`);
236
+ }
237
+ } catch (error) {
238
+ console.error('Failed to generate override report:', error.message);
239
+ throw error;
240
+ }
241
+ }
242
+
243
+ generateSummaryReport(overrides, ledger) {
244
+ const blockedEvents = ledger.ledger.filter(e => e.decision === 'BLOCKED');
245
+ const uniqueCommits = [...new Set(overrides.map(o => o.commit))];
246
+
247
+ return {
248
+ total_overrides: overrides.length,
249
+ total_blocked_events: blockedEvents.length,
250
+ unique_commits_with_overrides: uniqueCommits.length,
251
+ most_common_owner: this.getMostCommonValue(overrides, 'author'),
252
+ recent_overrides: overrides.slice(-5).map(o => ({
253
+ commit: o.commit,
254
+ owner: o.author,
255
+ reason: o.override.reason.substring(0, 50) + '...'
256
+ }))
257
+ };
258
+ }
259
+
260
+ generateDetailedReport(overrides, ledger) {
261
+ return {
262
+ report_generated: new Date().toISOString(),
263
+ statistics: this.generateSummaryReport(overrides, ledger),
264
+ all_overrides: overrides.map(override => ({
265
+ event_id: override.event_id,
266
+ timestamp: override.timestamp,
267
+ commit: override.commit,
268
+ owner: override.author,
269
+ reason: override.override.reason,
270
+ original_violations: this.getOriginalViolations(override.original_blocked_event, ledger)
271
+ }))
272
+ };
273
+ }
274
+
275
+ getMostCommonValue(array, key) {
276
+ const counts = {};
277
+ array.forEach(item => {
278
+ const value = item[key];
279
+ counts[value] = (counts[value] || 0) + 1;
280
+ });
281
+
282
+ let maxCount = 0;
283
+ let mostCommon = null;
284
+
285
+ for (const [value, count] of Object.entries(counts)) {
286
+ if (count > maxCount) {
287
+ maxCount = count;
288
+ mostCommon = value;
289
+ }
290
+ }
291
+
292
+ return mostCommon;
293
+ }
294
+
295
+ getOriginalViolations(eventId, ledger) {
296
+ const blockedEvent = ledger.ledger.find(e => e.event_id === eventId);
297
+ return blockedEvent?.violations || [];
298
+ }
299
+ }
300
+
301
+ // Export singleton instance
302
+ const overrideHandler = new OverrideHandler();
303
+
304
+ module.exports = { OverrideHandler, overrideHandler };
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Ownership Resolver - Resolves ownership of system concepts
3
+ * Establishes single source of truth for each concept in the system
4
+ */
5
+
6
+ class OwnershipResolver {
7
+ constructor() {
8
+ this.ownershipMap = {};
9
+ }
10
+
11
+ /**
12
+ * Resolve ownership for concepts in the architecture map
13
+ * @param {Object} architectureMap - The architecture map with nodes and edges
14
+ * @returns {Object} Ownership map showing source of truth, derivations, and consumers
15
+ */
16
+ resolveOwnership(architectureMap) {
17
+ const { nodes, edges } = architectureMap;
18
+ this.ownershipMap = {};
19
+
20
+ // Group nodes by concept (based on file names, roles, etc.)
21
+ const conceptGroups = this.groupNodesByConcept(nodes);
22
+
23
+ // For each concept group, determine ownership
24
+ for (const [concept, conceptNodes] of Object.entries(conceptGroups)) {
25
+ const ownershipInfo = this.determineOwnershipForConcept(concept, conceptNodes, edges);
26
+ if (ownershipInfo) {
27
+ this.ownershipMap[concept] = ownershipInfo;
28
+ }
29
+ }
30
+
31
+ return this.ownershipMap;
32
+ }
33
+
34
+ /**
35
+ * Group nodes by concept based on naming patterns and roles
36
+ */
37
+ groupNodesByConcept(nodes) {
38
+ const groups = {};
39
+
40
+ for (const node of nodes) {
41
+ // Extract concept from filename or path
42
+ const concept = this.extractConceptFromPath(node.path || node.id);
43
+
44
+ if (!groups[concept]) {
45
+ groups[concept] = [];
46
+ }
47
+
48
+ groups[concept].push(node);
49
+ }
50
+
51
+ return groups;
52
+ }
53
+
54
+ /**
55
+ * Extract concept from file path
56
+ */
57
+ extractConceptFromPath(filePath) {
58
+ if (!filePath) return 'unknown';
59
+
60
+ // Remove file extension and extract base concept
61
+ const baseName = filePath.replace(/\.[^/.]+$/, '');
62
+ const parts = baseName.split(/[\/\\]/);
63
+ const fileName = parts[parts.length - 1];
64
+
65
+ // Normalize common patterns
66
+ return fileName.toLowerCase()
67
+ .replace(/controller|service|handler|manager|store|provider|hook/g, '')
68
+ .replace(/_/g, '')
69
+ .replace(/-/g, '');
70
+ }
71
+
72
+ /**
73
+ * Determine ownership for a specific concept
74
+ */
75
+ determineOwnershipForConcept(concept, conceptNodes, edges) {
76
+ // Sort nodes by authority indicators (blast radius, criticality, etc.)
77
+ const sortedNodes = [...conceptNodes].sort((a, b) => {
78
+ const aScore = this.calculateOwnershipScore(a);
79
+ const bScore = this.calculateOwnershipScore(b);
80
+ return bScore - aScore;
81
+ });
82
+
83
+ if (sortedNodes.length === 0) {
84
+ return null;
85
+ }
86
+
87
+ // Pick the highest authority node as the source of truth
88
+ const sourceOfTruth = sortedNodes[0];
89
+
90
+ // Find derived nodes and consumers
91
+ const derivedNodes = [];
92
+ const consumers = [];
93
+ const invalidators = [];
94
+
95
+ // Analyze edges to determine relationships
96
+ for (const edge of edges) {
97
+ if (edge.from === sourceOfTruth.id) {
98
+ const targetNode = conceptNodes.find(n => n.id === edge.target);
99
+ if (targetNode && targetNode.id !== sourceOfTruth.id) {
100
+ if (edge.relation === 'imports' || edge.relation === 'depends_on') {
101
+ consumers.push(targetNode.path || targetNode.id);
102
+ } else if (edge.relation === 'calls') {
103
+ consumers.push(targetNode.path || targetNode.id);
104
+ }
105
+ }
106
+ }
107
+
108
+ // Check if other nodes modify the source of truth
109
+ if (edge.to === sourceOfTruth.id && edge.relation === 'calls') {
110
+ const sourceNode = conceptNodes.find(n => n.id === edge.from);
111
+ if (sourceNode && sourceNode.id !== sourceOfTruth.id) {
112
+ invalidators.push(sourceNode.path || sourceNode.id);
113
+ }
114
+ }
115
+ }
116
+
117
+ // Find derived nodes based on analysis
118
+ for (const node of conceptNodes) {
119
+ if (node.id !== sourceOfTruth.id) {
120
+ const derived = this.isDerivedNode(node, sourceOfTruth, edges);
121
+ if (derived) {
122
+ derivedNodes.push(node.path || node.id);
123
+ }
124
+ }
125
+ }
126
+
127
+ // Enhance with additional ownership analysis
128
+ const enhancedOwnership = {
129
+ source_of_truth: sourceOfTruth.path || sourceOfTruth.id,
130
+ derived_in: derivedNodes,
131
+ consumers: [...new Set(consumers)], // Remove duplicates
132
+ invalidators: [...new Set(invalidators)] // Remove duplicates
133
+ };
134
+
135
+ // Add additional ownership indicators
136
+ enhancedOwnership.ownership_strength = this.calculateOwnershipStrength(sourceOfTruth, conceptNodes);
137
+ enhancedOwnership.confidence = this.calculateOwnershipConfidence(enhancedOwnership);
138
+
139
+ return enhancedOwnership;
140
+ }
141
+
142
+ /**
143
+ * Calculate ownership strength for a node
144
+ */
145
+ calculateOwnershipStrength(sourceNode, allNodes) {
146
+ const signals = sourceNode.signals || {};
147
+ const intelligence = sourceNode.intelligence || {};
148
+
149
+ // Calculate based on various factors
150
+ return {
151
+ blast_radius_factor: (signals.blast_radius || 0) / 10,
152
+ criticality_factor: (signals.criticality || 0) / 10,
153
+ incoming_deps_factor: (signals.incoming_deps || 0) / 5,
154
+ function_count: intelligence.functions ? intelligence.functions.length : 0,
155
+ export_count: intelligence.exports ? intelligence.exports.length : 0,
156
+ total_score: this.calculateOwnershipScore(sourceNode)
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Calculate confidence in ownership assignment
162
+ */
163
+ calculateOwnershipConfidence(ownershipInfo) {
164
+ // Base confidence on the strength of the source of truth
165
+ let confidence = 0.7; // Base confidence
166
+
167
+ // Increase confidence based on number of consumers
168
+ if (ownershipInfo.consumers && ownershipInfo.consumers.length > 0) {
169
+ confidence += Math.min(ownershipInfo.consumers.length * 0.05, 0.2);
170
+ }
171
+
172
+ // Increase confidence based on presence of invalidators
173
+ if (ownershipInfo.invalidators && ownershipInfo.invalidators.length > 0) {
174
+ confidence += Math.min(ownershipInfo.invalidators.length * 0.05, 0.15);
175
+ }
176
+
177
+ // Increase confidence based on derived nodes
178
+ if (ownershipInfo.derived_in && ownershipInfo.derived_in.length > 0) {
179
+ confidence += Math.min(ownershipInfo.derived_in.length * 0.03, 0.1);
180
+ }
181
+
182
+ // Cap confidence at 0.95
183
+ return Math.min(confidence, 0.95);
184
+ }
185
+
186
+ /**
187
+ * Calculate ownership score for a node
188
+ */
189
+ calculateOwnershipScore(node) {
190
+ const signals = node.signals || {};
191
+ const intelligence = node.intelligence || {};
192
+
193
+ // Factors that contribute to ownership:
194
+ // - High blast radius indicates wide influence
195
+ // - High criticality indicates importance
196
+ // - Many incoming deps indicate central role
197
+ // - Functions/exports indicate active role
198
+ return (
199
+ (signals.blast_radius || 0) * 2 +
200
+ (signals.criticality || 0) * 1.5 +
201
+ (signals.incoming_deps || 0) * 0.5 +
202
+ (intelligence.functions ? intelligence.functions.length : 0) * 0.3 +
203
+ (intelligence.exports ? intelligence.exports.length : 0) * 0.2
204
+ );
205
+ }
206
+
207
+ /**
208
+ * Determine if a node is derived from another
209
+ */
210
+ isDerivedNode(node, sourceOfTruth, edges) {
211
+ // Check if this node imports from or depends on the source of truth
212
+ const hasImportEdge = edges.some(edge =>
213
+ edge.to === node.id && edge.from === sourceOfTruth.id &&
214
+ (edge.relation === 'imports' || edge.relation === 'depends_on')
215
+ );
216
+
217
+ // Check if this node uses data from the source of truth
218
+ const hasCallEdge = edges.some(edge =>
219
+ edge.to === node.id && edge.from === sourceOfTruth.id &&
220
+ edge.relation === 'calls'
221
+ );
222
+
223
+ return hasImportEdge || hasCallEdge;
224
+ }
225
+ }
226
+
227
+ module.exports = { OwnershipResolver };