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