@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.
Files changed (152) hide show
  1. package/README.md +14 -18
  2. package/bin/verax.js +7 -0
  3. package/package.json +3 -3
  4. package/src/cli/commands/baseline.js +104 -0
  5. package/src/cli/commands/default.js +79 -25
  6. package/src/cli/commands/ga.js +243 -0
  7. package/src/cli/commands/gates.js +95 -0
  8. package/src/cli/commands/inspect.js +131 -2
  9. package/src/cli/commands/release-check.js +213 -0
  10. package/src/cli/commands/run.js +246 -35
  11. package/src/cli/commands/security-check.js +211 -0
  12. package/src/cli/commands/truth.js +114 -0
  13. package/src/cli/entry.js +304 -67
  14. package/src/cli/util/angular-component-extractor.js +179 -0
  15. package/src/cli/util/angular-navigation-detector.js +141 -0
  16. package/src/cli/util/angular-network-detector.js +161 -0
  17. package/src/cli/util/angular-state-detector.js +162 -0
  18. package/src/cli/util/ast-interactive-detector.js +546 -0
  19. package/src/cli/util/ast-network-detector.js +603 -0
  20. package/src/cli/util/ast-usestate-detector.js +602 -0
  21. package/src/cli/util/bootstrap-guard.js +86 -0
  22. package/src/cli/util/determinism-runner.js +123 -0
  23. package/src/cli/util/determinism-writer.js +129 -0
  24. package/src/cli/util/env-url.js +4 -0
  25. package/src/cli/util/expectation-extractor.js +369 -73
  26. package/src/cli/util/findings-writer.js +126 -16
  27. package/src/cli/util/learn-writer.js +3 -1
  28. package/src/cli/util/observe-writer.js +3 -1
  29. package/src/cli/util/paths.js +3 -12
  30. package/src/cli/util/project-discovery.js +3 -0
  31. package/src/cli/util/project-writer.js +3 -1
  32. package/src/cli/util/run-resolver.js +64 -0
  33. package/src/cli/util/source-requirement.js +55 -0
  34. package/src/cli/util/summary-writer.js +1 -0
  35. package/src/cli/util/svelte-navigation-detector.js +163 -0
  36. package/src/cli/util/svelte-network-detector.js +80 -0
  37. package/src/cli/util/svelte-sfc-extractor.js +147 -0
  38. package/src/cli/util/svelte-state-detector.js +243 -0
  39. package/src/cli/util/vue-navigation-detector.js +177 -0
  40. package/src/cli/util/vue-sfc-extractor.js +162 -0
  41. package/src/cli/util/vue-state-detector.js +215 -0
  42. package/src/verax/cli/finding-explainer.js +56 -3
  43. package/src/verax/core/artifacts/registry.js +154 -0
  44. package/src/verax/core/artifacts/verifier.js +980 -0
  45. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  46. package/src/verax/core/baseline/baseline.snapshot.js +231 -0
  47. package/src/verax/core/capabilities/gates.js +499 -0
  48. package/src/verax/core/capabilities/registry.js +475 -0
  49. package/src/verax/core/confidence/confidence-compute.js +137 -0
  50. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  51. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  52. package/src/verax/core/confidence/confidence-weights.js +44 -0
  53. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  54. package/src/verax/core/confidence/confidence.loader.js +79 -0
  55. package/src/verax/core/confidence/confidence.schema.js +94 -0
  56. package/src/verax/core/confidence-engine-refactor.js +484 -0
  57. package/src/verax/core/confidence-engine.js +486 -0
  58. package/src/verax/core/confidence-engine.js.backup +471 -0
  59. package/src/verax/core/contracts/index.js +29 -0
  60. package/src/verax/core/contracts/types.js +185 -0
  61. package/src/verax/core/contracts/validators.js +381 -0
  62. package/src/verax/core/decision-snapshot.js +30 -3
  63. package/src/verax/core/decisions/decision.trace.js +276 -0
  64. package/src/verax/core/determinism/contract-writer.js +89 -0
  65. package/src/verax/core/determinism/contract.js +139 -0
  66. package/src/verax/core/determinism/diff.js +364 -0
  67. package/src/verax/core/determinism/engine.js +221 -0
  68. package/src/verax/core/determinism/finding-identity.js +148 -0
  69. package/src/verax/core/determinism/normalize.js +438 -0
  70. package/src/verax/core/determinism/report-writer.js +92 -0
  71. package/src/verax/core/determinism/run-fingerprint.js +118 -0
  72. package/src/verax/core/dynamic-route-intelligence.js +528 -0
  73. package/src/verax/core/evidence/evidence-capture-service.js +307 -0
  74. package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
  75. package/src/verax/core/evidence-builder.js +487 -0
  76. package/src/verax/core/execution-mode-context.js +77 -0
  77. package/src/verax/core/execution-mode-detector.js +190 -0
  78. package/src/verax/core/failures/exit-codes.js +86 -0
  79. package/src/verax/core/failures/failure-summary.js +76 -0
  80. package/src/verax/core/failures/failure.factory.js +225 -0
  81. package/src/verax/core/failures/failure.ledger.js +132 -0
  82. package/src/verax/core/failures/failure.types.js +196 -0
  83. package/src/verax/core/failures/index.js +10 -0
  84. package/src/verax/core/ga/ga-report-writer.js +43 -0
  85. package/src/verax/core/ga/ga.artifact.js +49 -0
  86. package/src/verax/core/ga/ga.contract.js +434 -0
  87. package/src/verax/core/ga/ga.enforcer.js +86 -0
  88. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  89. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  90. package/src/verax/core/guardrails/policy.loader.js +83 -0
  91. package/src/verax/core/guardrails/policy.schema.js +110 -0
  92. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  93. package/src/verax/core/guardrails-engine.js +505 -0
  94. package/src/verax/core/observe/run-timeline.js +316 -0
  95. package/src/verax/core/perf/perf.contract.js +186 -0
  96. package/src/verax/core/perf/perf.display.js +65 -0
  97. package/src/verax/core/perf/perf.enforcer.js +91 -0
  98. package/src/verax/core/perf/perf.monitor.js +209 -0
  99. package/src/verax/core/perf/perf.report.js +198 -0
  100. package/src/verax/core/pipeline-tracker.js +238 -0
  101. package/src/verax/core/product-definition.js +127 -0
  102. package/src/verax/core/release/provenance.builder.js +271 -0
  103. package/src/verax/core/release/release-report-writer.js +40 -0
  104. package/src/verax/core/release/release.enforcer.js +159 -0
  105. package/src/verax/core/release/reproducibility.check.js +221 -0
  106. package/src/verax/core/release/sbom.builder.js +283 -0
  107. package/src/verax/core/report/cross-index.js +192 -0
  108. package/src/verax/core/report/human-summary.js +222 -0
  109. package/src/verax/core/route-intelligence.js +419 -0
  110. package/src/verax/core/security/secrets.scan.js +326 -0
  111. package/src/verax/core/security/security-report.js +50 -0
  112. package/src/verax/core/security/security.enforcer.js +124 -0
  113. package/src/verax/core/security/supplychain.defaults.json +38 -0
  114. package/src/verax/core/security/supplychain.policy.js +326 -0
  115. package/src/verax/core/security/vuln.scan.js +265 -0
  116. package/src/verax/core/truth/truth.certificate.js +250 -0
  117. package/src/verax/core/ui-feedback-intelligence.js +515 -0
  118. package/src/verax/detect/confidence-engine.js +628 -40
  119. package/src/verax/detect/confidence-helper.js +33 -0
  120. package/src/verax/detect/detection-engine.js +18 -1
  121. package/src/verax/detect/dynamic-route-findings.js +335 -0
  122. package/src/verax/detect/expectation-chain-detector.js +417 -0
  123. package/src/verax/detect/expectation-model.js +3 -1
  124. package/src/verax/detect/findings-writer.js +141 -5
  125. package/src/verax/detect/index.js +229 -5
  126. package/src/verax/detect/journey-stall-detector.js +558 -0
  127. package/src/verax/detect/route-findings.js +218 -0
  128. package/src/verax/detect/ui-feedback-findings.js +207 -0
  129. package/src/verax/detect/verdict-engine.js +57 -3
  130. package/src/verax/detect/view-switch-correlator.js +242 -0
  131. package/src/verax/index.js +413 -45
  132. package/src/verax/learn/action-contract-extractor.js +682 -64
  133. package/src/verax/learn/route-validator.js +4 -1
  134. package/src/verax/observe/index.js +88 -843
  135. package/src/verax/observe/interaction-runner.js +25 -8
  136. package/src/verax/observe/observe-context.js +205 -0
  137. package/src/verax/observe/observe-helpers.js +191 -0
  138. package/src/verax/observe/observe-runner.js +226 -0
  139. package/src/verax/observe/observers/budget-observer.js +185 -0
  140. package/src/verax/observe/observers/console-observer.js +102 -0
  141. package/src/verax/observe/observers/coverage-observer.js +107 -0
  142. package/src/verax/observe/observers/interaction-observer.js +471 -0
  143. package/src/verax/observe/observers/navigation-observer.js +132 -0
  144. package/src/verax/observe/observers/network-observer.js +87 -0
  145. package/src/verax/observe/observers/safety-observer.js +82 -0
  146. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  147. package/src/verax/observe/ui-feedback-detector.js +742 -0
  148. package/src/verax/observe/ui-signal-sensor.js +148 -2
  149. package/src/verax/scan-summary-writer.js +42 -8
  150. package/src/verax/shared/artifact-manager.js +8 -5
  151. package/src/verax/shared/css-spinner-rules.js +204 -0
  152. 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
+