@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.
- package/README.md +123 -88
- package/bin/verax.js +11 -452
- package/package.json +24 -36
- package/src/cli/commands/default.js +681 -0
- package/src/cli/commands/doctor.js +197 -0
- package/src/cli/commands/inspect.js +109 -0
- package/src/cli/commands/run.js +586 -0
- package/src/cli/entry.js +196 -0
- package/src/cli/util/atomic-write.js +37 -0
- package/src/cli/util/detection-engine.js +297 -0
- package/src/cli/util/env-url.js +33 -0
- package/src/cli/util/errors.js +44 -0
- package/src/cli/util/events.js +110 -0
- package/src/cli/util/expectation-extractor.js +388 -0
- package/src/cli/util/findings-writer.js +32 -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 +412 -0
- package/src/cli/util/observe-writer.js +25 -0
- package/src/cli/util/paths.js +30 -0
- package/src/cli/util/project-discovery.js +297 -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/runtime-budget.js +147 -0
- package/src/cli/util/summary-writer.js +43 -0
- package/src/types/global.d.ts +28 -0
- package/src/types/ts-ast.d.ts +24 -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 +111 -0
- package/src/verax/cli/wizard.js +109 -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 +432 -0
- package/src/verax/core/incremental-store.js +245 -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 +523 -0
- package/src/verax/detect/comparison.js +7 -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 +127 -0
- package/src/verax/detect/expectation-model.js +241 -168
- 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 +41 -12
- package/src/verax/detect/flow-detector.js +366 -0
- package/src/verax/detect/index.js +200 -288
- package/src/verax/detect/interactive-findings.js +612 -0
- package/src/verax/detect/signal-mapper.js +308 -0
- package/src/verax/detect/skip-classifier.js +4 -4
- package/src/verax/detect/verdict-engine.js +561 -0
- package/src/verax/evidence-index-writer.js +61 -0
- package/src/verax/flow/flow-engine.js +3 -2
- package/src/verax/flow/flow-spec.js +1 -2
- package/src/verax/index.js +103 -15
- 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 +642 -0
- package/src/verax/intel/vue-router-extractor.js +325 -0
- package/src/verax/learn/action-contract-extractor.js +338 -104
- package/src/verax/learn/ast-contract-extractor.js +148 -6
- package/src/verax/learn/flow-extractor.js +172 -0
- package/src/verax/learn/index.js +36 -2
- package/src/verax/learn/manifest-writer.js +122 -58
- package/src/verax/learn/project-detector.js +40 -0
- package/src/verax/learn/route-extractor.js +28 -97
- package/src/verax/learn/route-validator.js +8 -7
- 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 +119 -10
- package/src/verax/learn/truth-assessor.js +24 -21
- package/src/verax/learn/ts-contract-resolver.js +14 -12
- package/src/verax/observe/aria-sensor.js +211 -0
- package/src/verax/observe/browser.js +30 -6
- package/src/verax/observe/console-sensor.js +2 -18
- package/src/verax/observe/domain-boundary.js +10 -1
- package/src/verax/observe/expectation-executor.js +513 -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 +660 -273
- package/src/verax/observe/index.js +910 -26
- package/src/verax/observe/interaction-discovery.js +378 -15
- package/src/verax/observe/interaction-runner.js +562 -197
- package/src/verax/observe/loading-sensor.js +145 -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 +38 -17
- package/src/verax/observe/state-sensor.js +393 -0
- package/src/verax/observe/state-ui-sensor.js +7 -1
- package/src/verax/observe/timing-sensor.js +228 -0
- package/src/verax/observe/traces-writer.js +73 -21
- package/src/verax/observe/ui-signal-sensor.js +143 -17
- package/src/verax/scan-summary-writer.js +80 -15
- package/src/verax/shared/artifact-manager.js +111 -9
- package/src/verax/shared/budget-profiles.js +136 -0
- package/src/verax/shared/caching.js +1 -1
- package/src/verax/shared/ci-detection.js +39 -0
- package/src/verax/shared/config-loader.js +169 -0
- package/src/verax/shared/dynamic-route-utils.js +224 -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 +9 -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 +66 -0
- package/src/verax/validate/context-validator.js +244 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VERAX Doctor (Phase 8.1)
|
|
3
|
+
* Environment diagnostics for enterprise hardening.
|
|
4
|
+
* Never throws on check failures; only invalid usage should bubble out.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync } from 'fs';
|
|
8
|
+
import { platform } from 'os';
|
|
9
|
+
import { createRequire } from 'module';
|
|
10
|
+
import { UsageError } from '../util/errors.js';
|
|
11
|
+
|
|
12
|
+
export async function doctorCommand(options = {}) {
|
|
13
|
+
const { json = false, extraFlags = [] } = options;
|
|
14
|
+
|
|
15
|
+
if (extraFlags.length > 0) {
|
|
16
|
+
throw new UsageError(`Unknown flag(s): ${extraFlags.join(', ')}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const checks = [];
|
|
20
|
+
const recommendations = [];
|
|
21
|
+
const platformName = platform();
|
|
22
|
+
const nodeVersion = process.versions.node;
|
|
23
|
+
let playwrightVersion = null;
|
|
24
|
+
let playwright = null;
|
|
25
|
+
let chromiumPath = null;
|
|
26
|
+
const require = createRequire(import.meta.url);
|
|
27
|
+
|
|
28
|
+
const addCheck = (name, status, details, recommendation) => {
|
|
29
|
+
checks.push({ name, status, details });
|
|
30
|
+
if (recommendation) {
|
|
31
|
+
recommendations.push(recommendation);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// 1) Node.js version
|
|
36
|
+
const nodeMajor = parseInt(nodeVersion.split('.')[0], 10);
|
|
37
|
+
if (Number.isFinite(nodeMajor) && nodeMajor >= 18) {
|
|
38
|
+
addCheck('Node.js version', 'pass', `Detected v${nodeVersion} (>=18 required)`);
|
|
39
|
+
} else {
|
|
40
|
+
addCheck('Node.js version', 'fail', `Detected v${nodeVersion} (<18)`, 'Upgrade Node.js to v18+');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 2) Playwright package presence + version
|
|
44
|
+
try {
|
|
45
|
+
playwright = await import('playwright');
|
|
46
|
+
try {
|
|
47
|
+
const pkg = require('playwright/package.json');
|
|
48
|
+
playwrightVersion = pkg?.version || null;
|
|
49
|
+
} catch {
|
|
50
|
+
playwrightVersion = null;
|
|
51
|
+
}
|
|
52
|
+
addCheck('Playwright package', 'pass', `Installed${playwrightVersion ? ` v${playwrightVersion}` : ''}`);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
addCheck(
|
|
55
|
+
'Playwright package',
|
|
56
|
+
'fail',
|
|
57
|
+
'Not installed or not resolvable',
|
|
58
|
+
'npm install -D playwright'
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 3) Playwright Chromium binaries
|
|
63
|
+
if (playwright && playwright.chromium) {
|
|
64
|
+
try {
|
|
65
|
+
chromiumPath = playwright.chromium.executablePath();
|
|
66
|
+
if (chromiumPath && existsSync(chromiumPath)) {
|
|
67
|
+
addCheck('Playwright Chromium', 'pass', `Executable found at ${chromiumPath}`);
|
|
68
|
+
} else {
|
|
69
|
+
addCheck(
|
|
70
|
+
'Playwright Chromium',
|
|
71
|
+
'fail',
|
|
72
|
+
'Chromium binary not found',
|
|
73
|
+
'npx playwright install --with-deps chromium'
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
addCheck(
|
|
78
|
+
'Playwright Chromium',
|
|
79
|
+
'fail',
|
|
80
|
+
`Unable to resolve Chromium executable (${error.message})`,
|
|
81
|
+
'npx playwright install --with-deps chromium'
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
addCheck(
|
|
86
|
+
'Playwright Chromium',
|
|
87
|
+
'fail',
|
|
88
|
+
'Skipped because Playwright is missing',
|
|
89
|
+
'npm install -D playwright && npx playwright install --with-deps chromium'
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 4) Headless launch smoke test
|
|
94
|
+
if (playwright && playwright.chromium && chromiumPath && existsSync(chromiumPath)) {
|
|
95
|
+
/** @type {any} */
|
|
96
|
+
let browser = null;
|
|
97
|
+
try {
|
|
98
|
+
// Configurable timeout via env (default 5000ms, CI can override to 3000ms)
|
|
99
|
+
const smokeTimeoutMs = parseInt(process.env.VERAX_DOCTOR_SMOKE_TIMEOUT_MS || '5000', 10);
|
|
100
|
+
|
|
101
|
+
// Wrap entire smoke test in hard timeout
|
|
102
|
+
const smokeTestPromise = (async () => {
|
|
103
|
+
browser = await playwright.chromium.launch({ headless: true, timeout: smokeTimeoutMs });
|
|
104
|
+
const page = await browser.newPage();
|
|
105
|
+
await page.goto('about:blank', { timeout: smokeTimeoutMs });
|
|
106
|
+
return true;
|
|
107
|
+
})();
|
|
108
|
+
|
|
109
|
+
// Race against timeout
|
|
110
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
111
|
+
setTimeout(() => reject(new Error('Smoke test timed out')), smokeTimeoutMs);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await Promise.race([smokeTestPromise, timeoutPromise]);
|
|
115
|
+
addCheck('Headless smoke test', 'pass', 'Chromium launched headless and closed successfully');
|
|
116
|
+
} catch (error) {
|
|
117
|
+
addCheck(
|
|
118
|
+
'Headless smoke test',
|
|
119
|
+
'fail',
|
|
120
|
+
`Headless launch failed: ${error.message}`,
|
|
121
|
+
platformName === 'linux'
|
|
122
|
+
? 'Try: npx playwright install --with-deps chromium && launch with --no-sandbox in constrained environments'
|
|
123
|
+
: 'Reinstall playwright: npx playwright install --with-deps chromium'
|
|
124
|
+
);
|
|
125
|
+
} finally {
|
|
126
|
+
// CRITICAL: Always close browser to prevent hanging processes
|
|
127
|
+
// Bound close operation too (max 2s)
|
|
128
|
+
if (browser) {
|
|
129
|
+
try {
|
|
130
|
+
const closePromise = browser.close();
|
|
131
|
+
const closeTimeout = new Promise((resolve) => setTimeout(resolve, 2000));
|
|
132
|
+
await Promise.race([closePromise, closeTimeout]);
|
|
133
|
+
} catch (closeError) {
|
|
134
|
+
// Ignore close errors - force kill if needed
|
|
135
|
+
try {
|
|
136
|
+
await browser.close();
|
|
137
|
+
} catch {
|
|
138
|
+
// Final attempt failed, process will clean up
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
addCheck(
|
|
145
|
+
'Headless smoke test',
|
|
146
|
+
'fail',
|
|
147
|
+
'Skipped because Chromium executable is unavailable',
|
|
148
|
+
'npx playwright install --with-deps chromium'
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 5) Linux sandbox guidance
|
|
153
|
+
if (platformName === 'linux') {
|
|
154
|
+
const ciHints = detectCIHints();
|
|
155
|
+
const detailParts = ['Sandbox guidance: if launch fails, use --no-sandbox or ensure libnss3/libatk are installed'];
|
|
156
|
+
if (ciHints) detailParts.push(`Detected CI: ${ciHints}`);
|
|
157
|
+
addCheck('Linux sandbox guidance', 'pass', detailParts.join(' | '));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const ok = checks.every((c) => c.status === 'pass');
|
|
161
|
+
|
|
162
|
+
if (json) {
|
|
163
|
+
const report = {
|
|
164
|
+
ok,
|
|
165
|
+
platform: platformName,
|
|
166
|
+
nodeVersion,
|
|
167
|
+
playwrightVersion,
|
|
168
|
+
checks,
|
|
169
|
+
recommendations,
|
|
170
|
+
};
|
|
171
|
+
console.log(JSON.stringify(report, null, 2));
|
|
172
|
+
return report;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Human-readable output
|
|
176
|
+
console.log('VERAX Doctor — Environment Diagnostics');
|
|
177
|
+
checks.forEach((c) => {
|
|
178
|
+
const label = c.status === 'pass' ? 'PASS' : 'FAIL';
|
|
179
|
+
console.log(`[${label}] ${c.name}: ${c.details}`);
|
|
180
|
+
});
|
|
181
|
+
console.log(`
|
|
182
|
+
Overall: ${ok ? 'OK' : 'Issues found'} (${checks.filter(c => c.status === 'fail').length} failing checks)`);
|
|
183
|
+
if (recommendations.length > 0) {
|
|
184
|
+
console.log('\nRecommended actions:');
|
|
185
|
+
recommendations.forEach((r) => console.log(`- ${r}`));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return { ok, checks, recommendations };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function detectCIHints() {
|
|
192
|
+
if (process.env.GITHUB_ACTIONS === 'true') return 'GITHUB_ACTIONS';
|
|
193
|
+
if (process.env.CI === 'true') return 'CI';
|
|
194
|
+
if (process.env.BITBUCKET_BUILD_NUMBER) return 'BITBUCKET';
|
|
195
|
+
if (process.env.GITLAB_CI) return 'GITLAB_CI';
|
|
196
|
+
return '';
|
|
197
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { resolve } from 'path';
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
3
|
+
import { DataError } from '../util/errors.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `verax inspect` command
|
|
7
|
+
* Read an existing run folder and display summary
|
|
8
|
+
*/
|
|
9
|
+
export async function inspectCommand(runPath, options = {}) {
|
|
10
|
+
const { json = false } = options;
|
|
11
|
+
|
|
12
|
+
const fullPath = resolve(runPath);
|
|
13
|
+
|
|
14
|
+
// Validate run directory exists
|
|
15
|
+
if (!existsSync(fullPath)) {
|
|
16
|
+
throw new DataError(`Run directory not found: ${fullPath}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Check for required files
|
|
20
|
+
const requiredFiles = ['summary.json', 'findings.json'];
|
|
21
|
+
const missingFiles = [];
|
|
22
|
+
|
|
23
|
+
for (const file of requiredFiles) {
|
|
24
|
+
const filePath = `${fullPath}/${file}`;
|
|
25
|
+
if (!existsSync(filePath)) {
|
|
26
|
+
missingFiles.push(file);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (missingFiles.length > 0) {
|
|
31
|
+
throw new DataError(
|
|
32
|
+
`Invalid run directory. Missing files: ${missingFiles.join(', ')}`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Read summary and findings
|
|
37
|
+
let summary, findings;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
summary = JSON.parse(readFileSync(`${fullPath}/summary.json`, 'utf8'));
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw new DataError(`Failed to parse summary.json: ${error.message}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
findings = JSON.parse(readFileSync(`${fullPath}/findings.json`, 'utf8'));
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw new DataError(`Failed to parse findings.json: ${error.message}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check for evidence directory
|
|
52
|
+
const evidenceDir = `${fullPath}/evidence`;
|
|
53
|
+
const hasEvidence = existsSync(evidenceDir);
|
|
54
|
+
let evidenceCount = 0;
|
|
55
|
+
|
|
56
|
+
if (hasEvidence) {
|
|
57
|
+
try {
|
|
58
|
+
evidenceCount = readdirSync(evidenceDir).length;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
evidenceCount = 0;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Build output
|
|
65
|
+
const output = {
|
|
66
|
+
runId: summary.runId || 'unknown',
|
|
67
|
+
status: summary.status || 'unknown',
|
|
68
|
+
startedAt: summary.startedAt || null,
|
|
69
|
+
completedAt: summary.completedAt || null,
|
|
70
|
+
url: summary.url || null,
|
|
71
|
+
findingsCount: Array.isArray(findings) ? findings.length : 0,
|
|
72
|
+
evidenceDir: hasEvidence ? evidenceDir : null,
|
|
73
|
+
evidenceFileCount: evidenceCount,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (json) {
|
|
77
|
+
// Output as single JSON object
|
|
78
|
+
console.log(JSON.stringify(output, null, 2));
|
|
79
|
+
} else {
|
|
80
|
+
// Output as human-readable summary
|
|
81
|
+
console.log('\n=== Run Summary ===\n');
|
|
82
|
+
console.log(`Run ID: ${output.runId}`);
|
|
83
|
+
console.log(`Status: ${output.status}`);
|
|
84
|
+
|
|
85
|
+
if (output.startedAt) {
|
|
86
|
+
console.log(`Started: ${output.startedAt}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (output.completedAt) {
|
|
90
|
+
console.log(`Completed: ${output.completedAt}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (output.url) {
|
|
94
|
+
console.log(`URL: ${output.url}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log(`\nFindings: ${output.findingsCount}`);
|
|
98
|
+
|
|
99
|
+
if (output.evidenceDir) {
|
|
100
|
+
console.log(`Evidence: ${output.evidenceDir} (${output.evidenceFileCount} files)`);
|
|
101
|
+
} else {
|
|
102
|
+
console.log(`Evidence: not found`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log('');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return output;
|
|
109
|
+
}
|