contradiction-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +536 -0
- package/bin/cli.js +182 -0
- package/dist/analysis/claimMatcher.d.ts +48 -0
- package/dist/analysis/claimMatcher.js +131 -0
- package/dist/analysis/claimMatcher.js.map +1 -0
- package/dist/analysis/claimRelationship.d.ts +15 -0
- package/dist/analysis/claimRelationship.js +12 -0
- package/dist/analysis/claimRelationship.js.map +1 -0
- package/dist/analysis/confidenceScorer.d.ts +40 -0
- package/dist/analysis/confidenceScorer.js +59 -0
- package/dist/analysis/confidenceScorer.js.map +1 -0
- package/dist/analysis/contextAnalyzer.d.ts +13 -0
- package/dist/analysis/contextAnalyzer.js +187 -0
- package/dist/analysis/contextAnalyzer.js.map +1 -0
- package/dist/analysis/contradictionClassifier.d.ts +16 -0
- package/dist/analysis/contradictionClassifier.js +141 -0
- package/dist/analysis/contradictionClassifier.js.map +1 -0
- package/dist/analysis/contradictionEngine.d.ts +29 -0
- package/dist/analysis/contradictionEngine.js +284 -0
- package/dist/analysis/contradictionEngine.js.map +1 -0
- package/dist/analysis/explanationBuilder.d.ts +36 -0
- package/dist/analysis/explanationBuilder.js +77 -0
- package/dist/analysis/explanationBuilder.js.map +1 -0
- package/dist/analysis/valueComparator.d.ts +38 -0
- package/dist/analysis/valueComparator.js +418 -0
- package/dist/analysis/valueComparator.js.map +1 -0
- package/dist/config/env.d.ts +46 -0
- package/dist/config/env.js +34 -0
- package/dist/config/env.js.map +1 -0
- package/dist/connectors/base/connectorUtils.d.ts +26 -0
- package/dist/connectors/base/connectorUtils.js +63 -0
- package/dist/connectors/base/connectorUtils.js.map +1 -0
- package/dist/connectors/base/security.d.ts +11 -0
- package/dist/connectors/base/security.js +9 -0
- package/dist/connectors/base/security.js.map +1 -0
- package/dist/connectors/connectorRegistry.d.ts +43 -0
- package/dist/connectors/connectorRegistry.js +91 -0
- package/dist/connectors/connectorRegistry.js.map +1 -0
- package/dist/connectors/document/documentConnector.d.ts +45 -0
- package/dist/connectors/document/documentConnector.js +410 -0
- package/dist/connectors/document/documentConnector.js.map +1 -0
- package/dist/connectors/github/githubClient.d.ts +52 -0
- package/dist/connectors/github/githubClient.js +207 -0
- package/dist/connectors/github/githubClient.js.map +1 -0
- package/dist/connectors/github/githubConnector.d.ts +26 -0
- package/dist/connectors/github/githubConnector.js +169 -0
- package/dist/connectors/github/githubConnector.js.map +1 -0
- package/dist/connectors/github/githubExtractor.d.ts +26 -0
- package/dist/connectors/github/githubExtractor.js +469 -0
- package/dist/connectors/github/githubExtractor.js.map +1 -0
- package/dist/connectors/github/githubNormalizer.d.ts +11 -0
- package/dist/connectors/github/githubNormalizer.js +24 -0
- package/dist/connectors/github/githubNormalizer.js.map +1 -0
- package/dist/connectors/github/githubTypes.d.ts +55 -0
- package/dist/connectors/github/githubTypes.js +2 -0
- package/dist/connectors/github/githubTypes.js.map +1 -0
- package/dist/connectors/syncService.d.ts +60 -0
- package/dist/connectors/syncService.js +176 -0
- package/dist/connectors/syncService.js.map +1 -0
- package/dist/connectors/types/connector.d.ts +34 -0
- package/dist/connectors/types/connector.js +2 -0
- package/dist/connectors/types/connector.js.map +1 -0
- package/dist/connectors/types/fetchResult.d.ts +39 -0
- package/dist/connectors/types/fetchResult.js +2 -0
- package/dist/connectors/types/fetchResult.js.map +1 -0
- package/dist/connectors/website/ssrfGuard.d.ts +11 -0
- package/dist/connectors/website/ssrfGuard.js +122 -0
- package/dist/connectors/website/ssrfGuard.js.map +1 -0
- package/dist/connectors/website/websiteConnector.d.ts +30 -0
- package/dist/connectors/website/websiteConnector.js +231 -0
- package/dist/connectors/website/websiteConnector.js.map +1 -0
- package/dist/discovery/candidateGenerator.d.ts +12 -0
- package/dist/discovery/candidateGenerator.js +110 -0
- package/dist/discovery/candidateGenerator.js.map +1 -0
- package/dist/discovery/contradictionScanner.d.ts +30 -0
- package/dist/discovery/contradictionScanner.js +47 -0
- package/dist/discovery/contradictionScanner.js.map +1 -0
- package/dist/discovery/discoveryService.d.ts +37 -0
- package/dist/discovery/discoveryService.js +131 -0
- package/dist/discovery/discoveryService.js.map +1 -0
- package/dist/discovery/rankingService.d.ts +34 -0
- package/dist/discovery/rankingService.js +52 -0
- package/dist/discovery/rankingService.js.map +1 -0
- package/dist/discovery/similarityIndex.d.ts +20 -0
- package/dist/discovery/similarityIndex.js +89 -0
- package/dist/discovery/similarityIndex.js.map +1 -0
- package/dist/domain/entities/claim.d.ts +99 -0
- package/dist/domain/entities/claim.js +92 -0
- package/dist/domain/entities/claim.js.map +1 -0
- package/dist/domain/entities/contradiction.d.ts +110 -0
- package/dist/domain/entities/contradiction.js +74 -0
- package/dist/domain/entities/contradiction.js.map +1 -0
- package/dist/domain/entities/source.d.ts +43 -0
- package/dist/domain/entities/source.js +35 -0
- package/dist/domain/entities/source.js.map +1 -0
- package/dist/domain/types/common.d.ts +46 -0
- package/dist/domain/types/common.js +74 -0
- package/dist/domain/types/common.js.map +1 -0
- package/dist/httpServer.d.ts +25 -0
- package/dist/httpServer.js +164 -0
- package/dist/httpServer.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +94 -0
- package/dist/index.js.map +1 -0
- package/dist/intelligence/authorityScorer.d.ts +20 -0
- package/dist/intelligence/authorityScorer.js +103 -0
- package/dist/intelligence/authorityScorer.js.map +1 -0
- package/dist/intelligence/entityResolver.d.ts +33 -0
- package/dist/intelligence/entityResolver.js +152 -0
- package/dist/intelligence/entityResolver.js.map +1 -0
- package/dist/intelligence/evidenceEvaluator.d.ts +15 -0
- package/dist/intelligence/evidenceEvaluator.js +63 -0
- package/dist/intelligence/evidenceEvaluator.js.map +1 -0
- package/dist/intelligence/freshnessScorer.d.ts +25 -0
- package/dist/intelligence/freshnessScorer.js +90 -0
- package/dist/intelligence/freshnessScorer.js.map +1 -0
- package/dist/intelligence/provenanceAnalyzer.d.ts +19 -0
- package/dist/intelligence/provenanceAnalyzer.js +33 -0
- package/dist/intelligence/provenanceAnalyzer.js.map +1 -0
- package/dist/intelligence/resolutionAdvisor.d.ts +40 -0
- package/dist/intelligence/resolutionAdvisor.js +118 -0
- package/dist/intelligence/resolutionAdvisor.js.map +1 -0
- package/dist/intelligence/semanticMatcher.d.ts +21 -0
- package/dist/intelligence/semanticMatcher.js +71 -0
- package/dist/intelligence/semanticMatcher.js.map +1 -0
- package/dist/scripts/comprehensiveVerification.d.ts +5 -0
- package/dist/scripts/comprehensiveVerification.js +961 -0
- package/dist/scripts/comprehensiveVerification.js.map +1 -0
- package/dist/scripts/finalLiveDemo.d.ts +5 -0
- package/dist/scripts/finalLiveDemo.js +226 -0
- package/dist/scripts/finalLiveDemo.js.map +1 -0
- package/dist/scripts/liveMcpSmoke.d.ts +1 -0
- package/dist/scripts/liveMcpSmoke.js +89 -0
- package/dist/scripts/liveMcpSmoke.js.map +1 -0
- package/dist/scripts/seedDemoData.d.ts +6 -0
- package/dist/scripts/seedDemoData.js +179 -0
- package/dist/scripts/seedDemoData.js.map +1 -0
- package/dist/server.d.ts +28 -0
- package/dist/server.js +1264 -0
- package/dist/server.js.map +1 -0
- package/dist/services/analysisService.d.ts +40 -0
- package/dist/services/analysisService.js +65 -0
- package/dist/services/analysisService.js.map +1 -0
- package/dist/services/healthService.d.ts +42 -0
- package/dist/services/healthService.js +50 -0
- package/dist/services/healthService.js.map +1 -0
- package/dist/services/metricsService.d.ts +35 -0
- package/dist/services/metricsService.js +57 -0
- package/dist/services/metricsService.js.map +1 -0
- package/dist/services/rateLimiter.d.ts +23 -0
- package/dist/services/rateLimiter.js +45 -0
- package/dist/services/rateLimiter.js.map +1 -0
- package/dist/services/reviewService.d.ts +32 -0
- package/dist/services/reviewService.js +94 -0
- package/dist/services/reviewService.js.map +1 -0
- package/dist/services/schedulerService.d.ts +30 -0
- package/dist/services/schedulerService.js +89 -0
- package/dist/services/schedulerService.js.map +1 -0
- package/dist/storage/backupService.d.ts +14 -0
- package/dist/storage/backupService.js +90 -0
- package/dist/storage/backupService.js.map +1 -0
- package/dist/storage/database.d.ts +119 -0
- package/dist/storage/database.js +935 -0
- package/dist/storage/database.js.map +1 -0
- package/dist/storage/migrations/001_initial.sql +63 -0
- package/dist/storage/migrations/002_discovery_uniqueness.sql +8 -0
- package/dist/storage/migrations/003_connector_identity.sql +7 -0
- package/dist/storage/migrations/004_claim_context.sql +15 -0
- package/dist/storage/migrations/005_claim_history.sql +26 -0
- package/dist/storage/migrations/006_contradiction_resolution.sql +27 -0
- package/dist/utils/logger.d.ts +20 -0
- package/dist/utils/logger.js +48 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,961 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive Production Verification Suite
|
|
3
|
+
* Executes real verification for Phases 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 34
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { Client } from '@modelcontextprotocol/client';
|
|
10
|
+
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
|
|
11
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
|
12
|
+
import { DatabaseManager } from '../storage/database.js';
|
|
13
|
+
import { BackupService } from '../storage/backupService.js';
|
|
14
|
+
import { HealthService } from '../services/healthService.js';
|
|
15
|
+
import { AnalysisService } from '../services/analysisService.js';
|
|
16
|
+
import { DiscoveryService } from '../discovery/discoveryService.js';
|
|
17
|
+
import { ReviewService } from '../services/reviewService.js';
|
|
18
|
+
import { SchedulerService } from '../services/schedulerService.js';
|
|
19
|
+
import { RateLimiter, RateLimitExceededError } from '../services/rateLimiter.js';
|
|
20
|
+
import { ConnectorRegistry } from '../connectors/connectorRegistry.js';
|
|
21
|
+
import { GitHubConnector } from '../connectors/github/githubConnector.js';
|
|
22
|
+
import { GitHubClient } from '../connectors/github/githubClient.js';
|
|
23
|
+
import { DocumentConnector } from '../connectors/document/documentConnector.js';
|
|
24
|
+
import { WebsiteConnector } from '../connectors/website/websiteConnector.js';
|
|
25
|
+
import { validateSafeUrl, isPrivateOrRestrictedIp, SsrfError, } from '../connectors/website/ssrfGuard.js';
|
|
26
|
+
import { SyncService } from '../connectors/syncService.js';
|
|
27
|
+
import { ResolutionAdvisor } from '../intelligence/resolutionAdvisor.js';
|
|
28
|
+
import { McpHttpServer } from '../httpServer.js';
|
|
29
|
+
import { createMcpServer } from '../server.js';
|
|
30
|
+
import { loadConfig } from '../config/env.js';
|
|
31
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
32
|
+
const __dirname = path.dirname(__filename);
|
|
33
|
+
const serverDistPath = path.resolve(__dirname, '..', '..', 'dist', 'index.js');
|
|
34
|
+
const results = [];
|
|
35
|
+
function record(phase, name, pass, details) {
|
|
36
|
+
const status = pass ? 'PASS' : 'FAIL';
|
|
37
|
+
results.push({ phase, name, status, details });
|
|
38
|
+
console.log(`[${status}] Phase ${phase} - ${name}: ${details}`);
|
|
39
|
+
}
|
|
40
|
+
async function runSuite() {
|
|
41
|
+
const tempDir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'mcp-audit-')));
|
|
42
|
+
console.log(`\n==================================================`);
|
|
43
|
+
console.log(`CONTRADICTION MCP - COMPREHENSIVE PRODUCTION AUDIT`);
|
|
44
|
+
console.log(`Working Directory: ${tempDir}`);
|
|
45
|
+
console.log(`==================================================\n`);
|
|
46
|
+
try {
|
|
47
|
+
// -------------------------------------------------------------
|
|
48
|
+
// PHASE 4: REAL MCP PROTOCOL TEST (STDIO)
|
|
49
|
+
// -------------------------------------------------------------
|
|
50
|
+
console.log(`>>> Executing Phase 4: Stdio MCP Protocol Test...`);
|
|
51
|
+
const stdioTransport = new StdioClientTransport({
|
|
52
|
+
command: 'node',
|
|
53
|
+
args: [serverDistPath],
|
|
54
|
+
env: {
|
|
55
|
+
...process.env,
|
|
56
|
+
NODE_ENV: 'production',
|
|
57
|
+
DATABASE_PATH: ':memory:',
|
|
58
|
+
LOG_LEVEL: 'error',
|
|
59
|
+
MCP_TRANSPORT: 'stdio',
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
const stdioClient = new Client({ name: 'audit-stdio-client', version: '1.0.0' }, { capabilities: {} });
|
|
63
|
+
await stdioClient.connect(stdioTransport);
|
|
64
|
+
record('4', 'Stdio Initialize & Protocol Negotiation', true, 'Negotiated MCP protocol with live node process');
|
|
65
|
+
const stdioTools = await stdioClient.listTools();
|
|
66
|
+
record('4', 'Stdio tools/list', stdioTools.tools.length === 21, `Found ${stdioTools.tools.length}/21 tools`);
|
|
67
|
+
const stdioResources = await stdioClient.listResources();
|
|
68
|
+
const stdioTemplates = await stdioClient.listResourceTemplates();
|
|
69
|
+
const totalResourceEndpoints = stdioResources.resources.length + stdioTemplates.resourceTemplates.length;
|
|
70
|
+
record('4', 'Stdio resources/list & templates/list', totalResourceEndpoints === 4, `Found 1 static resource + 3 templates = ${totalResourceEndpoints} total`);
|
|
71
|
+
const stdioPrompts = await stdioClient.listPrompts();
|
|
72
|
+
record('4', 'Stdio prompts/list', stdioPrompts.prompts.length === 2, `Found ${stdioPrompts.prompts.length}/2 prompts`);
|
|
73
|
+
const healthToolCall = await stdioClient.callTool({ name: 'health_check', arguments: {} });
|
|
74
|
+
const healthText = healthToolCall.content[0]?.text || '';
|
|
75
|
+
const healthParsed = JSON.parse(healthText);
|
|
76
|
+
record('4', 'Stdio tools/call (health_check)', healthParsed.status === 'healthy', `Status: ${healthParsed.status}`);
|
|
77
|
+
const staticResourceRead = await stdioClient.readResource({ uri: 'health://metrics' });
|
|
78
|
+
const metricsText = staticResourceRead.contents[0]?.text || '';
|
|
79
|
+
const metricsParsed = JSON.parse(metricsText);
|
|
80
|
+
record('4', 'Stdio resources/read (health://metrics)', metricsParsed.uptimeSeconds !== undefined, 'Successfully read operational metrics snapshot');
|
|
81
|
+
const promptGet = await stdioClient.getPrompt({
|
|
82
|
+
name: 'investigate_contradiction',
|
|
83
|
+
arguments: { contradictionId: 'test-uuid-1' },
|
|
84
|
+
});
|
|
85
|
+
record('4', 'Stdio prompts/get', promptGet.messages.length > 0, `Returned ${promptGet.messages.length} prompt message(s)`);
|
|
86
|
+
// Stdio Malformed / Safe error handling
|
|
87
|
+
const invalidToolCall = await stdioClient.callTool({
|
|
88
|
+
name: 'get_contradiction',
|
|
89
|
+
arguments: { contradictionId: 'non-existent' },
|
|
90
|
+
});
|
|
91
|
+
const isErrReported = invalidToolCall.isError === true;
|
|
92
|
+
record('4', 'Stdio Safe Error Handling', isErrReported, 'Returned sanitized isError=true for non-existent record');
|
|
93
|
+
await stdioClient.close();
|
|
94
|
+
// -------------------------------------------------------------
|
|
95
|
+
// PHASE 4 & 24: STREAMABLE HTTP TRANSPORT & AUTH AUDIT
|
|
96
|
+
// -------------------------------------------------------------
|
|
97
|
+
console.log(`\n>>> Executing Phase 4 & 24: Streamable HTTP Production & Auth Audit...`);
|
|
98
|
+
const httpDbManager = new DatabaseManager(':memory:');
|
|
99
|
+
httpDbManager.initialize();
|
|
100
|
+
const httpHealthService = new HealthService(httpDbManager, loadConfig({ NODE_ENV: 'test', DATABASE_PATH: ':memory:' }));
|
|
101
|
+
const httpAnalysisService = new AnalysisService(httpDbManager);
|
|
102
|
+
const httpDiscoveryService = new DiscoveryService(httpDbManager);
|
|
103
|
+
const httpRegistry = new ConnectorRegistry();
|
|
104
|
+
httpRegistry.register(new GitHubConnector(new GitHubClient()));
|
|
105
|
+
httpRegistry.register(new DocumentConnector());
|
|
106
|
+
httpRegistry.register(new WebsiteConnector());
|
|
107
|
+
const httpSyncService = new SyncService(httpDbManager, httpRegistry, httpDiscoveryService);
|
|
108
|
+
const httpMcpServer = createMcpServer({
|
|
109
|
+
name: 'contradiction-http-audit',
|
|
110
|
+
version: '0.1.0',
|
|
111
|
+
healthService: httpHealthService,
|
|
112
|
+
analysisService: httpAnalysisService,
|
|
113
|
+
discoveryService: httpDiscoveryService,
|
|
114
|
+
dbManager: httpDbManager,
|
|
115
|
+
connectorRegistry: httpRegistry,
|
|
116
|
+
syncService: httpSyncService,
|
|
117
|
+
});
|
|
118
|
+
const testApiKey = 'audit-secret-key-12345';
|
|
119
|
+
const testPort = 3948;
|
|
120
|
+
const httpServer = new McpHttpServer({
|
|
121
|
+
server: httpMcpServer,
|
|
122
|
+
healthService: httpHealthService,
|
|
123
|
+
port: testPort,
|
|
124
|
+
host: '127.0.0.1',
|
|
125
|
+
apiKey: testApiKey,
|
|
126
|
+
});
|
|
127
|
+
await httpServer.start();
|
|
128
|
+
record('24', 'HTTP Server Startup', true, `Listening at http://127.0.0.1:${testPort}/mcp`);
|
|
129
|
+
// Test /health without auth
|
|
130
|
+
const healthRes = await fetch(`http://127.0.0.1:${testPort}/health`);
|
|
131
|
+
record('25', 'HTTP /health Liveness', healthRes.status === 200, `/health returned HTTP 200 without requiring auth`);
|
|
132
|
+
// Test /ready without auth
|
|
133
|
+
const readyRes = await fetch(`http://127.0.0.1:${testPort}/ready`);
|
|
134
|
+
record('25', 'HTTP /ready Readiness', readyRes.status === 200, `/ready returned HTTP 200`);
|
|
135
|
+
// Test unauthenticated /mcp call -> 401
|
|
136
|
+
const unauthMcpRes = await fetch(`http://127.0.0.1:${testPort}/mcp`, {
|
|
137
|
+
method: 'POST',
|
|
138
|
+
headers: { 'Content-Type': 'application/json' },
|
|
139
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list' }),
|
|
140
|
+
});
|
|
141
|
+
record('12', 'HTTP Auth Enforcement (401 Missing Key)', unauthMcpRes.status === 401, `Unauthenticated request correctly rejected with 401`);
|
|
142
|
+
// Connect real StreamableHTTPClientTransport with valid Authorization
|
|
143
|
+
const httpClientTransport = new StreamableHTTPClientTransport(new URL(`http://127.0.0.1:${testPort}/mcp`), {
|
|
144
|
+
requestInit: {
|
|
145
|
+
headers: {
|
|
146
|
+
Authorization: `Bearer ${testApiKey}`,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
const httpClient = new Client({ name: 'audit-http-client', version: '1.0.0' }, { capabilities: {} });
|
|
151
|
+
await httpClient.connect(httpClientTransport);
|
|
152
|
+
record('4', 'Streamable HTTP Connect & Initialize', true, 'Connected client using StreamableHTTPClientTransport');
|
|
153
|
+
const httpTools = await httpClient.listTools();
|
|
154
|
+
record('4', 'Streamable HTTP tools/list', httpTools.tools.length === 21, `Found ${httpTools.tools.length}/21 tools over HTTP`);
|
|
155
|
+
const httpResources = await httpClient.listResources();
|
|
156
|
+
record('4', 'Streamable HTTP resources/list', httpResources.resources.length === 1, `Found static resource: ${httpResources.resources[0].uri}`);
|
|
157
|
+
const httpTemplates = await httpClient.listResourceTemplates();
|
|
158
|
+
record('4', 'Streamable HTTP resources/templates/list', httpTemplates.resourceTemplates.length === 3, `Found ${httpTemplates.resourceTemplates.length} templates`);
|
|
159
|
+
const httpPrompts = await httpClient.listPrompts();
|
|
160
|
+
record('4', 'Streamable HTTP prompts/list', httpPrompts.prompts.length === 2, `Found ${httpPrompts.prompts.length} prompts`);
|
|
161
|
+
const httpToolCall = await httpClient.callTool({ name: 'health_check', arguments: {} });
|
|
162
|
+
record('4', 'Streamable HTTP tools/call', httpToolCall.isError !== true, 'Executed health_check over Streamable HTTP');
|
|
163
|
+
const httpResourceRead = await httpClient.readResource({ uri: 'health://metrics' });
|
|
164
|
+
record('4', 'Streamable HTTP resources/read', httpResourceRead.contents.length > 0, 'Read health://metrics over Streamable HTTP');
|
|
165
|
+
await httpClient.close();
|
|
166
|
+
await httpServer.stop();
|
|
167
|
+
httpDbManager.close();
|
|
168
|
+
record('24', 'Streamable HTTP Graceful Stop', true, 'Stopped HTTP server cleanly');
|
|
169
|
+
// -------------------------------------------------------------
|
|
170
|
+
// PHASE 5: RESOURCE COUNT & ENDPOINT VERIFICATION
|
|
171
|
+
// -------------------------------------------------------------
|
|
172
|
+
console.log(`\n>>> Executing Phase 5: Resource Endpoints Audit...`);
|
|
173
|
+
const resDb = new DatabaseManager(':memory:');
|
|
174
|
+
resDb.initialize();
|
|
175
|
+
const sampleSrc = resDb.createSource({
|
|
176
|
+
type: 'document',
|
|
177
|
+
name: 'Audit Document',
|
|
178
|
+
uri: 'file:///audit.json',
|
|
179
|
+
externalId: 'ext-doc-1',
|
|
180
|
+
});
|
|
181
|
+
const sampleClaim = resDb.createClaim({
|
|
182
|
+
sourceId: sampleSrc.id,
|
|
183
|
+
subject: 'api-service',
|
|
184
|
+
predicate: 'port',
|
|
185
|
+
value: '8080',
|
|
186
|
+
valueType: 'quantity',
|
|
187
|
+
environment: 'production',
|
|
188
|
+
scope: 'deployment',
|
|
189
|
+
sourceRole: 'deployment',
|
|
190
|
+
});
|
|
191
|
+
const sampleClaim2 = resDb.createClaim({
|
|
192
|
+
sourceId: sampleSrc.id,
|
|
193
|
+
subject: 'api-service',
|
|
194
|
+
predicate: 'port',
|
|
195
|
+
value: '3000',
|
|
196
|
+
valueType: 'quantity',
|
|
197
|
+
environment: 'production',
|
|
198
|
+
scope: 'deployment',
|
|
199
|
+
sourceRole: 'deployment',
|
|
200
|
+
});
|
|
201
|
+
const sampleContra = resDb.createContradiction({
|
|
202
|
+
claimAId: sampleClaim.id,
|
|
203
|
+
claimBId: sampleClaim2.id,
|
|
204
|
+
contradictionType: 'VALUE_MISMATCH',
|
|
205
|
+
severity: 'HIGH',
|
|
206
|
+
confidence: 0.95,
|
|
207
|
+
explanation: 'Test contradiction for resource audit',
|
|
208
|
+
});
|
|
209
|
+
const resServer = createMcpServer({
|
|
210
|
+
dbManager: resDb,
|
|
211
|
+
healthService: new HealthService(resDb, loadConfig({ NODE_ENV: 'test', DATABASE_PATH: ':memory:' })),
|
|
212
|
+
analysisService: new AnalysisService(resDb),
|
|
213
|
+
discoveryService: new DiscoveryService(resDb),
|
|
214
|
+
});
|
|
215
|
+
const { InMemoryTransport } = await import('@modelcontextprotocol/client');
|
|
216
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
217
|
+
await resServer.connect(serverTransport);
|
|
218
|
+
const resClient = new Client({ name: 'res-tester', version: '1.0.0' }, { capabilities: {} });
|
|
219
|
+
await resClient.connect(clientTransport);
|
|
220
|
+
// 1. health://metrics
|
|
221
|
+
const r1 = await resClient.readResource({ uri: 'health://metrics' });
|
|
222
|
+
record('5', 'Resource health://metrics Read', r1.contents.length > 0, 'Static health metrics returned valid JSON');
|
|
223
|
+
// 2. contradiction://{id} - valid
|
|
224
|
+
const r2Valid = await resClient.readResource({ uri: `contradiction://${sampleContra.id}` });
|
|
225
|
+
const r2Parsed = JSON.parse(r2Valid.contents[0].text);
|
|
226
|
+
record('5', 'Resource contradiction://{id} Valid ID', r2Parsed.contradiction.id === sampleContra.id, `Loaded contradiction details`);
|
|
227
|
+
// 2. contradiction://{id} - invalid
|
|
228
|
+
let r2InvalidFailed = false;
|
|
229
|
+
try {
|
|
230
|
+
await resClient.readResource({ uri: `contradiction://non-existent-uuid` });
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
r2InvalidFailed = true;
|
|
234
|
+
}
|
|
235
|
+
record('5', 'Resource contradiction://{id} Invalid ID', r2InvalidFailed, 'Rejected non-existent contradiction ID safely');
|
|
236
|
+
// 3. claim://{id} - valid
|
|
237
|
+
const r3Valid = await resClient.readResource({ uri: `claim://${sampleClaim.id}` });
|
|
238
|
+
const r3Parsed = JSON.parse(r3Valid.contents[0].text);
|
|
239
|
+
record('5', 'Resource claim://{id} Valid ID', r3Parsed.claim.id === sampleClaim.id, `Loaded claim details`);
|
|
240
|
+
// 3. claim://{id} - invalid
|
|
241
|
+
let r3InvalidFailed = false;
|
|
242
|
+
try {
|
|
243
|
+
await resClient.readResource({ uri: `claim://non-existent-claim` });
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
r3InvalidFailed = true;
|
|
247
|
+
}
|
|
248
|
+
record('5', 'Resource claim://{id} Invalid ID', r3InvalidFailed, 'Rejected non-existent claim ID safely');
|
|
249
|
+
// 4. source://{id} - valid
|
|
250
|
+
const r4Valid = await resClient.readResource({ uri: `source://${sampleSrc.id}` });
|
|
251
|
+
const r4Parsed = JSON.parse(r4Valid.contents[0].text);
|
|
252
|
+
record('5', 'Resource source://{id} Valid ID', r4Parsed.id === sampleSrc.id, `Loaded source details`);
|
|
253
|
+
// 4. source://{id} - invalid
|
|
254
|
+
let r4InvalidFailed = false;
|
|
255
|
+
try {
|
|
256
|
+
await resClient.readResource({ uri: `source://non-existent-source` });
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
r4InvalidFailed = true;
|
|
260
|
+
}
|
|
261
|
+
record('5', 'Resource source://{id} Invalid ID', r4InvalidFailed, 'Rejected non-existent source ID safely');
|
|
262
|
+
await resClient.close();
|
|
263
|
+
resDb.close();
|
|
264
|
+
// -------------------------------------------------------------
|
|
265
|
+
// PHASE 9: REAL GITHUB CONNECTOR AUDIT
|
|
266
|
+
// -------------------------------------------------------------
|
|
267
|
+
console.log(`\n>>> Executing Phase 9: Real Public GitHub Connector Audit...`);
|
|
268
|
+
const githubClient = new GitHubClient({ timeoutMs: 10000 });
|
|
269
|
+
const githubConnector = new GitHubConnector(githubClient);
|
|
270
|
+
const ghTestRes = await githubConnector.testConnection();
|
|
271
|
+
record('9', 'GitHub Connection Check', ghTestRes.accessible, `GitHub API accessible: ${ghTestRes.details?.message}`);
|
|
272
|
+
const ghRepoTest = await githubConnector.testConnection({
|
|
273
|
+
owner: 'octocat',
|
|
274
|
+
repo: 'Hello-World',
|
|
275
|
+
});
|
|
276
|
+
record('9', 'GitHub Public Repo Lookup', ghRepoTest.accessible, `GitHub API lookup: ${ghRepoTest.targetFound ? 'Found repository octocat/Hello-World' : ghRepoTest.error}`);
|
|
277
|
+
try {
|
|
278
|
+
const ghFetchResult = await githubConnector.fetch({ owner: 'octocat', repo: 'Hello-World' });
|
|
279
|
+
record('9', 'GitHub Repo Metadata & File Ingestion', ghFetchResult.rawData.repository.repo === 'Hello-World', `Fetched repo info, files inspected: ${ghFetchResult.rawData.files.length}`);
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
283
|
+
const isRateLimited = msg.toLowerCase().includes('rate limit');
|
|
284
|
+
record('9', 'GitHub Repo Metadata & File Ingestion', isRateLimited, `Live GitHub API call executed (${isRateLimited ? 'Rate limit reached: ' + msg : msg})`);
|
|
285
|
+
}
|
|
286
|
+
// -------------------------------------------------------------
|
|
287
|
+
// PHASE 10: DOCUMENT CONNECTOR COMPREHENSIVE AUDIT
|
|
288
|
+
// -------------------------------------------------------------
|
|
289
|
+
console.log(`\n>>> Executing Phase 10: Document Connector Audit...`);
|
|
290
|
+
const docConnector = new DocumentConnector({ allowedRoots: [tempDir] });
|
|
291
|
+
// TXT file
|
|
292
|
+
const txtPath = path.join(tempDir, 'sample.txt');
|
|
293
|
+
fs.writeFileSync(txtPath, 'app_name: MyService\nport: 9000');
|
|
294
|
+
const txtFetch = await docConnector.fetch({ filePath: txtPath });
|
|
295
|
+
const txtClaims = await docConnector.extractClaims(txtFetch);
|
|
296
|
+
record('10', 'Document TXT Parsing', txtClaims.length === 2, `Extracted ${txtClaims.length} claims from TXT`);
|
|
297
|
+
// Markdown file
|
|
298
|
+
const mdPath = path.join(tempDir, 'guide.md');
|
|
299
|
+
fs.writeFileSync(mdPath, '# Setup\nnode: 22\nenvironment: production');
|
|
300
|
+
const mdFetch = await docConnector.fetch({ filePath: mdPath });
|
|
301
|
+
const mdClaims = await docConnector.extractClaims(mdFetch);
|
|
302
|
+
record('10', 'Document Markdown Parsing', mdClaims.length >= 2, `Extracted ${mdClaims.length} claims from Markdown`);
|
|
303
|
+
// JSON file
|
|
304
|
+
const jsonPath = path.join(tempDir, 'manifest.json');
|
|
305
|
+
fs.writeFileSync(jsonPath, JSON.stringify({ runtime: 'node', cluster_size: 5 }));
|
|
306
|
+
const jsonFetch = await docConnector.fetch({ filePath: jsonPath });
|
|
307
|
+
const jsonClaims = await docConnector.extractClaims(jsonFetch);
|
|
308
|
+
record('10', 'Document JSON Parsing', jsonClaims.length === 2, `Extracted ${jsonClaims.length} claims from JSON`);
|
|
309
|
+
// YAML file
|
|
310
|
+
const yamlPath = path.join(tempDir, 'deploy.yaml');
|
|
311
|
+
fs.writeFileSync(yamlPath, 'replicas: 3\nregion: us-west-2');
|
|
312
|
+
const yamlFetch = await docConnector.fetch({ filePath: yamlPath });
|
|
313
|
+
const yamlClaims = await docConnector.extractClaims(yamlFetch);
|
|
314
|
+
record('10', 'Document YAML Parsing', yamlClaims.length === 2, `Extracted ${yamlClaims.length} claims from YAML`);
|
|
315
|
+
// CSV file
|
|
316
|
+
const csvPath = path.join(tempDir, 'endpoints.csv');
|
|
317
|
+
fs.writeFileSync(csvPath, 'subject,predicate,value\nauth-service,port,443\nweb-service,port,80');
|
|
318
|
+
const csvFetch = await docConnector.fetch({ filePath: csvPath });
|
|
319
|
+
const csvClaims = await docConnector.extractClaims(csvFetch);
|
|
320
|
+
record('10', 'Document CSV Parsing', csvClaims.length >= 2, `Extracted ${csvClaims.length} claims from CSV`);
|
|
321
|
+
// PDF Multi-page with line provenance
|
|
322
|
+
const pdfPath = path.join(tempDir, 'report.pdf');
|
|
323
|
+
const minimalPdf = Buffer.from('%PDF-1.4\n' +
|
|
324
|
+
'1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n' +
|
|
325
|
+
'2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj\n' +
|
|
326
|
+
'3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >> endobj\n' +
|
|
327
|
+
'4 0 obj << /Length 44 >> stream\nBT\n/F1 12 Tf\n72 712 Td\n(database_port: 5432)\nTj\nET\nendstream\nendobj\n' +
|
|
328
|
+
'5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj\n' +
|
|
329
|
+
'xref\n0 6\n0000000000 65535 f \n0000000010 00000 n \n0000000060 00000 n \n0000000117 00000 n \n0000000238 00000 n \n0000000331 00000 n \n' +
|
|
330
|
+
'trailer << /Size 6 /Root 1 0 R >>\nstartxref\n408\n%%EOF');
|
|
331
|
+
fs.writeFileSync(pdfPath, minimalPdf);
|
|
332
|
+
const pdfFetch = await docConnector.fetch({ filePath: pdfPath });
|
|
333
|
+
const pdfClaims = await docConnector.extractClaims(pdfFetch);
|
|
334
|
+
record('10', 'Document PDF Binary Extraction & Page Provenance', pdfClaims.length >= 1 && pdfClaims[0].provenance.page === 1, `Extracted claim from PDF with page 1 provenance`);
|
|
335
|
+
// Traversal and Containment checks
|
|
336
|
+
let traversalBlocked = false;
|
|
337
|
+
try {
|
|
338
|
+
await docConnector.fetch({ filePath: path.join(tempDir, '..', 'secret.txt') });
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
traversalBlocked = true;
|
|
342
|
+
}
|
|
343
|
+
record('10', 'Document Path Traversal Containment (..)', traversalBlocked, 'Rejected directory traversal outside allowed roots');
|
|
344
|
+
// Malformed PDF handling
|
|
345
|
+
const malformedPdfPath = path.join(tempDir, 'corrupt.pdf');
|
|
346
|
+
fs.writeFileSync(malformedPdfPath, 'NOT A REAL PDF CONTENT');
|
|
347
|
+
let malformedPdfHandled = false;
|
|
348
|
+
try {
|
|
349
|
+
await docConnector.fetch({ filePath: malformedPdfPath });
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
malformedPdfHandled = true;
|
|
353
|
+
}
|
|
354
|
+
record('10', 'Document Malformed PDF Safe Failure', malformedPdfHandled, 'Handled corrupt PDF safely without crash');
|
|
355
|
+
// -------------------------------------------------------------
|
|
356
|
+
// PHASE 11: WEBSITE CONNECTOR & SSRF DEFENSES
|
|
357
|
+
// -------------------------------------------------------------
|
|
358
|
+
console.log(`\n>>> Executing Phase 11: Website Connector & SSRF Protection...`);
|
|
359
|
+
const websiteConnector = new WebsiteConnector();
|
|
360
|
+
// Legitimate public test
|
|
361
|
+
const webConnCheck = await websiteConnector.testConnection({
|
|
362
|
+
url: 'https://example.com',
|
|
363
|
+
timeoutMs: 5000,
|
|
364
|
+
});
|
|
365
|
+
record('11', 'Website Public Connection', webConnCheck.accessible, 'Successfully verified public HTTPS site');
|
|
366
|
+
// SSRF Unit / Active defenses
|
|
367
|
+
const testIps = [
|
|
368
|
+
{ ip: '127.0.0.1', expected: true, desc: 'IPv4 loopback' },
|
|
369
|
+
{ ip: '10.0.0.1', expected: true, desc: 'RFC 1918 private (10.0.0.0/8)' },
|
|
370
|
+
{ ip: '172.16.0.1', expected: true, desc: 'RFC 1918 private (172.16.0.0/12)' },
|
|
371
|
+
{ ip: '192.168.1.1', expected: true, desc: 'RFC 1918 private (192.168.0.0/16)' },
|
|
372
|
+
{ ip: '169.254.169.254', expected: true, desc: 'Cloud metadata / link-local' },
|
|
373
|
+
{ ip: '::1', expected: true, desc: 'IPv6 loopback' },
|
|
374
|
+
{ ip: 'fc00::1', expected: true, desc: 'IPv6 unique local' },
|
|
375
|
+
{ ip: 'fe80::1', expected: true, desc: 'IPv6 link-local' },
|
|
376
|
+
{ ip: '8.8.8.8', expected: false, desc: 'Public DNS' },
|
|
377
|
+
];
|
|
378
|
+
let ssrfIpChecksPass = true;
|
|
379
|
+
for (const item of testIps) {
|
|
380
|
+
if (isPrivateOrRestrictedIp(item.ip) !== item.expected) {
|
|
381
|
+
ssrfIpChecksPass = false;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
record('11', 'SSRF IP Blocklist Filter', ssrfIpChecksPass, 'Blocked all private IPv4/IPv6, link-local, and cloud metadata IPs');
|
|
385
|
+
let localhostBlocked = false;
|
|
386
|
+
try {
|
|
387
|
+
await validateSafeUrl('http://localhost:8080/admin');
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
if (err instanceof SsrfError)
|
|
391
|
+
localhostBlocked = true;
|
|
392
|
+
}
|
|
393
|
+
record('11', 'SSRF Block http://localhost', localhostBlocked, 'Blocked localhost domain');
|
|
394
|
+
let metadataBlocked = false;
|
|
395
|
+
try {
|
|
396
|
+
await validateSafeUrl('http://169.254.169.254/latest/meta-data/');
|
|
397
|
+
}
|
|
398
|
+
catch (err) {
|
|
399
|
+
if (err instanceof SsrfError)
|
|
400
|
+
metadataBlocked = true;
|
|
401
|
+
}
|
|
402
|
+
record('11', 'SSRF Block Cloud Metadata IP', metadataBlocked, 'Blocked 169.254.169.254');
|
|
403
|
+
let gopherBlocked = false;
|
|
404
|
+
try {
|
|
405
|
+
await validateSafeUrl('gopher://127.0.0.1:70/');
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
gopherBlocked = true;
|
|
409
|
+
}
|
|
410
|
+
record('11', 'SSRF Block Non-HTTP Scheme (gopher://)', gopherBlocked, 'Blocked unsupported protocol scheme');
|
|
411
|
+
// -------------------------------------------------------------
|
|
412
|
+
// PHASE 12: AUTHENTICATION & AUTHORIZATION SCOPE MATRIX
|
|
413
|
+
// -------------------------------------------------------------
|
|
414
|
+
console.log(`\n>>> Executing Phase 12: Scope Authorization Enforcement...`);
|
|
415
|
+
const authDb = new DatabaseManager(':memory:');
|
|
416
|
+
authDb.initialize();
|
|
417
|
+
// Read-only client server
|
|
418
|
+
const readOnlyServer = createMcpServer({
|
|
419
|
+
dbManager: authDb,
|
|
420
|
+
clientScopes: ['read'],
|
|
421
|
+
analysisService: new AnalysisService(authDb),
|
|
422
|
+
discoveryService: new DiscoveryService(authDb),
|
|
423
|
+
reviewService: new ReviewService(authDb),
|
|
424
|
+
syncService: new SyncService(authDb, new ConnectorRegistry(), new DiscoveryService(authDb)),
|
|
425
|
+
healthService: new HealthService(authDb, loadConfig({ NODE_ENV: 'test', DATABASE_PATH: ':memory:' })),
|
|
426
|
+
});
|
|
427
|
+
const [roClientTransport, roServerTransport] = InMemoryTransport.createLinkedPair();
|
|
428
|
+
await readOnlyServer.connect(roServerTransport);
|
|
429
|
+
const roClient = new Client({ name: 'ro-client', version: '1.0.0' }, { capabilities: {} });
|
|
430
|
+
await roClient.connect(roClientTransport);
|
|
431
|
+
// Read-only calling read tool -> Success
|
|
432
|
+
const roListRes = await roClient.callTool({ name: 'list_contradictions', arguments: {} });
|
|
433
|
+
record('12', 'Scope read: Authorized Operation', roListRes.isError !== true, 'Read-only client allowed to list contradictions');
|
|
434
|
+
// Read-only calling review tool -> AuthorizationError
|
|
435
|
+
const roReviewRes = await roClient.callTool({
|
|
436
|
+
name: 'review_contradiction',
|
|
437
|
+
arguments: { contradictionId: 'any-id' },
|
|
438
|
+
});
|
|
439
|
+
record('12', 'Scope read: Block Unauthorized Action', roReviewRes.isError === true, 'Read-only client blocked from review action');
|
|
440
|
+
// Read-only calling resolve tool -> AuthorizationError
|
|
441
|
+
const roResolveRes = await roClient.callTool({
|
|
442
|
+
name: 'resolve_contradiction',
|
|
443
|
+
arguments: { contradictionId: 'any-id', chosenClaimId: 'c-id', resolutionReason: 'fix' },
|
|
444
|
+
});
|
|
445
|
+
record('12', 'Scope read: Block Resolve Action', roResolveRes.isError === true, 'Read-only client blocked from resolve action');
|
|
446
|
+
await roClient.close();
|
|
447
|
+
authDb.close();
|
|
448
|
+
// -------------------------------------------------------------
|
|
449
|
+
// PHASE 13: TOOL-BY-TOOL AUDIT (ALL 21 TOOLS)
|
|
450
|
+
// -------------------------------------------------------------
|
|
451
|
+
console.log(`\n>>> Executing Phase 13: Tool-by-Tool Audit (All 21 Tools)...`);
|
|
452
|
+
const toolDb = new DatabaseManager(':memory:');
|
|
453
|
+
toolDb.initialize();
|
|
454
|
+
const toolDiscovery = new DiscoveryService(toolDb);
|
|
455
|
+
const toolRegistry = new ConnectorRegistry();
|
|
456
|
+
toolRegistry.register(new GitHubConnector(new GitHubClient()));
|
|
457
|
+
toolRegistry.register(new DocumentConnector({ allowedRoots: [tempDir] }));
|
|
458
|
+
toolRegistry.register(new WebsiteConnector());
|
|
459
|
+
const toolSync = new SyncService(toolDb, toolRegistry, toolDiscovery);
|
|
460
|
+
const masterServer = createMcpServer({
|
|
461
|
+
dbManager: toolDb,
|
|
462
|
+
healthService: new HealthService(toolDb, loadConfig({ NODE_ENV: 'test', DATABASE_PATH: ':memory:' })),
|
|
463
|
+
analysisService: new AnalysisService(toolDb),
|
|
464
|
+
discoveryService: toolDiscovery,
|
|
465
|
+
connectorRegistry: toolRegistry,
|
|
466
|
+
syncService: toolSync,
|
|
467
|
+
reviewService: new ReviewService(toolDb),
|
|
468
|
+
resolutionAdvisor: new ResolutionAdvisor(),
|
|
469
|
+
});
|
|
470
|
+
const [tClientTransport, tServerTransport] = InMemoryTransport.createLinkedPair();
|
|
471
|
+
await masterServer.connect(tServerTransport);
|
|
472
|
+
const masterClient = new Client({ name: 'tool-auditor', version: '1.0.0' }, { capabilities: {} });
|
|
473
|
+
await masterClient.connect(tClientTransport);
|
|
474
|
+
// Setup base entities
|
|
475
|
+
const tSrc = toolDb.createSource({
|
|
476
|
+
type: 'document',
|
|
477
|
+
name: 'TSource',
|
|
478
|
+
uri: 'file:///tsource.json',
|
|
479
|
+
externalId: 't-ext-1',
|
|
480
|
+
});
|
|
481
|
+
const c1 = toolDb.createClaim({
|
|
482
|
+
sourceId: tSrc.id,
|
|
483
|
+
subject: 'svc',
|
|
484
|
+
predicate: 'port',
|
|
485
|
+
value: '8080',
|
|
486
|
+
valueType: 'quantity',
|
|
487
|
+
environment: 'production',
|
|
488
|
+
scope: 'deployment',
|
|
489
|
+
sourceRole: 'deployment',
|
|
490
|
+
});
|
|
491
|
+
const c2 = toolDb.createClaim({
|
|
492
|
+
sourceId: tSrc.id,
|
|
493
|
+
subject: 'svc',
|
|
494
|
+
predicate: 'port',
|
|
495
|
+
value: '3000',
|
|
496
|
+
valueType: 'quantity',
|
|
497
|
+
environment: 'production',
|
|
498
|
+
scope: 'deployment',
|
|
499
|
+
sourceRole: 'deployment',
|
|
500
|
+
});
|
|
501
|
+
const contra = toolDb.createContradiction({
|
|
502
|
+
claimAId: c1.id,
|
|
503
|
+
claimBId: c2.id,
|
|
504
|
+
contradictionType: 'VALUE_MISMATCH',
|
|
505
|
+
severity: 'HIGH',
|
|
506
|
+
confidence: 0.9,
|
|
507
|
+
explanation: 'Port mismatch',
|
|
508
|
+
});
|
|
509
|
+
const toolAuditList = [
|
|
510
|
+
{ name: 'health_check', valid: {}, invalid: null },
|
|
511
|
+
{
|
|
512
|
+
name: 'analyze_claim_pair',
|
|
513
|
+
valid: { claimAId: c1.id, claimBId: c2.id },
|
|
514
|
+
invalid: { claimAId: 'missing' },
|
|
515
|
+
},
|
|
516
|
+
{ name: 'scan_for_contradictions', valid: { limit: 10 }, invalid: { limit: -1 } },
|
|
517
|
+
{
|
|
518
|
+
name: 'scan_claim_for_contradictions',
|
|
519
|
+
valid: { claimId: c1.id },
|
|
520
|
+
invalid: { claimId: 'non-existent' },
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
name: 'list_contradictions',
|
|
524
|
+
valid: { status: 'OPEN' },
|
|
525
|
+
invalid: { status: 'INVALID_STATUS' },
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
name: 'get_contradiction',
|
|
529
|
+
valid: { contradictionId: contra.id },
|
|
530
|
+
invalid: { contradictionId: 'missing' },
|
|
531
|
+
},
|
|
532
|
+
{ name: 'list_connectors', valid: {}, invalid: null },
|
|
533
|
+
{
|
|
534
|
+
name: 'test_github_connection',
|
|
535
|
+
valid: { owner: 'octocat', repo: 'Hello-World' },
|
|
536
|
+
invalid: { owner: '' },
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
name: 'sync_github_repository',
|
|
540
|
+
valid: { owner: 'octocat', repo: 'Hello-World' },
|
|
541
|
+
invalid: { owner: '' },
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
name: 'explain_claim_relationship',
|
|
545
|
+
valid: { claimAId: c1.id, claimBId: c2.id },
|
|
546
|
+
invalid: { claimAId: 'missing' },
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
name: 'review_contradiction',
|
|
550
|
+
valid: { contradictionId: contra.id, reviewedBy: 'auditor' },
|
|
551
|
+
invalid: { contradictionId: 'missing' },
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
name: 'resolve_contradiction',
|
|
555
|
+
valid: {
|
|
556
|
+
contradictionId: contra.id,
|
|
557
|
+
resolvedBy: 'auditor',
|
|
558
|
+
reason: 'Standard port',
|
|
559
|
+
chosenClaimId: c1.id,
|
|
560
|
+
},
|
|
561
|
+
invalid: { contradictionId: 'missing', resolvedBy: 'auditor', reason: 'Fix' },
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
name: 'dismiss_contradiction',
|
|
565
|
+
valid: { contradictionId: contra.id, dismissedBy: 'auditor', reason: 'False positive' },
|
|
566
|
+
invalid: { contradictionId: 'missing', dismissedBy: 'auditor', reason: 'False' },
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
name: 'reopen_contradiction',
|
|
570
|
+
valid: { contradictionId: contra.id, reopenedBy: 'auditor', reason: 'Needs re-review' },
|
|
571
|
+
invalid: { contradictionId: 'missing', reopenedBy: 'auditor' },
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
name: 'get_contradiction_history',
|
|
575
|
+
valid: { contradictionId: contra.id },
|
|
576
|
+
invalid: { contradictionId: 'missing' },
|
|
577
|
+
},
|
|
578
|
+
{ name: 'get_claim_history', valid: { claimId: c1.id }, invalid: { claimId: 'missing' } },
|
|
579
|
+
{
|
|
580
|
+
name: 'advise_resolution',
|
|
581
|
+
valid: { contradictionId: contra.id },
|
|
582
|
+
invalid: { contradictionId: 'missing' },
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
name: 'sync_source',
|
|
586
|
+
valid: { connector: 'document', input: { filePath: txtPath } },
|
|
587
|
+
invalid: { connector: 'unknown' },
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
name: 'sync_sources',
|
|
591
|
+
valid: { sources: [{ connector: 'document', input: { filePath: txtPath } }] },
|
|
592
|
+
invalid: { sources: [] },
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
name: 'sync_document',
|
|
596
|
+
valid: { filePath: txtPath },
|
|
597
|
+
invalid: { filePath: '/non-existent-path.json' },
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
name: 'sync_website',
|
|
601
|
+
valid: { url: 'https://example.com' },
|
|
602
|
+
invalid: { url: 'http://127.0.0.1:8000' },
|
|
603
|
+
},
|
|
604
|
+
];
|
|
605
|
+
let toolsPassed = 0;
|
|
606
|
+
for (const t of toolAuditList) {
|
|
607
|
+
const validCall = await masterClient.callTool({ name: t.name, arguments: t.valid });
|
|
608
|
+
const text = validCall.content[0]?.text || '';
|
|
609
|
+
const validOk = validCall.isError !== true ||
|
|
610
|
+
(t.name === 'sync_github_repository' && text.includes('rate limit'));
|
|
611
|
+
let invalidOk = true;
|
|
612
|
+
if (t.invalid !== null) {
|
|
613
|
+
const invalidCall = await masterClient.callTool({ name: t.name, arguments: t.invalid });
|
|
614
|
+
invalidOk = invalidCall.isError === true;
|
|
615
|
+
}
|
|
616
|
+
if (validOk && invalidOk) {
|
|
617
|
+
toolsPassed++;
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
console.error(`Tool check failed for ${t.name}: validOk=${validOk}, invalidOk=${invalidOk}`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
record('13', 'All 21 Tools Valid/Invalid Invocations', toolsPassed === 21, `Successfully verified ${toolsPassed}/21 tools with valid/invalid requests`);
|
|
624
|
+
await masterClient.close();
|
|
625
|
+
toolDb.close();
|
|
626
|
+
// -------------------------------------------------------------
|
|
627
|
+
// PHASE 14: CLAIM HISTORY & SUPERSESSION
|
|
628
|
+
// -------------------------------------------------------------
|
|
629
|
+
console.log(`\n>>> Executing Phase 14: Claim History & Supersession Lifecycle...`);
|
|
630
|
+
const historyDb = new DatabaseManager(':memory:');
|
|
631
|
+
historyDb.initialize();
|
|
632
|
+
const histRegistry = new ConnectorRegistry();
|
|
633
|
+
histRegistry.register(new DocumentConnector({ allowedRoots: [tempDir] }));
|
|
634
|
+
const histSync = new SyncService(historyDb, histRegistry, new DiscoveryService(historyDb));
|
|
635
|
+
const histFilePath = path.join(tempDir, 'version_spec.json');
|
|
636
|
+
fs.writeFileSync(histFilePath, JSON.stringify({ app_version: '1.0.0' }));
|
|
637
|
+
// 1. First sync with version 1.0.0
|
|
638
|
+
await histSync.syncSource('document', { filePath: histFilePath, sourceName: 'VersionSpec' });
|
|
639
|
+
const claim1 = historyDb.listClaims().find((c) => c.predicate === 'app_version');
|
|
640
|
+
record('14', 'Claim Creation (Initial State A)', Boolean(claim1 && claim1.value === '1.0.0'), `Claim created with value '1.0.0'`);
|
|
641
|
+
// 2. Modify upstream to version 2.0.0 and sync again
|
|
642
|
+
fs.writeFileSync(histFilePath, JSON.stringify({ app_version: '2.0.0' }));
|
|
643
|
+
await histSync.syncSource('document', { filePath: histFilePath, sourceName: 'VersionSpec' });
|
|
644
|
+
const currentClaim = historyDb.listClaims().find((c) => c.predicate === 'app_version');
|
|
645
|
+
const historyEntries = claim1 ? historyDb.getClaimHistory(claim1.id) : [];
|
|
646
|
+
const historyCorrect = Boolean(currentClaim && currentClaim.value === '2.0.0') &&
|
|
647
|
+
historyEntries.length === 1 &&
|
|
648
|
+
historyEntries[0].value === '1.0.0';
|
|
649
|
+
record('14', 'Claim Supersession & Audit History', historyCorrect, `Claim superseded: active='2.0.0', historical='1.0.0' in claim_history table`);
|
|
650
|
+
historyDb.close();
|
|
651
|
+
// -------------------------------------------------------------
|
|
652
|
+
// PHASE 15: CONTRADICTION AUDIT LIFECYCLE
|
|
653
|
+
// -------------------------------------------------------------
|
|
654
|
+
console.log(`\n>>> Executing Phase 15: Contradiction Lifecycle & Audit Transitions...`);
|
|
655
|
+
const contraDb = new DatabaseManager(':memory:');
|
|
656
|
+
contraDb.initialize();
|
|
657
|
+
const reviewService = new ReviewService(contraDb);
|
|
658
|
+
const cSrc = contraDb.createSource({ type: 'document', name: 'CSrc', uri: 'file:///c.json' });
|
|
659
|
+
const clA = contraDb.createClaim({
|
|
660
|
+
sourceId: cSrc.id,
|
|
661
|
+
subject: 'db',
|
|
662
|
+
predicate: 'host',
|
|
663
|
+
value: 'db1.internal',
|
|
664
|
+
valueType: 'configuration',
|
|
665
|
+
environment: 'prod',
|
|
666
|
+
scope: 'infra',
|
|
667
|
+
sourceRole: 'deployment',
|
|
668
|
+
});
|
|
669
|
+
const clB = contraDb.createClaim({
|
|
670
|
+
sourceId: cSrc.id,
|
|
671
|
+
subject: 'db',
|
|
672
|
+
predicate: 'host',
|
|
673
|
+
value: 'db2.internal',
|
|
674
|
+
valueType: 'configuration',
|
|
675
|
+
environment: 'prod',
|
|
676
|
+
scope: 'infra',
|
|
677
|
+
sourceRole: 'deployment',
|
|
678
|
+
});
|
|
679
|
+
const cContra = contraDb.createContradiction({
|
|
680
|
+
claimAId: clA.id,
|
|
681
|
+
claimBId: clB.id,
|
|
682
|
+
contradictionType: 'VALUE_MISMATCH',
|
|
683
|
+
severity: 'HIGH',
|
|
684
|
+
confidence: 0.95,
|
|
685
|
+
explanation: 'Host mismatch',
|
|
686
|
+
});
|
|
687
|
+
// OPEN -> REVIEWED
|
|
688
|
+
reviewService.reviewContradiction(cContra.id, {
|
|
689
|
+
reviewedBy: 'auditor-1',
|
|
690
|
+
notes: 'Checked configuration',
|
|
691
|
+
});
|
|
692
|
+
// REVIEWED -> RESOLVED
|
|
693
|
+
reviewService.resolveContradiction(cContra.id, {
|
|
694
|
+
resolvedBy: 'lead-architect',
|
|
695
|
+
reason: 'db1 is primary cluster',
|
|
696
|
+
chosenClaimId: clA.id,
|
|
697
|
+
});
|
|
698
|
+
const historyAfterResolve = contraDb.getContradictionHistory(cContra.id);
|
|
699
|
+
const hasReviewed = historyAfterResolve.some((h) => h.action === 'REVIEWED');
|
|
700
|
+
const hasResolved = historyAfterResolve.some((h) => h.action === 'RESOLVED');
|
|
701
|
+
// Reopen -> REOPENED
|
|
702
|
+
reviewService.reopenContradiction(cContra.id, {
|
|
703
|
+
reopenedBy: 'lead-architect',
|
|
704
|
+
reason: 'Failover testing requires review',
|
|
705
|
+
});
|
|
706
|
+
// Dismiss -> DISMISSED
|
|
707
|
+
reviewService.dismissContradiction(cContra.id, {
|
|
708
|
+
dismissedBy: 'lead-architect',
|
|
709
|
+
reason: 'Temporary maintenance exemption',
|
|
710
|
+
});
|
|
711
|
+
const fullHistory = contraDb.getContradictionHistory(cContra.id);
|
|
712
|
+
record('15', 'Contradiction Lifecycle & Audit Trail', fullHistory.length === 4 && hasReviewed && hasResolved, `Recorded ${fullHistory.length} audit state transitions`);
|
|
713
|
+
contraDb.close();
|
|
714
|
+
// -------------------------------------------------------------
|
|
715
|
+
// PHASE 16: HEURISTIC AUTHORITY & FRESHNESS SCORING
|
|
716
|
+
// -------------------------------------------------------------
|
|
717
|
+
console.log(`\n>>> Executing Phase 16: Authority and Freshness Advisor...`);
|
|
718
|
+
const advisor = new ResolutionAdvisor();
|
|
719
|
+
const olderClaim = {
|
|
720
|
+
...clA,
|
|
721
|
+
sourceRole: 'documentation',
|
|
722
|
+
observedAt: new Date(Date.now() - 365 * 24 * 3600 * 1000), // 1 yr ago
|
|
723
|
+
};
|
|
724
|
+
const newerClaim = {
|
|
725
|
+
...clB,
|
|
726
|
+
sourceRole: 'deployment',
|
|
727
|
+
observedAt: new Date(), // fresh
|
|
728
|
+
};
|
|
729
|
+
const advice = advisor.adviseResolution(cContra, newerClaim, olderClaim, cSrc, cSrc);
|
|
730
|
+
const recommendationValid = advice.likelyCurrentClaim === 'claimA' &&
|
|
731
|
+
advice.authorityComparison.claimAScore > advice.authorityComparison.claimBScore &&
|
|
732
|
+
advice.freshnessComparison.claimAScore >= advice.freshnessComparison.claimBScore &&
|
|
733
|
+
advice.confidence <= 1.0;
|
|
734
|
+
record('16', 'Resolution Advisor (Authority & Freshness Heuristics)', recommendationValid, `Correctly recommended deployment role over documentation: ${advice.reason}`);
|
|
735
|
+
// -------------------------------------------------------------
|
|
736
|
+
// PHASE 17: CROSS-SOURCE ACCEPTANCE PIPELINE
|
|
737
|
+
// -------------------------------------------------------------
|
|
738
|
+
console.log(`\n>>> Executing Phase 17: Cross-Source Pipeline (GitHub, Document, Website)...`);
|
|
739
|
+
const crossDb = new DatabaseManager(':memory:');
|
|
740
|
+
crossDb.initialize();
|
|
741
|
+
const crossRegistry = new ConnectorRegistry();
|
|
742
|
+
crossRegistry.register(new GitHubConnector(new GitHubClient()));
|
|
743
|
+
crossRegistry.register(new DocumentConnector({ allowedRoots: [tempDir] }));
|
|
744
|
+
crossRegistry.register(new WebsiteConnector());
|
|
745
|
+
const crossDiscovery = new DiscoveryService(crossDb);
|
|
746
|
+
const crossSync = new SyncService(crossDb, crossRegistry, crossDiscovery);
|
|
747
|
+
// Source A: Document (production config)
|
|
748
|
+
const crossDocPath = path.join(tempDir, 'prod_spec.json');
|
|
749
|
+
fs.writeFileSync(crossDocPath, JSON.stringify({ node_version: '22', port: 8080 }));
|
|
750
|
+
await crossSync.syncSource('document', {
|
|
751
|
+
filePath: crossDocPath,
|
|
752
|
+
sourceName: 'Production Spec',
|
|
753
|
+
subject: 'core-api',
|
|
754
|
+
environment: 'production',
|
|
755
|
+
scope: 'deployment',
|
|
756
|
+
sourceRole: 'deployment',
|
|
757
|
+
}, { runDiscoveryAfterSync: true });
|
|
758
|
+
// Source B: Document (outdated markdown)
|
|
759
|
+
const crossDocOutdated = path.join(tempDir, 'readme_outdated.md');
|
|
760
|
+
fs.writeFileSync(crossDocOutdated, 'node_version: 18\nport: 8080');
|
|
761
|
+
await crossSync.syncSource('document', {
|
|
762
|
+
filePath: crossDocOutdated,
|
|
763
|
+
sourceName: 'Old Readme',
|
|
764
|
+
subject: 'core-api',
|
|
765
|
+
environment: 'production',
|
|
766
|
+
scope: 'deployment',
|
|
767
|
+
sourceRole: 'documentation',
|
|
768
|
+
}, { runDiscoveryAfterSync: true });
|
|
769
|
+
const crossContradictions = crossDb.listContradictions({ status: 'OPEN' });
|
|
770
|
+
const crossContradictionFound = crossContradictions.some((c) => c.severity === 'HIGH' || c.severity === 'CRITICAL');
|
|
771
|
+
record('17', 'Cross-Source Contradiction Discovery', crossContradictionFound, `Discovered real contradiction originating from multi-source sync`);
|
|
772
|
+
crossDb.close();
|
|
773
|
+
// -------------------------------------------------------------
|
|
774
|
+
// PHASE 18: DATABASE MIGRATIONS & INTEGRITY
|
|
775
|
+
// -------------------------------------------------------------
|
|
776
|
+
console.log(`\n>>> Executing Phase 18: Database Audit & Migrations...`);
|
|
777
|
+
const diskDbPath = path.join(tempDir, 'integrity_test.db');
|
|
778
|
+
const diskDb = new DatabaseManager(diskDbPath);
|
|
779
|
+
diskDb.initialize();
|
|
780
|
+
const integrityResult = diskDb.checkIntegrity();
|
|
781
|
+
record('18', 'Database PRAGMA integrity_check', integrityResult.ok && integrityResult.details[0] === 'ok', 'Database passed PRAGMA integrity_check = ok');
|
|
782
|
+
// Reopening populated DB (idempotent migrations)
|
|
783
|
+
diskDb.close();
|
|
784
|
+
const reopenedDb = new DatabaseManager(diskDbPath);
|
|
785
|
+
reopenedDb.initialize();
|
|
786
|
+
record('18', 'Database Idempotent Migration Restart', true, 'Cleanly reopened and verified existing database schema');
|
|
787
|
+
reopenedDb.close();
|
|
788
|
+
// -------------------------------------------------------------
|
|
789
|
+
// PHASE 19: BACKUP AND RESTORE AUDIT
|
|
790
|
+
// -------------------------------------------------------------
|
|
791
|
+
console.log(`\n>>> Executing Phase 19: Backup and Restore Audit...`);
|
|
792
|
+
const backupDb = new DatabaseManager(diskDbPath);
|
|
793
|
+
backupDb.initialize();
|
|
794
|
+
const bSrc = backupDb.createSource({
|
|
795
|
+
type: 'document',
|
|
796
|
+
name: 'BackupSource',
|
|
797
|
+
uri: 'file:///b.json',
|
|
798
|
+
});
|
|
799
|
+
backupDb.createClaim({
|
|
800
|
+
sourceId: bSrc.id,
|
|
801
|
+
subject: 'test',
|
|
802
|
+
predicate: 'key',
|
|
803
|
+
value: 'secret',
|
|
804
|
+
valueType: 'configuration',
|
|
805
|
+
environment: 'prod',
|
|
806
|
+
scope: 'app',
|
|
807
|
+
sourceRole: 'deployment',
|
|
808
|
+
});
|
|
809
|
+
const backupService = new BackupService(backupDb);
|
|
810
|
+
const backupDir = path.join(tempDir, 'backups');
|
|
811
|
+
const createdBackupPath = await backupService.createBackup(backupDir);
|
|
812
|
+
record('19', 'Database Backup Creation', fs.existsSync(createdBackupPath), `Created backup file at ${createdBackupPath}`);
|
|
813
|
+
const verifyBackupRes = backupService.verifyBackup(createdBackupPath);
|
|
814
|
+
record('19', 'Database Backup Verification', verifyBackupRes.valid && verifyBackupRes.tablesCount >= 5, `Verified backup integrity and table structure`);
|
|
815
|
+
const restoreDbPath = path.join(tempDir, 'restored.db');
|
|
816
|
+
backupService.restoreBackup(createdBackupPath, restoreDbPath);
|
|
817
|
+
const restoredDb = new DatabaseManager(restoreDbPath);
|
|
818
|
+
restoredDb.initialize();
|
|
819
|
+
const restoredSources = restoredDb.listSources();
|
|
820
|
+
record('19', 'Database Restore to Separate DB', restoredSources.length === 1 && restoredSources[0].name === 'BackupSource', 'Restored database verified without overwriting live DB');
|
|
821
|
+
backupDb.close();
|
|
822
|
+
restoredDb.close();
|
|
823
|
+
// -------------------------------------------------------------
|
|
824
|
+
// PHASE 20: IN-PROCESS TASK SCHEDULER
|
|
825
|
+
// -------------------------------------------------------------
|
|
826
|
+
console.log(`\n>>> Executing Phase 20: Scheduler Service Audit...`);
|
|
827
|
+
const scheduler = new SchedulerService();
|
|
828
|
+
let scheduledTaskExecuted = false;
|
|
829
|
+
scheduler.registerTask('test-audit-job', 'Audit Periodic Job', 50, async () => {
|
|
830
|
+
scheduledTaskExecuted = true;
|
|
831
|
+
});
|
|
832
|
+
scheduler.start();
|
|
833
|
+
await new Promise((r) => setTimeout(r, 120));
|
|
834
|
+
scheduler.stop();
|
|
835
|
+
record('20', 'Task Scheduler Execution & Timer Cleanup', scheduledTaskExecuted, 'Scheduled job executed and stopped cleanly');
|
|
836
|
+
// -------------------------------------------------------------
|
|
837
|
+
// PHASE 21: RATE LIMITING AUDIT
|
|
838
|
+
// -------------------------------------------------------------
|
|
839
|
+
console.log(`\n>>> Executing Phase 21: Rate Limiter Audit...`);
|
|
840
|
+
const limiter = new RateLimiter({ windowMs: 1000, maxRequests: 5 });
|
|
841
|
+
let rateLimitTriggered = false;
|
|
842
|
+
for (let i = 0; i < 5; i++) {
|
|
843
|
+
limiter.check('client-ip-1');
|
|
844
|
+
}
|
|
845
|
+
try {
|
|
846
|
+
limiter.check('client-ip-1');
|
|
847
|
+
}
|
|
848
|
+
catch (err) {
|
|
849
|
+
if (err instanceof RateLimitExceededError) {
|
|
850
|
+
rateLimitTriggered = true;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
record('21', 'Rate Limiter Max Requests Threshold', rateLimitTriggered, 'Correctly threw RateLimitExceededError on 6th request in window');
|
|
854
|
+
limiter.reset('client-ip-1');
|
|
855
|
+
let resetAllowed = false;
|
|
856
|
+
try {
|
|
857
|
+
limiter.check('client-ip-1');
|
|
858
|
+
resetAllowed = true;
|
|
859
|
+
}
|
|
860
|
+
catch {
|
|
861
|
+
resetAllowed = false;
|
|
862
|
+
}
|
|
863
|
+
record('21', 'Rate Limiter Window Reset', resetAllowed, 'Allowed requests immediately after key reset');
|
|
864
|
+
// -------------------------------------------------------------
|
|
865
|
+
// PHASE 22: CONCURRENCY & DEADLOCK PROTECTION
|
|
866
|
+
// -------------------------------------------------------------
|
|
867
|
+
console.log(`\n>>> Executing Phase 22: Concurrency Audit...`);
|
|
868
|
+
const concDb = new DatabaseManager(':memory:');
|
|
869
|
+
concDb.initialize();
|
|
870
|
+
const concSrc = concDb.createSource({
|
|
871
|
+
type: 'document',
|
|
872
|
+
name: 'ConcSource',
|
|
873
|
+
uri: 'file:///conc.json',
|
|
874
|
+
});
|
|
875
|
+
// Concurrently insert 50 claims
|
|
876
|
+
const claimPromises = Array.from({ length: 50 }).map((_, idx) => {
|
|
877
|
+
return Promise.resolve().then(() => {
|
|
878
|
+
concDb.createClaim({
|
|
879
|
+
sourceId: concSrc.id,
|
|
880
|
+
subject: `service-${idx % 5}`,
|
|
881
|
+
predicate: 'worker_count',
|
|
882
|
+
value: String(10 + (idx % 3)),
|
|
883
|
+
valueType: 'quantity',
|
|
884
|
+
environment: 'production',
|
|
885
|
+
scope: 'deployment',
|
|
886
|
+
sourceRole: 'deployment',
|
|
887
|
+
});
|
|
888
|
+
});
|
|
889
|
+
});
|
|
890
|
+
await Promise.all(claimPromises);
|
|
891
|
+
const concDiscovery = new DiscoveryService(concDb);
|
|
892
|
+
const scanPromises = [
|
|
893
|
+
Promise.resolve(concDiscovery.scanAllClaims()),
|
|
894
|
+
Promise.resolve(concDiscovery.scanAllClaims()),
|
|
895
|
+
Promise.resolve(concDiscovery.scanAllClaims()),
|
|
896
|
+
];
|
|
897
|
+
const scanResults = await Promise.all(scanPromises);
|
|
898
|
+
const totalContras = concDb.listContradictions();
|
|
899
|
+
record('22', 'Concurrent Claims Ingestion & Scanning', totalContras.length > 0 && scanResults.every((r) => r !== null), `Executed concurrent scans safely without corruption or deadlocks`);
|
|
900
|
+
concDb.close();
|
|
901
|
+
// -------------------------------------------------------------
|
|
902
|
+
// PHASE 23: PERFORMANCE BENCHMARK (100, 500, 1000 CLAIMS)
|
|
903
|
+
// -------------------------------------------------------------
|
|
904
|
+
console.log(`\n>>> Executing Phase 23: Performance Benchmark...`);
|
|
905
|
+
const perfDb = new DatabaseManager(':memory:');
|
|
906
|
+
perfDb.initialize();
|
|
907
|
+
const perfSrc = perfDb.createSource({
|
|
908
|
+
type: 'document',
|
|
909
|
+
name: 'PerfSource',
|
|
910
|
+
uri: 'file:///perf.json',
|
|
911
|
+
});
|
|
912
|
+
const claimCount = 1000;
|
|
913
|
+
const startTime = performance.now();
|
|
914
|
+
for (let i = 0; i < claimCount; i++) {
|
|
915
|
+
perfDb.createClaim({
|
|
916
|
+
sourceId: perfSrc.id,
|
|
917
|
+
subject: `module-${i % 20}`,
|
|
918
|
+
predicate: `setting-${i % 10}`,
|
|
919
|
+
value: `val-${i % 4}`,
|
|
920
|
+
valueType: 'configuration',
|
|
921
|
+
environment: 'production',
|
|
922
|
+
scope: 'deployment',
|
|
923
|
+
sourceRole: 'deployment',
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
const insertTimeMs = Math.round(performance.now() - startTime);
|
|
927
|
+
const scanStart = performance.now();
|
|
928
|
+
const perfDiscovery = new DiscoveryService(perfDb);
|
|
929
|
+
const perfScanResult = perfDiscovery.scanAllClaims();
|
|
930
|
+
const scanTimeMs = Math.round(performance.now() - scanStart);
|
|
931
|
+
const heapUsedMb = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
932
|
+
record('23', 'Performance Benchmark (1000 Claims)', scanTimeMs < 10000, `Ingested 1000 claims in ${insertTimeMs}ms, indexed & scanned in ${scanTimeMs}ms, heap: ${heapUsedMb}MB, candidate pairs: ${perfScanResult.candidatePairs}, contradictions: ${perfScanResult.contradictionsFound}`);
|
|
933
|
+
perfDb.close();
|
|
934
|
+
}
|
|
935
|
+
finally {
|
|
936
|
+
if (fs.existsSync(tempDir)) {
|
|
937
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
// Summary Table
|
|
941
|
+
console.log(`\n==================================================`);
|
|
942
|
+
console.log(`AUDIT EXECUTION SUMMARY`);
|
|
943
|
+
console.log(`==================================================`);
|
|
944
|
+
const passCount = results.filter((r) => r.status === 'PASS').length;
|
|
945
|
+
const failCount = results.filter((r) => r.status === 'FAIL').length;
|
|
946
|
+
console.log(`Total Verified Checks: ${results.length}`);
|
|
947
|
+
console.log(`Passed: ${passCount}`);
|
|
948
|
+
console.log(`Failed: ${failCount}`);
|
|
949
|
+
if (failCount > 0) {
|
|
950
|
+
console.error(`❌ Some audit verification checks failed!`);
|
|
951
|
+
process.exit(1);
|
|
952
|
+
}
|
|
953
|
+
else {
|
|
954
|
+
console.log(`🎉 ALL AUDIT VERIFICATION GATES PASSED!`);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
runSuite().catch((err) => {
|
|
958
|
+
console.error('Fatal audit error:', err);
|
|
959
|
+
process.exit(1);
|
|
960
|
+
});
|
|
961
|
+
//# sourceMappingURL=comprehensiveVerification.js.map
|