@veraxhq/verax 0.3.0 → 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 +28 -20
- package/bin/verax.js +11 -18
- package/package.json +28 -7
- package/src/cli/commands/baseline.js +1 -2
- package/src/cli/commands/default.js +72 -81
- package/src/cli/commands/doctor.js +29 -0
- package/src/cli/commands/ga.js +3 -0
- package/src/cli/commands/gates.js +1 -1
- package/src/cli/commands/inspect.js +6 -133
- package/src/cli/commands/release-check.js +2 -0
- package/src/cli/commands/run.js +74 -246
- package/src/cli/commands/security-check.js +2 -1
- package/src/cli/commands/truth.js +0 -1
- package/src/cli/entry.js +82 -309
- package/src/cli/util/angular-component-extractor.js +2 -2
- package/src/cli/util/angular-navigation-detector.js +2 -2
- package/src/cli/util/ast-interactive-detector.js +4 -6
- package/src/cli/util/ast-network-detector.js +3 -3
- package/src/cli/util/ast-promise-extractor.js +581 -0
- package/src/cli/util/ast-usestate-detector.js +3 -3
- package/src/cli/util/atomic-write.js +12 -1
- 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 +2 -1
- package/src/cli/util/determinism-writer.js +1 -1
- package/src/cli/util/digest-engine.js +359 -0
- package/src/cli/util/dom-diff.js +226 -0
- package/src/cli/util/env-url.js +0 -4
- package/src/cli/util/evidence-engine.js +287 -0
- package/src/cli/util/expectation-extractor.js +217 -367
- package/src/cli/util/findings-writer.js +19 -126
- 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 -2
- 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 -2
- package/src/cli/util/paths.js +12 -3
- package/src/cli/util/project-discovery.js +284 -3
- package/src/cli/util/project-writer.js +2 -2
- package/src/cli/util/run-id.js +23 -27
- package/src/cli/util/run-result.js +778 -0
- package/src/cli/util/selector-resolver.js +235 -0
- package/src/cli/util/summary-writer.js +2 -1
- package/src/cli/util/svelte-navigation-detector.js +3 -3
- package/src/cli/util/svelte-sfc-extractor.js +0 -1
- package/src/cli/util/svelte-state-detector.js +1 -2
- 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 +4 -3
- package/src/cli/util/vue-sfc-extractor.js +1 -2
- package/src/cli/util/vue-state-detector.js +1 -1
- 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/finding-explainer.js +3 -56
- package/src/verax/cli/init.js +4 -18
- package/src/verax/core/action-classifier.js +4 -3
- package/src/verax/core/artifacts/registry.js +0 -15
- package/src/verax/core/artifacts/verifier.js +18 -8
- package/src/verax/core/baseline/baseline.snapshot.js +2 -0
- package/src/verax/core/capabilities/gates.js +7 -1
- package/src/verax/core/confidence/confidence-compute.js +14 -7
- package/src/verax/core/confidence/confidence.loader.js +1 -0
- package/src/verax/core/confidence-engine-refactor.js +8 -3
- package/src/verax/core/confidence-engine.js +162 -23
- package/src/verax/core/contracts/types.js +1 -0
- package/src/verax/core/contracts/validators.js +79 -4
- package/src/verax/core/decision-snapshot.js +3 -30
- package/src/verax/core/decisions/decision.trace.js +2 -0
- package/src/verax/core/determinism/contract-writer.js +2 -2
- package/src/verax/core/determinism/contract.js +1 -1
- package/src/verax/core/determinism/diff.js +42 -1
- package/src/verax/core/determinism/engine.js +7 -6
- package/src/verax/core/determinism/finding-identity.js +3 -2
- package/src/verax/core/determinism/normalize.js +32 -4
- package/src/verax/core/determinism/report-writer.js +1 -0
- package/src/verax/core/determinism/run-fingerprint.js +7 -2
- package/src/verax/core/dynamic-route-intelligence.js +8 -7
- package/src/verax/core/evidence/evidence-capture-service.js +1 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +2 -1
- package/src/verax/core/evidence-builder.js +2 -2
- package/src/verax/core/execution-mode-context.js +1 -1
- package/src/verax/core/execution-mode-detector.js +5 -3
- package/src/verax/core/failures/exit-codes.js +39 -37
- package/src/verax/core/failures/failure-summary.js +1 -1
- package/src/verax/core/failures/failure.factory.js +3 -3
- package/src/verax/core/failures/failure.ledger.js +3 -2
- package/src/verax/core/ga/ga.artifact.js +1 -1
- package/src/verax/core/ga/ga.contract.js +3 -2
- package/src/verax/core/ga/ga.enforcer.js +1 -0
- package/src/verax/core/guardrails/policy.loader.js +1 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +1 -1
- package/src/verax/core/guardrails-engine.js +2 -2
- 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 +2 -0
- package/src/verax/core/perf/perf.report.js +2 -0
- package/src/verax/core/pipeline-tracker.js +5 -0
- package/src/verax/core/release/provenance.builder.js +73 -214
- package/src/verax/core/release/release.enforcer.js +14 -9
- package/src/verax/core/release/reproducibility.check.js +1 -0
- package/src/verax/core/release/sbom.builder.js +32 -23
- 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 +6 -3
- package/src/verax/core/report/human-summary.js +141 -1
- package/src/verax/core/route-intelligence.js +4 -3
- 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 +10 -7
- package/src/verax/core/security/security.enforcer.js +4 -0
- package/src/verax/core/security/supplychain.policy.js +9 -1
- package/src/verax/core/security/vuln.scan.js +2 -2
- package/src/verax/core/truth/truth.certificate.js +3 -1
- package/src/verax/core/ui-feedback-intelligence.js +12 -46
- package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
- package/src/verax/detect/confidence-engine.js +100 -660
- package/src/verax/detect/confidence-helper.js +1 -0
- package/src/verax/detect/detection-engine.js +1 -18
- package/src/verax/detect/dynamic-route-findings.js +17 -14
- package/src/verax/detect/expectation-chain-detector.js +1 -1
- package/src/verax/detect/expectation-model.js +3 -5
- package/src/verax/detect/failure-cause-inference.js +293 -0
- package/src/verax/detect/findings-writer.js +126 -166
- 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 +51 -234
- package/src/verax/detect/invariants-enforcer.js +147 -0
- package/src/verax/detect/journey-stall-detector.js +4 -4
- 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 +7 -6
- 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 +18 -18
- package/src/verax/detect/verdict-engine.js +3 -57
- package/src/verax/detect/view-switch-correlator.js +2 -2
- package/src/verax/flow/flow-engine.js +2 -1
- package/src/verax/flow/flow-spec.js +0 -6
- package/src/verax/index.js +48 -412
- 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 +67 -682
- 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/route-validator.js +1 -4
- 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 +735 -84
- package/src/verax/observe/interaction-executor.js +192 -0
- package/src/verax/observe/interaction-runner.js +782 -530
- 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 +1 -1
- package/src/verax/observe/observe-helpers.js +2 -1
- package/src/verax/observe/observe-runner.js +28 -24
- package/src/verax/observe/observers/budget-observer.js +3 -3
- package/src/verax/observe/observers/console-observer.js +4 -4
- package/src/verax/observe/observers/coverage-observer.js +4 -4
- package/src/verax/observe/observers/interaction-observer.js +3 -3
- package/src/verax/observe/observers/navigation-observer.js +4 -4
- package/src/verax/observe/observers/network-observer.js +4 -4
- package/src/verax/observe/observers/safety-observer.js +1 -1
- package/src/verax/observe/observers/ui-feedback-observer.js +4 -4
- package/src/verax/observe/page-traversal.js +138 -0
- package/src/verax/observe/snapshot-ops.js +94 -0
- package/src/verax/observe/ui-signal-sensor.js +2 -148
- package/src/verax/scan-summary-writer.js +10 -42
- package/src/verax/shared/artifact-manager.js +30 -13
- package/src/verax/shared/caching.js +1 -0
- package/src/verax/shared/expectation-tracker.js +1 -0
- package/src/verax/shared/zip-artifacts.js +6 -0
- package/src/verax/core/confidence-engine.js.backup +0 -471
- package/src/verax/shared/config-loader.js +0 -169
- /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SNAPSHOT OPERATIONS MODULE
|
|
3
|
+
*
|
|
4
|
+
* Encapsulates snapshot loading, comparison, building, and saving logic.
|
|
5
|
+
* Extracted from observe/index.js (STAGE D2.1).
|
|
6
|
+
*
|
|
7
|
+
* Preserves 100% of original behavior:
|
|
8
|
+
* - Same incremental mode conditions
|
|
9
|
+
* - Same snapshot JSON shape
|
|
10
|
+
* - Same load/save timing
|
|
11
|
+
* - Same trace filtering
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
loadPreviousSnapshot,
|
|
16
|
+
buildSnapshot,
|
|
17
|
+
compareSnapshots,
|
|
18
|
+
saveSnapshot
|
|
19
|
+
} from '../core/incremental-store.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Initialize snapshot operations at the beginning of observation
|
|
23
|
+
*
|
|
24
|
+
* Loads previous snapshot, compares with baseline, determines incremental mode.
|
|
25
|
+
* Extracted from lines 108-111 of observe/index.js
|
|
26
|
+
*
|
|
27
|
+
* @param {string} projectDir - Project directory
|
|
28
|
+
* @param {Object} manifest - Loaded manifest object
|
|
29
|
+
* @returns {Promise<{oldSnapshot: Object|null, snapshotDiff: Object|null, incrementalMode: boolean}>}
|
|
30
|
+
*/
|
|
31
|
+
export async function initializeSnapshot(projectDir, manifest) {
|
|
32
|
+
let oldSnapshot = null;
|
|
33
|
+
let snapshotDiff = null;
|
|
34
|
+
let incrementalMode = false;
|
|
35
|
+
|
|
36
|
+
// SCALE INTELLIGENCE: Load previous snapshot for incremental mode
|
|
37
|
+
oldSnapshot = loadPreviousSnapshot(projectDir);
|
|
38
|
+
if (oldSnapshot) {
|
|
39
|
+
const currentSnapshot = buildSnapshot(manifest, []);
|
|
40
|
+
snapshotDiff = compareSnapshots(oldSnapshot, currentSnapshot);
|
|
41
|
+
incrementalMode = !snapshotDiff.hasChanges; // Use incremental if nothing changed
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
oldSnapshot,
|
|
46
|
+
snapshotDiff,
|
|
47
|
+
incrementalMode
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Finalize snapshot operations at the end of observation
|
|
53
|
+
*
|
|
54
|
+
* Builds current snapshot from observed interactions, saves for next run,
|
|
55
|
+
* and creates incremental metadata for observation object.
|
|
56
|
+
* Extracted from lines 789-811 of observe/index.js
|
|
57
|
+
*
|
|
58
|
+
* @param {Object} manifest - Loaded manifest object (or null if not provided)
|
|
59
|
+
* @param {Array} traces - All collected traces
|
|
60
|
+
* @param {Array} skippedInteractions - Array of skipped interactions
|
|
61
|
+
* @param {boolean} incrementalMode - Whether incremental mode was enabled
|
|
62
|
+
* @param {Object} snapshotDiff - Snapshot diff from initialization (or null)
|
|
63
|
+
* @param {string} projectDir - Project directory
|
|
64
|
+
* @param {string} runId - Run identifier
|
|
65
|
+
* @param {string} url - Initial URL (fallback for trace.before.url)
|
|
66
|
+
* @returns {Promise<{enabled: boolean, snapshotDiff: Object, skippedInteractionsCount: number}|null>}
|
|
67
|
+
*/
|
|
68
|
+
export async function finalizeSnapshot(manifest, traces, skippedInteractions, incrementalMode, snapshotDiff, projectDir, runId, url) {
|
|
69
|
+
let incrementalMetadata = null;
|
|
70
|
+
|
|
71
|
+
// SCALE INTELLIGENCE: Save snapshot for next incremental run
|
|
72
|
+
if (manifest) {
|
|
73
|
+
// Build snapshot from current run (extract interactions from traces)
|
|
74
|
+
const observedInteractions = traces
|
|
75
|
+
.filter(t => t.interaction && !t.incremental)
|
|
76
|
+
.map(t => ({
|
|
77
|
+
type: t.interaction?.type,
|
|
78
|
+
selector: t.interaction?.selector,
|
|
79
|
+
url: t.before?.url || url
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
const currentSnapshot = buildSnapshot(manifest, observedInteractions);
|
|
83
|
+
saveSnapshot(projectDir, currentSnapshot, runId);
|
|
84
|
+
|
|
85
|
+
// Add incremental mode metadata to observation
|
|
86
|
+
incrementalMetadata = {
|
|
87
|
+
enabled: incrementalMode,
|
|
88
|
+
snapshotDiff: snapshotDiff,
|
|
89
|
+
skippedInteractionsCount: skippedInteractions.filter(s => s.reason === 'incremental_unchanged').length
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return incrementalMetadata;
|
|
94
|
+
}
|
|
@@ -2,30 +2,14 @@
|
|
|
2
2
|
* WAVE 3: UI Signal Sensor
|
|
3
3
|
* Detects user-visible feedback signals: loading states, dialogs, error messages
|
|
4
4
|
* Conservative: only count signals with accessibility semantics or explicit attributes
|
|
5
|
-
*
|
|
6
|
-
* CSS SPINNER DETECTION: Detects CSS-only loading indicators without semantic attributes
|
|
7
5
|
*/
|
|
8
6
|
|
|
9
|
-
import {
|
|
10
|
-
isBorderSpinnerPattern,
|
|
11
|
-
isRotationAnimation,
|
|
12
|
-
isPulseAnimation,
|
|
13
|
-
isSpinnerSize,
|
|
14
|
-
isDecorativeElement,
|
|
15
|
-
CSS_SPINNER_REASON_CODES
|
|
16
|
-
} from '../shared/css-spinner-rules.js';
|
|
17
|
-
|
|
18
7
|
export class UISignalSensor {
|
|
19
8
|
/**
|
|
20
9
|
* Snapshot current UI signals on the page.
|
|
21
|
-
* Returns: { hasLoadingIndicator, hasDialog, buttonStateChanged, errorSignals, explanation
|
|
22
|
-
*
|
|
23
|
-
* @param {Object} page - Playwright page object
|
|
24
|
-
* @param {number} interactionTime - Optional: timestamp of interaction (for timing window)
|
|
25
|
-
* @param {Object} beforeSnapshot - Optional: snapshot before interaction (for correlation)
|
|
26
|
-
* @returns {Promise<Object>} UI signals snapshot
|
|
10
|
+
* Returns: { hasLoadingIndicator, hasDialog, buttonStateChanged, errorSignals, explanation }
|
|
27
11
|
*/
|
|
28
|
-
async snapshot(page
|
|
12
|
+
async snapshot(page) {
|
|
29
13
|
const signals = await page.evaluate(() => {
|
|
30
14
|
const result = {
|
|
31
15
|
hasLoadingIndicator: false,
|
|
@@ -101,136 +85,6 @@ export class UISignalSensor {
|
|
|
101
85
|
);
|
|
102
86
|
}
|
|
103
87
|
|
|
104
|
-
// CSS SPINNER DETECTION: Detect CSS-only loading indicators
|
|
105
|
-
result.cssSpinners = [];
|
|
106
|
-
result.cssSpinnerDetected = false;
|
|
107
|
-
|
|
108
|
-
const allElements = Array.from(document.querySelectorAll('*'));
|
|
109
|
-
const currentTime = Date.now();
|
|
110
|
-
const MAX_SPINNER_SIZE = 100;
|
|
111
|
-
const MIN_SPINNER_SIZE = 8;
|
|
112
|
-
const SPINNER_TIMING_WINDOW_MS = 2000;
|
|
113
|
-
|
|
114
|
-
// Helper functions (inlined for browser context)
|
|
115
|
-
const isBorderSpinnerPattern = (style) => {
|
|
116
|
-
const borderWidth = parseFloat(style.borderWidth) || 0;
|
|
117
|
-
const borderTopWidth = parseFloat(style.borderTopWidth) || 0;
|
|
118
|
-
if (borderWidth < 2 && borderTopWidth < 2) return false;
|
|
119
|
-
|
|
120
|
-
const borderColor = style.borderColor || '';
|
|
121
|
-
const borderTopColor = style.borderTopColor || '';
|
|
122
|
-
const hasDifferentTopColor = borderTopColor && borderColor && borderTopColor !== borderColor && borderTopColor !== 'rgba(0, 0, 0, 0)';
|
|
123
|
-
|
|
124
|
-
const borderRadius = style.borderRadius || '';
|
|
125
|
-
const isCircular = borderRadius.includes('50%') || borderRadius.includes('999') || parseFloat(borderRadius) > 20;
|
|
126
|
-
|
|
127
|
-
return hasDifferentTopColor && isCircular;
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const isRotationAnimation = (style) => {
|
|
131
|
-
const animationName = (style.animationName || '').toLowerCase();
|
|
132
|
-
const animation = (style.animation || '').toLowerCase();
|
|
133
|
-
const transform = style.transform || '';
|
|
134
|
-
const animationDuration = style.animationDuration || '';
|
|
135
|
-
|
|
136
|
-
const spinnerKeywords = ['spin', 'rotate', 'loading', 'loader'];
|
|
137
|
-
const hasSpinnerKeyword = spinnerKeywords.some(k => animationName.includes(k) || animation.includes(k));
|
|
138
|
-
const hasRotation = transform.includes('rotate');
|
|
139
|
-
const isAnimated = animationDuration && animationDuration !== '0s' && !animationDuration.includes('none');
|
|
140
|
-
|
|
141
|
-
return (hasRotation || hasSpinnerKeyword) && isAnimated;
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
const isPulseAnimation = (style) => {
|
|
145
|
-
const animationName = (style.animationName || '').toLowerCase();
|
|
146
|
-
const animation = (style.animation || '').toLowerCase();
|
|
147
|
-
const animationDuration = style.animationDuration || '';
|
|
148
|
-
const opacity = parseFloat(style.opacity) || 1;
|
|
149
|
-
|
|
150
|
-
const pulseKeywords = ['pulse', 'fade', 'loading'];
|
|
151
|
-
const hasPulseKeyword = pulseKeywords.some(k => animationName.includes(k) || animation.includes(k));
|
|
152
|
-
const isAnimated = animationDuration && animationDuration !== '0s' && !animationDuration.includes('none');
|
|
153
|
-
const hasOpacityVariation = opacity < 1;
|
|
154
|
-
|
|
155
|
-
return hasPulseKeyword && isAnimated && hasOpacityVariation;
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
const isSpinnerSize = (width, height) => {
|
|
159
|
-
const maxDim = Math.max(width, height);
|
|
160
|
-
const minDim = Math.min(width, height);
|
|
161
|
-
if (maxDim > MAX_SPINNER_SIZE || minDim < MIN_SPINNER_SIZE) return false;
|
|
162
|
-
const aspectRatio = maxDim / (minDim || 1);
|
|
163
|
-
return aspectRatio <= 2.0;
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
for (const el of allElements) {
|
|
167
|
-
const style = window.getComputedStyle(el);
|
|
168
|
-
// @ts-expect-error
|
|
169
|
-
if (el.offsetParent === null || style.visibility === 'hidden' || style.display === 'none' || style.opacity === '0') {
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Skip if element has semantic loading attributes
|
|
174
|
-
if (el.hasAttribute('aria-busy') || el.hasAttribute('data-loading') || el.getAttribute('role') === 'progressbar') {
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const width = el.offsetWidth || 0;
|
|
179
|
-
const height = el.offsetHeight || 0;
|
|
180
|
-
const elementId = el.id || `${el.tagName}-${el.className}`;
|
|
181
|
-
|
|
182
|
-
// Check size bounds
|
|
183
|
-
if (!isSpinnerSize(width, height)) {
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Check for spinner patterns
|
|
188
|
-
let spinnerType = null;
|
|
189
|
-
let reasonCode = null;
|
|
190
|
-
|
|
191
|
-
if (isBorderSpinnerPattern(style)) {
|
|
192
|
-
spinnerType = 'border-spinner';
|
|
193
|
-
reasonCode = 'UI_CSS_SPINNER_DETECTED_BORDER';
|
|
194
|
-
} else if (isRotationAnimation(style)) {
|
|
195
|
-
spinnerType = 'rotation-spinner';
|
|
196
|
-
reasonCode = 'UI_CSS_SPINNER_DETECTED_ROTATION';
|
|
197
|
-
} else if (isPulseAnimation(style)) {
|
|
198
|
-
spinnerType = 'pulse-spinner';
|
|
199
|
-
reasonCode = 'UI_CSS_SPINNER_DETECTED_PULSE';
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (spinnerType) {
|
|
203
|
-
result.cssSpinners.push({
|
|
204
|
-
type: spinnerType,
|
|
205
|
-
reasonCode,
|
|
206
|
-
elementId,
|
|
207
|
-
width,
|
|
208
|
-
height
|
|
209
|
-
});
|
|
210
|
-
result.cssSpinnerDetected = true;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Require 2+ independent signals for CONFIRMED CSS spinner feedback
|
|
215
|
-
if (result.cssSpinnerDetected) {
|
|
216
|
-
const hasDisabledButton = result.disabledElements.length > 0;
|
|
217
|
-
const hasPointerEventsDisabled = allElements.some(el => {
|
|
218
|
-
const style = window.getComputedStyle(el);
|
|
219
|
-
return style.pointerEvents === 'none';
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
const corroboratingSignals = [hasDisabledButton, hasPointerEventsDisabled].filter(Boolean).length;
|
|
223
|
-
|
|
224
|
-
if (corroboratingSignals >= 1) {
|
|
225
|
-
result.hasLoadingIndicator = true;
|
|
226
|
-
result.explanation.push(`CSS spinner detected with ${corroboratingSignals} corroborating signal(s)`);
|
|
227
|
-
result.cssSpinnerReasonCode = 'UI_CSS_SPINNER_ACCEPTED_WITH_CORROBORATION';
|
|
228
|
-
} else {
|
|
229
|
-
result.explanation.push('CSS spinner detected but no corroborating signals (SUSPECTED)');
|
|
230
|
-
result.cssSpinnerReasonCode = 'UI_CSS_SPINNER_REJECTED_NO_CORROBORATION';
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
88
|
// VALIDATION INTELLIGENCE v1: Detect visible validation feedback
|
|
235
89
|
// Check for aria-invalid="true" with visible error text nearby
|
|
236
90
|
const invalidElements = Array.from(document.querySelectorAll('[aria-invalid="true"]'));
|
|
@@ -3,11 +3,8 @@ import { writeFileSync, mkdirSync, readFileSync, existsSync } from 'fs';
|
|
|
3
3
|
import { computeExpectationsSummary } from './shared/artifact-manager.js';
|
|
4
4
|
import { createImpactSummary } from './core/silence-impact.js';
|
|
5
5
|
import { computeDecisionSnapshot } from './core/decision-snapshot.js';
|
|
6
|
-
import { VERAX_PRODUCT_DEFINITION } from './core/product-definition.js';
|
|
7
|
-
import { ARTIFACT_REGISTRY, getArtifactVersions } from './core/artifacts/registry.js';
|
|
8
|
-
import { generateHumanSummary } from './core/report/human-summary.js';
|
|
9
6
|
|
|
10
|
-
export
|
|
7
|
+
export function writeScanSummary(projectDir, url, projectType, learnTruth, observeTruth, detectTruth, manifestPath, tracesPath, findingsPath, runDirOpt, findingsArray = null) {
|
|
11
8
|
if (!runDirOpt) {
|
|
12
9
|
throw new Error('runDirOpt is required');
|
|
13
10
|
}
|
|
@@ -18,6 +15,7 @@ export async function writeScanSummary(projectDir, url, projectType, learnTruth,
|
|
|
18
15
|
let expectationsSummary = { total: 0, navigation: 0, networkActions: 0, stateActions: 0 };
|
|
19
16
|
if (manifestPath && existsSync(manifestPath)) {
|
|
20
17
|
try {
|
|
18
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
21
19
|
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
22
20
|
expectationsSummary = computeExpectationsSummary(manifest);
|
|
23
21
|
} catch (error) {
|
|
@@ -32,30 +30,21 @@ export async function writeScanSummary(projectDir, url, projectType, learnTruth,
|
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
// PHASE 6: Compute determinism summary from decisions.json
|
|
35
|
-
// PHASE 21.2: Use HARD verdict from determinism contract
|
|
36
33
|
let determinismSummary = null;
|
|
37
34
|
if (runDirOpt && observeTruth?.runId) {
|
|
38
35
|
const decisionsPath = resolve(runDirOpt, 'decisions.json');
|
|
39
36
|
if (existsSync(decisionsPath)) {
|
|
40
37
|
try {
|
|
38
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
41
39
|
const decisions = JSON.parse(readFileSync(decisionsPath, 'utf-8'));
|
|
42
|
-
const { DecisionRecorder } =
|
|
40
|
+
const { DecisionRecorder } = require('./core/determinism-model.js');
|
|
43
41
|
const recorder = DecisionRecorder.fromExport(decisions);
|
|
44
42
|
const summary = recorder.getSummary();
|
|
45
43
|
|
|
46
|
-
// PHASE 21.2: Compute HARD verdict from adaptive events
|
|
47
|
-
const { computeDeterminismVerdict } = await import('./core/determinism/contract.js');
|
|
48
|
-
const verdict = computeDeterminismVerdict(recorder);
|
|
49
|
-
|
|
50
44
|
determinismSummary = {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
adaptiveEventsCount: verdict.adaptiveEvents.length,
|
|
55
|
-
// Legacy fields for backward compatibility
|
|
56
|
-
isDeterministic: verdict.verdict === 'DETERMINISTIC',
|
|
57
|
-
totalDecisions: summary.total,
|
|
58
|
-
decisionsByCategory: summary.byCategory,
|
|
45
|
+
isDeterministic: summary.isDeterministic,
|
|
46
|
+
totalDecisions: summary.totalDecisions,
|
|
47
|
+
decisionsByCategory: summary.decisionsByCategory,
|
|
59
48
|
decisionsPath: decisionsPath
|
|
60
49
|
};
|
|
61
50
|
} catch (error) {
|
|
@@ -71,37 +60,19 @@ export async function writeScanSummary(projectDir, url, projectType, learnTruth,
|
|
|
71
60
|
decisionSnapshot = computeDecisionSnapshot(findingsArray, detectTruth, observeTruth, silences);
|
|
72
61
|
}
|
|
73
62
|
|
|
74
|
-
// PHASE 21.10: Generate human summary
|
|
75
|
-
let humanSummary = null;
|
|
76
|
-
if (observeTruth?.runId) {
|
|
77
|
-
try {
|
|
78
|
-
humanSummary = await generateHumanSummary(projectDir, observeTruth.runId);
|
|
79
|
-
} catch {
|
|
80
|
-
// Ignore errors generating human summary
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
63
|
const summary = {
|
|
85
64
|
version: 1,
|
|
86
|
-
contractVersion: 1, // PHASE 0: Track schema changes
|
|
87
65
|
scannedAt: new Date().toISOString(),
|
|
88
66
|
url: url,
|
|
89
67
|
projectType: projectType,
|
|
90
68
|
expectationsSummary: expectationsSummary,
|
|
91
|
-
// PHASE 0: Include Evidence Law statement
|
|
92
|
-
evidenceLaw: {
|
|
93
|
-
statement: VERAX_PRODUCT_DEFINITION.evidenceLaw.statement,
|
|
94
|
-
description: VERAX_PRODUCT_DEFINITION.evidenceLaw.definition,
|
|
95
|
-
enforcement: VERAX_PRODUCT_DEFINITION.evidenceLaw.enforcement
|
|
96
|
-
},
|
|
97
69
|
// PHASE 7: Decision snapshot first (most important for human decision-making)
|
|
98
70
|
decisionSnapshot: decisionSnapshot,
|
|
99
71
|
// PHASE 7: Misinterpretation guards (explicit warnings)
|
|
100
72
|
interpretationGuards: {
|
|
101
73
|
zeroFindings: 'Zero findings does NOT mean no problems. Check unverified count and confidence level.',
|
|
102
74
|
deterministicRun: 'Deterministic run does NOT mean correct site. Only means scan was reproducible.',
|
|
103
|
-
highSilenceImpact: 'High silence impact does NOT mean failures exist. Only means unknowns affect confidence.'
|
|
104
|
-
evidenceLaw: 'Not all findings are CONFIRMED. Some may be SUSPECTED (insufficient evidence). Only CONFIRMED findings are actionable.'
|
|
75
|
+
highSilenceImpact: 'High silence impact does NOT mean failures exist. Only means unknowns affect confidence.'
|
|
105
76
|
},
|
|
106
77
|
truth: {
|
|
107
78
|
learn: learnTruth,
|
|
@@ -119,17 +90,14 @@ export async function writeScanSummary(projectDir, url, projectType, learnTruth,
|
|
|
119
90
|
} : null,
|
|
120
91
|
// PHASE 6: Add determinism summary
|
|
121
92
|
determinism: determinismSummary,
|
|
122
|
-
// PHASE 21.10: Add human summary
|
|
123
|
-
humanSummary: humanSummary,
|
|
124
93
|
paths: {
|
|
125
94
|
manifest: manifestPath,
|
|
126
95
|
traces: tracesPath,
|
|
127
96
|
findings: findingsPath
|
|
128
|
-
}
|
|
129
|
-
artifactVersions: getArtifactVersions()
|
|
97
|
+
}
|
|
130
98
|
};
|
|
131
99
|
|
|
132
|
-
const summaryPath = resolve(scanDir,
|
|
100
|
+
const summaryPath = resolve(scanDir, 'scan-summary.json');
|
|
133
101
|
writeFileSync(summaryPath, JSON.stringify(summary, null, 2) + '\n');
|
|
134
102
|
|
|
135
103
|
return {
|
|
@@ -14,15 +14,34 @@
|
|
|
14
14
|
|
|
15
15
|
import { existsSync, mkdirSync, writeFileSync, appendFileSync } from 'fs';
|
|
16
16
|
import { resolve } from 'path';
|
|
17
|
-
import {
|
|
18
|
-
|
|
17
|
+
import { generateRunId as generateDeterministicRunId } from '../core/run-id.js';
|
|
18
|
+
|
|
19
|
+
const ZERO_BUDGET = Object.freeze({
|
|
20
|
+
maxScanDurationMs: 0,
|
|
21
|
+
maxInteractionsPerPage: 0,
|
|
22
|
+
maxUniqueUrls: 0,
|
|
23
|
+
interactionTimeoutMs: 0,
|
|
24
|
+
navigationTimeoutMs: 0,
|
|
25
|
+
});
|
|
19
26
|
|
|
20
27
|
/**
|
|
21
28
|
* Generate a unique run ID.
|
|
22
29
|
* @returns {string} - 8-character hex ID
|
|
23
30
|
*/
|
|
24
|
-
export function generateRunId() {
|
|
25
|
-
|
|
31
|
+
export function generateRunId(seed = 'about:blank') {
|
|
32
|
+
let baseOrigin = 'about:blank';
|
|
33
|
+
try {
|
|
34
|
+
baseOrigin = new URL(seed).origin;
|
|
35
|
+
} catch {
|
|
36
|
+
baseOrigin = seed;
|
|
37
|
+
}
|
|
38
|
+
return generateDeterministicRunId({
|
|
39
|
+
url: seed,
|
|
40
|
+
safetyFlags: {},
|
|
41
|
+
baseOrigin,
|
|
42
|
+
scanBudget: ZERO_BUDGET,
|
|
43
|
+
manifestPath: null,
|
|
44
|
+
});
|
|
26
45
|
}
|
|
27
46
|
|
|
28
47
|
/**
|
|
@@ -31,22 +50,20 @@ export function generateRunId() {
|
|
|
31
50
|
* @param {string} runId - Run identifier
|
|
32
51
|
* @returns {Object} - Paths to each artifact location
|
|
33
52
|
*/
|
|
34
|
-
export function initArtifactPaths(projectRoot, runId = null) {
|
|
35
|
-
const id = runId || generateRunId();
|
|
53
|
+
export function initArtifactPaths(projectRoot, runId = null, seed = projectRoot) {
|
|
54
|
+
const id = runId || generateRunId(seed);
|
|
36
55
|
const runDir = resolve(projectRoot, '.verax', 'runs', id);
|
|
37
|
-
const registryPaths = buildRunArtifactPaths(runDir);
|
|
38
56
|
|
|
39
57
|
const paths = {
|
|
40
58
|
runId: id,
|
|
41
59
|
runDir,
|
|
42
|
-
summary:
|
|
43
|
-
findings:
|
|
60
|
+
summary: resolve(runDir, 'summary.json'),
|
|
61
|
+
findings: resolve(runDir, 'findings.json'),
|
|
44
62
|
expectations: resolve(runDir, 'expectations.json'),
|
|
45
|
-
traces:
|
|
46
|
-
evidence:
|
|
63
|
+
traces: resolve(runDir, 'traces.jsonl'),
|
|
64
|
+
evidence: resolve(runDir, 'evidence'),
|
|
47
65
|
flows: resolve(runDir, 'flows'),
|
|
48
|
-
artifacts: resolve(projectRoot, '.verax', 'artifacts')
|
|
49
|
-
registry: registryPaths.artifactVersions
|
|
66
|
+
artifacts: resolve(projectRoot, '.verax', 'artifacts') // Legacy compat
|
|
50
67
|
};
|
|
51
68
|
|
|
52
69
|
// Create directories
|
|
@@ -45,6 +45,7 @@ export async function createArtifactsZip(runDir, outputPath = null) {
|
|
|
45
45
|
|
|
46
46
|
// Handle errors
|
|
47
47
|
archive.on('error', (err) => {
|
|
48
|
+
output.destroy();
|
|
48
49
|
reject(err);
|
|
49
50
|
});
|
|
50
51
|
|
|
@@ -52,6 +53,11 @@ export async function createArtifactsZip(runDir, outputPath = null) {
|
|
|
52
53
|
resolvePromise(outputPath);
|
|
53
54
|
});
|
|
54
55
|
|
|
56
|
+
output.on('error', (err) => {
|
|
57
|
+
archive.destroy();
|
|
58
|
+
reject(err);
|
|
59
|
+
});
|
|
60
|
+
|
|
55
61
|
// Pipe archive data to file
|
|
56
62
|
archive.pipe(output);
|
|
57
63
|
|