@veraxhq/verax 0.1.0 → 0.2.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/README.md +123 -88
- package/bin/verax.js +11 -452
- package/package.json +14 -36
- package/src/cli/commands/default.js +523 -0
- package/src/cli/commands/doctor.js +165 -0
- package/src/cli/commands/inspect.js +109 -0
- package/src/cli/commands/run.js +402 -0
- package/src/cli/entry.js +196 -0
- package/src/cli/util/atomic-write.js +37 -0
- package/src/cli/util/detection-engine.js +296 -0
- package/src/cli/util/env-url.js +33 -0
- package/src/cli/util/errors.js +44 -0
- package/src/cli/util/events.js +34 -0
- package/src/cli/util/expectation-extractor.js +378 -0
- package/src/cli/util/findings-writer.js +31 -0
- package/src/cli/util/idgen.js +87 -0
- package/src/cli/util/learn-writer.js +39 -0
- package/src/cli/util/observation-engine.js +366 -0
- package/src/cli/util/observe-writer.js +25 -0
- package/src/cli/util/paths.js +29 -0
- package/src/cli/util/project-discovery.js +277 -0
- package/src/cli/util/project-writer.js +26 -0
- package/src/cli/util/redact.js +128 -0
- package/src/cli/util/run-id.js +30 -0
- package/src/cli/util/summary-writer.js +32 -0
- package/src/verax/cli/ci-summary.js +35 -0
- package/src/verax/cli/context-explanation.js +89 -0
- package/src/verax/cli/doctor.js +277 -0
- package/src/verax/cli/error-normalizer.js +154 -0
- package/src/verax/cli/explain-output.js +105 -0
- package/src/verax/cli/finding-explainer.js +130 -0
- package/src/verax/cli/init.js +237 -0
- package/src/verax/cli/run-overview.js +163 -0
- package/src/verax/cli/url-safety.js +101 -0
- package/src/verax/cli/wizard.js +98 -0
- package/src/verax/cli/zero-findings-explainer.js +57 -0
- package/src/verax/cli/zero-interaction-explainer.js +127 -0
- package/src/verax/core/action-classifier.js +86 -0
- package/src/verax/core/budget-engine.js +218 -0
- package/src/verax/core/canonical-outcomes.js +157 -0
- package/src/verax/core/decision-snapshot.js +335 -0
- package/src/verax/core/determinism-model.js +403 -0
- package/src/verax/core/incremental-store.js +237 -0
- package/src/verax/core/invariants.js +356 -0
- package/src/verax/core/promise-model.js +230 -0
- package/src/verax/core/replay-validator.js +350 -0
- package/src/verax/core/replay.js +222 -0
- package/src/verax/core/run-id.js +175 -0
- package/src/verax/core/run-manifest.js +99 -0
- package/src/verax/core/silence-impact.js +369 -0
- package/src/verax/core/silence-model.js +521 -0
- package/src/verax/detect/comparison.js +2 -34
- package/src/verax/detect/confidence-engine.js +764 -329
- package/src/verax/detect/detection-engine.js +293 -0
- package/src/verax/detect/evidence-index.js +177 -0
- package/src/verax/detect/expectation-model.js +194 -172
- package/src/verax/detect/explanation-helpers.js +187 -0
- package/src/verax/detect/finding-detector.js +450 -0
- package/src/verax/detect/findings-writer.js +44 -8
- package/src/verax/detect/flow-detector.js +366 -0
- package/src/verax/detect/index.js +172 -286
- package/src/verax/detect/interactive-findings.js +613 -0
- package/src/verax/detect/signal-mapper.js +308 -0
- package/src/verax/detect/verdict-engine.js +563 -0
- package/src/verax/evidence-index-writer.js +61 -0
- package/src/verax/index.js +90 -14
- package/src/verax/intel/effect-detector.js +368 -0
- package/src/verax/intel/handler-mapper.js +249 -0
- package/src/verax/intel/index.js +281 -0
- package/src/verax/intel/route-extractor.js +280 -0
- package/src/verax/intel/ts-program.js +256 -0
- package/src/verax/intel/vue-navigation-extractor.js +579 -0
- package/src/verax/intel/vue-router-extractor.js +323 -0
- package/src/verax/learn/action-contract-extractor.js +335 -101
- package/src/verax/learn/ast-contract-extractor.js +95 -5
- package/src/verax/learn/flow-extractor.js +172 -0
- package/src/verax/learn/manifest-writer.js +97 -47
- package/src/verax/learn/project-detector.js +40 -0
- package/src/verax/learn/route-extractor.js +27 -96
- package/src/verax/learn/state-extractor.js +212 -0
- package/src/verax/learn/static-extractor-navigation.js +114 -0
- package/src/verax/learn/static-extractor-validation.js +88 -0
- package/src/verax/learn/static-extractor.js +112 -4
- package/src/verax/learn/truth-assessor.js +24 -21
- package/src/verax/observe/aria-sensor.js +211 -0
- package/src/verax/observe/browser.js +10 -5
- package/src/verax/observe/console-sensor.js +1 -17
- package/src/verax/observe/domain-boundary.js +10 -1
- package/src/verax/observe/expectation-executor.js +512 -0
- package/src/verax/observe/flow-matcher.js +143 -0
- package/src/verax/observe/focus-sensor.js +196 -0
- package/src/verax/observe/human-driver.js +643 -275
- package/src/verax/observe/index.js +908 -27
- package/src/verax/observe/index.js.backup +1 -0
- package/src/verax/observe/interaction-discovery.js +365 -14
- package/src/verax/observe/interaction-runner.js +563 -198
- package/src/verax/observe/loading-sensor.js +139 -0
- package/src/verax/observe/navigation-sensor.js +255 -0
- package/src/verax/observe/network-sensor.js +55 -7
- package/src/verax/observe/observed-expectation-deriver.js +186 -0
- package/src/verax/observe/observed-expectation.js +305 -0
- package/src/verax/observe/page-frontier.js +234 -0
- package/src/verax/observe/settle.js +37 -17
- package/src/verax/observe/state-sensor.js +389 -0
- package/src/verax/observe/timing-sensor.js +228 -0
- package/src/verax/observe/traces-writer.js +61 -20
- package/src/verax/observe/ui-signal-sensor.js +136 -17
- package/src/verax/scan-summary-writer.js +77 -15
- package/src/verax/shared/artifact-manager.js +110 -8
- package/src/verax/shared/budget-profiles.js +136 -0
- package/src/verax/shared/ci-detection.js +39 -0
- package/src/verax/shared/config-loader.js +170 -0
- package/src/verax/shared/dynamic-route-utils.js +218 -0
- package/src/verax/shared/expectation-coverage.js +44 -0
- package/src/verax/shared/expectation-prover.js +81 -0
- package/src/verax/shared/expectation-tracker.js +201 -0
- package/src/verax/shared/expectations-writer.js +60 -0
- package/src/verax/shared/first-run.js +44 -0
- package/src/verax/shared/progress-reporter.js +171 -0
- package/src/verax/shared/retry-policy.js +14 -1
- package/src/verax/shared/root-artifacts.js +49 -0
- package/src/verax/shared/scan-budget.js +86 -0
- package/src/verax/shared/url-normalizer.js +162 -0
- package/src/verax/shared/zip-artifacts.js +65 -0
- package/src/verax/validate/context-validator.js +244 -0
- package/src/verax/validate/context-validator.js.bak +0 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signal Mapper
|
|
3
|
+
* Enriches findings with decision-ready signals:
|
|
4
|
+
* - Impact Level (LOW | MEDIUM | HIGH)
|
|
5
|
+
* - User Risk (BLOCKS | CONFUSES | DEGRADES)
|
|
6
|
+
* - Ownership Hint (FRONTEND | BACKEND | INTEGRATION | ACCESSIBILITY | PERFORMANCE)
|
|
7
|
+
* - Grouping Metadata
|
|
8
|
+
*
|
|
9
|
+
* Deterministic mapping only, no heuristics.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Map finding to impact level based on failure type, route criticality, and confidence
|
|
14
|
+
*/
|
|
15
|
+
export function mapImpactLevel(finding, manifest = {}) {
|
|
16
|
+
const findingType = finding.type || 'unknown';
|
|
17
|
+
const confidence = finding.confidence?.level || 'UNKNOWN';
|
|
18
|
+
const routePath = extractRouteFromFinding(finding, manifest);
|
|
19
|
+
const isCriticalRoute = isRouteCritical(routePath, manifest);
|
|
20
|
+
|
|
21
|
+
// HIGH impact: blocking failures on critical routes with high confidence
|
|
22
|
+
if (
|
|
23
|
+
(findingType.includes('navigation') || findingType.includes('auth') || findingType === 'observed_break') &&
|
|
24
|
+
isCriticalRoute &&
|
|
25
|
+
(confidence === 'HIGH' || confidence === 'MEDIUM')
|
|
26
|
+
) {
|
|
27
|
+
return 'HIGH';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// HIGH impact: accessibility failures (always high impact)
|
|
31
|
+
if (
|
|
32
|
+
findingType.includes('focus') ||
|
|
33
|
+
findingType.includes('aria') ||
|
|
34
|
+
findingType.includes('keyboard_trap')
|
|
35
|
+
) {
|
|
36
|
+
return 'HIGH';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// HIGH impact: loading stuck or freeze-like failures
|
|
40
|
+
if (
|
|
41
|
+
findingType.includes('loading_stuck') ||
|
|
42
|
+
findingType.includes('freeze_like')
|
|
43
|
+
) {
|
|
44
|
+
return 'HIGH';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// MEDIUM impact: network failures on critical routes
|
|
48
|
+
if (
|
|
49
|
+
findingType.includes('network') ||
|
|
50
|
+
findingType.includes('partial_success') ||
|
|
51
|
+
findingType.includes('async_state')
|
|
52
|
+
) {
|
|
53
|
+
return isCriticalRoute ? 'MEDIUM' : 'LOW';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// MEDIUM impact: feedback gap failures
|
|
57
|
+
if (findingType.includes('feedback_gap')) {
|
|
58
|
+
return 'MEDIUM';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// MEDIUM impact: validation failures
|
|
62
|
+
if (findingType.includes('validation')) {
|
|
63
|
+
return 'MEDIUM';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// MEDIUM impact: state failures
|
|
67
|
+
if (findingType.includes('state')) {
|
|
68
|
+
return isCriticalRoute ? 'MEDIUM' : 'LOW';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// LOW impact: hover, file upload failures (less critical)
|
|
72
|
+
if (
|
|
73
|
+
findingType.includes('hover') ||
|
|
74
|
+
findingType.includes('file_upload')
|
|
75
|
+
) {
|
|
76
|
+
return 'LOW';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// LOW impact: low confidence on non-critical routes
|
|
80
|
+
if (confidence === 'LOW' && !isCriticalRoute) {
|
|
81
|
+
return 'LOW';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Default: MEDIUM for other cases
|
|
85
|
+
return 'MEDIUM';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Map finding to user risk level
|
|
90
|
+
*/
|
|
91
|
+
export function mapUserRisk(finding) {
|
|
92
|
+
const findingType = finding.type || 'unknown';
|
|
93
|
+
const interactionType = finding.interaction?.type || '';
|
|
94
|
+
|
|
95
|
+
// BLOCKS: User cannot complete intended action
|
|
96
|
+
if (
|
|
97
|
+
findingType.includes('navigation') ||
|
|
98
|
+
findingType.includes('auth') ||
|
|
99
|
+
findingType.includes('loading_stuck') ||
|
|
100
|
+
findingType.includes('freeze_like') ||
|
|
101
|
+
findingType === 'observed_break' ||
|
|
102
|
+
(findingType.includes('network') && interactionType === 'form')
|
|
103
|
+
) {
|
|
104
|
+
return 'BLOCKS';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// CONFUSES: User action appears to work but provides no feedback
|
|
108
|
+
if (
|
|
109
|
+
findingType.includes('feedback_gap') ||
|
|
110
|
+
findingType.includes('partial_success') ||
|
|
111
|
+
findingType.includes('async_state') ||
|
|
112
|
+
findingType.includes('validation')
|
|
113
|
+
) {
|
|
114
|
+
return 'CONFUSES';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// DEGRADES: Functionality works but with reduced quality/accessibility
|
|
118
|
+
if (
|
|
119
|
+
findingType.includes('focus') ||
|
|
120
|
+
findingType.includes('aria') ||
|
|
121
|
+
findingType.includes('keyboard_trap') ||
|
|
122
|
+
findingType.includes('hover') ||
|
|
123
|
+
findingType.includes('file_upload')
|
|
124
|
+
) {
|
|
125
|
+
return 'DEGRADES';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Default: CONFUSES for unknown cases
|
|
129
|
+
return 'CONFUSES';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Map finding to ownership hint based on failure type and sensors
|
|
134
|
+
*/
|
|
135
|
+
export function mapOwnership(finding, trace = {}) {
|
|
136
|
+
const findingType = finding.type || 'unknown';
|
|
137
|
+
const sensors = trace.sensors || {};
|
|
138
|
+
const hasNetwork = (sensors.network?.totalRequests || 0) > 0;
|
|
139
|
+
const hasAria = sensors.aria !== undefined;
|
|
140
|
+
const hasFocus = sensors.focus !== undefined;
|
|
141
|
+
const hasTiming = sensors.timing !== undefined;
|
|
142
|
+
|
|
143
|
+
// ACCESSIBILITY: Focus, ARIA, keyboard trap failures
|
|
144
|
+
if (
|
|
145
|
+
findingType.includes('focus') ||
|
|
146
|
+
findingType.includes('aria') ||
|
|
147
|
+
findingType.includes('keyboard_trap')
|
|
148
|
+
) {
|
|
149
|
+
return 'ACCESSIBILITY';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// PERFORMANCE: Timing-related failures
|
|
153
|
+
if (
|
|
154
|
+
findingType.includes('loading_stuck') ||
|
|
155
|
+
findingType.includes('freeze_like') ||
|
|
156
|
+
findingType.includes('feedback_gap') ||
|
|
157
|
+
hasTiming
|
|
158
|
+
) {
|
|
159
|
+
return 'PERFORMANCE';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// BACKEND: Network failures without DOM/UI changes
|
|
163
|
+
if (
|
|
164
|
+
findingType.includes('network') ||
|
|
165
|
+
findingType.includes('partial_success') ||
|
|
166
|
+
findingType.includes('async_state')
|
|
167
|
+
) {
|
|
168
|
+
// If network request occurred but no UI feedback, likely backend issue
|
|
169
|
+
if (hasNetwork && !sensors.uiSignals?.diff?.changed) {
|
|
170
|
+
return 'BACKEND';
|
|
171
|
+
}
|
|
172
|
+
// Otherwise integration issue
|
|
173
|
+
return 'INTEGRATION';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// BACKEND: Auth failures (typically backend-related)
|
|
177
|
+
if (findingType.includes('auth') || findingType.includes('logout')) {
|
|
178
|
+
return 'BACKEND';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// FRONTEND: UI-only failures (no network, no backend involvement)
|
|
182
|
+
if (
|
|
183
|
+
findingType.includes('navigation') ||
|
|
184
|
+
findingType.includes('validation') ||
|
|
185
|
+
findingType.includes('hover') ||
|
|
186
|
+
findingType.includes('file_upload') ||
|
|
187
|
+
findingType === 'observed_break'
|
|
188
|
+
) {
|
|
189
|
+
if (!hasNetwork) {
|
|
190
|
+
return 'FRONTEND';
|
|
191
|
+
}
|
|
192
|
+
return 'INTEGRATION';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// INTEGRATION: Default for unclear cases
|
|
196
|
+
return 'INTEGRATION';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Generate grouping metadata for findings
|
|
201
|
+
*/
|
|
202
|
+
export function generateGroupingMetadata(finding, manifest = {}) {
|
|
203
|
+
const routePath = extractRouteFromFinding(finding, manifest);
|
|
204
|
+
const findingType = finding.type || 'unknown';
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
groupByRoute: routePath || '*',
|
|
208
|
+
groupByFailureType: findingType,
|
|
209
|
+
groupByFeature: extractFeatureFromRoute(routePath)
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Extract route path from finding
|
|
215
|
+
*/
|
|
216
|
+
function extractRouteFromFinding(finding, manifest) {
|
|
217
|
+
// Try to get route from evidence
|
|
218
|
+
const beforeUrl = finding.evidence?.beforeUrl || finding.evidence?.before?.url;
|
|
219
|
+
if (beforeUrl) {
|
|
220
|
+
try {
|
|
221
|
+
const url = new URL(beforeUrl);
|
|
222
|
+
return url.pathname;
|
|
223
|
+
} catch {
|
|
224
|
+
return extractPathFromUrl(beforeUrl);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Try to get route from expectation
|
|
229
|
+
if (finding.expectationId) {
|
|
230
|
+
const expectation = manifest.staticExpectations?.find(e => e.id === finding.expectationId);
|
|
231
|
+
if (expectation?.fromPath) {
|
|
232
|
+
return expectation.fromPath;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return '*';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Extract path from URL string
|
|
241
|
+
*/
|
|
242
|
+
function extractPathFromUrl(url) {
|
|
243
|
+
if (!url) return '*';
|
|
244
|
+
try {
|
|
245
|
+
const urlObj = new URL(url);
|
|
246
|
+
return urlObj.pathname;
|
|
247
|
+
} catch {
|
|
248
|
+
// Try to extract pathname manually
|
|
249
|
+
const match = url.match(/\/[^?#]*/);
|
|
250
|
+
return match ? match[0] : '*';
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Check if route is critical (has expectations)
|
|
256
|
+
*/
|
|
257
|
+
function isRouteCritical(routePath, manifest) {
|
|
258
|
+
if (!manifest.staticExpectations) return false;
|
|
259
|
+
|
|
260
|
+
const normalizedRoute = normalizePath(routePath);
|
|
261
|
+
return manifest.staticExpectations.some(exp => {
|
|
262
|
+
const expFromPath = normalizePath(exp.fromPath || '*');
|
|
263
|
+
return expFromPath === normalizedRoute || expFromPath === '*';
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Normalize path for comparison
|
|
269
|
+
*/
|
|
270
|
+
function normalizePath(path) {
|
|
271
|
+
if (!path || path === '*') return '*';
|
|
272
|
+
return path.replace(/\/$/, '') || '/';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Extract feature name from route path
|
|
277
|
+
*/
|
|
278
|
+
function extractFeatureFromRoute(routePath) {
|
|
279
|
+
if (!routePath || routePath === '*') return 'unknown';
|
|
280
|
+
|
|
281
|
+
// Extract feature from common route patterns
|
|
282
|
+
const normalized = normalizePath(routePath);
|
|
283
|
+
|
|
284
|
+
// Common patterns: /users, /dashboard, /settings, etc.
|
|
285
|
+
const parts = normalized.split('/').filter(p => p);
|
|
286
|
+
if (parts.length > 0) {
|
|
287
|
+
return parts[0]; // First segment is typically the feature
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return 'root';
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Main function to enrich finding with all signals
|
|
295
|
+
*/
|
|
296
|
+
export function enrichFindingWithSignals(finding, trace = {}, manifest = {}) {
|
|
297
|
+
const signals = {
|
|
298
|
+
impact: mapImpactLevel(finding, manifest),
|
|
299
|
+
userRisk: mapUserRisk(finding),
|
|
300
|
+
ownership: mapOwnership(finding, trace),
|
|
301
|
+
grouping: generateGroupingMetadata(finding, manifest)
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
...finding,
|
|
306
|
+
signals
|
|
307
|
+
};
|
|
308
|
+
}
|