@veraxhq/verax 0.2.1 → 0.4.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 +10 -6
- package/bin/verax.js +11 -11
- package/package.json +29 -8
- package/src/cli/commands/baseline.js +103 -0
- package/src/cli/commands/default.js +51 -6
- package/src/cli/commands/doctor.js +29 -0
- package/src/cli/commands/ga.js +246 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +4 -2
- package/src/cli/commands/release-check.js +215 -0
- package/src/cli/commands/run.js +45 -6
- package/src/cli/commands/security-check.js +212 -0
- package/src/cli/commands/truth.js +113 -0
- package/src/cli/entry.js +30 -20
- 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 +544 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-promise-extractor.js +581 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/atomic-write.js +12 -1
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/console-reporter.js +72 -0
- package/src/cli/util/detection-engine.js +105 -41
- package/src/cli/util/determinism-runner.js +124 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/digest-engine.js +359 -0
- package/src/cli/util/dom-diff.js +226 -0
- package/src/cli/util/evidence-engine.js +287 -0
- package/src/cli/util/expectation-extractor.js +151 -5
- package/src/cli/util/findings-writer.js +3 -0
- package/src/cli/util/framework-detector.js +572 -0
- package/src/cli/util/idgen.js +1 -1
- package/src/cli/util/interaction-planner.js +529 -0
- package/src/cli/util/learn-writer.js +2 -0
- package/src/cli/util/ledger-writer.js +110 -0
- package/src/cli/util/monorepo-resolver.js +162 -0
- package/src/cli/util/observation-engine.js +127 -278
- package/src/cli/util/observe-writer.js +2 -0
- package/src/cli/util/project-discovery.js +284 -0
- package/src/cli/util/project-writer.js +2 -0
- package/src/cli/util/run-id.js +23 -27
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/run-result.js +778 -0
- package/src/cli/util/selector-resolver.js +235 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +2 -0
- 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 +146 -0
- package/src/cli/util/svelte-state-detector.js +242 -0
- package/src/cli/util/trust-activation-integration.js +496 -0
- package/src/cli/util/trust-activation-wrapper.js +85 -0
- package/src/cli/util/trust-integration-hooks.js +164 -0
- package/src/cli/util/types.js +153 -0
- package/src/cli/util/url-validation.js +40 -0
- package/src/cli/util/vue-navigation-detector.js +178 -0
- package/src/cli/util/vue-sfc-extractor.js +161 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/types/fs-augment.d.ts +23 -0
- package/src/types/global.d.ts +137 -0
- package/src/types/internal-types.d.ts +35 -0
- package/src/verax/cli/init.js +4 -18
- package/src/verax/core/action-classifier.js +4 -3
- package/src/verax/core/artifacts/registry.js +139 -0
- package/src/verax/core/artifacts/verifier.js +990 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +233 -0
- package/src/verax/core/capabilities/gates.js +505 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +144 -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 +80 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +489 -0
- package/src/verax/core/confidence-engine.js +625 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +186 -0
- package/src/verax/core/contracts/validators.js +456 -0
- package/src/verax/core/decisions/decision.trace.js +278 -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 +405 -0
- package/src/verax/core/determinism/engine.js +222 -0
- package/src/verax/core/determinism/finding-identity.js +149 -0
- package/src/verax/core/determinism/normalize.js +466 -0
- package/src/verax/core/determinism/report-writer.js +93 -0
- package/src/verax/core/determinism/run-fingerprint.js +123 -0
- package/src/verax/core/dynamic-route-intelligence.js +529 -0
- package/src/verax/core/evidence/evidence-capture-service.js +308 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +166 -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 +192 -0
- package/src/verax/core/failures/exit-codes.js +88 -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 +133 -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 +435 -0
- package/src/verax/core/ga/ga.enforcer.js +87 -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 +84 -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 +1 -0
- package/src/verax/core/integrity/budget.js +138 -0
- package/src/verax/core/integrity/determinism.js +342 -0
- package/src/verax/core/integrity/integrity.js +208 -0
- package/src/verax/core/integrity/poisoning.js +108 -0
- package/src/verax/core/integrity/transaction.js +140 -0
- package/src/verax/core/observe/run-timeline.js +318 -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 +200 -0
- package/src/verax/core/pipeline-tracker.js +243 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +130 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +164 -0
- package/src/verax/core/release/reproducibility.check.js +222 -0
- package/src/verax/core/release/sbom.builder.js +292 -0
- package/src/verax/core/replay-validator.js +2 -0
- package/src/verax/core/replay.js +4 -0
- package/src/verax/core/report/cross-index.js +195 -0
- package/src/verax/core/report/human-summary.js +362 -0
- package/src/verax/core/route-intelligence.js +420 -0
- package/src/verax/core/run-id.js +6 -3
- package/src/verax/core/run-manifest.js +4 -3
- package/src/verax/core/security/secrets.scan.js +329 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +128 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +334 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/truth/truth.certificate.js +252 -0
- package/src/verax/core/ui-feedback-intelligence.js +481 -0
- package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
- package/src/verax/detect/confidence-engine.js +62 -34
- package/src/verax/detect/confidence-helper.js +34 -0
- package/src/verax/detect/dynamic-route-findings.js +338 -0
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +2 -2
- package/src/verax/detect/failure-cause-inference.js +293 -0
- package/src/verax/detect/findings-writer.js +131 -35
- package/src/verax/detect/flow-detector.js +2 -2
- package/src/verax/detect/form-silent-failure.js +98 -0
- package/src/verax/detect/index.js +46 -5
- package/src/verax/detect/invariants-enforcer.js +147 -0
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/navigation-silent-failure.js +82 -0
- package/src/verax/detect/problem-aggregator.js +361 -0
- package/src/verax/detect/route-findings.js +219 -0
- package/src/verax/detect/summary-writer.js +477 -0
- package/src/verax/detect/test-failure-cause-inference.js +314 -0
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/flow/flow-engine.js +2 -1
- package/src/verax/flow/flow-spec.js +0 -6
- package/src/verax/index.js +4 -0
- package/src/verax/intel/ts-program.js +1 -0
- package/src/verax/intel/vue-navigation-extractor.js +3 -0
- package/src/verax/learn/action-contract-extractor.js +3 -0
- package/src/verax/learn/ast-contract-extractor.js +1 -1
- package/src/verax/learn/flow-extractor.js +1 -0
- package/src/verax/learn/project-detector.js +5 -0
- package/src/verax/learn/react-router-extractor.js +2 -0
- package/src/verax/learn/source-instrumenter.js +1 -0
- package/src/verax/learn/state-extractor.js +2 -1
- package/src/verax/learn/static-extractor.js +1 -0
- package/src/verax/observe/coverage-gaps.js +132 -0
- package/src/verax/observe/expectation-handler.js +126 -0
- package/src/verax/observe/incremental-skip.js +46 -0
- package/src/verax/observe/index.js +51 -155
- package/src/verax/observe/interaction-executor.js +192 -0
- package/src/verax/observe/interaction-runner.js +782 -513
- package/src/verax/observe/network-firewall.js +86 -0
- package/src/verax/observe/observation-builder.js +169 -0
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +192 -0
- package/src/verax/observe/observe-runner.js +230 -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/page-traversal.js +138 -0
- package/src/verax/observe/snapshot-ops.js +94 -0
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/scan-summary-writer.js +2 -0
- package/src/verax/shared/artifact-manager.js +25 -5
- package/src/verax/shared/caching.js +1 -0
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/expectation-tracker.js +1 -0
- package/src/verax/shared/view-switch-rules.js +208 -0
- package/src/verax/shared/zip-artifacts.js +6 -0
- package/src/verax/shared/config-loader.js +0 -169
- /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Formats and prints standardized CLI output for VERAX runs.
|
|
4
|
+
*
|
|
5
|
+
* Output format (decision-oriented, minimal):
|
|
6
|
+
* - ✔ VERAX finished
|
|
7
|
+
* - ✔ 14 interactions tested
|
|
8
|
+
* - ✖ 3 user problems found (2 HIGH, 1 MEDIUM)
|
|
9
|
+
* - → Open .verax/SUMMARY.md
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Format console output for a completed run
|
|
14
|
+
*
|
|
15
|
+
* @param {Object} stats - Run statistics
|
|
16
|
+
* @param {number} stats.flowsScanned - Public flows scanned
|
|
17
|
+
* @param {number} stats.silentFailures - Silent failures detected
|
|
18
|
+
* @param {string} stats.outDir - Output directory path
|
|
19
|
+
* @returns {string} Formatted output (multiple lines)
|
|
20
|
+
*/
|
|
21
|
+
export function formatConsoleOutput(stats) {
|
|
22
|
+
const lines = [];
|
|
23
|
+
|
|
24
|
+
const flowsScanned = stats?.flowsScanned || 0;
|
|
25
|
+
const silentFailures = stats?.silentFailures || 0;
|
|
26
|
+
const outDir = stats?.outDir || '.verax';
|
|
27
|
+
|
|
28
|
+
// Line 1: Scan complete summary
|
|
29
|
+
const flowsStr = `${flowsScanned} flow${flowsScanned !== 1 ? 's' : ''}`;
|
|
30
|
+
const failuresStr = `${silentFailures} silent failure${silentFailures !== 1 ? 's' : ''}`;
|
|
31
|
+
lines.push(`✓ Scan complete — ${flowsStr} checked, ${failuresStr} found`);
|
|
32
|
+
|
|
33
|
+
// Line 2: Point to SUMMARY.md
|
|
34
|
+
lines.push(`→ See ${outDir}/SUMMARY.md for details`);
|
|
35
|
+
|
|
36
|
+
// Line 3: Mention output directory
|
|
37
|
+
lines.push('');
|
|
38
|
+
lines.push(`Output saved to ${outDir}/`);
|
|
39
|
+
lines.push(` • SUMMARY.md — Human-readable findings`);
|
|
40
|
+
lines.push(` • REPORT.json — Machine-readable results`);
|
|
41
|
+
|
|
42
|
+
return lines.join('\n');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Print formatted console output to stdout
|
|
47
|
+
*/
|
|
48
|
+
export function printConsoleOutput(stats) {
|
|
49
|
+
const output = formatConsoleOutput(stats);
|
|
50
|
+
console.log(output);
|
|
51
|
+
return output;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Format and print error output when fatal error occurs
|
|
56
|
+
*
|
|
57
|
+
* @param {string} message - Error message
|
|
58
|
+
* @param {boolean} [_debug] - If true, include more details; if false, minimal
|
|
59
|
+
*/
|
|
60
|
+
export function formatErrorOutput(message, _debug = false) {
|
|
61
|
+
// Single-line error message with [ERROR] prefix
|
|
62
|
+
return `[ERROR] ${message}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Print error output
|
|
67
|
+
*/
|
|
68
|
+
export function printErrorOutput(message, _debug = false) {
|
|
69
|
+
const output = formatErrorOutput(message, _debug);
|
|
70
|
+
console.error(output);
|
|
71
|
+
return output;
|
|
72
|
+
}
|
|
@@ -75,6 +75,14 @@ export async function detectFindings(learnData, observeData, projectPath, onProg
|
|
|
75
75
|
findings,
|
|
76
76
|
stats,
|
|
77
77
|
detectedAt: new Date().toISOString(),
|
|
78
|
+
enforcement: {
|
|
79
|
+
evidenceLawEnforced: true,
|
|
80
|
+
contractVersion: 1,
|
|
81
|
+
timestamp: new Date().toISOString(),
|
|
82
|
+
droppedCount: 0,
|
|
83
|
+
downgradedCount: 0,
|
|
84
|
+
downgrades: []
|
|
85
|
+
}
|
|
78
86
|
};
|
|
79
87
|
}
|
|
80
88
|
|
|
@@ -100,7 +108,9 @@ function getObservationForExpectation(expectation, observationMap) {
|
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
function classificationIcon(classification) {
|
|
103
|
-
|
|
111
|
+
// Handle both old format and new taxonomy format
|
|
112
|
+
const baseClassification = classification.split(':')[0];
|
|
113
|
+
switch (baseClassification) {
|
|
104
114
|
case 'observed':
|
|
105
115
|
return '✓';
|
|
106
116
|
case 'silent-failure':
|
|
@@ -115,7 +125,9 @@ function classificationIcon(classification) {
|
|
|
115
125
|
}
|
|
116
126
|
|
|
117
127
|
function findingStatKey(classification) {
|
|
118
|
-
|
|
128
|
+
// Handle both old format and new taxonomy format
|
|
129
|
+
const baseClassification = classification.split(':')[0];
|
|
130
|
+
switch (baseClassification) {
|
|
119
131
|
case 'silent-failure':
|
|
120
132
|
return 'silentFailures';
|
|
121
133
|
case 'observed':
|
|
@@ -131,6 +143,8 @@ function findingStatKey(classification) {
|
|
|
131
143
|
|
|
132
144
|
/**
|
|
133
145
|
* Classify a single expectation according to deterministic rules.
|
|
146
|
+
* EVIDENCE GATE: silent-failure REQUIRES evidence.
|
|
147
|
+
* OUTCOME BINDING: Use attempt.cause to provide precise taxonomy.
|
|
134
148
|
*/
|
|
135
149
|
function classifyExpectation(expectation, observation) {
|
|
136
150
|
const finding = {
|
|
@@ -148,13 +162,15 @@ function classifyExpectation(expectation, observation) {
|
|
|
148
162
|
const attempted = Boolean(observation?.attempted);
|
|
149
163
|
const observed = observation?.observed === true;
|
|
150
164
|
const reason = observation?.reason || null;
|
|
165
|
+
const cause = observation?.cause || null; // NEW: precise cause from planner
|
|
151
166
|
|
|
152
167
|
const evidence = normalizeEvidence(observation?.evidenceFiles || []);
|
|
153
168
|
finding.evidence = evidence;
|
|
154
169
|
|
|
155
170
|
const evidenceSignals = analyzeEvidenceSignals(observation, evidence);
|
|
171
|
+
const hasAnyEvidence = evidence.length > 0 || evidenceSignals.hasDomChange;
|
|
156
172
|
|
|
157
|
-
// 1) observed
|
|
173
|
+
// 1) observed (success)
|
|
158
174
|
if (observed) {
|
|
159
175
|
finding.classification = 'observed';
|
|
160
176
|
finding.reason = 'Expectation observed at runtime';
|
|
@@ -163,8 +179,8 @@ function classifyExpectation(expectation, observation) {
|
|
|
163
179
|
return finding;
|
|
164
180
|
}
|
|
165
181
|
|
|
166
|
-
// 2) coverage-gap (not attempted or
|
|
167
|
-
if (!attempted
|
|
182
|
+
// 2) coverage-gap (not attempted or safety skip)
|
|
183
|
+
if (!attempted) {
|
|
168
184
|
finding.classification = 'coverage-gap';
|
|
169
185
|
finding.reason = reason || 'No observation attempt recorded';
|
|
170
186
|
finding.impact = 'LOW';
|
|
@@ -172,29 +188,48 @@ function classifyExpectation(expectation, observation) {
|
|
|
172
188
|
return finding;
|
|
173
189
|
}
|
|
174
190
|
|
|
175
|
-
// 3)
|
|
176
|
-
if (attempted && observation?.observed === false && !isSafetySkip(reason)) {
|
|
177
|
-
finding.classification = 'silent-failure';
|
|
178
|
-
finding.reason = reason || 'Expected behavior not observed';
|
|
179
|
-
finding.impact = getImpact(expectation);
|
|
180
|
-
finding.confidence = calculateConfidence(expectation, evidenceSignals, 'silent-failure');
|
|
181
|
-
return finding;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// 4) unproven (attempted, ambiguous evidence)
|
|
191
|
+
// 3) Attempted but not observed - apply EVIDENCE GATE + OUTCOME BINDING
|
|
185
192
|
if (attempted && !observed) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
193
|
+
// CRITICAL: Evidence Gate - silent-failure REQUIRES evidence
|
|
194
|
+
if (!hasAnyEvidence) {
|
|
195
|
+
// NO EVIDENCE → cannot prove silence → coverage-gap or unproven
|
|
196
|
+
if (isSafetySkip(reason)) {
|
|
197
|
+
finding.classification = 'coverage-gap';
|
|
198
|
+
finding.reason = reason || 'Blocked or skipped for safety';
|
|
199
|
+
finding.impact = 'LOW';
|
|
200
|
+
finding.confidence = 0;
|
|
201
|
+
} else {
|
|
202
|
+
finding.classification = 'unproven';
|
|
203
|
+
finding.reason = reason || 'Attempted but no evidence captured';
|
|
204
|
+
finding.impact = 'MEDIUM';
|
|
205
|
+
finding.confidence = 0;
|
|
206
|
+
}
|
|
207
|
+
return finding;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// HAS EVIDENCE → can classify as silent-failure with PRECISE taxonomy
|
|
211
|
+
let taxonomy = 'no-change'; // default
|
|
212
|
+
|
|
213
|
+
if (cause) {
|
|
214
|
+
// Use the cause from interaction planner (most precise)
|
|
215
|
+
taxonomy = cause; // 'not-found' | 'blocked' | 'prevented-submit' | 'timeout' | 'no-change' | 'error'
|
|
216
|
+
} else {
|
|
217
|
+
// Fallback to signal-based detection (legacy)
|
|
218
|
+
taxonomy = determineSilenceTaxonomy(reason, evidenceSignals);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
finding.classification = `silent-failure:${taxonomy}`;
|
|
222
|
+
finding.reason = reason || `Expected behavior not observed (${taxonomy})`;
|
|
223
|
+
finding.impact = getImpact(expectation);
|
|
224
|
+
finding.confidence = calculateConfidenceFromEvidence(evidenceSignals);
|
|
190
225
|
return finding;
|
|
191
226
|
}
|
|
192
227
|
|
|
193
|
-
//
|
|
228
|
+
// 4) Fallback
|
|
194
229
|
finding.classification = 'informational';
|
|
195
230
|
finding.reason = reason || 'No classification rule matched';
|
|
196
231
|
finding.impact = 'LOW';
|
|
197
|
-
finding.confidence =
|
|
232
|
+
finding.confidence = 0;
|
|
198
233
|
return finding;
|
|
199
234
|
}
|
|
200
235
|
|
|
@@ -206,32 +241,61 @@ function isSafetySkip(reason) {
|
|
|
206
241
|
}
|
|
207
242
|
|
|
208
243
|
/**
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
* Screenshots only => ~0.6
|
|
212
|
-
* Weak signals => <0.5
|
|
244
|
+
* Determine silence taxonomy based on reason and evidence signals.
|
|
245
|
+
* Returns: no-change | blocked | not-found | timeout | prevented-submit
|
|
213
246
|
*/
|
|
214
|
-
function
|
|
215
|
-
if (
|
|
216
|
-
|
|
247
|
+
function determineSilenceTaxonomy(reason, evidenceSignals) {
|
|
248
|
+
if (!reason) {
|
|
249
|
+
// No explicit reason - check evidence
|
|
250
|
+
if (evidenceSignals.hasScreenshots || evidenceSignals.hasDomChange) {
|
|
251
|
+
return 'no-change'; // Evidence exists but no observed change
|
|
252
|
+
}
|
|
253
|
+
return 'no-change';
|
|
254
|
+
}
|
|
217
255
|
|
|
218
|
-
const
|
|
256
|
+
const lower = reason.toLowerCase();
|
|
219
257
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (hasScreenshots && hasDomChange) return 0.7;
|
|
224
|
-
if (hasScreenshots) return 0.6;
|
|
225
|
-
if (hasNetworkLogs || hasDomChange) return 0.5;
|
|
226
|
-
return 0.4; // weak signals, attempted but no evidence
|
|
258
|
+
// Check for specific conditions
|
|
259
|
+
if (lower.includes('timeout') || lower.includes('timed out')) {
|
|
260
|
+
return 'timeout';
|
|
227
261
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
262
|
+
if (lower.includes('not found') || lower.includes('element not found') || lower.includes('selector not found') || lower.includes('not-found')) {
|
|
263
|
+
return 'not-found';
|
|
264
|
+
}
|
|
265
|
+
if (lower.includes('blocked') || lower.includes('not-interactable') || lower.includes('interactable')) {
|
|
266
|
+
return 'blocked';
|
|
232
267
|
}
|
|
268
|
+
if (lower.includes('prevented') || lower.includes('prevented-submit') || lower.includes('submit-prevented')) {
|
|
269
|
+
return 'prevented-submit';
|
|
270
|
+
}
|
|
271
|
+
if (lower.includes('no matching event') || lower.includes('no change') || lower.includes('no-change')) {
|
|
272
|
+
return 'no-change';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Default to no-change if evidence exists
|
|
276
|
+
return 'no-change';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Calculate confidence from evidence ONLY.
|
|
281
|
+
* No evidence = 0 confidence.
|
|
282
|
+
*/
|
|
283
|
+
function calculateConfidenceFromEvidence(evidenceSignals) {
|
|
284
|
+
const { hasScreenshots, hasNetworkLogs, hasDomChange } = evidenceSignals;
|
|
233
285
|
|
|
234
|
-
|
|
286
|
+
// Multiple strong signals
|
|
287
|
+
if (hasScreenshots && hasNetworkLogs && hasDomChange) return 0.85;
|
|
288
|
+
if (hasScreenshots && hasNetworkLogs) return 0.75;
|
|
289
|
+
if (hasScreenshots && hasDomChange) return 0.7;
|
|
290
|
+
|
|
291
|
+
// Single strong signal
|
|
292
|
+
if (hasScreenshots) return 0.6;
|
|
293
|
+
|
|
294
|
+
// Weak signals
|
|
295
|
+
if (hasNetworkLogs || hasDomChange) return 0.5;
|
|
296
|
+
|
|
297
|
+
// No evidence
|
|
298
|
+
return 0;
|
|
235
299
|
}
|
|
236
300
|
|
|
237
301
|
function analyzeEvidenceSignals(observation, evidence) {
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 18 — Determinism Runner
|
|
3
|
+
*
|
|
4
|
+
* Wraps a scan execution to run it multiple times and compare results.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { resolve } from 'path';
|
|
8
|
+
import { readFileSync, existsSync } from 'fs';
|
|
9
|
+
import { runDeterminismCheck } from '../../verax/core/determinism/engine.js';
|
|
10
|
+
import { verifyRun } from '../../verax/core/artifacts/verifier.js';
|
|
11
|
+
import { writeDeterminismReport } from './determinism-writer.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* PHASE 18: Run scan with determinism checking
|
|
15
|
+
*
|
|
16
|
+
* @param {Function} scanFn - Function that executes a scan and returns { runId }
|
|
17
|
+
* @param {Object} options - Options
|
|
18
|
+
* @param {number} options.runs - Number of runs (default: 2)
|
|
19
|
+
* @param {string} options.out - Output directory
|
|
20
|
+
* @returns {Promise<Object>} Determinism check results
|
|
21
|
+
*/
|
|
22
|
+
export async function runWithDeterminism(scanFn, options = { runs: 2, out: '.verax' }) {
|
|
23
|
+
const { runs = 2, out = '.verax' } = options;
|
|
24
|
+
|
|
25
|
+
// Wrap scan function to return artifact paths
|
|
26
|
+
const runFn = async (runConfig) => {
|
|
27
|
+
const result = await scanFn({ ...options, ...runConfig });
|
|
28
|
+
|
|
29
|
+
// Extract artifact paths from result
|
|
30
|
+
const artifactPaths = {};
|
|
31
|
+
if (result.runId) {
|
|
32
|
+
const runDir = resolve(out, 'runs', result.runId);
|
|
33
|
+
|
|
34
|
+
// Map artifact keys to paths
|
|
35
|
+
const artifactMap = {
|
|
36
|
+
findings: resolve(runDir, 'findings.json'),
|
|
37
|
+
runStatus: resolve(runDir, 'run.status.json'),
|
|
38
|
+
summary: resolve(runDir, 'summary.json'),
|
|
39
|
+
learn: resolve(runDir, 'learn.json'),
|
|
40
|
+
observe: resolve(runDir, 'observe.json'),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
for (const [key, path] of Object.entries(artifactMap)) {
|
|
44
|
+
if (existsSync(path)) {
|
|
45
|
+
artifactPaths[key] = path;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
runId: result.runId,
|
|
52
|
+
artifactPaths,
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// PHASE 25: Load run fingerprints from run.meta.json
|
|
57
|
+
const loadRunFingerprints = async (runMeta) => {
|
|
58
|
+
if (runMeta.runId) {
|
|
59
|
+
const runDir = resolve(out, 'runs', runMeta.runId);
|
|
60
|
+
const metaPath = resolve(runDir, 'run.meta.json');
|
|
61
|
+
if (existsSync(metaPath)) {
|
|
62
|
+
try {
|
|
63
|
+
const metaContent = readFileSync(metaPath, 'utf-8');
|
|
64
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
65
|
+
const meta = JSON.parse(metaContent);
|
|
66
|
+
return meta.runFingerprint || null;
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Run determinism check
|
|
76
|
+
const determinismResult = await runDeterminismCheck(runFn, {
|
|
77
|
+
runs,
|
|
78
|
+
config: options,
|
|
79
|
+
normalize: true,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// PHASE 25: Load run fingerprints for each run
|
|
83
|
+
for (const runMeta of determinismResult.runsMeta) {
|
|
84
|
+
runMeta.runFingerprint = await loadRunFingerprints(runMeta);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Verify each run
|
|
88
|
+
const verificationResults = [];
|
|
89
|
+
for (const runMeta of determinismResult.runsMeta) {
|
|
90
|
+
if (runMeta.runId) {
|
|
91
|
+
const runDir = resolve(out, 'runs', runMeta.runId);
|
|
92
|
+
if (existsSync(runDir)) {
|
|
93
|
+
try {
|
|
94
|
+
const { getArtifactVersions } = await import('../../verax/core/artifacts/registry.js');
|
|
95
|
+
const verification = verifyRun(runDir, getArtifactVersions());
|
|
96
|
+
verificationResults.push({
|
|
97
|
+
runId: runMeta.runId,
|
|
98
|
+
verification,
|
|
99
|
+
});
|
|
100
|
+
} catch (error) {
|
|
101
|
+
// Verification failed
|
|
102
|
+
verificationResults.push({
|
|
103
|
+
runId: runMeta.runId,
|
|
104
|
+
verification: { ok: false, errors: [error.message] },
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Write determinism report
|
|
112
|
+
const reportPath = await writeDeterminismReport(
|
|
113
|
+
determinismResult,
|
|
114
|
+
verificationResults,
|
|
115
|
+
out
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
...determinismResult,
|
|
120
|
+
verificationResults,
|
|
121
|
+
reportPath,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 18 — Determinism Report Writer
|
|
3
|
+
*
|
|
4
|
+
* Writes determinism check results to artifact.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { resolve } from 'path';
|
|
8
|
+
import { mkdirSync, writeFileSync } from 'fs';
|
|
9
|
+
import { ARTIFACT_REGISTRY, getArtifactVersions } from '../../verax/core/artifacts/registry.js'; // eslint-disable-line no-unused-vars
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* PHASE 18: Write determinism report
|
|
13
|
+
* PHASE 25: Enhanced with runFingerprint, contract validation, and structured verdicts
|
|
14
|
+
*
|
|
15
|
+
* @param {Object} determinismResult - Result from runDeterminismCheck
|
|
16
|
+
* @param {Array} verificationResults - Verification results for each run
|
|
17
|
+
* @param {string} outDir - Output directory
|
|
18
|
+
* @returns {Promise<string>} Path to determinism report
|
|
19
|
+
*/
|
|
20
|
+
export async function writeDeterminismReport(determinismResult, verificationResults, outDir) {
|
|
21
|
+
const reportsDir = resolve(outDir, 'determinism');
|
|
22
|
+
mkdirSync(reportsDir, { recursive: true });
|
|
23
|
+
|
|
24
|
+
const reportId = `determinism-${Date.now()}`;
|
|
25
|
+
const reportPath = resolve(reportsDir, `${reportId}.json`);
|
|
26
|
+
|
|
27
|
+
// PHASE 25: Check run fingerprints
|
|
28
|
+
const runFingerprints = [];
|
|
29
|
+
const runFingerprintMismatches = [];
|
|
30
|
+
for (const runMeta of determinismResult.runsMeta) {
|
|
31
|
+
if (runMeta.runFingerprint) {
|
|
32
|
+
runFingerprints.push(runMeta.runFingerprint);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (runFingerprints.length > 1) {
|
|
36
|
+
const firstFingerprint = runFingerprints[0];
|
|
37
|
+
for (let i = 1; i < runFingerprints.length; i++) {
|
|
38
|
+
if (runFingerprints[i] !== firstFingerprint) {
|
|
39
|
+
runFingerprintMismatches.push({
|
|
40
|
+
runIndex: i + 1,
|
|
41
|
+
expected: firstFingerprint,
|
|
42
|
+
actual: runFingerprints[i]
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// PHASE 25: Check verifier errors
|
|
49
|
+
const verifierErrors = [];
|
|
50
|
+
for (const verification of verificationResults) {
|
|
51
|
+
if (verification.verification && !verification.verification.ok) {
|
|
52
|
+
verifierErrors.push({
|
|
53
|
+
runId: verification.runId,
|
|
54
|
+
errors: verification.verification.errors || [],
|
|
55
|
+
verdictStatus: verification.verification.verdictStatus
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// PHASE 25: Determine final verdict with expected/unexpected distinction
|
|
61
|
+
let finalVerdict = determinismResult.verdict;
|
|
62
|
+
if (runFingerprintMismatches.length > 0) {
|
|
63
|
+
finalVerdict = 'NON_DETERMINISTIC_UNEXPECTED';
|
|
64
|
+
} else if (verifierErrors.length > 0) {
|
|
65
|
+
finalVerdict = 'NON_DETERMINISTIC_UNEXPECTED';
|
|
66
|
+
} else if (determinismResult.adaptiveEvents && determinismResult.adaptiveEvents.length > 0) {
|
|
67
|
+
finalVerdict = 'NON_DETERMINISTIC_EXPECTED';
|
|
68
|
+
} else if (determinismResult.diffs && determinismResult.diffs.length > 0) {
|
|
69
|
+
const blockerDiffs = determinismResult.diffs.filter(d => d.severity === 'BLOCKER');
|
|
70
|
+
if (blockerDiffs.length > 0) {
|
|
71
|
+
finalVerdict = 'NON_DETERMINISTIC_UNEXPECTED';
|
|
72
|
+
} else {
|
|
73
|
+
finalVerdict = 'NON_DETERMINISTIC_EXPECTED';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// PHASE 25: Build top reasons
|
|
78
|
+
const topReasons = [];
|
|
79
|
+
if (runFingerprintMismatches.length > 0) {
|
|
80
|
+
topReasons.push({ code: 'RUN_FINGERPRINT_MISMATCH', count: runFingerprintMismatches.length });
|
|
81
|
+
}
|
|
82
|
+
if (verifierErrors.length > 0) {
|
|
83
|
+
topReasons.push({ code: 'VERIFIER_ERRORS_DETECTED', count: verifierErrors.length });
|
|
84
|
+
}
|
|
85
|
+
if (determinismResult.adaptiveEvents && determinismResult.adaptiveEvents.length > 0) {
|
|
86
|
+
topReasons.push({ code: 'EXPECTED_ADAPTIVE_BEHAVIOR', count: determinismResult.adaptiveEvents.length });
|
|
87
|
+
}
|
|
88
|
+
if (determinismResult.diffs && determinismResult.diffs.length > 0) {
|
|
89
|
+
const blockerCount = determinismResult.diffs.filter(d => d.severity === 'BLOCKER').length;
|
|
90
|
+
if (blockerCount > 0) {
|
|
91
|
+
topReasons.push({ code: 'ARTIFACT_DIFF_DETECTED', count: blockerCount });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const report = {
|
|
96
|
+
version: 2, // PHASE 25: Bump version
|
|
97
|
+
contractVersion: 1,
|
|
98
|
+
artifactVersions: getArtifactVersions(),
|
|
99
|
+
generatedAt: new Date().toISOString(),
|
|
100
|
+
verdict: finalVerdict,
|
|
101
|
+
summary: {
|
|
102
|
+
...determinismResult.summary,
|
|
103
|
+
runFingerprintMismatches: runFingerprintMismatches.length,
|
|
104
|
+
verifierErrors: verifierErrors.length,
|
|
105
|
+
topReasons: topReasons.slice(0, 10)
|
|
106
|
+
},
|
|
107
|
+
runsMeta: determinismResult.runsMeta,
|
|
108
|
+
runFingerprints,
|
|
109
|
+
runFingerprintMismatches,
|
|
110
|
+
verificationResults,
|
|
111
|
+
verifierErrors,
|
|
112
|
+
normalizationRulesApplied: [
|
|
113
|
+
'Stripped timestamps (startedAt, completedAt, detectedAt, etc.)',
|
|
114
|
+
'Stripped runId fields',
|
|
115
|
+
'Normalized absolute paths to relative',
|
|
116
|
+
'Normalized floating scores to 3 decimals',
|
|
117
|
+
'Sorted arrays by stable keys',
|
|
118
|
+
'Normalized evidence paths but preserved presence/absence',
|
|
119
|
+
],
|
|
120
|
+
diffs: determinismResult.diffs,
|
|
121
|
+
adaptiveEvents: determinismResult.adaptiveEvents || [],
|
|
122
|
+
stabilityScore: determinismResult.summary.stabilityScore,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
writeFileSync(reportPath, JSON.stringify(report, null, 2) + '\n');
|
|
126
|
+
|
|
127
|
+
return reportPath;
|
|
128
|
+
}
|
|
129
|
+
|