@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,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Focus Sensor
|
|
3
|
+
* Tracks focus changes and detects focus-related silent failures
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class FocusSensor {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.focusHistory = [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Capture current focus state before interaction
|
|
13
|
+
*/
|
|
14
|
+
async captureBefore(page) {
|
|
15
|
+
const focusData = await page.evaluate(() => {
|
|
16
|
+
const active = document.activeElement;
|
|
17
|
+
let selector = 'unknown';
|
|
18
|
+
|
|
19
|
+
if (active === document.body) {
|
|
20
|
+
selector = 'body';
|
|
21
|
+
} else if (active === document.documentElement) {
|
|
22
|
+
selector = 'html';
|
|
23
|
+
} else if (!active) {
|
|
24
|
+
selector = 'null';
|
|
25
|
+
} else {
|
|
26
|
+
if (active.id) {
|
|
27
|
+
selector = `#${active.id}`;
|
|
28
|
+
} else if (active.className) {
|
|
29
|
+
const classes = Array.from(active.classList || []).slice(0, 2).join('.');
|
|
30
|
+
selector = active.tagName.toLowerCase() + (classes ? `.${classes}` : '');
|
|
31
|
+
} else {
|
|
32
|
+
selector = active.tagName.toLowerCase();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
selector,
|
|
38
|
+
tagName: active?.tagName || 'null',
|
|
39
|
+
id: active?.id || null,
|
|
40
|
+
role: active?.getAttribute('role') || null,
|
|
41
|
+
ariaLabel: active?.getAttribute('aria-label') || null
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
this.focusBefore = focusData;
|
|
46
|
+
return focusData;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Capture focus state after interaction and track history
|
|
51
|
+
*/
|
|
52
|
+
async captureAfter(page) {
|
|
53
|
+
const focusData = await page.evaluate(() => {
|
|
54
|
+
const active = document.activeElement;
|
|
55
|
+
let selector = 'unknown';
|
|
56
|
+
|
|
57
|
+
if (active === document.body) {
|
|
58
|
+
selector = 'body';
|
|
59
|
+
} else if (active === document.documentElement) {
|
|
60
|
+
selector = 'html';
|
|
61
|
+
} else if (!active) {
|
|
62
|
+
selector = 'null';
|
|
63
|
+
} else {
|
|
64
|
+
if (active.id) {
|
|
65
|
+
selector = `#${active.id}`;
|
|
66
|
+
} else if (active.className) {
|
|
67
|
+
const classes = Array.from(active.classList || []).slice(0, 2).join('.');
|
|
68
|
+
selector = active.tagName.toLowerCase() + (classes ? `.${classes}` : '');
|
|
69
|
+
} else {
|
|
70
|
+
selector = active.tagName.toLowerCase();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check if there's a modal present
|
|
75
|
+
const modal = document.querySelector('[role="dialog"], [aria-modal="true"], .modal, [data-modal]');
|
|
76
|
+
const hasModal = Boolean(modal);
|
|
77
|
+
|
|
78
|
+
// Check if focus is within modal
|
|
79
|
+
const focusInModal = active?.closest('[role="dialog"], [aria-modal="true"], .modal, [data-modal]');
|
|
80
|
+
const isWithinModal = Boolean(focusInModal);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
selector,
|
|
84
|
+
tagName: active?.tagName || 'null',
|
|
85
|
+
id: active?.id || null,
|
|
86
|
+
role: active?.getAttribute('role') || null,
|
|
87
|
+
ariaLabel: active?.getAttribute('aria-label') || null,
|
|
88
|
+
hasModal: hasModal,
|
|
89
|
+
focusInModal: isWithinModal
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
this.focusAfter = focusData;
|
|
94
|
+
this.focusHistory.push(focusData);
|
|
95
|
+
return focusData;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Perform keyboard navigation and track focus sequence to detect traps
|
|
100
|
+
*/
|
|
101
|
+
async captureKeyboardSequence(page, steps = 10) {
|
|
102
|
+
const sequence = [];
|
|
103
|
+
|
|
104
|
+
// Start with current focus
|
|
105
|
+
let current = await page.evaluate(() => {
|
|
106
|
+
const active = document.activeElement;
|
|
107
|
+
if (active === document.body) return 'body';
|
|
108
|
+
if (active === document.documentElement) return 'html';
|
|
109
|
+
if (!active) return 'null';
|
|
110
|
+
if (active.id) return `#${active.id}`;
|
|
111
|
+
return active.tagName.toLowerCase();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
sequence.push(current);
|
|
115
|
+
|
|
116
|
+
// Tab through elements and record focus changes
|
|
117
|
+
for (let i = 0; i < steps; i++) {
|
|
118
|
+
await page.press('body', 'Tab');
|
|
119
|
+
await page.waitForTimeout(50); // Small delay for focus to settle
|
|
120
|
+
|
|
121
|
+
const next = await page.evaluate(() => {
|
|
122
|
+
const active = document.activeElement;
|
|
123
|
+
if (active === document.body) return 'body';
|
|
124
|
+
if (active === document.documentElement) return 'html';
|
|
125
|
+
if (!active) return 'null';
|
|
126
|
+
if (active.id) return `#${active.id}`;
|
|
127
|
+
return active.tagName.toLowerCase();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
sequence.push(next);
|
|
131
|
+
|
|
132
|
+
// Check for trap: if we've cycled the same elements
|
|
133
|
+
if (i >= 3) {
|
|
134
|
+
const recentUnique = new Set(sequence.slice(-3));
|
|
135
|
+
if (recentUnique.size <= 2) {
|
|
136
|
+
// Likely in a trap
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return sequence;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Detect if focus is lost or stuck
|
|
147
|
+
*/
|
|
148
|
+
detectFocusLoss() {
|
|
149
|
+
if (!this.focusAfter) return false;
|
|
150
|
+
|
|
151
|
+
// Focus lost to body/null after interaction
|
|
152
|
+
return this.focusAfter.selector === 'body' || this.focusAfter.selector === 'null';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Detect if focus didn't move into modal (expected but didn't happen)
|
|
157
|
+
*/
|
|
158
|
+
detectModalFocusFailure(page) {
|
|
159
|
+
// This is checked via modal detection - focus should move to modal
|
|
160
|
+
// Presence of modal without focus change = failure
|
|
161
|
+
return false; // Caller will check if modal opened
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Detect keyboard trap: focus cycles within small set
|
|
166
|
+
*/
|
|
167
|
+
detectKeyboardTrap(sequence, threshold = 4) {
|
|
168
|
+
if (!sequence || sequence.length < 3) return false;
|
|
169
|
+
|
|
170
|
+
// If same focus element repeats consecutively, it's a trap
|
|
171
|
+
for (let i = 1; i < sequence.length; i++) {
|
|
172
|
+
if (sequence[i] === sequence[i - 1]) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// If we cycle through same 2-3 elements repeatedly
|
|
178
|
+
const recentSet = new Set(sequence.slice(-threshold));
|
|
179
|
+
if (recentSet.size <= 2 && sequence.length >= threshold) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get focus diff for evidence
|
|
188
|
+
*/
|
|
189
|
+
getFocusDiff() {
|
|
190
|
+
return {
|
|
191
|
+
before: this.focusBefore,
|
|
192
|
+
after: this.focusAfter,
|
|
193
|
+
changed: this.focusBefore?.selector !== this.focusAfter?.selector
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|