@veraxhq/verax 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +123 -88
  2. package/bin/verax.js +11 -452
  3. package/package.json +24 -36
  4. package/src/cli/commands/default.js +681 -0
  5. package/src/cli/commands/doctor.js +197 -0
  6. package/src/cli/commands/inspect.js +109 -0
  7. package/src/cli/commands/run.js +586 -0
  8. package/src/cli/entry.js +196 -0
  9. package/src/cli/util/atomic-write.js +37 -0
  10. package/src/cli/util/detection-engine.js +297 -0
  11. package/src/cli/util/env-url.js +33 -0
  12. package/src/cli/util/errors.js +44 -0
  13. package/src/cli/util/events.js +110 -0
  14. package/src/cli/util/expectation-extractor.js +388 -0
  15. package/src/cli/util/findings-writer.js +32 -0
  16. package/src/cli/util/idgen.js +87 -0
  17. package/src/cli/util/learn-writer.js +39 -0
  18. package/src/cli/util/observation-engine.js +412 -0
  19. package/src/cli/util/observe-writer.js +25 -0
  20. package/src/cli/util/paths.js +30 -0
  21. package/src/cli/util/project-discovery.js +297 -0
  22. package/src/cli/util/project-writer.js +26 -0
  23. package/src/cli/util/redact.js +128 -0
  24. package/src/cli/util/run-id.js +30 -0
  25. package/src/cli/util/runtime-budget.js +147 -0
  26. package/src/cli/util/summary-writer.js +43 -0
  27. package/src/types/global.d.ts +28 -0
  28. package/src/types/ts-ast.d.ts +24 -0
  29. package/src/verax/cli/ci-summary.js +35 -0
  30. package/src/verax/cli/context-explanation.js +89 -0
  31. package/src/verax/cli/doctor.js +277 -0
  32. package/src/verax/cli/error-normalizer.js +154 -0
  33. package/src/verax/cli/explain-output.js +105 -0
  34. package/src/verax/cli/finding-explainer.js +130 -0
  35. package/src/verax/cli/init.js +237 -0
  36. package/src/verax/cli/run-overview.js +163 -0
  37. package/src/verax/cli/url-safety.js +111 -0
  38. package/src/verax/cli/wizard.js +109 -0
  39. package/src/verax/cli/zero-findings-explainer.js +57 -0
  40. package/src/verax/cli/zero-interaction-explainer.js +127 -0
  41. package/src/verax/core/action-classifier.js +86 -0
  42. package/src/verax/core/budget-engine.js +218 -0
  43. package/src/verax/core/canonical-outcomes.js +157 -0
  44. package/src/verax/core/decision-snapshot.js +335 -0
  45. package/src/verax/core/determinism-model.js +432 -0
  46. package/src/verax/core/incremental-store.js +245 -0
  47. package/src/verax/core/invariants.js +356 -0
  48. package/src/verax/core/promise-model.js +230 -0
  49. package/src/verax/core/replay-validator.js +350 -0
  50. package/src/verax/core/replay.js +222 -0
  51. package/src/verax/core/run-id.js +175 -0
  52. package/src/verax/core/run-manifest.js +99 -0
  53. package/src/verax/core/silence-impact.js +369 -0
  54. package/src/verax/core/silence-model.js +523 -0
  55. package/src/verax/detect/comparison.js +7 -34
  56. package/src/verax/detect/confidence-engine.js +764 -329
  57. package/src/verax/detect/detection-engine.js +293 -0
  58. package/src/verax/detect/evidence-index.js +127 -0
  59. package/src/verax/detect/expectation-model.js +241 -168
  60. package/src/verax/detect/explanation-helpers.js +187 -0
  61. package/src/verax/detect/finding-detector.js +450 -0
  62. package/src/verax/detect/findings-writer.js +41 -12
  63. package/src/verax/detect/flow-detector.js +366 -0
  64. package/src/verax/detect/index.js +200 -288
  65. package/src/verax/detect/interactive-findings.js +612 -0
  66. package/src/verax/detect/signal-mapper.js +308 -0
  67. package/src/verax/detect/skip-classifier.js +4 -4
  68. package/src/verax/detect/verdict-engine.js +561 -0
  69. package/src/verax/evidence-index-writer.js +61 -0
  70. package/src/verax/flow/flow-engine.js +3 -2
  71. package/src/verax/flow/flow-spec.js +1 -2
  72. package/src/verax/index.js +103 -15
  73. package/src/verax/intel/effect-detector.js +368 -0
  74. package/src/verax/intel/handler-mapper.js +249 -0
  75. package/src/verax/intel/index.js +281 -0
  76. package/src/verax/intel/route-extractor.js +280 -0
  77. package/src/verax/intel/ts-program.js +256 -0
  78. package/src/verax/intel/vue-navigation-extractor.js +642 -0
  79. package/src/verax/intel/vue-router-extractor.js +325 -0
  80. package/src/verax/learn/action-contract-extractor.js +338 -104
  81. package/src/verax/learn/ast-contract-extractor.js +148 -6
  82. package/src/verax/learn/flow-extractor.js +172 -0
  83. package/src/verax/learn/index.js +36 -2
  84. package/src/verax/learn/manifest-writer.js +122 -58
  85. package/src/verax/learn/project-detector.js +40 -0
  86. package/src/verax/learn/route-extractor.js +28 -97
  87. package/src/verax/learn/route-validator.js +8 -7
  88. package/src/verax/learn/state-extractor.js +212 -0
  89. package/src/verax/learn/static-extractor-navigation.js +114 -0
  90. package/src/verax/learn/static-extractor-validation.js +88 -0
  91. package/src/verax/learn/static-extractor.js +119 -10
  92. package/src/verax/learn/truth-assessor.js +24 -21
  93. package/src/verax/learn/ts-contract-resolver.js +14 -12
  94. package/src/verax/observe/aria-sensor.js +211 -0
  95. package/src/verax/observe/browser.js +30 -6
  96. package/src/verax/observe/console-sensor.js +2 -18
  97. package/src/verax/observe/domain-boundary.js +10 -1
  98. package/src/verax/observe/expectation-executor.js +513 -0
  99. package/src/verax/observe/flow-matcher.js +143 -0
  100. package/src/verax/observe/focus-sensor.js +196 -0
  101. package/src/verax/observe/human-driver.js +660 -273
  102. package/src/verax/observe/index.js +910 -26
  103. package/src/verax/observe/interaction-discovery.js +378 -15
  104. package/src/verax/observe/interaction-runner.js +562 -197
  105. package/src/verax/observe/loading-sensor.js +145 -0
  106. package/src/verax/observe/navigation-sensor.js +255 -0
  107. package/src/verax/observe/network-sensor.js +55 -7
  108. package/src/verax/observe/observed-expectation-deriver.js +186 -0
  109. package/src/verax/observe/observed-expectation.js +305 -0
  110. package/src/verax/observe/page-frontier.js +234 -0
  111. package/src/verax/observe/settle.js +38 -17
  112. package/src/verax/observe/state-sensor.js +393 -0
  113. package/src/verax/observe/state-ui-sensor.js +7 -1
  114. package/src/verax/observe/timing-sensor.js +228 -0
  115. package/src/verax/observe/traces-writer.js +73 -21
  116. package/src/verax/observe/ui-signal-sensor.js +143 -17
  117. package/src/verax/scan-summary-writer.js +80 -15
  118. package/src/verax/shared/artifact-manager.js +111 -9
  119. package/src/verax/shared/budget-profiles.js +136 -0
  120. package/src/verax/shared/caching.js +1 -1
  121. package/src/verax/shared/ci-detection.js +39 -0
  122. package/src/verax/shared/config-loader.js +169 -0
  123. package/src/verax/shared/dynamic-route-utils.js +224 -0
  124. package/src/verax/shared/expectation-coverage.js +44 -0
  125. package/src/verax/shared/expectation-prover.js +81 -0
  126. package/src/verax/shared/expectation-tracker.js +201 -0
  127. package/src/verax/shared/expectations-writer.js +60 -0
  128. package/src/verax/shared/first-run.js +44 -0
  129. package/src/verax/shared/progress-reporter.js +171 -0
  130. package/src/verax/shared/retry-policy.js +9 -1
  131. package/src/verax/shared/root-artifacts.js +49 -0
  132. package/src/verax/shared/scan-budget.js +86 -0
  133. package/src/verax/shared/url-normalizer.js +162 -0
  134. package/src/verax/shared/zip-artifacts.js +66 -0
  135. package/src/verax/validate/context-validator.js +244 -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
+ }
@@ -54,8 +54,8 @@ export function classifySkipReason(manifest, interaction, beforeUrl, validation
54
54
  const interactionSelector = interaction.selector || '';
55
55
 
56
56
  if (selectorHint && interactionSelector) {
57
- const normalizedSelectorHint = selectorHint.replace(/[\[\]()]/g, '');
58
- const normalizedInteractionSelector = interactionSelector.replace(/[\[\]()]/g, '');
57
+ const normalizedSelectorHint = selectorHint.replace(/[[\]()]/g, '');
58
+ const normalizedInteractionSelector = interactionSelector.replace(/[[\]()]/g, '');
59
59
 
60
60
  if (selectorHint === interactionSelector ||
61
61
  selectorHint.includes(interactionSelector) ||
@@ -76,8 +76,8 @@ export function classifySkipReason(manifest, interaction, beforeUrl, validation
76
76
  const interactionSelector = interaction.selector || '';
77
77
 
78
78
  if (selectorHint && interactionSelector) {
79
- const normalizedSelectorHint = selectorHint.replace(/[\[\]()]/g, '');
80
- const normalizedInteractionSelector = interactionSelector.replace(/[\[\]()]/g, '');
79
+ const normalizedSelectorHint = selectorHint.replace(/[[\]()]/g, '');
80
+ const normalizedInteractionSelector = interactionSelector.replace(/[[\]()]/g, '');
81
81
 
82
82
  if (selectorHint === interactionSelector ||
83
83
  selectorHint.includes(interactionSelector) ||