@veraxhq/verax 0.2.0 → 0.3.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 +14 -18
- package/bin/verax.js +7 -0
- package/package.json +15 -5
- package/src/cli/commands/baseline.js +104 -0
- package/src/cli/commands/default.js +323 -111
- package/src/cli/commands/doctor.js +36 -4
- package/src/cli/commands/ga.js +243 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +131 -2
- package/src/cli/commands/release-check.js +213 -0
- package/src/cli/commands/run.js +498 -103
- package/src/cli/commands/security-check.js +211 -0
- package/src/cli/commands/truth.js +114 -0
- package/src/cli/entry.js +305 -68
- package/src/cli/util/angular-component-extractor.js +179 -0
- package/src/cli/util/angular-navigation-detector.js +141 -0
- package/src/cli/util/angular-network-detector.js +161 -0
- package/src/cli/util/angular-state-detector.js +162 -0
- package/src/cli/util/ast-interactive-detector.js +546 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/detection-engine.js +4 -3
- package/src/cli/util/determinism-runner.js +123 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/env-url.js +4 -0
- package/src/cli/util/events.js +76 -0
- package/src/cli/util/expectation-extractor.js +380 -74
- package/src/cli/util/findings-writer.js +126 -15
- package/src/cli/util/learn-writer.js +3 -1
- package/src/cli/util/observation-engine.js +69 -23
- package/src/cli/util/observe-writer.js +3 -1
- package/src/cli/util/paths.js +6 -14
- package/src/cli/util/project-discovery.js +23 -0
- package/src/cli/util/project-writer.js +3 -1
- package/src/cli/util/redact.js +2 -2
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/runtime-budget.js +147 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +13 -1
- package/src/cli/util/svelte-navigation-detector.js +163 -0
- package/src/cli/util/svelte-network-detector.js +80 -0
- package/src/cli/util/svelte-sfc-extractor.js +147 -0
- package/src/cli/util/svelte-state-detector.js +243 -0
- package/src/cli/util/vue-navigation-detector.js +177 -0
- package/src/cli/util/vue-sfc-extractor.js +162 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/types/global.d.ts +28 -0
- package/src/types/ts-ast.d.ts +24 -0
- package/src/verax/cli/doctor.js +2 -2
- package/src/verax/cli/finding-explainer.js +56 -3
- package/src/verax/cli/init.js +1 -1
- package/src/verax/cli/url-safety.js +12 -2
- package/src/verax/cli/wizard.js +13 -2
- package/src/verax/core/artifacts/registry.js +154 -0
- package/src/verax/core/artifacts/verifier.js +980 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +231 -0
- package/src/verax/core/budget-engine.js +1 -1
- package/src/verax/core/capabilities/gates.js +499 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +137 -0
- package/src/verax/core/confidence/confidence-invariants.js +234 -0
- package/src/verax/core/confidence/confidence-report-writer.js +112 -0
- package/src/verax/core/confidence/confidence-weights.js +44 -0
- package/src/verax/core/confidence/confidence.defaults.js +65 -0
- package/src/verax/core/confidence/confidence.loader.js +79 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +484 -0
- package/src/verax/core/confidence-engine.js +486 -0
- package/src/verax/core/confidence-engine.js.backup +471 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +185 -0
- package/src/verax/core/contracts/validators.js +381 -0
- package/src/verax/core/decision-snapshot.js +31 -4
- package/src/verax/core/decisions/decision.trace.js +276 -0
- package/src/verax/core/determinism/contract-writer.js +89 -0
- package/src/verax/core/determinism/contract.js +139 -0
- package/src/verax/core/determinism/diff.js +364 -0
- package/src/verax/core/determinism/engine.js +221 -0
- package/src/verax/core/determinism/finding-identity.js +148 -0
- package/src/verax/core/determinism/normalize.js +438 -0
- package/src/verax/core/determinism/report-writer.js +92 -0
- package/src/verax/core/determinism/run-fingerprint.js +118 -0
- package/src/verax/core/determinism-model.js +35 -6
- package/src/verax/core/dynamic-route-intelligence.js +528 -0
- package/src/verax/core/evidence/evidence-capture-service.js +307 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
- package/src/verax/core/evidence-builder.js +487 -0
- package/src/verax/core/execution-mode-context.js +77 -0
- package/src/verax/core/execution-mode-detector.js +190 -0
- package/src/verax/core/failures/exit-codes.js +86 -0
- package/src/verax/core/failures/failure-summary.js +76 -0
- package/src/verax/core/failures/failure.factory.js +225 -0
- package/src/verax/core/failures/failure.ledger.js +132 -0
- package/src/verax/core/failures/failure.types.js +196 -0
- package/src/verax/core/failures/index.js +10 -0
- package/src/verax/core/ga/ga-report-writer.js +43 -0
- package/src/verax/core/ga/ga.artifact.js +49 -0
- package/src/verax/core/ga/ga.contract.js +434 -0
- package/src/verax/core/ga/ga.enforcer.js +86 -0
- package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
- package/src/verax/core/guardrails/policy.defaults.js +210 -0
- package/src/verax/core/guardrails/policy.loader.js +83 -0
- package/src/verax/core/guardrails/policy.schema.js +110 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
- package/src/verax/core/guardrails-engine.js +505 -0
- package/src/verax/core/incremental-store.js +15 -7
- package/src/verax/core/observe/run-timeline.js +316 -0
- package/src/verax/core/perf/perf.contract.js +186 -0
- package/src/verax/core/perf/perf.display.js +65 -0
- package/src/verax/core/perf/perf.enforcer.js +91 -0
- package/src/verax/core/perf/perf.monitor.js +209 -0
- package/src/verax/core/perf/perf.report.js +198 -0
- package/src/verax/core/pipeline-tracker.js +238 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +271 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +159 -0
- package/src/verax/core/release/reproducibility.check.js +221 -0
- package/src/verax/core/release/sbom.builder.js +283 -0
- package/src/verax/core/replay-validator.js +4 -4
- package/src/verax/core/replay.js +1 -1
- package/src/verax/core/report/cross-index.js +192 -0
- package/src/verax/core/report/human-summary.js +222 -0
- package/src/verax/core/route-intelligence.js +419 -0
- package/src/verax/core/security/secrets.scan.js +326 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +124 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +326 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/silence-impact.js +1 -1
- package/src/verax/core/silence-model.js +9 -7
- package/src/verax/core/truth/truth.certificate.js +250 -0
- package/src/verax/core/ui-feedback-intelligence.js +515 -0
- package/src/verax/detect/comparison.js +8 -3
- package/src/verax/detect/confidence-engine.js +645 -57
- package/src/verax/detect/confidence-helper.js +33 -0
- package/src/verax/detect/detection-engine.js +19 -2
- package/src/verax/detect/dynamic-route-findings.js +335 -0
- package/src/verax/detect/evidence-index.js +15 -65
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +56 -3
- package/src/verax/detect/explanation-helpers.js +1 -1
- package/src/verax/detect/finding-detector.js +2 -2
- package/src/verax/detect/findings-writer.js +149 -20
- package/src/verax/detect/flow-detector.js +4 -4
- package/src/verax/detect/index.js +265 -15
- package/src/verax/detect/interactive-findings.js +3 -4
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/route-findings.js +218 -0
- package/src/verax/detect/signal-mapper.js +2 -2
- package/src/verax/detect/skip-classifier.js +4 -4
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/verdict-engine.js +61 -9
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/flow/flow-engine.js +3 -2
- package/src/verax/flow/flow-spec.js +1 -2
- package/src/verax/index.js +413 -33
- package/src/verax/intel/effect-detector.js +1 -1
- package/src/verax/intel/index.js +2 -2
- package/src/verax/intel/route-extractor.js +3 -3
- package/src/verax/intel/vue-navigation-extractor.js +81 -18
- package/src/verax/intel/vue-router-extractor.js +4 -2
- package/src/verax/learn/action-contract-extractor.js +684 -66
- package/src/verax/learn/ast-contract-extractor.js +53 -1
- package/src/verax/learn/index.js +36 -2
- package/src/verax/learn/manifest-writer.js +28 -14
- package/src/verax/learn/route-extractor.js +1 -1
- package/src/verax/learn/route-validator.js +12 -8
- package/src/verax/learn/state-extractor.js +1 -1
- package/src/verax/learn/static-extractor-navigation.js +1 -1
- package/src/verax/learn/static-extractor-validation.js +2 -2
- package/src/verax/learn/static-extractor.js +8 -7
- package/src/verax/learn/ts-contract-resolver.js +14 -12
- package/src/verax/observe/browser.js +22 -3
- package/src/verax/observe/console-sensor.js +2 -2
- package/src/verax/observe/expectation-executor.js +2 -1
- package/src/verax/observe/focus-sensor.js +1 -1
- package/src/verax/observe/human-driver.js +29 -10
- package/src/verax/observe/index.js +92 -844
- package/src/verax/observe/interaction-discovery.js +27 -15
- package/src/verax/observe/interaction-runner.js +31 -14
- package/src/verax/observe/loading-sensor.js +6 -0
- package/src/verax/observe/navigation-sensor.js +1 -1
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +191 -0
- package/src/verax/observe/observe-runner.js +226 -0
- package/src/verax/observe/observers/budget-observer.js +185 -0
- package/src/verax/observe/observers/console-observer.js +102 -0
- package/src/verax/observe/observers/coverage-observer.js +107 -0
- package/src/verax/observe/observers/interaction-observer.js +471 -0
- package/src/verax/observe/observers/navigation-observer.js +132 -0
- package/src/verax/observe/observers/network-observer.js +87 -0
- package/src/verax/observe/observers/safety-observer.js +82 -0
- package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
- package/src/verax/observe/settle.js +1 -0
- package/src/verax/observe/state-sensor.js +8 -4
- package/src/verax/observe/state-ui-sensor.js +7 -1
- package/src/verax/observe/traces-writer.js +27 -16
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/observe/ui-signal-sensor.js +155 -2
- package/src/verax/scan-summary-writer.js +46 -9
- package/src/verax/shared/artifact-manager.js +9 -6
- package/src/verax/shared/budget-profiles.js +2 -2
- package/src/verax/shared/caching.js +1 -1
- package/src/verax/shared/config-loader.js +1 -2
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/dynamic-route-utils.js +12 -6
- package/src/verax/shared/retry-policy.js +1 -6
- package/src/verax/shared/root-artifacts.js +1 -1
- package/src/verax/shared/view-switch-rules.js +208 -0
- package/src/verax/shared/zip-artifacts.js +1 -0
- package/src/verax/validate/context-validator.js +1 -1
- package/src/verax/observe/index.js.backup +0 -1
- package/src/verax/validate/context-validator.js.bak +0 -0
|
@@ -3,7 +3,8 @@ import { existsSync, readFileSync } from 'fs';
|
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { dirname } from 'path';
|
|
5
5
|
import inquirer from 'inquirer';
|
|
6
|
-
import {
|
|
6
|
+
import { assertExecutionBootstrapAllowed } from '../util/bootstrap-guard.js';
|
|
7
|
+
import { DataError } from '../util/errors.js';
|
|
7
8
|
import { generateRunId } from '../util/run-id.js';
|
|
8
9
|
import { getRunPaths, ensureRunDirectories } from '../util/paths.js';
|
|
9
10
|
import { atomicWriteJson, atomicWriteText } from '../util/atomic-write.js';
|
|
@@ -18,6 +19,8 @@ import { writeObserveJson } from '../util/observe-writer.js';
|
|
|
18
19
|
import { detectFindings } from '../util/detection-engine.js';
|
|
19
20
|
import { writeFindingsJson } from '../util/findings-writer.js';
|
|
20
21
|
import { writeSummaryJson } from '../util/summary-writer.js';
|
|
22
|
+
import { computeRuntimeBudget, withTimeout } from '../util/runtime-budget.js';
|
|
23
|
+
import { assertHasLocalSource } from '../util/source-requirement.js';
|
|
21
24
|
|
|
22
25
|
const __filename = fileURLToPath(import.meta.url);
|
|
23
26
|
const __dirname = dirname(__filename);
|
|
@@ -28,7 +31,7 @@ function getVersion() {
|
|
|
28
31
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
29
32
|
return pkg.version;
|
|
30
33
|
} catch {
|
|
31
|
-
return '0.
|
|
34
|
+
return '0.3.0';
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
|
|
@@ -43,6 +46,8 @@ export async function defaultCommand(options = {}) {
|
|
|
43
46
|
url = null,
|
|
44
47
|
json = false,
|
|
45
48
|
verbose = false,
|
|
49
|
+
determinism = false,
|
|
50
|
+
determinismRuns = 2,
|
|
46
51
|
} = options;
|
|
47
52
|
|
|
48
53
|
const projectRoot = resolve(process.cwd());
|
|
@@ -52,6 +57,9 @@ export async function defaultCommand(options = {}) {
|
|
|
52
57
|
if (!existsSync(srcPath)) {
|
|
53
58
|
throw new DataError(`Source directory not found: ${srcPath}`);
|
|
54
59
|
}
|
|
60
|
+
|
|
61
|
+
// Enforce local source availability (no URL-only scans)
|
|
62
|
+
assertHasLocalSource(srcPath);
|
|
55
63
|
|
|
56
64
|
// Create event emitter
|
|
57
65
|
const events = new RunEventEmitter();
|
|
@@ -72,6 +80,83 @@ export async function defaultCommand(options = {}) {
|
|
|
72
80
|
});
|
|
73
81
|
}
|
|
74
82
|
|
|
83
|
+
let runId = null;
|
|
84
|
+
/** @type {ReturnType<typeof getRunPaths> | null} */
|
|
85
|
+
let paths = null;
|
|
86
|
+
let startedAt = null;
|
|
87
|
+
let watchdogTimer = null;
|
|
88
|
+
let budget = null;
|
|
89
|
+
let timedOut = false;
|
|
90
|
+
|
|
91
|
+
// Graceful finalization function
|
|
92
|
+
const finalizeOnTimeout = async (reason) => {
|
|
93
|
+
if (timedOut) return; // Prevent double finalization
|
|
94
|
+
timedOut = true;
|
|
95
|
+
|
|
96
|
+
events.stopHeartbeat();
|
|
97
|
+
|
|
98
|
+
// TypeScript narrowing: paths is guaranteed to be non-null here due to control flow
|
|
99
|
+
if (paths && runId && startedAt) {
|
|
100
|
+
try {
|
|
101
|
+
const failedAt = new Date().toISOString();
|
|
102
|
+
atomicWriteJson(paths.runStatusJson, {
|
|
103
|
+
contractVersion: 1,
|
|
104
|
+
artifactVersions: paths.artifactVersions,
|
|
105
|
+
status: 'FAILED',
|
|
106
|
+
runId,
|
|
107
|
+
startedAt,
|
|
108
|
+
failedAt,
|
|
109
|
+
error: reason,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
atomicWriteJson(paths.runMetaJson, {
|
|
113
|
+
contractVersion: 1,
|
|
114
|
+
artifactVersions: paths.artifactVersions,
|
|
115
|
+
veraxVersion: getVersion(),
|
|
116
|
+
nodeVersion: process.version,
|
|
117
|
+
platform: process.platform,
|
|
118
|
+
cwd: projectRoot,
|
|
119
|
+
command: 'default',
|
|
120
|
+
args: { url: url || null, src },
|
|
121
|
+
url: url || null,
|
|
122
|
+
src: srcPath,
|
|
123
|
+
startedAt,
|
|
124
|
+
completedAt: failedAt,
|
|
125
|
+
error: reason,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
writeSummaryJson(paths.summaryJson, {
|
|
130
|
+
runId,
|
|
131
|
+
status: 'FAILED',
|
|
132
|
+
startedAt,
|
|
133
|
+
completedAt: failedAt,
|
|
134
|
+
command: 'default',
|
|
135
|
+
url: url || null,
|
|
136
|
+
notes: `Run timed out: ${reason}`,
|
|
137
|
+
}, {
|
|
138
|
+
expectationsTotal: 0,
|
|
139
|
+
attempted: 0,
|
|
140
|
+
observed: 0,
|
|
141
|
+
silentFailures: 0,
|
|
142
|
+
coverageGaps: 0,
|
|
143
|
+
unproven: 0,
|
|
144
|
+
informational: 0,
|
|
145
|
+
});
|
|
146
|
+
} catch (summaryError) {
|
|
147
|
+
// Ignore summary write errors during timeout handling
|
|
148
|
+
}
|
|
149
|
+
} catch (statusError) {
|
|
150
|
+
// Ignore errors when writing failure status
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
events.emit('error', {
|
|
155
|
+
message: reason,
|
|
156
|
+
type: 'timeout',
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
|
|
75
160
|
try {
|
|
76
161
|
events.emit('phase:started', {
|
|
77
162
|
phase: 'Detect Project',
|
|
@@ -155,6 +240,9 @@ export async function defaultCommand(options = {}) {
|
|
|
155
240
|
console.log(''); // blank line
|
|
156
241
|
}
|
|
157
242
|
|
|
243
|
+
// PHASE 21.6.1: Runtime guard - crash if called during inspection
|
|
244
|
+
assertExecutionBootstrapAllowed('inquirer.prompt');
|
|
245
|
+
|
|
158
246
|
const answer = await inquirer.prompt([
|
|
159
247
|
{
|
|
160
248
|
type: 'input',
|
|
@@ -201,12 +289,16 @@ export async function defaultCommand(options = {}) {
|
|
|
201
289
|
let startedAt = now.toISOString();
|
|
202
290
|
|
|
203
291
|
atomicWriteJson(paths.runStatusJson, {
|
|
292
|
+
contractVersion: 1,
|
|
293
|
+
artifactVersions: paths.artifactVersions,
|
|
204
294
|
status: 'RUNNING',
|
|
205
295
|
runId,
|
|
206
296
|
startedAt,
|
|
207
297
|
});
|
|
208
298
|
|
|
209
299
|
atomicWriteJson(paths.runMetaJson, {
|
|
300
|
+
contractVersion: 1,
|
|
301
|
+
artifactVersions: paths.artifactVersions,
|
|
210
302
|
veraxVersion: getVersion(),
|
|
211
303
|
nodeVersion: process.version,
|
|
212
304
|
platform: process.platform,
|
|
@@ -231,8 +323,17 @@ export async function defaultCommand(options = {}) {
|
|
|
231
323
|
message: 'Analyzing project structure...',
|
|
232
324
|
});
|
|
233
325
|
|
|
234
|
-
|
|
235
|
-
|
|
326
|
+
events.startHeartbeat('Learn', json);
|
|
327
|
+
|
|
328
|
+
let expectations, skipped;
|
|
329
|
+
try {
|
|
330
|
+
// Extract expectations
|
|
331
|
+
const result = await extractExpectations(projectProfile, projectProfile.sourceRoot);
|
|
332
|
+
expectations = result.expectations;
|
|
333
|
+
skipped = result.skipped;
|
|
334
|
+
} finally {
|
|
335
|
+
events.stopHeartbeat();
|
|
336
|
+
}
|
|
236
337
|
|
|
237
338
|
if (!json) {
|
|
238
339
|
console.log(`Found ${expectations.length} expectations`);
|
|
@@ -256,55 +357,109 @@ export async function defaultCommand(options = {}) {
|
|
|
256
357
|
});
|
|
257
358
|
}
|
|
258
359
|
|
|
360
|
+
// Compute runtime budget based on expectations count
|
|
361
|
+
budget = computeRuntimeBudget({
|
|
362
|
+
expectationsCount: expectations.length,
|
|
363
|
+
mode: 'default',
|
|
364
|
+
framework: projectProfile.framework,
|
|
365
|
+
fileCount: projectProfile.fileCount || expectations.length,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Set up global watchdog timer
|
|
369
|
+
watchdogTimer = setTimeout(async () => {
|
|
370
|
+
await finalizeOnTimeout(`Global timeout exceeded: ${budget.totalMaxMs}ms`);
|
|
371
|
+
// Exit with code 0 (tool executed, just timed out)
|
|
372
|
+
process.exit(0);
|
|
373
|
+
}, budget.totalMaxMs);
|
|
374
|
+
|
|
375
|
+
// Wrap Learn phase with timeout
|
|
376
|
+
try {
|
|
377
|
+
await withTimeout(
|
|
378
|
+
budget.learnMaxMs,
|
|
379
|
+
Promise.resolve(), // Learn phase already completed
|
|
380
|
+
'Learn'
|
|
381
|
+
);
|
|
382
|
+
} catch (error) {
|
|
383
|
+
if (error.message.includes('timeout')) {
|
|
384
|
+
await finalizeOnTimeout(`Learn phase timeout: ${budget.learnMaxMs}ms`);
|
|
385
|
+
process.exit(0);
|
|
386
|
+
}
|
|
387
|
+
throw error;
|
|
388
|
+
}
|
|
389
|
+
|
|
259
390
|
events.emit('phase:completed', {
|
|
260
391
|
phase: 'Learn',
|
|
261
392
|
message: 'Project analysis complete',
|
|
262
393
|
});
|
|
263
394
|
|
|
264
|
-
// Observe phase
|
|
395
|
+
// Observe phase with timeout
|
|
265
396
|
events.emit('phase:started', {
|
|
266
397
|
phase: 'Observe',
|
|
267
398
|
message: 'Launching browser and observing expectations...',
|
|
268
399
|
});
|
|
269
400
|
|
|
401
|
+
events.startHeartbeat('Observe', json);
|
|
402
|
+
|
|
270
403
|
let observeData = null;
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
404
|
+
try {
|
|
405
|
+
if (expectations.length > 0) {
|
|
406
|
+
try {
|
|
407
|
+
observeData = await withTimeout(
|
|
408
|
+
budget.observeMaxMs,
|
|
409
|
+
observeExpectations(
|
|
410
|
+
expectations,
|
|
411
|
+
resolvedUrl,
|
|
412
|
+
paths.evidenceDir,
|
|
413
|
+
(progress) => {
|
|
414
|
+
events.emit(progress.event, progress);
|
|
415
|
+
if (!json && progress.event === 'observe:result') {
|
|
416
|
+
const status = progress.observed ? '✓' : '✗';
|
|
417
|
+
console.log(` ${status} ${progress.index}/${expectations.length}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
),
|
|
421
|
+
'Observe'
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
if (!json) {
|
|
425
|
+
console.log(`Observed: ${observeData.stats.observed}/${expectations.length}`);
|
|
426
|
+
}
|
|
427
|
+
} catch (error) {
|
|
428
|
+
if (error.message.includes('timeout')) {
|
|
429
|
+
if (!json) {
|
|
430
|
+
console.error(`Observe error: timeout after ${budget.observeMaxMs}ms`);
|
|
282
431
|
}
|
|
432
|
+
events.emit('observe:error', {
|
|
433
|
+
message: `Observe phase timeout: ${budget.observeMaxMs}ms`,
|
|
434
|
+
});
|
|
435
|
+
observeData = {
|
|
436
|
+
observations: [],
|
|
437
|
+
stats: { attempted: 0, observed: 0, notObserved: 0 },
|
|
438
|
+
observedAt: new Date().toISOString(),
|
|
439
|
+
};
|
|
440
|
+
} else {
|
|
441
|
+
if (!json) {
|
|
442
|
+
console.error(`Observe error: ${error.message}`);
|
|
443
|
+
}
|
|
444
|
+
events.emit('observe:error', {
|
|
445
|
+
message: error.message,
|
|
446
|
+
});
|
|
447
|
+
observeData = {
|
|
448
|
+
observations: [],
|
|
449
|
+
stats: { attempted: 0, observed: 0, notObserved: 0 },
|
|
450
|
+
observedAt: new Date().toISOString(),
|
|
451
|
+
};
|
|
283
452
|
}
|
|
284
|
-
);
|
|
285
|
-
|
|
286
|
-
if (!json) {
|
|
287
|
-
console.log(`Observed: ${observeData.stats.observed}/${expectations.length}`);
|
|
288
|
-
}
|
|
289
|
-
} catch (error) {
|
|
290
|
-
if (!json) {
|
|
291
|
-
console.error(`Observe error: ${error.message}`);
|
|
292
453
|
}
|
|
293
|
-
|
|
294
|
-
message: error.message,
|
|
295
|
-
});
|
|
454
|
+
} else {
|
|
296
455
|
observeData = {
|
|
297
456
|
observations: [],
|
|
298
457
|
stats: { attempted: 0, observed: 0, notObserved: 0 },
|
|
299
458
|
observedAt: new Date().toISOString(),
|
|
300
459
|
};
|
|
301
460
|
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
observations: [],
|
|
305
|
-
stats: { attempted: 0, observed: 0, notObserved: 0 },
|
|
306
|
-
observedAt: new Date().toISOString(),
|
|
307
|
-
};
|
|
461
|
+
} finally {
|
|
462
|
+
events.stopHeartbeat();
|
|
308
463
|
}
|
|
309
464
|
|
|
310
465
|
events.emit('phase:completed', {
|
|
@@ -312,47 +467,71 @@ export async function defaultCommand(options = {}) {
|
|
|
312
467
|
message: 'Browser observation complete',
|
|
313
468
|
});
|
|
314
469
|
|
|
315
|
-
// Detect phase
|
|
470
|
+
// Detect phase with timeout
|
|
316
471
|
events.emit('phase:started', {
|
|
317
472
|
phase: 'Detect',
|
|
318
473
|
message: 'Analyzing findings and detecting silent failures...',
|
|
319
474
|
});
|
|
320
475
|
|
|
476
|
+
events.startHeartbeat('Detect', json);
|
|
477
|
+
|
|
321
478
|
// Load learn and observe data for detection
|
|
322
479
|
let learnData = { expectations: [] };
|
|
323
480
|
let detectData = null;
|
|
324
481
|
|
|
325
482
|
try {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
483
|
+
try {
|
|
484
|
+
learnData = {
|
|
485
|
+
expectations,
|
|
486
|
+
skipped,
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
detectData = await withTimeout(
|
|
490
|
+
budget.detectMaxMs,
|
|
491
|
+
detectFindings(learnData, observeData, projectRoot, (progress) => {
|
|
492
|
+
events.emit(progress.event, progress);
|
|
493
|
+
if (!json && progress.event === 'detect:classified') {
|
|
494
|
+
const symbol = progress.classification === 'silent-failure' ? '✗' :
|
|
495
|
+
progress.classification === 'observed' ? '✓' :
|
|
496
|
+
progress.classification === 'coverage-gap' ? '⊘' : '⚠';
|
|
497
|
+
console.log(` ${symbol} ${progress.index}/${learnData.expectations.length}`);
|
|
498
|
+
}
|
|
499
|
+
}),
|
|
500
|
+
'Detect'
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
if (!json && detectData.stats.silentFailures > 0) {
|
|
504
|
+
console.log(`Silent failures detected: ${detectData.stats.silentFailures}`);
|
|
505
|
+
}
|
|
506
|
+
} catch (error) {
|
|
507
|
+
if (error.message.includes('timeout')) {
|
|
508
|
+
if (!json) {
|
|
509
|
+
console.error(`Detect error: timeout after ${budget.detectMaxMs}ms`);
|
|
510
|
+
}
|
|
511
|
+
events.emit('detect:error', {
|
|
512
|
+
message: `Detect phase timeout: ${budget.detectMaxMs}ms`,
|
|
513
|
+
});
|
|
514
|
+
detectData = {
|
|
515
|
+
findings: [],
|
|
516
|
+
stats: { total: 0, silentFailures: 0, observed: 0, coverageGaps: 0, unproven: 0, informational: 0 },
|
|
517
|
+
detectedAt: new Date().toISOString(),
|
|
518
|
+
};
|
|
519
|
+
} else {
|
|
520
|
+
if (!json) {
|
|
521
|
+
console.error(`Detect error: ${error.message}`);
|
|
522
|
+
}
|
|
523
|
+
events.emit('detect:error', {
|
|
524
|
+
message: error.message,
|
|
525
|
+
});
|
|
526
|
+
detectData = {
|
|
527
|
+
findings: [],
|
|
528
|
+
stats: { total: 0, silentFailures: 0, observed: 0, coverageGaps: 0, unproven: 0, informational: 0 },
|
|
529
|
+
detectedAt: new Date().toISOString(),
|
|
530
|
+
};
|
|
338
531
|
}
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
if (!json && detectData.stats.silentFailures > 0) {
|
|
342
|
-
console.log(`Silent failures detected: ${detectData.stats.silentFailures}`);
|
|
343
|
-
}
|
|
344
|
-
} catch (error) {
|
|
345
|
-
if (!json) {
|
|
346
|
-
console.error(`Detect error: ${error.message}`);
|
|
347
532
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
});
|
|
351
|
-
detectData = {
|
|
352
|
-
findings: [],
|
|
353
|
-
stats: { total: 0, silentFailures: 0, observed: 0, coverageGaps: 0, unproven: 0, informational: 0 },
|
|
354
|
-
detectedAt: new Date().toISOString(),
|
|
355
|
-
};
|
|
533
|
+
} finally {
|
|
534
|
+
events.stopHeartbeat();
|
|
356
535
|
}
|
|
357
536
|
|
|
358
537
|
events.emit('phase:completed', {
|
|
@@ -360,35 +539,25 @@ export async function defaultCommand(options = {}) {
|
|
|
360
539
|
message: 'Silent failure detection complete',
|
|
361
540
|
});
|
|
362
541
|
|
|
542
|
+
// Clear watchdog timer on successful completion
|
|
543
|
+
if (watchdogTimer) {
|
|
544
|
+
clearTimeout(watchdogTimer);
|
|
545
|
+
watchdogTimer = null;
|
|
546
|
+
}
|
|
547
|
+
|
|
363
548
|
// Finalize Artifacts
|
|
364
549
|
events.emit('phase:started', {
|
|
365
550
|
phase: 'Finalize Artifacts',
|
|
366
551
|
message: 'Writing run results...',
|
|
367
552
|
});
|
|
368
553
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
atomicWriteJson(paths.runStatusJson, {
|
|
372
|
-
status: 'COMPLETE',
|
|
373
|
-
runId,
|
|
374
|
-
startedAt,
|
|
375
|
-
completedAt,
|
|
376
|
-
});
|
|
554
|
+
events.stopHeartbeat();
|
|
377
555
|
|
|
378
|
-
|
|
379
|
-
veraxVersion: getVersion(),
|
|
380
|
-
nodeVersion: process.version,
|
|
381
|
-
platform: process.platform,
|
|
382
|
-
cwd: projectRoot,
|
|
383
|
-
command: 'default',
|
|
384
|
-
args: { url: resolvedUrl, src },
|
|
385
|
-
url: resolvedUrl,
|
|
386
|
-
src: srcPath,
|
|
387
|
-
startedAt,
|
|
388
|
-
completedAt,
|
|
389
|
-
error: null,
|
|
390
|
-
});
|
|
556
|
+
const completedAt = new Date().toISOString();
|
|
391
557
|
|
|
558
|
+
// Write detect results (or empty if detection failed)
|
|
559
|
+
const findingsResult = writeFindingsJson(paths.baseDir, detectData);
|
|
560
|
+
|
|
392
561
|
// Write summary with stable digest
|
|
393
562
|
writeSummaryJson(paths.summaryJson, {
|
|
394
563
|
runId,
|
|
@@ -408,33 +577,11 @@ export async function defaultCommand(options = {}) {
|
|
|
408
577
|
informational: detectData.stats?.informational || 0,
|
|
409
578
|
});
|
|
410
579
|
|
|
411
|
-
// Write
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
type: 'phase:started',
|
|
417
|
-
timestamp: startedAt,
|
|
418
|
-
phase: 'Detect Project',
|
|
419
|
-
},
|
|
420
|
-
{
|
|
421
|
-
type: 'phase:completed',
|
|
422
|
-
timestamp: new Date().toISOString(),
|
|
423
|
-
phase: 'Detect Project',
|
|
424
|
-
},
|
|
425
|
-
{
|
|
426
|
-
type: 'phase:started',
|
|
427
|
-
timestamp: new Date().toISOString(),
|
|
428
|
-
phase: 'Learn',
|
|
429
|
-
},
|
|
430
|
-
{
|
|
431
|
-
type: 'phase:completed',
|
|
432
|
-
timestamp: completedAt,
|
|
433
|
-
phase: 'Learn',
|
|
434
|
-
},
|
|
435
|
-
];
|
|
436
|
-
|
|
437
|
-
const tracesContent = traces.map(t => JSON.stringify(t)).join('\n') + '\n';
|
|
580
|
+
// Write traces (include all events including heartbeats)
|
|
581
|
+
const allEvents = events.getEvents();
|
|
582
|
+
const tracesContent = allEvents
|
|
583
|
+
.map(e => JSON.stringify(e))
|
|
584
|
+
.join('\n') + '\n';
|
|
438
585
|
atomicWriteText(paths.tracesJsonl, tracesContent);
|
|
439
586
|
|
|
440
587
|
// Write project profile
|
|
@@ -445,6 +592,53 @@ export async function defaultCommand(options = {}) {
|
|
|
445
592
|
|
|
446
593
|
// Write observe results
|
|
447
594
|
writeObserveJson(paths.baseDir, observeData);
|
|
595
|
+
|
|
596
|
+
// PHASE 6: Verify artifacts after all writers finish
|
|
597
|
+
const { verifyRun } = await import('../../verax/core/artifacts/verifier.js');
|
|
598
|
+
const verification = verifyRun(paths.baseDir, paths.artifactVersions);
|
|
599
|
+
|
|
600
|
+
// Determine final status based on verification
|
|
601
|
+
let finalStatus = 'COMPLETE';
|
|
602
|
+
if (!verification.ok) {
|
|
603
|
+
finalStatus = 'INVALID';
|
|
604
|
+
} else if (verification.warnings.length > 0) {
|
|
605
|
+
finalStatus = 'VALID_WITH_WARNINGS';
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Write completed status with contract + enforcement snapshot + verification
|
|
609
|
+
atomicWriteJson(paths.runStatusJson, {
|
|
610
|
+
contractVersion: 1,
|
|
611
|
+
artifactVersions: paths.artifactVersions,
|
|
612
|
+
status: finalStatus,
|
|
613
|
+
runId,
|
|
614
|
+
startedAt,
|
|
615
|
+
completedAt,
|
|
616
|
+
enforcement: findingsResult?.payload?.enforcement || null,
|
|
617
|
+
verification: {
|
|
618
|
+
ok: verification.ok,
|
|
619
|
+
status: finalStatus,
|
|
620
|
+
errorsCount: verification.errors.length,
|
|
621
|
+
warningsCount: verification.warnings.length,
|
|
622
|
+
verifiedAt: verification.verifiedAt
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
// Update metadata with completion time
|
|
627
|
+
atomicWriteJson(paths.runMetaJson, {
|
|
628
|
+
contractVersion: 1,
|
|
629
|
+
artifactVersions: paths.artifactVersions,
|
|
630
|
+
veraxVersion: getVersion(),
|
|
631
|
+
nodeVersion: process.version,
|
|
632
|
+
platform: process.platform,
|
|
633
|
+
cwd: projectRoot,
|
|
634
|
+
command: 'default',
|
|
635
|
+
args: { url: resolvedUrl, src },
|
|
636
|
+
url: resolvedUrl,
|
|
637
|
+
src: srcPath,
|
|
638
|
+
startedAt,
|
|
639
|
+
completedAt,
|
|
640
|
+
error: null,
|
|
641
|
+
});
|
|
448
642
|
|
|
449
643
|
events.emit('phase:completed', {
|
|
450
644
|
phase: 'Finalize Artifacts',
|
|
@@ -456,15 +650,31 @@ export async function defaultCommand(options = {}) {
|
|
|
456
650
|
console.log('\nRun complete.');
|
|
457
651
|
console.log(`Run ID: ${runId}`);
|
|
458
652
|
console.log(`Artifacts: ${paths.baseDir}`);
|
|
653
|
+
|
|
654
|
+
// PHASE 6: Display verification results
|
|
655
|
+
const { formatVerificationOutput } = await import('../../verax/core/artifacts/verifier.js');
|
|
656
|
+
const verificationOutput = formatVerificationOutput(verification, verbose);
|
|
657
|
+
console.log('');
|
|
658
|
+
console.log(verificationOutput);
|
|
459
659
|
}
|
|
460
660
|
|
|
461
661
|
return { runId, paths, url: resolvedUrl, success: true };
|
|
462
662
|
} catch (error) {
|
|
663
|
+
// Clear watchdog timer on error
|
|
664
|
+
if (watchdogTimer) {
|
|
665
|
+
clearTimeout(watchdogTimer);
|
|
666
|
+
watchdogTimer = null;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
events.stopHeartbeat();
|
|
670
|
+
|
|
463
671
|
// Mark run as FAILED if we have paths
|
|
464
|
-
if (paths && runId && startedAt) {
|
|
672
|
+
if (paths && runId && startedAt && typeof paths === 'object') {
|
|
465
673
|
try {
|
|
466
674
|
const failedAt = new Date().toISOString();
|
|
467
675
|
atomicWriteJson(paths.runStatusJson, {
|
|
676
|
+
contractVersion: 1,
|
|
677
|
+
artifactVersions: paths.artifactVersions,
|
|
468
678
|
status: 'FAILED',
|
|
469
679
|
runId,
|
|
470
680
|
startedAt,
|
|
@@ -474,6 +684,8 @@ export async function defaultCommand(options = {}) {
|
|
|
474
684
|
|
|
475
685
|
// Update metadata
|
|
476
686
|
atomicWriteJson(paths.runMetaJson, {
|
|
687
|
+
contractVersion: 1,
|
|
688
|
+
artifactVersions: paths.artifactVersions,
|
|
477
689
|
veraxVersion: getVersion(),
|
|
478
690
|
nodeVersion: process.version,
|
|
479
691
|
platform: process.platform,
|
|
@@ -92,11 +92,26 @@ export async function doctorCommand(options = {}) {
|
|
|
92
92
|
|
|
93
93
|
// 4) Headless launch smoke test
|
|
94
94
|
if (playwright && playwright.chromium && chromiumPath && existsSync(chromiumPath)) {
|
|
95
|
+
/** @type {any} */
|
|
96
|
+
let browser = null;
|
|
95
97
|
try {
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
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]);
|
|
100
115
|
addCheck('Headless smoke test', 'pass', 'Chromium launched headless and closed successfully');
|
|
101
116
|
} catch (error) {
|
|
102
117
|
addCheck(
|
|
@@ -107,6 +122,23 @@ export async function doctorCommand(options = {}) {
|
|
|
107
122
|
? 'Try: npx playwright install --with-deps chromium && launch with --no-sandbox in constrained environments'
|
|
108
123
|
: 'Reinstall playwright: npx playwright install --with-deps chromium'
|
|
109
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
|
+
}
|
|
110
142
|
}
|
|
111
143
|
} else {
|
|
112
144
|
addCheck(
|