@veraxhq/verax 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -18
- package/bin/verax.js +7 -0
- package/package.json +3 -3
- package/src/cli/commands/baseline.js +104 -0
- package/src/cli/commands/default.js +79 -25
- package/src/cli/commands/ga.js +243 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +131 -2
- package/src/cli/commands/release-check.js +213 -0
- package/src/cli/commands/run.js +246 -35
- package/src/cli/commands/security-check.js +211 -0
- package/src/cli/commands/truth.js +114 -0
- package/src/cli/entry.js +304 -67
- package/src/cli/util/angular-component-extractor.js +179 -0
- package/src/cli/util/angular-navigation-detector.js +141 -0
- package/src/cli/util/angular-network-detector.js +161 -0
- package/src/cli/util/angular-state-detector.js +162 -0
- package/src/cli/util/ast-interactive-detector.js +546 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/determinism-runner.js +123 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/env-url.js +4 -0
- package/src/cli/util/expectation-extractor.js +369 -73
- package/src/cli/util/findings-writer.js +126 -16
- package/src/cli/util/learn-writer.js +3 -1
- package/src/cli/util/observe-writer.js +3 -1
- package/src/cli/util/paths.js +3 -12
- package/src/cli/util/project-discovery.js +3 -0
- package/src/cli/util/project-writer.js +3 -1
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +1 -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 +147 -0
- package/src/cli/util/svelte-state-detector.js +243 -0
- package/src/cli/util/vue-navigation-detector.js +177 -0
- package/src/cli/util/vue-sfc-extractor.js +162 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/verax/cli/finding-explainer.js +56 -3
- package/src/verax/core/artifacts/registry.js +154 -0
- package/src/verax/core/artifacts/verifier.js +980 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +231 -0
- package/src/verax/core/capabilities/gates.js +499 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +137 -0
- package/src/verax/core/confidence/confidence-invariants.js +234 -0
- package/src/verax/core/confidence/confidence-report-writer.js +112 -0
- package/src/verax/core/confidence/confidence-weights.js +44 -0
- package/src/verax/core/confidence/confidence.defaults.js +65 -0
- package/src/verax/core/confidence/confidence.loader.js +79 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +484 -0
- package/src/verax/core/confidence-engine.js +486 -0
- package/src/verax/core/confidence-engine.js.backup +471 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +185 -0
- package/src/verax/core/contracts/validators.js +381 -0
- package/src/verax/core/decision-snapshot.js +30 -3
- package/src/verax/core/decisions/decision.trace.js +276 -0
- package/src/verax/core/determinism/contract-writer.js +89 -0
- package/src/verax/core/determinism/contract.js +139 -0
- package/src/verax/core/determinism/diff.js +364 -0
- package/src/verax/core/determinism/engine.js +221 -0
- package/src/verax/core/determinism/finding-identity.js +148 -0
- package/src/verax/core/determinism/normalize.js +438 -0
- package/src/verax/core/determinism/report-writer.js +92 -0
- package/src/verax/core/determinism/run-fingerprint.js +118 -0
- package/src/verax/core/dynamic-route-intelligence.js +528 -0
- package/src/verax/core/evidence/evidence-capture-service.js +307 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
- package/src/verax/core/evidence-builder.js +487 -0
- package/src/verax/core/execution-mode-context.js +77 -0
- package/src/verax/core/execution-mode-detector.js +190 -0
- package/src/verax/core/failures/exit-codes.js +86 -0
- package/src/verax/core/failures/failure-summary.js +76 -0
- package/src/verax/core/failures/failure.factory.js +225 -0
- package/src/verax/core/failures/failure.ledger.js +132 -0
- package/src/verax/core/failures/failure.types.js +196 -0
- package/src/verax/core/failures/index.js +10 -0
- package/src/verax/core/ga/ga-report-writer.js +43 -0
- package/src/verax/core/ga/ga.artifact.js +49 -0
- package/src/verax/core/ga/ga.contract.js +434 -0
- package/src/verax/core/ga/ga.enforcer.js +86 -0
- package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
- package/src/verax/core/guardrails/policy.defaults.js +210 -0
- package/src/verax/core/guardrails/policy.loader.js +83 -0
- package/src/verax/core/guardrails/policy.schema.js +110 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
- package/src/verax/core/guardrails-engine.js +505 -0
- package/src/verax/core/observe/run-timeline.js +316 -0
- package/src/verax/core/perf/perf.contract.js +186 -0
- package/src/verax/core/perf/perf.display.js +65 -0
- package/src/verax/core/perf/perf.enforcer.js +91 -0
- package/src/verax/core/perf/perf.monitor.js +209 -0
- package/src/verax/core/perf/perf.report.js +198 -0
- package/src/verax/core/pipeline-tracker.js +238 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +271 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +159 -0
- package/src/verax/core/release/reproducibility.check.js +221 -0
- package/src/verax/core/release/sbom.builder.js +283 -0
- package/src/verax/core/report/cross-index.js +192 -0
- package/src/verax/core/report/human-summary.js +222 -0
- package/src/verax/core/route-intelligence.js +419 -0
- package/src/verax/core/security/secrets.scan.js +326 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +124 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +326 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/truth/truth.certificate.js +250 -0
- package/src/verax/core/ui-feedback-intelligence.js +515 -0
- package/src/verax/detect/confidence-engine.js +628 -40
- package/src/verax/detect/confidence-helper.js +33 -0
- package/src/verax/detect/detection-engine.js +18 -1
- package/src/verax/detect/dynamic-route-findings.js +335 -0
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +3 -1
- package/src/verax/detect/findings-writer.js +141 -5
- package/src/verax/detect/index.js +229 -5
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/route-findings.js +218 -0
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/verdict-engine.js +57 -3
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/index.js +413 -45
- package/src/verax/learn/action-contract-extractor.js +682 -64
- package/src/verax/learn/route-validator.js +4 -1
- package/src/verax/observe/index.js +88 -843
- package/src/verax/observe/interaction-runner.js +25 -8
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +191 -0
- package/src/verax/observe/observe-runner.js +226 -0
- package/src/verax/observe/observers/budget-observer.js +185 -0
- package/src/verax/observe/observers/console-observer.js +102 -0
- package/src/verax/observe/observers/coverage-observer.js +107 -0
- package/src/verax/observe/observers/interaction-observer.js +471 -0
- package/src/verax/observe/observers/navigation-observer.js +132 -0
- package/src/verax/observe/observers/network-observer.js +87 -0
- package/src/verax/observe/observers/safety-observer.js +82 -0
- package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/observe/ui-signal-sensor.js +148 -2
- package/src/verax/scan-summary-writer.js +42 -8
- package/src/verax/shared/artifact-manager.js +8 -5
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/view-switch-rules.js +208 -0
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 14 — Dynamic Routes: Truth, Intent & Safe Support
|
|
3
|
+
*
|
|
4
|
+
* Dynamic route intelligence layer that:
|
|
5
|
+
* - Classifies dynamic routes by verifiability
|
|
6
|
+
* - Correlates navigation promises with route definitions and UI outcomes
|
|
7
|
+
* - Produces evidence-backed findings or explicit ambiguity
|
|
8
|
+
* - Prevents false positives and false promises
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { isDynamicPath, normalizeDynamicRoute, normalizeNavigationTarget } from '../shared/dynamic-route-utils.js';
|
|
12
|
+
import { correlateNavigationWithRoute, evaluateRouteNavigation } from './route-intelligence.js';
|
|
13
|
+
import { scoreUIFeedback, detectUIFeedbackSignals } from './ui-feedback-intelligence.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* PHASE 14: Dynamic Route Verifiability Classification
|
|
17
|
+
*/
|
|
18
|
+
export const DYNAMIC_ROUTE_VERIFIABILITY = {
|
|
19
|
+
STATIC: 'STATIC',
|
|
20
|
+
VERIFIED_DYNAMIC: 'VERIFIED_DYNAMIC',
|
|
21
|
+
AMBIGUOUS_DYNAMIC: 'AMBIGUOUS_DYNAMIC',
|
|
22
|
+
UNVERIFIABLE_DYNAMIC: 'UNVERIFIABLE_DYNAMIC',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* PHASE 14: Route Verdict
|
|
27
|
+
*/
|
|
28
|
+
export const ROUTE_VERDICT = {
|
|
29
|
+
VERIFIED: 'VERIFIED',
|
|
30
|
+
SILENT_FAILURE: 'SILENT_FAILURE',
|
|
31
|
+
ROUTE_MISMATCH: 'ROUTE_MISMATCH',
|
|
32
|
+
AMBIGUOUS: 'AMBIGUOUS',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* PHASE 14: Classify dynamic route by verifiability
|
|
37
|
+
*
|
|
38
|
+
* @param {Object} routeModel - Route model from route intelligence
|
|
39
|
+
* @param {Object} trace - Interaction trace (optional, for runtime analysis)
|
|
40
|
+
* @returns {Object} Classification result
|
|
41
|
+
*/
|
|
42
|
+
export function classifyDynamicRoute(routeModel, trace = null) {
|
|
43
|
+
const path = routeModel.path || '';
|
|
44
|
+
const originalPattern = routeModel.originalPattern || path;
|
|
45
|
+
|
|
46
|
+
// STATIC: No dynamic parameters
|
|
47
|
+
if (!isDynamicPath(path) && !isDynamicPath(originalPattern)) {
|
|
48
|
+
return {
|
|
49
|
+
verifiability: DYNAMIC_ROUTE_VERIFIABILITY.STATIC,
|
|
50
|
+
reason: 'Route contains no dynamic parameters',
|
|
51
|
+
confidence: 1.0,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check if route can be normalized
|
|
56
|
+
const normalized = normalizeDynamicRoute(originalPattern);
|
|
57
|
+
if (!normalized || !normalized.examplePath) {
|
|
58
|
+
return {
|
|
59
|
+
verifiability: DYNAMIC_ROUTE_VERIFIABILITY.UNVERIFIABLE_DYNAMIC,
|
|
60
|
+
reason: 'Dynamic route pattern cannot be normalized to example path',
|
|
61
|
+
confidence: 0.9,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check route characteristics that affect verifiability
|
|
66
|
+
const isAuthGated = isAuthGatedRoute(routeModel, trace);
|
|
67
|
+
const isSSROnly = isSSROnlyRoute(routeModel, trace);
|
|
68
|
+
const isRuntimeOnly = isRuntimeOnlyRoute(routeModel, trace);
|
|
69
|
+
const hasObservableSignals = hasObservableSignals(routeModel, trace);
|
|
70
|
+
|
|
71
|
+
// UNVERIFIABLE: Auth-gated, SSR-only, or runtime-only without observable signals
|
|
72
|
+
if (isAuthGated || isSSROnly || (isRuntimeOnly && !hasObservableSignals)) {
|
|
73
|
+
return {
|
|
74
|
+
verifiability: DYNAMIC_ROUTE_VERIFIABILITY.UNVERIFIABLE_DYNAMIC,
|
|
75
|
+
reason: buildUnverifiableReason(isAuthGated, isSSROnly, isRuntimeOnly, hasObservableSignals),
|
|
76
|
+
confidence: 0.9,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check if we can verify the outcome
|
|
81
|
+
if (trace) {
|
|
82
|
+
const canVerify = canVerifyRouteOutcome(routeModel, trace);
|
|
83
|
+
|
|
84
|
+
if (canVerify.verifiable) {
|
|
85
|
+
return {
|
|
86
|
+
verifiability: DYNAMIC_ROUTE_VERIFIABILITY.VERIFIED_DYNAMIC,
|
|
87
|
+
reason: canVerify.reason,
|
|
88
|
+
confidence: canVerify.confidence,
|
|
89
|
+
};
|
|
90
|
+
} else {
|
|
91
|
+
return {
|
|
92
|
+
verifiability: DYNAMIC_ROUTE_VERIFIABILITY.AMBIGUOUS_DYNAMIC,
|
|
93
|
+
reason: canVerify.reason || 'Route pattern known but outcome unclear',
|
|
94
|
+
confidence: 0.6,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Default: AMBIGUOUS if pattern is known but we can't verify yet
|
|
100
|
+
return {
|
|
101
|
+
verifiability: DYNAMIC_ROUTE_VERIFIABILITY.AMBIGUOUS_DYNAMIC,
|
|
102
|
+
reason: 'Route pattern known but outcome cannot be verified without trace data',
|
|
103
|
+
confidence: 0.6,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Check if route is auth-gated
|
|
109
|
+
*/
|
|
110
|
+
function isAuthGatedRoute(routeModel, trace) {
|
|
111
|
+
// Check route path patterns
|
|
112
|
+
const path = routeModel.path || '';
|
|
113
|
+
const authPatterns = ['/admin', '/dashboard', '/account', '/settings', '/profile', '/user'];
|
|
114
|
+
|
|
115
|
+
if (authPatterns.some(pattern => path.includes(pattern))) {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Check trace for auth-related signals
|
|
120
|
+
if (trace) {
|
|
121
|
+
const sensors = trace.sensors || {};
|
|
122
|
+
const navSensor = sensors.navigation || {};
|
|
123
|
+
|
|
124
|
+
// If navigation was blocked or redirected to login
|
|
125
|
+
if (navSensor.blockedNavigations?.length > 0) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Check if route is SSR-only
|
|
135
|
+
*/
|
|
136
|
+
function isSSROnlyRoute(routeModel, trace) {
|
|
137
|
+
// Next.js app router with dynamic segments might be SSR-only
|
|
138
|
+
if (routeModel.framework === 'next-app' && routeModel.isDynamic) {
|
|
139
|
+
// Check if route has getServerSideProps or similar indicators
|
|
140
|
+
// For now, we'll be conservative and not mark as SSR-only without evidence
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Check if route is runtime-only (no static analysis possible)
|
|
149
|
+
*/
|
|
150
|
+
function isRuntimeOnlyRoute(routeModel, trace) {
|
|
151
|
+
// Routes with complex template literals or runtime variables
|
|
152
|
+
const originalPattern = routeModel.originalPattern || '';
|
|
153
|
+
|
|
154
|
+
// Pure variable references like ${path} or ${id} without pattern
|
|
155
|
+
if (originalPattern.includes('${') && !originalPattern.match(/\/[^$]+\$\{[^}]+\}/)) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Check if route has observable signals
|
|
164
|
+
*/
|
|
165
|
+
function hasObservableSignals(routeModel, trace) {
|
|
166
|
+
if (!trace) return false;
|
|
167
|
+
|
|
168
|
+
const sensors = trace.sensors || {};
|
|
169
|
+
const navSensor = sensors.navigation || {};
|
|
170
|
+
const uiSignals = sensors.uiSignals || {};
|
|
171
|
+
const uiFeedback = sensors.uiFeedback || {};
|
|
172
|
+
|
|
173
|
+
// Check for URL change
|
|
174
|
+
if (navSensor.urlChanged === true) {
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Check for UI feedback
|
|
179
|
+
if (uiSignals.diff?.changed === true || uiFeedback.overallUiFeedbackScore > 0.3) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Check for DOM change
|
|
184
|
+
if (trace.dom?.beforeHash !== trace.dom?.afterHash) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Check if route outcome can be verified
|
|
193
|
+
*/
|
|
194
|
+
function canVerifyRouteOutcome(routeModel, trace) {
|
|
195
|
+
const sensors = trace.sensors || {};
|
|
196
|
+
const navSensor = sensors.navigation || {};
|
|
197
|
+
const beforeUrl = trace.before?.url || navSensor.beforeUrl || '';
|
|
198
|
+
const afterUrl = trace.after?.url || navSensor.afterUrl || '';
|
|
199
|
+
|
|
200
|
+
// Check URL change
|
|
201
|
+
const urlChanged = navSensor.urlChanged === true || (beforeUrl && afterUrl && beforeUrl !== afterUrl);
|
|
202
|
+
|
|
203
|
+
// Check if URL matches route pattern
|
|
204
|
+
const afterPath = extractPathFromUrl(afterUrl);
|
|
205
|
+
const routeMatched = matchDynamicPattern(afterPath, routeModel.originalPattern || routeModel.path);
|
|
206
|
+
|
|
207
|
+
// Check UI feedback
|
|
208
|
+
const uiSignals = detectUIFeedbackSignals(trace);
|
|
209
|
+
const hasUIFeedback = uiSignals.length > 0;
|
|
210
|
+
|
|
211
|
+
// Check DOM change
|
|
212
|
+
const domChanged = trace.dom?.beforeHash !== trace.dom?.afterHash;
|
|
213
|
+
|
|
214
|
+
if (urlChanged && routeMatched && (hasUIFeedback || domChanged)) {
|
|
215
|
+
return {
|
|
216
|
+
verifiable: true,
|
|
217
|
+
reason: 'URL changed, route pattern matched, and UI feedback or DOM change observed',
|
|
218
|
+
confidence: 0.9,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (urlChanged && routeMatched) {
|
|
223
|
+
return {
|
|
224
|
+
verifiable: true,
|
|
225
|
+
reason: 'URL changed and route pattern matched',
|
|
226
|
+
confidence: 0.8,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (urlChanged && !routeMatched) {
|
|
231
|
+
return {
|
|
232
|
+
verifiable: false,
|
|
233
|
+
reason: 'URL changed but does not match route pattern',
|
|
234
|
+
confidence: 0.7,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
verifiable: false,
|
|
240
|
+
reason: 'No URL change or observable signals',
|
|
241
|
+
confidence: 0.5,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Build reason for unverifiable route
|
|
247
|
+
*/
|
|
248
|
+
function buildUnverifiableReason(isAuthGated, isSSROnly, isRuntimeOnly, hasObservableSignals) {
|
|
249
|
+
const reasons = [];
|
|
250
|
+
|
|
251
|
+
if (isAuthGated) {
|
|
252
|
+
reasons.push('auth-gated');
|
|
253
|
+
}
|
|
254
|
+
if (isSSROnly) {
|
|
255
|
+
reasons.push('SSR-only');
|
|
256
|
+
}
|
|
257
|
+
if (isRuntimeOnly) {
|
|
258
|
+
reasons.push('runtime-only');
|
|
259
|
+
}
|
|
260
|
+
if (!hasObservableSignals) {
|
|
261
|
+
reasons.push('no observable signals');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return `Route is ${reasons.join(', ')}`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Match dynamic pattern against actual path
|
|
269
|
+
*/
|
|
270
|
+
function matchDynamicPattern(actualPath, pattern) {
|
|
271
|
+
if (!actualPath || !pattern) return false;
|
|
272
|
+
|
|
273
|
+
// Convert pattern to regex
|
|
274
|
+
let regexPattern = pattern;
|
|
275
|
+
|
|
276
|
+
// Replace :param with (\w+)
|
|
277
|
+
regexPattern = regexPattern.replace(/:(\w+)/g, '(\\w+)');
|
|
278
|
+
|
|
279
|
+
// Replace [param] with (\w+)
|
|
280
|
+
regexPattern = regexPattern.replace(/\[(\w+)\]/g, '(\\w+)');
|
|
281
|
+
|
|
282
|
+
// Escape other special characters
|
|
283
|
+
regexPattern = regexPattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
284
|
+
|
|
285
|
+
// Restore the capture groups
|
|
286
|
+
regexPattern = regexPattern.replace(/\\\(\\\\w\+\\\)/g, '(\\w+)');
|
|
287
|
+
|
|
288
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
289
|
+
return regex.test(actualPath);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Extract path from URL
|
|
294
|
+
*/
|
|
295
|
+
function extractPathFromUrl(url) {
|
|
296
|
+
if (!url || typeof url !== 'string') return '';
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
const urlObj = new URL(url);
|
|
300
|
+
return urlObj.pathname;
|
|
301
|
+
} catch {
|
|
302
|
+
// Relative URL
|
|
303
|
+
const pathMatch = url.match(/^([^?#]+)/);
|
|
304
|
+
return pathMatch ? pathMatch[1] : url;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* PHASE 14: Correlate navigation promise with dynamic route and UI feedback
|
|
310
|
+
*
|
|
311
|
+
* @param {Object} expectation - Navigation expectation
|
|
312
|
+
* @param {Object} routeModel - Route model
|
|
313
|
+
* @param {Object} trace - Interaction trace
|
|
314
|
+
* @returns {Object} Correlation result with verdict
|
|
315
|
+
*/
|
|
316
|
+
export function correlateDynamicRouteNavigation(expectation, routeModel, trace) {
|
|
317
|
+
const classification = classifyDynamicRoute(routeModel, trace);
|
|
318
|
+
|
|
319
|
+
// If route is UNVERIFIABLE, return skip
|
|
320
|
+
if (classification.verifiability === DYNAMIC_ROUTE_VERIFIABILITY.UNVERIFIABLE_DYNAMIC) {
|
|
321
|
+
return {
|
|
322
|
+
verdict: null,
|
|
323
|
+
skip: true,
|
|
324
|
+
skipReason: classification.reason,
|
|
325
|
+
confidence: classification.confidence,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Normalize navigation target
|
|
330
|
+
const navigationTarget = expectation.targetPath || expectation.expectedTarget || '';
|
|
331
|
+
const normalized = normalizeNavigationTarget(navigationTarget);
|
|
332
|
+
const targetToMatch = normalized.exampleTarget || navigationTarget;
|
|
333
|
+
|
|
334
|
+
// Match route pattern
|
|
335
|
+
const routeMatched = matchDynamicPattern(targetToMatch, routeModel.originalPattern || routeModel.path);
|
|
336
|
+
|
|
337
|
+
// Get route evaluation from route intelligence
|
|
338
|
+
const correlation = correlateNavigationWithRoute(navigationTarget, [routeModel]);
|
|
339
|
+
const routeEvaluation = correlation ? evaluateRouteNavigation(correlation, trace, trace.before?.url || '', trace.after?.url || '') : null;
|
|
340
|
+
|
|
341
|
+
// Get UI feedback score
|
|
342
|
+
const uiSignals = detectUIFeedbackSignals(trace);
|
|
343
|
+
const feedbackScore = scoreUIFeedback(uiSignals, expectation, trace);
|
|
344
|
+
|
|
345
|
+
// Determine verdict
|
|
346
|
+
const sensors = trace.sensors || {};
|
|
347
|
+
const navSensor = sensors.navigation || {};
|
|
348
|
+
const urlChanged = navSensor.urlChanged === true;
|
|
349
|
+
const afterPath = extractPathFromUrl(trace.after?.url || navSensor.afterUrl || '');
|
|
350
|
+
|
|
351
|
+
// VERIFIED: URL changed, route matched, and UI feedback present
|
|
352
|
+
if (urlChanged && routeMatched && feedbackScore.score === 'FEEDBACK_CONFIRMED') {
|
|
353
|
+
return {
|
|
354
|
+
verdict: ROUTE_VERDICT.VERIFIED,
|
|
355
|
+
skip: false,
|
|
356
|
+
confidence: 0.9,
|
|
357
|
+
reason: 'Navigation successful: URL changed, route matched, and UI feedback confirmed',
|
|
358
|
+
evidence: {
|
|
359
|
+
urlChanged: true,
|
|
360
|
+
routeMatched: true,
|
|
361
|
+
uiFeedback: feedbackScore.score,
|
|
362
|
+
afterPath,
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// VERIFIED: URL changed and route matched (even without explicit UI feedback)
|
|
368
|
+
if (urlChanged && routeMatched) {
|
|
369
|
+
return {
|
|
370
|
+
verdict: ROUTE_VERDICT.VERIFIED,
|
|
371
|
+
skip: false,
|
|
372
|
+
confidence: 0.85,
|
|
373
|
+
reason: 'Navigation successful: URL changed and route matched',
|
|
374
|
+
evidence: {
|
|
375
|
+
urlChanged: true,
|
|
376
|
+
routeMatched: true,
|
|
377
|
+
afterPath,
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ROUTE_MISMATCH: URL changed but doesn't match route
|
|
383
|
+
if (urlChanged && !routeMatched) {
|
|
384
|
+
return {
|
|
385
|
+
verdict: ROUTE_VERDICT.ROUTE_MISMATCH,
|
|
386
|
+
skip: false,
|
|
387
|
+
confidence: 0.8,
|
|
388
|
+
reason: `Navigation occurred but target route does not match. Expected pattern: ${routeModel.originalPattern}, Actual: ${afterPath}`,
|
|
389
|
+
evidence: {
|
|
390
|
+
urlChanged: true,
|
|
391
|
+
routeMatched: false,
|
|
392
|
+
expectedPattern: routeModel.originalPattern,
|
|
393
|
+
actualPath: afterPath,
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// SILENT_FAILURE: No URL change, no route match, no UI feedback
|
|
399
|
+
if (!urlChanged && !routeMatched && feedbackScore.score === 'FEEDBACK_MISSING') {
|
|
400
|
+
return {
|
|
401
|
+
verdict: ROUTE_VERDICT.SILENT_FAILURE,
|
|
402
|
+
skip: false,
|
|
403
|
+
confidence: 0.85,
|
|
404
|
+
reason: 'Navigation promise not fulfilled: no URL change, route mismatch, and no UI feedback',
|
|
405
|
+
evidence: {
|
|
406
|
+
urlChanged: false,
|
|
407
|
+
routeMatched: false,
|
|
408
|
+
uiFeedback: feedbackScore.score,
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// AMBIGUOUS: Unclear outcome
|
|
414
|
+
if (classification.verifiability === DYNAMIC_ROUTE_VERIFIABILITY.AMBIGUOUS_DYNAMIC) {
|
|
415
|
+
return {
|
|
416
|
+
verdict: ROUTE_VERDICT.AMBIGUOUS,
|
|
417
|
+
skip: false,
|
|
418
|
+
confidence: 0.6,
|
|
419
|
+
reason: classification.reason || 'Dynamic route outcome is ambiguous',
|
|
420
|
+
evidence: {
|
|
421
|
+
classification: classification.verifiability,
|
|
422
|
+
urlChanged,
|
|
423
|
+
routeMatched,
|
|
424
|
+
uiFeedback: feedbackScore.score,
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Default: AMBIGUOUS
|
|
430
|
+
return {
|
|
431
|
+
verdict: ROUTE_VERDICT.AMBIGUOUS,
|
|
432
|
+
skip: false,
|
|
433
|
+
confidence: 0.5,
|
|
434
|
+
reason: 'Route navigation outcome unclear',
|
|
435
|
+
evidence: {
|
|
436
|
+
urlChanged,
|
|
437
|
+
routeMatched,
|
|
438
|
+
uiFeedback: feedbackScore.score,
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* PHASE 14: Build evidence for dynamic route finding
|
|
445
|
+
*
|
|
446
|
+
* @param {Object} expectation - Navigation expectation
|
|
447
|
+
* @param {Object} routeModel - Route model
|
|
448
|
+
* @param {Object} correlation - Correlation result
|
|
449
|
+
* @param {Object} trace - Interaction trace
|
|
450
|
+
* @returns {Object} Evidence object
|
|
451
|
+
*/
|
|
452
|
+
export function buildDynamicRouteEvidence(expectation, routeModel, correlation, trace) {
|
|
453
|
+
const classification = classifyDynamicRoute(routeModel, trace);
|
|
454
|
+
const uiSignals = detectUIFeedbackSignals(trace);
|
|
455
|
+
const feedbackScore = scoreUIFeedback(uiSignals, expectation, trace);
|
|
456
|
+
|
|
457
|
+
const evidence = {
|
|
458
|
+
routeDefinition: {
|
|
459
|
+
path: routeModel.path,
|
|
460
|
+
originalPattern: routeModel.originalPattern || routeModel.path,
|
|
461
|
+
type: routeModel.type,
|
|
462
|
+
stability: routeModel.stability,
|
|
463
|
+
source: routeModel.source,
|
|
464
|
+
sourceRef: routeModel.sourceRef,
|
|
465
|
+
verifiability: classification.verifiability,
|
|
466
|
+
verifiabilityReason: classification.reason,
|
|
467
|
+
},
|
|
468
|
+
navigationTrigger: {
|
|
469
|
+
target: expectation.targetPath || expectation.expectedTarget || null,
|
|
470
|
+
method: expectation.promise?.method || null,
|
|
471
|
+
astSource: expectation.source?.astSource || null,
|
|
472
|
+
context: expectation.source?.context || null,
|
|
473
|
+
},
|
|
474
|
+
beforeAfter: {
|
|
475
|
+
beforeUrl: trace.before?.url || trace.sensors?.navigation?.beforeUrl || null,
|
|
476
|
+
afterUrl: trace.after?.url || trace.sensors?.navigation?.afterUrl || null,
|
|
477
|
+
beforeScreenshot: trace.before?.screenshot || null,
|
|
478
|
+
afterScreenshot: trace.after?.screenshot || null,
|
|
479
|
+
beforeDomHash: trace.dom?.beforeHash || null,
|
|
480
|
+
afterDomHash: trace.dom?.afterHash || null,
|
|
481
|
+
},
|
|
482
|
+
signals: {
|
|
483
|
+
urlChanged: correlation.evidence?.urlChanged || false,
|
|
484
|
+
routeMatched: correlation.evidence?.routeMatched || false,
|
|
485
|
+
uiFeedback: feedbackScore.score,
|
|
486
|
+
uiFeedbackSignals: uiSignals.map(s => ({
|
|
487
|
+
type: s.type,
|
|
488
|
+
confidence: s.confidence,
|
|
489
|
+
})),
|
|
490
|
+
domChanged: trace.dom?.beforeHash !== trace.dom?.afterHash,
|
|
491
|
+
},
|
|
492
|
+
correlation: {
|
|
493
|
+
verdict: correlation.verdict,
|
|
494
|
+
confidence: correlation.confidence,
|
|
495
|
+
reason: correlation.reason,
|
|
496
|
+
skip: correlation.skip || false,
|
|
497
|
+
skipReason: correlation.skipReason || null,
|
|
498
|
+
},
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
return evidence;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* PHASE 14: Check if route should be skipped (unverifiable)
|
|
506
|
+
*
|
|
507
|
+
* @param {Object} routeModel - Route model
|
|
508
|
+
* @param {Object} trace - Interaction trace
|
|
509
|
+
* @returns {Object} Skip decision
|
|
510
|
+
*/
|
|
511
|
+
export function shouldSkipDynamicRoute(routeModel, trace) {
|
|
512
|
+
const classification = classifyDynamicRoute(routeModel, trace);
|
|
513
|
+
|
|
514
|
+
if (classification.verifiability === DYNAMIC_ROUTE_VERIFIABILITY.UNVERIFIABLE_DYNAMIC) {
|
|
515
|
+
return {
|
|
516
|
+
skip: true,
|
|
517
|
+
reason: classification.reason,
|
|
518
|
+
confidence: classification.confidence,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return {
|
|
523
|
+
skip: false,
|
|
524
|
+
reason: null,
|
|
525
|
+
confidence: 0,
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|