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.
- package/ARCVISION_DIRECTORY_STRUCTURE.md +104 -0
- package/CLI_STRUCTURE.md +110 -0
- package/CONFIGURATION.md +119 -0
- package/IMPLEMENTATION_SUMMARY.md +99 -0
- package/README.md +149 -89
- package/architecture.authority.ledger.json +46 -0
- package/arcvision-0.2.3.tgz +0 -0
- package/arcvision-0.2.4.tgz +0 -0
- package/arcvision-0.2.5.tgz +0 -0
- package/arcvision.context.diff.json +2181 -0
- package/arcvision.context.json +1021 -0
- package/arcvision.context.v1.json +2163 -0
- package/arcvision.context.v2.json +2173 -0
- package/arcvision_context/README.md +93 -0
- package/arcvision_context/architecture.authority.ledger.json +83 -0
- package/arcvision_context/arcvision.context.json +6884 -0
- package/debug-cycle-detection.js +56 -0
- package/dist/index.js +1626 -25
- package/docs/ENHANCED_ACCURACY_SAFETY_PROTOCOL.md +172 -0
- package/docs/accuracy-enhancement-artifacts/enhanced-validation-config.json +98 -0
- package/docs/acig-robustness-guide.md +164 -0
- package/docs/authoritative-gate-implementation.md +168 -0
- package/docs/cli-strengthening-summary.md +232 -0
- package/docs/invariant-system-summary.md +100 -0
- package/docs/invariant-system.md +112 -0
- package/generate_large_test.js +42 -0
- package/large_test_repo.json +1 -0
- package/output1.json +2163 -0
- package/output2.json +2163 -0
- package/package.json +46 -36
- package/scan_calcom_report.txt +0 -0
- package/scan_leafmint_report.txt +0 -0
- package/scan_output.txt +0 -0
- package/scan_trigger_report.txt +0 -0
- package/schema/arcvision_context_schema_v1.json +136 -1
- package/src/arcvision-guard.js +433 -0
- package/src/core/authority-core-detector.js +382 -0
- package/src/core/authority-ledger.js +300 -0
- package/src/core/blastRadius.js +299 -0
- package/src/core/call-resolver.js +196 -0
- package/src/core/change-evaluator.js +509 -0
- package/src/core/change-evaluator.js.backup +424 -0
- package/src/core/change-evaluator.ts +285 -0
- package/src/core/chunked-uploader.js +180 -0
- package/src/core/circular-dependency-detector.js +404 -0
- package/src/core/cli-error-handler.js +458 -0
- package/src/core/cli-validator.js +458 -0
- package/src/core/compression.js +64 -0
- package/src/core/context_builder.js +741 -0
- package/src/core/dependency-manager.js +134 -0
- package/src/core/di-detector.js +202 -0
- package/src/core/diff-analyzer.js +76 -0
- package/src/core/example-invariants.js +135 -0
- package/src/core/failure-mode-synthesizer.js +341 -0
- package/src/core/invariant-analyzer.js +294 -0
- package/src/core/invariant-detector.js +548 -0
- package/src/core/invariant-enforcer.js +171 -0
- package/src/core/invariant-evaluation-utils.js +172 -0
- package/src/core/invariant-hooks.js +152 -0
- package/src/core/invariant-integration-example.js +186 -0
- package/src/core/invariant-registry.js +298 -0
- package/src/core/invariant-registry.ts +100 -0
- package/src/core/invariant-types.js +66 -0
- package/src/core/invariants-index.js +88 -0
- package/src/core/method-tracker.js +170 -0
- package/src/core/override-handler.js +304 -0
- package/src/core/ownership-resolver.js +227 -0
- package/src/core/parser-enhanced.js +80 -0
- package/src/core/parser.js +610 -0
- package/src/core/path-resolver.js +240 -0
- package/src/core/pattern-matcher.js +246 -0
- package/src/core/progress-tracker.js +71 -0
- package/src/core/react-nextjs-detector.js +245 -0
- package/src/core/readme-generator.js +167 -0
- package/src/core/retry-handler.js +57 -0
- package/src/core/scanner.js +289 -0
- package/src/core/semantic-analyzer.js +204 -0
- package/src/core/structural-context-owner.js +442 -0
- package/src/core/symbol-indexer.js +164 -0
- package/src/core/tsconfig-utils.js +73 -0
- package/src/core/type-analyzer.js +272 -0
- package/src/core/watcher.js +18 -0
- package/src/core/workspace-scanner.js +88 -0
- package/src/engine/context_builder.js +280 -0
- package/src/engine/context_sorter.js +59 -0
- package/src/engine/context_validator.js +200 -0
- package/src/engine/id-generator.js +16 -0
- package/src/engine/pass1_facts.js +260 -0
- package/src/engine/pass2_semantics.js +333 -0
- package/src/engine/pass3_lifter.js +99 -0
- package/src/engine/pass4_signals.js +201 -0
- package/src/index.js +830 -0
- package/src/plugins/express-plugin.js +48 -0
- package/src/plugins/plugin-manager.js +58 -0
- package/src/plugins/react-plugin.js +54 -0
- package/temp_original.js +0 -0
- package/test/determinism-test.js +83 -0
- package/test-authoritative-context.js +53 -0
- package/test-real-authoritative-context.js +118 -0
- package/test-upload-enhancements.js +111 -0
- package/test_repos/allowed-clean-architecture/.arcvision/invariants.json +57 -0
- package/test_repos/allowed-clean-architecture/adapters/controllers/UserController.js +95 -0
- package/test_repos/allowed-clean-architecture/adapters/http/HttpServer.js +78 -0
- package/test_repos/allowed-clean-architecture/application/dtos/CreateUserRequest.js +37 -0
- package/test_repos/allowed-clean-architecture/application/services/UserService.js +61 -0
- package/test_repos/allowed-clean-architecture/arcvision_context/README.md +93 -0
- package/test_repos/allowed-clean-architecture/arcvision_context/arcvision.context.json +2796 -0
- package/test_repos/allowed-clean-architecture/domain/interfaces/UserRepository.js +25 -0
- package/test_repos/allowed-clean-architecture/domain/models/User.js +39 -0
- package/test_repos/allowed-clean-architecture/index.js +45 -0
- package/test_repos/allowed-clean-architecture/infrastructure/database/DatabaseConnection.js +56 -0
- package/test_repos/allowed-clean-architecture/infrastructure/repositories/InMemoryUserRepository.js +61 -0
- package/test_repos/allowed-clean-architecture/package.json +15 -0
- package/test_repos/blocked-legacy-monolith/.arcvision/invariants.json +78 -0
- package/test_repos/blocked-legacy-monolith/arcvision_context/README.md +93 -0
- package/test_repos/blocked-legacy-monolith/arcvision_context/arcvision.context.json +2882 -0
- package/test_repos/blocked-legacy-monolith/database/dbConnection.js +35 -0
- package/test_repos/blocked-legacy-monolith/index.js +38 -0
- package/test_repos/blocked-legacy-monolith/modules/emailService.js +31 -0
- package/test_repos/blocked-legacy-monolith/modules/paymentProcessor.js +37 -0
- package/test_repos/blocked-legacy-monolith/package.json +15 -0
- package/test_repos/blocked-legacy-monolith/shared/utils.js +19 -0
- package/test_repos/blocked-legacy-monolith/utils/helpers.js +23 -0
- package/test_repos/risky-microservices-concerns/.arcvision/invariants.json +69 -0
- package/test_repos/risky-microservices-concerns/arcvision_context/README.md +93 -0
- package/test_repos/risky-microservices-concerns/arcvision_context/arcvision.context.json +3070 -0
- package/test_repos/risky-microservices-concerns/common/utils.js +77 -0
- package/test_repos/risky-microservices-concerns/gateways/apiGateway.js +84 -0
- package/test_repos/risky-microservices-concerns/index.js +20 -0
- package/test_repos/risky-microservices-concerns/libs/deprecatedHelper.js +36 -0
- package/test_repos/risky-microservices-concerns/package.json +15 -0
- package/test_repos/risky-microservices-concerns/services/orderService.js +42 -0
- package/test_repos/risky-microservices-concerns/services/userService.js +48 -0
- 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 };
|