@vibecheckai/cli 3.2.2 → 3.2.4
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/bin/.generated +25 -25
- package/bin/dev/run-v2-torture.js +30 -30
- package/bin/runners/ENHANCEMENT_GUIDE.md +121 -121
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +117 -28
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +23 -14
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +72 -1
- package/bin/runners/lib/agent-firewall/interceptor/base.js +2 -2
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +6 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +34 -3
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +29 -4
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +12 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +21 -0
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
- package/bin/runners/lib/analyzers.js +606 -325
- package/bin/runners/lib/auth-truth.js +193 -193
- package/bin/runners/lib/backup.js +62 -62
- package/bin/runners/lib/billing.js +107 -107
- package/bin/runners/lib/claims.js +118 -118
- package/bin/runners/lib/cli-ui.js +540 -540
- package/bin/runners/lib/contracts/auth-contract.js +202 -202
- package/bin/runners/lib/contracts/env-contract.js +181 -181
- package/bin/runners/lib/contracts/external-contract.js +206 -206
- package/bin/runners/lib/contracts/guard.js +168 -168
- package/bin/runners/lib/contracts/index.js +89 -89
- package/bin/runners/lib/contracts/plan-validator.js +311 -311
- package/bin/runners/lib/contracts/route-contract.js +199 -199
- package/bin/runners/lib/contracts.js +804 -804
- package/bin/runners/lib/detect.js +89 -89
- package/bin/runners/lib/doctor/autofix.js +254 -254
- package/bin/runners/lib/doctor/index.js +37 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
- package/bin/runners/lib/doctor/modules/index.js +46 -46
- package/bin/runners/lib/doctor/modules/network.js +250 -250
- package/bin/runners/lib/doctor/modules/project.js +312 -312
- package/bin/runners/lib/doctor/modules/runtime.js +224 -224
- package/bin/runners/lib/doctor/modules/security.js +348 -348
- package/bin/runners/lib/doctor/modules/system.js +213 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
- package/bin/runners/lib/doctor/reporter.js +262 -262
- package/bin/runners/lib/doctor/service.js +262 -262
- package/bin/runners/lib/doctor/types.js +113 -113
- package/bin/runners/lib/doctor/ui.js +263 -263
- package/bin/runners/lib/doctor-v2.js +608 -608
- package/bin/runners/lib/drift.js +425 -425
- package/bin/runners/lib/enforcement.js +72 -72
- package/bin/runners/lib/engines/accessibility-engine.js +190 -0
- package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
- package/bin/runners/lib/engines/ast-cache.js +99 -0
- package/bin/runners/lib/engines/code-quality-engine.js +255 -0
- package/bin/runners/lib/engines/console-logs-engine.js +115 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
- package/bin/runners/lib/engines/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
- package/bin/runners/lib/engines/file-filter.js +131 -0
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
- package/bin/runners/lib/engines/mock-data-engine.js +272 -0
- package/bin/runners/lib/engines/parallel-processor.js +71 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
- package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
- package/bin/runners/lib/engines/type-aware-engine.js +152 -0
- package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
- package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
- package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
- package/bin/runners/lib/enterprise-detect.js +603 -603
- package/bin/runners/lib/enterprise-init.js +942 -942
- package/bin/runners/lib/env-resolver.js +417 -417
- package/bin/runners/lib/env-template.js +66 -66
- package/bin/runners/lib/env.js +189 -189
- package/bin/runners/lib/extractors/client-calls.js +990 -990
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
- package/bin/runners/lib/extractors/fastify-routes.js +426 -426
- package/bin/runners/lib/extractors/index.js +363 -363
- package/bin/runners/lib/extractors/next-routes.js +524 -524
- package/bin/runners/lib/extractors/proof-graph.js +431 -431
- package/bin/runners/lib/extractors/route-matcher.js +451 -451
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
- package/bin/runners/lib/extractors/ui-bindings.js +547 -547
- package/bin/runners/lib/findings-schema.js +281 -281
- package/bin/runners/lib/firewall-prompt.js +50 -50
- package/bin/runners/lib/global-flags.js +213 -213
- package/bin/runners/lib/graph/graph-builder.js +265 -265
- package/bin/runners/lib/graph/html-renderer.js +413 -413
- package/bin/runners/lib/graph/index.js +32 -32
- package/bin/runners/lib/graph/runtime-collector.js +215 -215
- package/bin/runners/lib/graph/static-extractor.js +518 -518
- package/bin/runners/lib/html-report.js +650 -650
- package/bin/runners/lib/interactive-menu.js +1496 -1496
- package/bin/runners/lib/llm.js +75 -75
- package/bin/runners/lib/meter.js +61 -61
- package/bin/runners/lib/missions/evidence.js +126 -126
- package/bin/runners/lib/patch.js +40 -40
- package/bin/runners/lib/permissions/auth-model.js +213 -213
- package/bin/runners/lib/permissions/idor-prover.js +205 -205
- package/bin/runners/lib/permissions/index.js +45 -45
- package/bin/runners/lib/permissions/matrix-builder.js +198 -198
- package/bin/runners/lib/pkgjson.js +28 -28
- package/bin/runners/lib/policy.js +295 -295
- package/bin/runners/lib/preflight.js +142 -142
- package/bin/runners/lib/reality/correlation-detectors.js +359 -359
- package/bin/runners/lib/reality/index.js +318 -318
- package/bin/runners/lib/reality/request-hashing.js +416 -416
- package/bin/runners/lib/reality/request-mapper.js +453 -453
- package/bin/runners/lib/reality/safety-rails.js +463 -463
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
- package/bin/runners/lib/reality/toast-detector.js +393 -393
- package/bin/runners/lib/reality-findings.js +84 -84
- package/bin/runners/lib/receipts.js +179 -179
- package/bin/runners/lib/redact.js +29 -29
- package/bin/runners/lib/replay/capsule-manager.js +154 -154
- package/bin/runners/lib/replay/index.js +263 -263
- package/bin/runners/lib/replay/player.js +348 -348
- package/bin/runners/lib/replay/recorder.js +331 -331
- package/bin/runners/lib/report-output.js +187 -187
- package/bin/runners/lib/report.js +135 -135
- package/bin/runners/lib/route-detection.js +1140 -1140
- package/bin/runners/lib/sandbox/index.js +59 -59
- package/bin/runners/lib/sandbox/proof-chain.js +399 -399
- package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
- package/bin/runners/lib/sandbox/worktree.js +174 -174
- package/bin/runners/lib/scan-output.js +525 -190
- package/bin/runners/lib/schema-validator.js +350 -350
- package/bin/runners/lib/schemas/contracts.schema.json +160 -160
- package/bin/runners/lib/schemas/finding.schema.json +100 -100
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
- package/bin/runners/lib/schemas/validator.js +438 -438
- package/bin/runners/lib/score-history.js +282 -282
- package/bin/runners/lib/share-pack.js +239 -239
- package/bin/runners/lib/snippets.js +67 -67
- package/bin/runners/lib/status-output.js +253 -253
- package/bin/runners/lib/terminal-ui.js +351 -271
- package/bin/runners/lib/upsell.js +510 -510
- package/bin/runners/lib/usage.js +153 -153
- package/bin/runners/lib/validate-patch.js +156 -156
- package/bin/runners/lib/verdict-engine.js +628 -628
- package/bin/runners/reality/engine.js +917 -917
- package/bin/runners/reality/flows.js +122 -122
- package/bin/runners/reality/report.js +378 -378
- package/bin/runners/reality/session.js +193 -193
- package/bin/runners/runGuard.js +168 -168
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runProve.js +8 -0
- package/bin/runners/runReality.js +14 -0
- package/bin/runners/runScan.js +17 -1
- package/bin/runners/runTruth.js +15 -3
- package/mcp-server/tier-auth.js +4 -4
- package/mcp-server/tools/index.js +72 -72
- package/package.json +1 -1
|
@@ -1,265 +1,265 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Graph Builder
|
|
3
|
-
* Builds the complete ProofGraph from static and runtime edges
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
"use strict";
|
|
7
|
-
|
|
8
|
-
const crypto = require("crypto");
|
|
9
|
-
|
|
10
|
-
function sha256(text) {
|
|
11
|
-
return crypto.createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Build ProofGraph from extracted nodes and edges
|
|
16
|
-
*/
|
|
17
|
-
function buildProofGraph({ nodes, edges, runtimeEdges = [], meta = {} }) {
|
|
18
|
-
const graph = {
|
|
19
|
-
meta: {
|
|
20
|
-
version: "1.0.0",
|
|
21
|
-
generatedAt: new Date().toISOString(),
|
|
22
|
-
commit: meta.commit || process.env.VIBECHECK_COMMIT_SHA || "unknown",
|
|
23
|
-
...meta
|
|
24
|
-
},
|
|
25
|
-
nodes: [],
|
|
26
|
-
edges: [],
|
|
27
|
-
brokenEdges: [],
|
|
28
|
-
coverage: {
|
|
29
|
-
static: 0,
|
|
30
|
-
runtime: 0,
|
|
31
|
-
total: 0
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// Dedupe and add nodes
|
|
36
|
-
const nodeMap = new Map();
|
|
37
|
-
for (const n of nodes) {
|
|
38
|
-
if (!nodeMap.has(n.id)) {
|
|
39
|
-
nodeMap.set(n.id, n);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
graph.nodes = Array.from(nodeMap.values());
|
|
43
|
-
|
|
44
|
-
// Process edges, separate broken ones
|
|
45
|
-
const brokenEdges = [];
|
|
46
|
-
const validEdges = [];
|
|
47
|
-
|
|
48
|
-
for (const edge of edges) {
|
|
49
|
-
if (edge.broken) {
|
|
50
|
-
brokenEdges.push({
|
|
51
|
-
edge: {
|
|
52
|
-
id: edge.id,
|
|
53
|
-
from: edge.from,
|
|
54
|
-
to: edge.to,
|
|
55
|
-
type: edge.type,
|
|
56
|
-
confidence: edge.confidence || "low"
|
|
57
|
-
},
|
|
58
|
-
reason: edge.brokenReason || "Edge target not found",
|
|
59
|
-
severity: determineSeverity(edge),
|
|
60
|
-
route: edge.toRoute || null
|
|
61
|
-
});
|
|
62
|
-
} else {
|
|
63
|
-
validEdges.push({
|
|
64
|
-
id: edge.id,
|
|
65
|
-
from: edge.from,
|
|
66
|
-
to: edge.to,
|
|
67
|
-
type: edge.type,
|
|
68
|
-
confidence: edge.confidence || "med",
|
|
69
|
-
verifiedAt: "static"
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Add runtime edges
|
|
75
|
-
for (const re of runtimeEdges) {
|
|
76
|
-
const existing = validEdges.find(e => e.from === re.from && e.to === re.to);
|
|
77
|
-
if (existing) {
|
|
78
|
-
existing.verifiedAt = "runtime";
|
|
79
|
-
existing.runtimeData = re.data;
|
|
80
|
-
} else {
|
|
81
|
-
validEdges.push({
|
|
82
|
-
id: re.id || `runtime_${sha256(re.from + re.to)}`,
|
|
83
|
-
from: re.from,
|
|
84
|
-
to: re.to,
|
|
85
|
-
type: re.type || "runtime",
|
|
86
|
-
confidence: "high",
|
|
87
|
-
verifiedAt: "runtime",
|
|
88
|
-
runtimeData: re.data
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
graph.edges = validEdges;
|
|
94
|
-
graph.brokenEdges = brokenEdges;
|
|
95
|
-
|
|
96
|
-
// Calculate coverage
|
|
97
|
-
const totalPossibleEdges = graph.edges.length + graph.brokenEdges.length;
|
|
98
|
-
graph.coverage = {
|
|
99
|
-
static: graph.edges.filter(e => e.verifiedAt === "static").length,
|
|
100
|
-
runtime: graph.edges.filter(e => e.verifiedAt === "runtime").length,
|
|
101
|
-
broken: graph.brokenEdges.length,
|
|
102
|
-
total: totalPossibleEdges,
|
|
103
|
-
percent: totalPossibleEdges > 0
|
|
104
|
-
? Math.round((graph.edges.length / totalPossibleEdges) * 100)
|
|
105
|
-
: 100
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
return graph;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function determineSeverity(edge) {
|
|
112
|
-
// Missing routes are always BLOCK
|
|
113
|
-
if (edge.toRoute) return "BLOCK";
|
|
114
|
-
|
|
115
|
-
// Unresolved function calls are WARN
|
|
116
|
-
if (edge.to?.startsWith("unresolved_")) return "WARN";
|
|
117
|
-
|
|
118
|
-
return "WARN";
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Merge runtime verification results into graph
|
|
123
|
-
*/
|
|
124
|
-
function mergeRuntimeResults(graph, runtimeResults) {
|
|
125
|
-
const updatedGraph = { ...graph };
|
|
126
|
-
|
|
127
|
-
for (const result of runtimeResults) {
|
|
128
|
-
// Find edge that matches this runtime result
|
|
129
|
-
const edge = updatedGraph.edges.find(e =>
|
|
130
|
-
e.to?.includes(result.path) ||
|
|
131
|
-
(e.type === "calls_route" && e.toRoute?.path === result.path)
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
if (edge) {
|
|
135
|
-
edge.verifiedAt = "runtime";
|
|
136
|
-
edge.runtimeData = {
|
|
137
|
-
status: result.status,
|
|
138
|
-
latencyMs: result.latencyMs,
|
|
139
|
-
error: result.error
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
// Check for runtime failures
|
|
143
|
-
if (result.status >= 400) {
|
|
144
|
-
const brokenEdge = {
|
|
145
|
-
edge: { ...edge },
|
|
146
|
-
reason: `Route returns ${result.status} at runtime`,
|
|
147
|
-
severity: result.status >= 500 ? "BLOCK" : "WARN",
|
|
148
|
-
runtimeData: edge.runtimeData
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
// Move to broken edges
|
|
152
|
-
updatedGraph.brokenEdges.push(brokenEdge);
|
|
153
|
-
updatedGraph.edges = updatedGraph.edges.filter(e => e.id !== edge.id);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Recalculate coverage
|
|
159
|
-
const totalPossibleEdges = updatedGraph.edges.length + updatedGraph.brokenEdges.length;
|
|
160
|
-
updatedGraph.coverage = {
|
|
161
|
-
static: updatedGraph.edges.filter(e => e.verifiedAt === "static").length,
|
|
162
|
-
runtime: updatedGraph.edges.filter(e => e.verifiedAt === "runtime").length,
|
|
163
|
-
broken: updatedGraph.brokenEdges.length,
|
|
164
|
-
total: totalPossibleEdges,
|
|
165
|
-
percent: totalPossibleEdges > 0
|
|
166
|
-
? Math.round((updatedGraph.edges.length / totalPossibleEdges) * 100)
|
|
167
|
-
: 100
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
return updatedGraph;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Compute graph diff between two graphs
|
|
175
|
-
*/
|
|
176
|
-
function computeGraphDiff(before, after) {
|
|
177
|
-
const diff = {
|
|
178
|
-
nodesAdded: [],
|
|
179
|
-
nodesRemoved: [],
|
|
180
|
-
edgesAdded: [],
|
|
181
|
-
edgesRemoved: [],
|
|
182
|
-
edgesFixed: [],
|
|
183
|
-
edgesBroken: []
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
const beforeNodeIds = new Set(before.nodes.map(n => n.id));
|
|
187
|
-
const afterNodeIds = new Set(after.nodes.map(n => n.id));
|
|
188
|
-
|
|
189
|
-
for (const n of after.nodes) {
|
|
190
|
-
if (!beforeNodeIds.has(n.id)) diff.nodesAdded.push(n);
|
|
191
|
-
}
|
|
192
|
-
for (const n of before.nodes) {
|
|
193
|
-
if (!afterNodeIds.has(n.id)) diff.nodesRemoved.push(n);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const beforeEdgeIds = new Set(before.edges.map(e => e.id));
|
|
197
|
-
const afterEdgeIds = new Set(after.edges.map(e => e.id));
|
|
198
|
-
const beforeBrokenIds = new Set(before.brokenEdges.map(e => e.edge.id));
|
|
199
|
-
const afterBrokenIds = new Set(after.brokenEdges.map(e => e.edge.id));
|
|
200
|
-
|
|
201
|
-
for (const e of after.edges) {
|
|
202
|
-
if (!beforeEdgeIds.has(e.id)) {
|
|
203
|
-
if (beforeBrokenIds.has(e.id)) {
|
|
204
|
-
diff.edgesFixed.push(e);
|
|
205
|
-
} else {
|
|
206
|
-
diff.edgesAdded.push(e);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
for (const e of before.edges) {
|
|
212
|
-
if (!afterEdgeIds.has(e.id)) {
|
|
213
|
-
if (afterBrokenIds.has(e.id)) {
|
|
214
|
-
diff.edgesBroken.push(e);
|
|
215
|
-
} else {
|
|
216
|
-
diff.edgesRemoved.push(e);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return diff;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Get findings from broken edges
|
|
226
|
-
*/
|
|
227
|
-
function getFindingsFromGraph(graph) {
|
|
228
|
-
const findings = [];
|
|
229
|
-
|
|
230
|
-
for (const broken of graph.brokenEdges) {
|
|
231
|
-
const finding = {
|
|
232
|
-
id: `GRAPH_${broken.edge.id}`,
|
|
233
|
-
severity: broken.severity,
|
|
234
|
-
category: "BrokenEdge",
|
|
235
|
-
title: broken.reason,
|
|
236
|
-
evidence: [],
|
|
237
|
-
graphEdge: broken.edge
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
if (broken.route) {
|
|
241
|
-
finding.title = `Route ${broken.route.method} ${broken.route.path} referenced but doesn't exist`;
|
|
242
|
-
finding.category = "MissingRoute";
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (broken.runtimeData) {
|
|
246
|
-
finding.title = `Route returns ${broken.runtimeData.status} at runtime`;
|
|
247
|
-
finding.category = "RuntimeFailure";
|
|
248
|
-
if (broken.runtimeData.status === 401) {
|
|
249
|
-
finding.title = "UI shows success but server returned 401 (auth required)";
|
|
250
|
-
finding.category = "CausalContradiction";
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
findings.push(finding);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return findings;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
module.exports = {
|
|
261
|
-
buildProofGraph,
|
|
262
|
-
mergeRuntimeResults,
|
|
263
|
-
computeGraphDiff,
|
|
264
|
-
getFindingsFromGraph
|
|
265
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Graph Builder
|
|
3
|
+
* Builds the complete ProofGraph from static and runtime edges
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use strict";
|
|
7
|
+
|
|
8
|
+
const crypto = require("crypto");
|
|
9
|
+
|
|
10
|
+
function sha256(text) {
|
|
11
|
+
return crypto.createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Build ProofGraph from extracted nodes and edges
|
|
16
|
+
*/
|
|
17
|
+
function buildProofGraph({ nodes, edges, runtimeEdges = [], meta = {} }) {
|
|
18
|
+
const graph = {
|
|
19
|
+
meta: {
|
|
20
|
+
version: "1.0.0",
|
|
21
|
+
generatedAt: new Date().toISOString(),
|
|
22
|
+
commit: meta.commit || process.env.VIBECHECK_COMMIT_SHA || "unknown",
|
|
23
|
+
...meta
|
|
24
|
+
},
|
|
25
|
+
nodes: [],
|
|
26
|
+
edges: [],
|
|
27
|
+
brokenEdges: [],
|
|
28
|
+
coverage: {
|
|
29
|
+
static: 0,
|
|
30
|
+
runtime: 0,
|
|
31
|
+
total: 0
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Dedupe and add nodes
|
|
36
|
+
const nodeMap = new Map();
|
|
37
|
+
for (const n of nodes) {
|
|
38
|
+
if (!nodeMap.has(n.id)) {
|
|
39
|
+
nodeMap.set(n.id, n);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
graph.nodes = Array.from(nodeMap.values());
|
|
43
|
+
|
|
44
|
+
// Process edges, separate broken ones
|
|
45
|
+
const brokenEdges = [];
|
|
46
|
+
const validEdges = [];
|
|
47
|
+
|
|
48
|
+
for (const edge of edges) {
|
|
49
|
+
if (edge.broken) {
|
|
50
|
+
brokenEdges.push({
|
|
51
|
+
edge: {
|
|
52
|
+
id: edge.id,
|
|
53
|
+
from: edge.from,
|
|
54
|
+
to: edge.to,
|
|
55
|
+
type: edge.type,
|
|
56
|
+
confidence: edge.confidence || "low"
|
|
57
|
+
},
|
|
58
|
+
reason: edge.brokenReason || "Edge target not found",
|
|
59
|
+
severity: determineSeverity(edge),
|
|
60
|
+
route: edge.toRoute || null
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
validEdges.push({
|
|
64
|
+
id: edge.id,
|
|
65
|
+
from: edge.from,
|
|
66
|
+
to: edge.to,
|
|
67
|
+
type: edge.type,
|
|
68
|
+
confidence: edge.confidence || "med",
|
|
69
|
+
verifiedAt: "static"
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Add runtime edges
|
|
75
|
+
for (const re of runtimeEdges) {
|
|
76
|
+
const existing = validEdges.find(e => e.from === re.from && e.to === re.to);
|
|
77
|
+
if (existing) {
|
|
78
|
+
existing.verifiedAt = "runtime";
|
|
79
|
+
existing.runtimeData = re.data;
|
|
80
|
+
} else {
|
|
81
|
+
validEdges.push({
|
|
82
|
+
id: re.id || `runtime_${sha256(re.from + re.to)}`,
|
|
83
|
+
from: re.from,
|
|
84
|
+
to: re.to,
|
|
85
|
+
type: re.type || "runtime",
|
|
86
|
+
confidence: "high",
|
|
87
|
+
verifiedAt: "runtime",
|
|
88
|
+
runtimeData: re.data
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
graph.edges = validEdges;
|
|
94
|
+
graph.brokenEdges = brokenEdges;
|
|
95
|
+
|
|
96
|
+
// Calculate coverage
|
|
97
|
+
const totalPossibleEdges = graph.edges.length + graph.brokenEdges.length;
|
|
98
|
+
graph.coverage = {
|
|
99
|
+
static: graph.edges.filter(e => e.verifiedAt === "static").length,
|
|
100
|
+
runtime: graph.edges.filter(e => e.verifiedAt === "runtime").length,
|
|
101
|
+
broken: graph.brokenEdges.length,
|
|
102
|
+
total: totalPossibleEdges,
|
|
103
|
+
percent: totalPossibleEdges > 0
|
|
104
|
+
? Math.round((graph.edges.length / totalPossibleEdges) * 100)
|
|
105
|
+
: 100
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return graph;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function determineSeverity(edge) {
|
|
112
|
+
// Missing routes are always BLOCK
|
|
113
|
+
if (edge.toRoute) return "BLOCK";
|
|
114
|
+
|
|
115
|
+
// Unresolved function calls are WARN
|
|
116
|
+
if (edge.to?.startsWith("unresolved_")) return "WARN";
|
|
117
|
+
|
|
118
|
+
return "WARN";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Merge runtime verification results into graph
|
|
123
|
+
*/
|
|
124
|
+
function mergeRuntimeResults(graph, runtimeResults) {
|
|
125
|
+
const updatedGraph = { ...graph };
|
|
126
|
+
|
|
127
|
+
for (const result of runtimeResults) {
|
|
128
|
+
// Find edge that matches this runtime result
|
|
129
|
+
const edge = updatedGraph.edges.find(e =>
|
|
130
|
+
e.to?.includes(result.path) ||
|
|
131
|
+
(e.type === "calls_route" && e.toRoute?.path === result.path)
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (edge) {
|
|
135
|
+
edge.verifiedAt = "runtime";
|
|
136
|
+
edge.runtimeData = {
|
|
137
|
+
status: result.status,
|
|
138
|
+
latencyMs: result.latencyMs,
|
|
139
|
+
error: result.error
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Check for runtime failures
|
|
143
|
+
if (result.status >= 400) {
|
|
144
|
+
const brokenEdge = {
|
|
145
|
+
edge: { ...edge },
|
|
146
|
+
reason: `Route returns ${result.status} at runtime`,
|
|
147
|
+
severity: result.status >= 500 ? "BLOCK" : "WARN",
|
|
148
|
+
runtimeData: edge.runtimeData
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Move to broken edges
|
|
152
|
+
updatedGraph.brokenEdges.push(brokenEdge);
|
|
153
|
+
updatedGraph.edges = updatedGraph.edges.filter(e => e.id !== edge.id);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Recalculate coverage
|
|
159
|
+
const totalPossibleEdges = updatedGraph.edges.length + updatedGraph.brokenEdges.length;
|
|
160
|
+
updatedGraph.coverage = {
|
|
161
|
+
static: updatedGraph.edges.filter(e => e.verifiedAt === "static").length,
|
|
162
|
+
runtime: updatedGraph.edges.filter(e => e.verifiedAt === "runtime").length,
|
|
163
|
+
broken: updatedGraph.brokenEdges.length,
|
|
164
|
+
total: totalPossibleEdges,
|
|
165
|
+
percent: totalPossibleEdges > 0
|
|
166
|
+
? Math.round((updatedGraph.edges.length / totalPossibleEdges) * 100)
|
|
167
|
+
: 100
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
return updatedGraph;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Compute graph diff between two graphs
|
|
175
|
+
*/
|
|
176
|
+
function computeGraphDiff(before, after) {
|
|
177
|
+
const diff = {
|
|
178
|
+
nodesAdded: [],
|
|
179
|
+
nodesRemoved: [],
|
|
180
|
+
edgesAdded: [],
|
|
181
|
+
edgesRemoved: [],
|
|
182
|
+
edgesFixed: [],
|
|
183
|
+
edgesBroken: []
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const beforeNodeIds = new Set(before.nodes.map(n => n.id));
|
|
187
|
+
const afterNodeIds = new Set(after.nodes.map(n => n.id));
|
|
188
|
+
|
|
189
|
+
for (const n of after.nodes) {
|
|
190
|
+
if (!beforeNodeIds.has(n.id)) diff.nodesAdded.push(n);
|
|
191
|
+
}
|
|
192
|
+
for (const n of before.nodes) {
|
|
193
|
+
if (!afterNodeIds.has(n.id)) diff.nodesRemoved.push(n);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const beforeEdgeIds = new Set(before.edges.map(e => e.id));
|
|
197
|
+
const afterEdgeIds = new Set(after.edges.map(e => e.id));
|
|
198
|
+
const beforeBrokenIds = new Set(before.brokenEdges.map(e => e.edge.id));
|
|
199
|
+
const afterBrokenIds = new Set(after.brokenEdges.map(e => e.edge.id));
|
|
200
|
+
|
|
201
|
+
for (const e of after.edges) {
|
|
202
|
+
if (!beforeEdgeIds.has(e.id)) {
|
|
203
|
+
if (beforeBrokenIds.has(e.id)) {
|
|
204
|
+
diff.edgesFixed.push(e);
|
|
205
|
+
} else {
|
|
206
|
+
diff.edgesAdded.push(e);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for (const e of before.edges) {
|
|
212
|
+
if (!afterEdgeIds.has(e.id)) {
|
|
213
|
+
if (afterBrokenIds.has(e.id)) {
|
|
214
|
+
diff.edgesBroken.push(e);
|
|
215
|
+
} else {
|
|
216
|
+
diff.edgesRemoved.push(e);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return diff;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get findings from broken edges
|
|
226
|
+
*/
|
|
227
|
+
function getFindingsFromGraph(graph) {
|
|
228
|
+
const findings = [];
|
|
229
|
+
|
|
230
|
+
for (const broken of graph.brokenEdges) {
|
|
231
|
+
const finding = {
|
|
232
|
+
id: `GRAPH_${broken.edge.id}`,
|
|
233
|
+
severity: broken.severity,
|
|
234
|
+
category: "BrokenEdge",
|
|
235
|
+
title: broken.reason,
|
|
236
|
+
evidence: [],
|
|
237
|
+
graphEdge: broken.edge
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
if (broken.route) {
|
|
241
|
+
finding.title = `Route ${broken.route.method} ${broken.route.path} referenced but doesn't exist`;
|
|
242
|
+
finding.category = "MissingRoute";
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (broken.runtimeData) {
|
|
246
|
+
finding.title = `Route returns ${broken.runtimeData.status} at runtime`;
|
|
247
|
+
finding.category = "RuntimeFailure";
|
|
248
|
+
if (broken.runtimeData.status === 401) {
|
|
249
|
+
finding.title = "UI shows success but server returned 401 (auth required)";
|
|
250
|
+
finding.category = "CausalContradiction";
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
findings.push(finding);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return findings;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
module.exports = {
|
|
261
|
+
buildProofGraph,
|
|
262
|
+
mergeRuntimeResults,
|
|
263
|
+
computeGraphDiff,
|
|
264
|
+
getFindingsFromGraph
|
|
265
|
+
};
|