@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,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VIEW SWITCH CORRELATOR
|
|
3
|
+
*
|
|
4
|
+
* Correlates view switch promises with observed UI changes (no URL change).
|
|
5
|
+
* Requires at least 2 independent signals for CONFIRMED.
|
|
6
|
+
*
|
|
7
|
+
* TRUTH BOUNDARY:
|
|
8
|
+
* - CONFIRMED: 2+ independent signals (DOM signature + landmark/focus/aria-live)
|
|
9
|
+
* - SUSPECTED: 1 signal only
|
|
10
|
+
* - INFORMATIONAL: Interaction blocked/disabled/prevented
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Reason codes for correlation decisions
|
|
15
|
+
*/
|
|
16
|
+
export const VIEW_SWITCH_REASON_CODES = {
|
|
17
|
+
CONFIRMED_TWO_SIGNALS: 'CONFIRMED_TWO_SIGNALS',
|
|
18
|
+
CONFIRMED_THREE_SIGNALS: 'CONFIRMED_THREE_SIGNALS',
|
|
19
|
+
SUSPECTED_ONE_SIGNAL: 'SUSPECTED_ONE_SIGNAL',
|
|
20
|
+
INFORMATIONAL_BLOCKED: 'INFORMATIONAL_BLOCKED',
|
|
21
|
+
INFORMATIONAL_DISABLED: 'INFORMATIONAL_DISABLED',
|
|
22
|
+
INFORMATIONAL_PREVENTED: 'INFORMATIONAL_PREVENTED',
|
|
23
|
+
NO_SIGNALS: 'NO_SIGNALS'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Correlate view switch promise with observed UI changes.
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} expectation - View switch promise expectation
|
|
30
|
+
* @param {Object} trace - Interaction trace with sensors
|
|
31
|
+
* @param {string} beforeUrl - URL before interaction
|
|
32
|
+
* @param {string} afterUrl - URL after interaction
|
|
33
|
+
* @returns {Object} - { outcome, severity, reasonCode, signals }
|
|
34
|
+
*/
|
|
35
|
+
export function correlateViewSwitch(expectation, trace, beforeUrl, afterUrl) {
|
|
36
|
+
if (!expectation || expectation.kind !== 'VIEW_SWITCH_PROMISE') {
|
|
37
|
+
return { outcome: null, severity: null, reasonCode: null, signals: [] };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const sensors = trace.sensors || {};
|
|
41
|
+
const navigation = sensors.navigation || {};
|
|
42
|
+
const _uiSignals = sensors.uiSignals || {};
|
|
43
|
+
const _stateUi = sensors.stateUi || {};
|
|
44
|
+
const uiFeedback = sensors.uiFeedback || {};
|
|
45
|
+
|
|
46
|
+
// Check if URL changed (if so, this is not a state-driven navigation)
|
|
47
|
+
const urlChanged = navigation.urlChanged === true || (beforeUrl !== afterUrl);
|
|
48
|
+
if (urlChanged) {
|
|
49
|
+
return { outcome: null, severity: null, reasonCode: 'URL_CHANGED', signals: [] };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check if interaction was blocked/disabled/prevented
|
|
53
|
+
const interaction = trace.interaction || {};
|
|
54
|
+
const isDisabled = interaction.disabled === true;
|
|
55
|
+
const isBlocked = interaction.blocked === true;
|
|
56
|
+
const isPrevented = interaction.prevented === true;
|
|
57
|
+
|
|
58
|
+
if (isDisabled) {
|
|
59
|
+
return {
|
|
60
|
+
outcome: 'INFORMATIONAL',
|
|
61
|
+
severity: 'INFORMATIONAL',
|
|
62
|
+
reasonCode: VIEW_SWITCH_REASON_CODES.INFORMATIONAL_DISABLED,
|
|
63
|
+
signals: []
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (isBlocked) {
|
|
68
|
+
return {
|
|
69
|
+
outcome: 'INFORMATIONAL',
|
|
70
|
+
severity: 'INFORMATIONAL',
|
|
71
|
+
reasonCode: VIEW_SWITCH_REASON_CODES.INFORMATIONAL_BLOCKED,
|
|
72
|
+
signals: []
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (isPrevented) {
|
|
77
|
+
return {
|
|
78
|
+
outcome: 'INFORMATIONAL',
|
|
79
|
+
severity: 'INFORMATIONAL',
|
|
80
|
+
reasonCode: VIEW_SWITCH_REASON_CODES.INFORMATIONAL_PREVENTED,
|
|
81
|
+
signals: []
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Collect independent signals
|
|
86
|
+
const signals = [];
|
|
87
|
+
|
|
88
|
+
// Signal 1: DOM signature change (stable hash)
|
|
89
|
+
const beforeDom = trace.before?.domSignature || trace.before?.domHash;
|
|
90
|
+
const afterDom = trace.after?.domSignature || trace.after?.domHash;
|
|
91
|
+
if (beforeDom && afterDom && beforeDom !== afterDom) {
|
|
92
|
+
signals.push({
|
|
93
|
+
type: 'DOM_SIGNATURE_CHANGE',
|
|
94
|
+
before: beforeDom,
|
|
95
|
+
after: afterDom
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Signal 2: Visible landmark change (heading/main role change)
|
|
100
|
+
const beforeLandmarks = extractLandmarks(trace.before);
|
|
101
|
+
const afterLandmarks = extractLandmarks(trace.after);
|
|
102
|
+
if (beforeLandmarks.length > 0 && afterLandmarks.length > 0) {
|
|
103
|
+
const landmarksChanged = JSON.stringify(beforeLandmarks) !== JSON.stringify(afterLandmarks);
|
|
104
|
+
if (landmarksChanged) {
|
|
105
|
+
signals.push({
|
|
106
|
+
type: 'LANDMARK_CHANGE',
|
|
107
|
+
before: beforeLandmarks,
|
|
108
|
+
after: afterLandmarks
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Signal 3: Focus moved to new container
|
|
114
|
+
const beforeFocus = trace.before?.focus || {};
|
|
115
|
+
const afterFocus = trace.after?.focus || {};
|
|
116
|
+
if (beforeFocus.selector && afterFocus.selector && beforeFocus.selector !== afterFocus.selector) {
|
|
117
|
+
const beforeContainer = getContainerSelector(beforeFocus.selector);
|
|
118
|
+
const afterContainer = getContainerSelector(afterFocus.selector);
|
|
119
|
+
if (beforeContainer !== afterContainer) {
|
|
120
|
+
signals.push({
|
|
121
|
+
type: 'FOCUS_CONTAINER_CHANGE',
|
|
122
|
+
before: beforeContainer,
|
|
123
|
+
after: afterContainer
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Signal 4: aria-live message
|
|
129
|
+
const ariaLiveBefore = extractAriaLive(trace.before);
|
|
130
|
+
const ariaLiveAfter = extractAriaLive(trace.after);
|
|
131
|
+
if (ariaLiveAfter.length > ariaLiveBefore.length) {
|
|
132
|
+
signals.push({
|
|
133
|
+
type: 'ARIA_LIVE_MESSAGE',
|
|
134
|
+
messages: ariaLiveAfter.slice(ariaLiveBefore.length)
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Signal 5: UI feedback signals (optional but counts)
|
|
139
|
+
if (uiFeedback.signals) {
|
|
140
|
+
const feedbackSignals = uiFeedback.signals;
|
|
141
|
+
if (feedbackSignals.domChange?.happened === true) {
|
|
142
|
+
signals.push({
|
|
143
|
+
type: 'UI_FEEDBACK_DOM_CHANGE',
|
|
144
|
+
details: feedbackSignals.domChange
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (feedbackSignals.focusChange?.happened === true) {
|
|
148
|
+
signals.push({
|
|
149
|
+
type: 'UI_FEEDBACK_FOCUS_CHANGE',
|
|
150
|
+
details: feedbackSignals.focusChange
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Determine outcome based on signal count
|
|
156
|
+
if (signals.length >= 2) {
|
|
157
|
+
return {
|
|
158
|
+
outcome: 'CONFIRMED',
|
|
159
|
+
severity: 'CONFIRMED',
|
|
160
|
+
reasonCode: signals.length >= 3
|
|
161
|
+
? VIEW_SWITCH_REASON_CODES.CONFIRMED_THREE_SIGNALS
|
|
162
|
+
: VIEW_SWITCH_REASON_CODES.CONFIRMED_TWO_SIGNALS,
|
|
163
|
+
signals
|
|
164
|
+
};
|
|
165
|
+
} else if (signals.length === 1) {
|
|
166
|
+
return {
|
|
167
|
+
outcome: 'SUSPECTED',
|
|
168
|
+
severity: 'SUSPECTED',
|
|
169
|
+
reasonCode: VIEW_SWITCH_REASON_CODES.SUSPECTED_ONE_SIGNAL,
|
|
170
|
+
signals
|
|
171
|
+
};
|
|
172
|
+
} else {
|
|
173
|
+
return {
|
|
174
|
+
outcome: 'NO_SIGNALS',
|
|
175
|
+
severity: 'SUSPECTED',
|
|
176
|
+
reasonCode: VIEW_SWITCH_REASON_CODES.NO_SIGNALS,
|
|
177
|
+
signals: []
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Extract landmarks (headings, main role) from trace snapshot
|
|
184
|
+
*/
|
|
185
|
+
function extractLandmarks(snapshot) {
|
|
186
|
+
if (!snapshot) return [];
|
|
187
|
+
|
|
188
|
+
const landmarks = [];
|
|
189
|
+
|
|
190
|
+
// Extract headings (h1-h6)
|
|
191
|
+
if (snapshot.headings) {
|
|
192
|
+
landmarks.push(...snapshot.headings.map(h => ({ type: 'heading', level: h.level, text: h.text?.slice(0, 50) })));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Extract main role elements
|
|
196
|
+
if (snapshot.mainElements) {
|
|
197
|
+
landmarks.push(...snapshot.mainElements.map(m => ({ type: 'main', text: m.text?.slice(0, 50) })));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return landmarks;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get container selector from element selector
|
|
205
|
+
*/
|
|
206
|
+
function getContainerSelector(selector) {
|
|
207
|
+
if (!selector) return null;
|
|
208
|
+
|
|
209
|
+
// Extract parent container (simplified - assumes common patterns)
|
|
210
|
+
const parts = selector.split(' > ');
|
|
211
|
+
if (parts.length > 1) {
|
|
212
|
+
return parts[parts.length - 2]; // Second to last part
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Try to extract container from selector
|
|
216
|
+
const match = selector.match(/([^>]+) > [^>]+$/);
|
|
217
|
+
if (match) {
|
|
218
|
+
return match[1].trim();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return selector; // Fallback to full selector
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Extract aria-live messages from trace snapshot
|
|
226
|
+
*/
|
|
227
|
+
function extractAriaLive(snapshot) {
|
|
228
|
+
if (!snapshot) return [];
|
|
229
|
+
|
|
230
|
+
const messages = [];
|
|
231
|
+
|
|
232
|
+
if (snapshot.ariaLiveRegions) {
|
|
233
|
+
snapshot.ariaLiveRegions.forEach(region => {
|
|
234
|
+
if (region.text) {
|
|
235
|
+
messages.push(region.text.slice(0, 100));
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return messages;
|
|
241
|
+
}
|
|
242
|
+
|
|
@@ -195,7 +195,8 @@ async function stepClick(page, step, result, spec) {
|
|
|
195
195
|
|
|
196
196
|
const fullContent = `${elementText} ${elementAriaLabel} ${elementValue}`.toLowerCase();
|
|
197
197
|
|
|
198
|
-
|
|
198
|
+
const denyKeywords = spec.denyKeywords || [];
|
|
199
|
+
for (const keyword of denyKeywords) {
|
|
199
200
|
if (fullContent.includes(keyword.toLowerCase())) {
|
|
200
201
|
result.reason = `Click blocked by safety gate: denyKeyword "${keyword}" found in element`;
|
|
201
202
|
result.findingType = 'blocked_by_safety_gate';
|
|
@@ -41,11 +41,6 @@ export function validateFlowSpec(spec) {
|
|
|
41
41
|
allowlist.pathsPrefix = ['/'];
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// Validate denyKeywords
|
|
45
|
-
const denyKeywords = spec.denyKeywords || [];
|
|
46
|
-
if (!Array.isArray(denyKeywords)) {
|
|
47
|
-
throw new Error('denyKeywords must be an array');
|
|
48
|
-
}
|
|
49
44
|
|
|
50
45
|
// Validate steps
|
|
51
46
|
const steps = spec.steps.map((step, idx) => {
|
|
@@ -100,7 +95,6 @@ export function validateFlowSpec(spec) {
|
|
|
100
95
|
name: spec.name,
|
|
101
96
|
baseUrl: spec.baseUrl,
|
|
102
97
|
allowlist,
|
|
103
|
-
denyKeywords,
|
|
104
98
|
secrets: spec.secrets || {},
|
|
105
99
|
steps
|
|
106
100
|
};
|
package/src/verax/index.js
CHANGED
|
@@ -11,11 +11,15 @@ import { createRunManifest, updateRunManifestHashes } from './core/run-manifest.
|
|
|
11
11
|
import { computeArtifactHashes } from './core/run-id.js';
|
|
12
12
|
|
|
13
13
|
export async function scan(projectDir, url, manifestPath = null, scanBudgetOverride = null, safetyFlags = {}) {
|
|
14
|
+
// VISION ENFORCEMENT: Zero-configuration mode (config files ignored unless explicit --use-config flag)
|
|
15
|
+
// VERAX adapts to the project, never requires project to adapt to VERAX
|
|
16
|
+
|
|
14
17
|
// If manifestPath is provided, read it first before learn() overwrites it
|
|
15
18
|
let loadedManifest = null;
|
|
16
19
|
if (manifestPath) {
|
|
17
20
|
const { readFileSync } = await import('fs');
|
|
18
21
|
try {
|
|
22
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
19
23
|
const manifestContent = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
20
24
|
loadedManifest = manifestContent;
|
|
21
25
|
} catch (e) {
|
|
@@ -85,6 +85,7 @@ function extractFromFile(filePath, projectRoot, _program) {
|
|
|
85
85
|
// to handle template literals and complex navigation calls
|
|
86
86
|
try {
|
|
87
87
|
const content = readFileSync(filePath, 'utf-8');
|
|
88
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
88
89
|
const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
|
|
89
90
|
if (scriptMatch) {
|
|
90
91
|
const scriptContent = scriptMatch[1];
|
|
@@ -162,6 +163,7 @@ function extractFromVueSFC(filePath, projectRoot) {
|
|
|
162
163
|
const content = readFileSync(filePath, 'utf-8');
|
|
163
164
|
|
|
164
165
|
// Extract template section
|
|
166
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
165
167
|
const templateMatch = content.match(/<template[^>]*>([\s\S]*?)<\/template>/);
|
|
166
168
|
if (templateMatch) {
|
|
167
169
|
const templateContent = templateMatch[1];
|
|
@@ -263,6 +265,7 @@ function extractFromVueSFC(filePath, projectRoot) {
|
|
|
263
265
|
}
|
|
264
266
|
|
|
265
267
|
// Extract script section for router.push/replace
|
|
268
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
266
269
|
const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
|
|
267
270
|
if (scriptMatch) {
|
|
268
271
|
const scriptContent = scriptMatch[1];
|
|
@@ -490,10 +490,13 @@ export async function scanForContracts(rootPath, workspaceRoot) {
|
|
|
490
490
|
const html = readFileSync(fullPath, 'utf-8');
|
|
491
491
|
const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
|
492
492
|
let match;
|
|
493
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
493
494
|
while ((match = scriptRegex.exec(html)) !== null) {
|
|
494
495
|
const tagOpen = html.slice(match.index, html.indexOf('>', match.index) + 1);
|
|
496
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
495
497
|
if (/\ssrc=/i.test(tagOpen)) continue; // skip external scripts
|
|
496
498
|
const before = html.slice(0, match.index);
|
|
499
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
497
500
|
const lineOffset = (before.match(/\n/g) || []).length;
|
|
498
501
|
const code = match[1];
|
|
499
502
|
const blockContracts = extractActionContractsFromCode(fullPath, workspaceRoot, code, lineOffset + 1);
|
|
@@ -3,7 +3,7 @@ import traverse from '@babel/traverse';
|
|
|
3
3
|
import { readFileSync } from 'fs';
|
|
4
4
|
import { glob } from 'glob';
|
|
5
5
|
import { resolve } from 'path';
|
|
6
|
-
import { ExpectationProof } from '../shared/expectation-
|
|
6
|
+
import { ExpectationProof } from '../shared/expectation-validation.js';
|
|
7
7
|
import { normalizeTemplateLiteral } from '../shared/dynamic-route-utils.js';
|
|
8
8
|
|
|
9
9
|
const MAX_FILES_TO_SCAN = 200;
|
|
@@ -14,6 +14,7 @@ import { isProvenExpectation } from '../shared/expectation-prover.js';
|
|
|
14
14
|
function generateFlowId(steps) {
|
|
15
15
|
const hashInput = steps.map(s => `${s.expectationType}:${s.source || s.handlerRef}`).join('|');
|
|
16
16
|
const hash = createHash('sha256').update(hashInput).digest('hex');
|
|
17
|
+
// @ts-expect-error - digest returns string
|
|
17
18
|
return `flow-${hash.substring(0, 8)}`;
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -7,6 +7,7 @@ async function hasReactDependency(projectDir) {
|
|
|
7
7
|
const packageJsonPath = resolve(projectDir, 'package.json');
|
|
8
8
|
if (!existsSync(packageJsonPath)) return false;
|
|
9
9
|
|
|
10
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
10
11
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
11
12
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
12
13
|
|
|
@@ -21,6 +22,7 @@ async function hasNextJs(projectDir) {
|
|
|
21
22
|
const packageJsonPath = resolve(projectDir, 'package.json');
|
|
22
23
|
if (!existsSync(packageJsonPath)) return false;
|
|
23
24
|
|
|
25
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
24
26
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
25
27
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
26
28
|
|
|
@@ -35,6 +37,7 @@ async function hasReactRouter(projectDir) {
|
|
|
35
37
|
const packageJsonPath = resolve(projectDir, 'package.json');
|
|
36
38
|
if (!existsSync(packageJsonPath)) return false;
|
|
37
39
|
|
|
40
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
38
41
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
39
42
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
40
43
|
|
|
@@ -49,6 +52,7 @@ async function hasVue(projectDir) {
|
|
|
49
52
|
const packageJsonPath = resolve(projectDir, 'package.json');
|
|
50
53
|
if (!existsSync(packageJsonPath)) return false;
|
|
51
54
|
|
|
55
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
52
56
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
53
57
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
54
58
|
|
|
@@ -63,6 +67,7 @@ async function hasVueRouter(projectDir) {
|
|
|
63
67
|
const packageJsonPath = resolve(projectDir, 'package.json');
|
|
64
68
|
if (!existsSync(packageJsonPath)) return false;
|
|
65
69
|
|
|
70
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
66
71
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
67
72
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
68
73
|
|
|
@@ -26,11 +26,13 @@ export async function extractReactRouterRoutes(projectDir) {
|
|
|
26
26
|
|
|
27
27
|
for (const pattern of routePatterns) {
|
|
28
28
|
let match;
|
|
29
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
29
30
|
while ((match = pattern.exec(content)) !== null) {
|
|
30
31
|
let path = match[1];
|
|
31
32
|
|
|
32
33
|
if (!path) {
|
|
33
34
|
if (match[0].includes('createBrowserRouter')) {
|
|
35
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
34
36
|
const routesMatch = content.match(/createBrowserRouter\s*\(\s*\[([^\]]+)\]/s);
|
|
35
37
|
if (routesMatch) {
|
|
36
38
|
const routesContent = routesMatch[1];
|
|
@@ -152,6 +152,7 @@ export async function instrumentFile(inputPath, outputPath, workspaceRoot) {
|
|
|
152
152
|
const { mkdirSync } = await import('fs');
|
|
153
153
|
|
|
154
154
|
const code = readFileSync(inputPath, 'utf-8');
|
|
155
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
155
156
|
const instrumented = instrumentJSX(code, inputPath, workspaceRoot);
|
|
156
157
|
|
|
157
158
|
// Ensure output directory exists
|
|
@@ -3,7 +3,7 @@ import traverse from '@babel/traverse';
|
|
|
3
3
|
import { readFileSync } from 'fs';
|
|
4
4
|
import { glob } from 'glob';
|
|
5
5
|
import { resolve } from 'path';
|
|
6
|
-
import { ExpectationProof } from '../shared/expectation-
|
|
6
|
+
import { ExpectationProof } from '../shared/expectation-validation.js';
|
|
7
7
|
|
|
8
8
|
const MAX_FILES_TO_SCAN = 200;
|
|
9
9
|
|
|
@@ -141,6 +141,7 @@ function detectStateStores(projectDir) {
|
|
|
141
141
|
try {
|
|
142
142
|
const pkgPath = resolve(projectDir, 'package.json');
|
|
143
143
|
const pkgContent = readFileSync(pkgPath, 'utf-8');
|
|
144
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
144
145
|
const pkg = JSON.parse(pkgContent);
|
|
145
146
|
|
|
146
147
|
const allDeps = {
|
|
@@ -276,6 +276,7 @@ export async function extractStaticExpectations(projectDir, routes) {
|
|
|
276
276
|
|
|
277
277
|
try {
|
|
278
278
|
const content = readFileSync(filePath, 'utf-8');
|
|
279
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
279
280
|
const root = parse(content);
|
|
280
281
|
|
|
281
282
|
const links = root.querySelectorAll('a[href]');
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage Gap Accumulation & Warning Generation Module
|
|
3
|
+
* Extracted from observe/index.js (STAGE D2.2)
|
|
4
|
+
*
|
|
5
|
+
* Responsibility: Manage coverage gap collection from multiple sources
|
|
6
|
+
* and generate corresponding warning messages about incomplete coverage.
|
|
7
|
+
*
|
|
8
|
+
* This module encapsulates the logic for:
|
|
9
|
+
* 1. Accumulating coverage gaps from remaining unexecuted interactions
|
|
10
|
+
* 2. Detecting and recording frontier capping events
|
|
11
|
+
* 3. Building the coverage metrics object
|
|
12
|
+
* 4. Generating appropriate warning messages
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Accumulate coverage gaps from remaining interactions and frontier capping.
|
|
17
|
+
*
|
|
18
|
+
* When the interaction execution loop exits early (due to budget constraints),
|
|
19
|
+
* any unexecuted interactions are collected as coverage gaps with metadata
|
|
20
|
+
* about why they couldn't be executed.
|
|
21
|
+
*
|
|
22
|
+
* @param {Array} remainingInteractionsGaps - Array of unexecuted interactions with reasons
|
|
23
|
+
* @param {Object} frontier - Frontier object with tracking metadata (frontierCapped, pagesVisited, pagesDiscovered)
|
|
24
|
+
* @param {string} pageUrl - Current page URL from page.url() for gap location context
|
|
25
|
+
* @param {Object} scanBudget - Scan budget configuration (maxUniqueUrls, maxTotalInteractions)
|
|
26
|
+
* @returns {Array} Array of coverage gap objects in expectationCoverageGaps format
|
|
27
|
+
*/
|
|
28
|
+
export function accumulateCoverageGaps(remainingInteractionsGaps, frontier, pageUrl, scanBudget) {
|
|
29
|
+
const gaps = [];
|
|
30
|
+
|
|
31
|
+
// Add remaining interactions as coverage gaps
|
|
32
|
+
// These are interactions that existed but couldn't be executed due to budget constraints
|
|
33
|
+
if (remainingInteractionsGaps.length > 0) {
|
|
34
|
+
gaps.push(...remainingInteractionsGaps.map(gap => ({
|
|
35
|
+
expectationId: null,
|
|
36
|
+
type: gap.interaction.type,
|
|
37
|
+
reason: gap.reason,
|
|
38
|
+
fromPath: gap.url,
|
|
39
|
+
source: null,
|
|
40
|
+
evidence: {
|
|
41
|
+
interaction: gap.interaction
|
|
42
|
+
}
|
|
43
|
+
})));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Record frontier capping as coverage gap if it occurred
|
|
47
|
+
// This indicates that the scan encountered the URL discovery limit
|
|
48
|
+
if (frontier.frontierCapped) {
|
|
49
|
+
gaps.push({
|
|
50
|
+
expectationId: null,
|
|
51
|
+
type: 'navigation',
|
|
52
|
+
reason: 'frontier_capped',
|
|
53
|
+
fromPath: pageUrl,
|
|
54
|
+
source: null,
|
|
55
|
+
evidence: {
|
|
56
|
+
message: `Frontier capped at ${scanBudget.maxUniqueUrls || 'unlimited'} unique URLs`
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return gaps;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build the coverage metrics object.
|
|
66
|
+
*
|
|
67
|
+
* The coverage object captures quantitative metrics about what was discovered
|
|
68
|
+
* versus what was actually executed, enabling downstream analysis of coverage
|
|
69
|
+
* completeness and bottlenecks.
|
|
70
|
+
*
|
|
71
|
+
* @param {number} totalInteractionsDiscovered - Total interactions found across all pages
|
|
72
|
+
* @param {number} totalInteractionsExecuted - Interactions actually executed
|
|
73
|
+
* @param {Object} scanBudget - Scan budget configuration
|
|
74
|
+
* @param {Object} frontier - Frontier object with pagesVisited and pagesDiscovered
|
|
75
|
+
* @param {Array} skippedInteractions - Array of interactions that were skipped for safety
|
|
76
|
+
* @param {Array} remainingInteractionsGaps - Array of unexecuted interactions
|
|
77
|
+
* @returns {Object} Coverage metrics object
|
|
78
|
+
*/
|
|
79
|
+
export function buildCoverageObject(
|
|
80
|
+
totalInteractionsDiscovered,
|
|
81
|
+
totalInteractionsExecuted,
|
|
82
|
+
scanBudget,
|
|
83
|
+
frontier,
|
|
84
|
+
skippedInteractions,
|
|
85
|
+
remainingInteractionsGaps
|
|
86
|
+
) {
|
|
87
|
+
return {
|
|
88
|
+
candidatesDiscovered: totalInteractionsDiscovered,
|
|
89
|
+
candidatesSelected: totalInteractionsExecuted,
|
|
90
|
+
cap: scanBudget.maxTotalInteractions,
|
|
91
|
+
capped: totalInteractionsExecuted >= scanBudget.maxTotalInteractions || remainingInteractionsGaps.length > 0,
|
|
92
|
+
pagesVisited: frontier.pagesVisited,
|
|
93
|
+
pagesDiscovered: frontier.pagesDiscovered,
|
|
94
|
+
skippedInteractions: skippedInteractions.length,
|
|
95
|
+
interactionsDiscovered: totalInteractionsDiscovered,
|
|
96
|
+
interactionsExecuted: totalInteractionsExecuted
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Generate warning messages based on coverage metrics.
|
|
102
|
+
*
|
|
103
|
+
* Warnings communicate to the user what limitations were encountered during
|
|
104
|
+
* observation, allowing them to understand coverage completeness and any
|
|
105
|
+
* safety-related interaction filtering.
|
|
106
|
+
*
|
|
107
|
+
* @param {Object} coverage - Coverage metrics object from buildCoverageObject()
|
|
108
|
+
* @param {Array} skippedInteractions - Array of interactions that were skipped for safety
|
|
109
|
+
* @returns {Array} Array of warning objects with code and message properties
|
|
110
|
+
*/
|
|
111
|
+
export function generateCoverageWarnings(coverage, skippedInteractions) {
|
|
112
|
+
const warnings = [];
|
|
113
|
+
|
|
114
|
+
// Warn if coverage was incomplete due to capping
|
|
115
|
+
if (coverage.capped) {
|
|
116
|
+
warnings.push({
|
|
117
|
+
code: 'INTERACTIONS_CAPPED',
|
|
118
|
+
message: `Interaction execution capped. Visited ${coverage.pagesVisited} pages, discovered ${coverage.pagesDiscovered}, executed ${coverage.candidatesSelected} of ${coverage.candidatesDiscovered} interactions. Coverage incomplete.`
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Warn if interactions were skipped for safety
|
|
123
|
+
if (skippedInteractions.length > 0) {
|
|
124
|
+
warnings.push({
|
|
125
|
+
code: 'INTERACTIONS_SKIPPED',
|
|
126
|
+
message: `Skipped ${skippedInteractions.length} dangerous interactions`,
|
|
127
|
+
details: skippedInteractions
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return warnings;
|
|
132
|
+
}
|