@veraxhq/verax 0.2.0 → 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 +15 -5
- package/src/cli/commands/baseline.js +104 -0
- package/src/cli/commands/default.js +323 -111
- package/src/cli/commands/doctor.js +36 -4
- 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 +498 -103
- package/src/cli/commands/security-check.js +211 -0
- package/src/cli/commands/truth.js +114 -0
- package/src/cli/entry.js +305 -68
- 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/detection-engine.js +4 -3
- 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/events.js +76 -0
- package/src/cli/util/expectation-extractor.js +380 -74
- package/src/cli/util/findings-writer.js +126 -15
- package/src/cli/util/learn-writer.js +3 -1
- package/src/cli/util/observation-engine.js +69 -23
- package/src/cli/util/observe-writer.js +3 -1
- package/src/cli/util/paths.js +6 -14
- package/src/cli/util/project-discovery.js +23 -0
- package/src/cli/util/project-writer.js +3 -1
- package/src/cli/util/redact.js +2 -2
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/runtime-budget.js +147 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +13 -1
- 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/types/global.d.ts +28 -0
- package/src/types/ts-ast.d.ts +24 -0
- package/src/verax/cli/doctor.js +2 -2
- package/src/verax/cli/finding-explainer.js +56 -3
- package/src/verax/cli/init.js +1 -1
- package/src/verax/cli/url-safety.js +12 -2
- package/src/verax/cli/wizard.js +13 -2
- 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/budget-engine.js +1 -1
- 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 +31 -4
- 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/determinism-model.js +35 -6
- 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/incremental-store.js +15 -7
- 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/replay-validator.js +4 -4
- package/src/verax/core/replay.js +1 -1
- 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/silence-impact.js +1 -1
- package/src/verax/core/silence-model.js +9 -7
- package/src/verax/core/truth/truth.certificate.js +250 -0
- package/src/verax/core/ui-feedback-intelligence.js +515 -0
- package/src/verax/detect/comparison.js +8 -3
- package/src/verax/detect/confidence-engine.js +645 -57
- package/src/verax/detect/confidence-helper.js +33 -0
- package/src/verax/detect/detection-engine.js +19 -2
- package/src/verax/detect/dynamic-route-findings.js +335 -0
- package/src/verax/detect/evidence-index.js +15 -65
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +56 -3
- package/src/verax/detect/explanation-helpers.js +1 -1
- package/src/verax/detect/finding-detector.js +2 -2
- package/src/verax/detect/findings-writer.js +149 -20
- package/src/verax/detect/flow-detector.js +4 -4
- package/src/verax/detect/index.js +265 -15
- package/src/verax/detect/interactive-findings.js +3 -4
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/route-findings.js +218 -0
- package/src/verax/detect/signal-mapper.js +2 -2
- package/src/verax/detect/skip-classifier.js +4 -4
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/verdict-engine.js +61 -9
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/flow/flow-engine.js +3 -2
- package/src/verax/flow/flow-spec.js +1 -2
- package/src/verax/index.js +413 -33
- package/src/verax/intel/effect-detector.js +1 -1
- package/src/verax/intel/index.js +2 -2
- package/src/verax/intel/route-extractor.js +3 -3
- package/src/verax/intel/vue-navigation-extractor.js +81 -18
- package/src/verax/intel/vue-router-extractor.js +4 -2
- package/src/verax/learn/action-contract-extractor.js +684 -66
- package/src/verax/learn/ast-contract-extractor.js +53 -1
- package/src/verax/learn/index.js +36 -2
- package/src/verax/learn/manifest-writer.js +28 -14
- package/src/verax/learn/route-extractor.js +1 -1
- package/src/verax/learn/route-validator.js +12 -8
- package/src/verax/learn/state-extractor.js +1 -1
- package/src/verax/learn/static-extractor-navigation.js +1 -1
- package/src/verax/learn/static-extractor-validation.js +2 -2
- package/src/verax/learn/static-extractor.js +8 -7
- package/src/verax/learn/ts-contract-resolver.js +14 -12
- package/src/verax/observe/browser.js +22 -3
- package/src/verax/observe/console-sensor.js +2 -2
- package/src/verax/observe/expectation-executor.js +2 -1
- package/src/verax/observe/focus-sensor.js +1 -1
- package/src/verax/observe/human-driver.js +29 -10
- package/src/verax/observe/index.js +92 -844
- package/src/verax/observe/interaction-discovery.js +27 -15
- package/src/verax/observe/interaction-runner.js +31 -14
- package/src/verax/observe/loading-sensor.js +6 -0
- package/src/verax/observe/navigation-sensor.js +1 -1
- 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/settle.js +1 -0
- package/src/verax/observe/state-sensor.js +8 -4
- package/src/verax/observe/state-ui-sensor.js +7 -1
- package/src/verax/observe/traces-writer.js +27 -16
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/observe/ui-signal-sensor.js +155 -2
- package/src/verax/scan-summary-writer.js +46 -9
- package/src/verax/shared/artifact-manager.js +9 -6
- package/src/verax/shared/budget-profiles.js +2 -2
- package/src/verax/shared/caching.js +1 -1
- package/src/verax/shared/config-loader.js +1 -2
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/dynamic-route-utils.js +12 -6
- package/src/verax/shared/retry-policy.js +1 -6
- package/src/verax/shared/root-artifacts.js +1 -1
- package/src/verax/shared/view-switch-rules.js +208 -0
- package/src/verax/shared/zip-artifacts.js +1 -0
- package/src/verax/validate/context-validator.js +1 -1
- package/src/verax/observe/index.js.backup +0 -1
- package/src/verax/validate/context-validator.js.bak +0 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 12 — Route Intelligence & Deep Route Detection
|
|
3
|
+
*
|
|
4
|
+
* Unified route model and intelligence layer that:
|
|
5
|
+
* - Defines route taxonomy (static, dynamic, ambiguous)
|
|
6
|
+
* - Correlates navigation promises with routes
|
|
7
|
+
* - Handles dynamic routes honestly
|
|
8
|
+
* - Provides evidence for route-related findings
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { normalizeDynamicRoute, isDynamicPath, normalizeNavigationTarget } from '../shared/dynamic-route-utils.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Route Taxonomy
|
|
15
|
+
*/
|
|
16
|
+
export const ROUTE_TYPE = {
|
|
17
|
+
STATIC: 'static',
|
|
18
|
+
DYNAMIC: 'dynamic',
|
|
19
|
+
AMBIGUOUS: 'ambiguous',
|
|
20
|
+
PROGRAMMATIC: 'programmatic'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const ROUTE_STABILITY = {
|
|
24
|
+
STATIC: 'static', // Fully static route
|
|
25
|
+
DYNAMIC: 'dynamic', // Dynamic but can be normalized
|
|
26
|
+
AMBIGUOUS: 'ambiguous' // Cannot be deterministically validated
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const ROUTE_SOURCE = {
|
|
30
|
+
FILE_SYSTEM: 'file_system', // Next.js pages/app router
|
|
31
|
+
JSX: 'jsx', // React Router <Route>
|
|
32
|
+
CONFIG: 'config', // Vue Router config
|
|
33
|
+
PROGRAMMATIC: 'programmatic' // navigate(), router.push()
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* PHASE 12: Unified Route Model
|
|
38
|
+
*
|
|
39
|
+
* @typedef {Object} RouteModel
|
|
40
|
+
* @property {string} path - Normalized route path
|
|
41
|
+
* @property {string} type - ROUTE_TYPE (static, dynamic, ambiguous, programmatic)
|
|
42
|
+
* @property {string} stability - ROUTE_STABILITY (static, dynamic, ambiguous)
|
|
43
|
+
* @property {string} source - ROUTE_SOURCE (file_system, jsx, config, programmatic)
|
|
44
|
+
* @property {string} framework - Framework identifier
|
|
45
|
+
* @property {string} sourceRef - Source reference (file:line:col)
|
|
46
|
+
* @property {string} file - Source file path
|
|
47
|
+
* @property {number} line - Source line number
|
|
48
|
+
* @property {string} [originalPattern] - Original dynamic pattern (if dynamic)
|
|
49
|
+
* @property {string[]} [parameters] - Dynamic parameters (if dynamic)
|
|
50
|
+
* @property {boolean} [isDynamic] - Whether route is dynamic
|
|
51
|
+
* @property {boolean} [exampleExecution] - Whether example path was generated
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Build unified route model from extracted routes
|
|
56
|
+
*
|
|
57
|
+
* @param {Array} extractedRoutes - Routes from route extractors
|
|
58
|
+
* @returns {Array<RouteModel>} Unified route models
|
|
59
|
+
*/
|
|
60
|
+
export function buildRouteModels(extractedRoutes) {
|
|
61
|
+
const routeModels = [];
|
|
62
|
+
|
|
63
|
+
for (const route of extractedRoutes) {
|
|
64
|
+
const path = route.path || '';
|
|
65
|
+
const isDynamicRoute = route.isDynamic || isDynamicPath(path);
|
|
66
|
+
|
|
67
|
+
// Determine route type
|
|
68
|
+
let type = ROUTE_TYPE.STATIC;
|
|
69
|
+
let stability = ROUTE_STABILITY.STATIC;
|
|
70
|
+
|
|
71
|
+
if (isDynamicRoute) {
|
|
72
|
+
type = ROUTE_TYPE.DYNAMIC;
|
|
73
|
+
|
|
74
|
+
// Check if dynamic route can be normalized
|
|
75
|
+
if (route.originalPattern || route.exampleExecution) {
|
|
76
|
+
stability = ROUTE_STABILITY.DYNAMIC; // Can be normalized
|
|
77
|
+
} else {
|
|
78
|
+
stability = ROUTE_STABILITY.AMBIGUOUS; // Cannot be deterministically validated
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Determine source
|
|
83
|
+
let source = ROUTE_SOURCE.PROGRAMMATIC;
|
|
84
|
+
if (route.framework === 'next-pages' || route.framework === 'next-app') {
|
|
85
|
+
source = ROUTE_SOURCE.FILE_SYSTEM;
|
|
86
|
+
} else if (route.framework === 'react-router') {
|
|
87
|
+
source = ROUTE_SOURCE.JSX;
|
|
88
|
+
} else if (route.framework === 'vue-router') {
|
|
89
|
+
source = ROUTE_SOURCE.CONFIG;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const routeModel = {
|
|
93
|
+
path,
|
|
94
|
+
type,
|
|
95
|
+
stability,
|
|
96
|
+
source,
|
|
97
|
+
framework: route.framework || 'unknown',
|
|
98
|
+
sourceRef: route.sourceRef || `${route.file || 'unknown'}:${route.line || 1}`,
|
|
99
|
+
file: route.file || null,
|
|
100
|
+
line: route.line || null,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Add dynamic route metadata
|
|
104
|
+
if (isDynamicRoute) {
|
|
105
|
+
routeModel.originalPattern = route.originalPattern || path;
|
|
106
|
+
routeModel.isDynamic = true;
|
|
107
|
+
routeModel.exampleExecution = route.exampleExecution || false;
|
|
108
|
+
routeModel.parameters = route.parameters || extractParameters(path);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
routeModels.push(routeModel);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return routeModels;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Extract parameter names from dynamic route path
|
|
119
|
+
*/
|
|
120
|
+
function extractParameters(path) {
|
|
121
|
+
const parameters = [];
|
|
122
|
+
|
|
123
|
+
// React/Vue Router :param
|
|
124
|
+
const reactMatches = path.matchAll(/:(\w+)/g);
|
|
125
|
+
for (const match of reactMatches) {
|
|
126
|
+
parameters.push(match[1]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Next.js [param]
|
|
130
|
+
const nextMatches = path.matchAll(/\[(\w+)\]/g);
|
|
131
|
+
for (const match of nextMatches) {
|
|
132
|
+
parameters.push(match[1]);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return parameters;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* PHASE 12: Correlate navigation promise with route
|
|
140
|
+
*
|
|
141
|
+
* @param {string} navigationTarget - Navigation target from promise
|
|
142
|
+
* @param {Array<RouteModel>} routeModels - Available route models
|
|
143
|
+
* @returns {Object|null} Correlation result or null
|
|
144
|
+
*/
|
|
145
|
+
export function correlateNavigationWithRoute(navigationTarget, routeModels) {
|
|
146
|
+
if (!navigationTarget || typeof navigationTarget !== 'string') {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Normalize navigation target (handle dynamic targets)
|
|
151
|
+
const normalized = normalizeNavigationTarget(navigationTarget);
|
|
152
|
+
const targetToMatch = normalized.exampleTarget || navigationTarget;
|
|
153
|
+
|
|
154
|
+
// Try exact match first
|
|
155
|
+
let matchedRoute = routeModels.find(r => r.path === targetToMatch);
|
|
156
|
+
|
|
157
|
+
if (matchedRoute) {
|
|
158
|
+
return {
|
|
159
|
+
route: matchedRoute,
|
|
160
|
+
matchType: 'exact',
|
|
161
|
+
confidence: 1.0,
|
|
162
|
+
normalizedTarget: targetToMatch,
|
|
163
|
+
originalTarget: navigationTarget,
|
|
164
|
+
isDynamic: normalized.isDynamic,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Try prefix match for dynamic routes
|
|
169
|
+
for (const route of routeModels) {
|
|
170
|
+
if (route.isDynamic && route.originalPattern) {
|
|
171
|
+
// Check if target matches dynamic pattern
|
|
172
|
+
const patternMatch = matchDynamicPattern(targetToMatch, route.originalPattern);
|
|
173
|
+
if (patternMatch) {
|
|
174
|
+
return {
|
|
175
|
+
route: route,
|
|
176
|
+
matchType: 'pattern',
|
|
177
|
+
confidence: 0.9,
|
|
178
|
+
normalizedTarget: targetToMatch,
|
|
179
|
+
originalTarget: navigationTarget,
|
|
180
|
+
isDynamic: true,
|
|
181
|
+
patternMatch: patternMatch,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// No match found
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Match a target path against a dynamic route pattern
|
|
193
|
+
*/
|
|
194
|
+
function matchDynamicPattern(target, pattern) {
|
|
195
|
+
// Convert pattern to regex
|
|
196
|
+
// React Router: /users/:id -> /users/(\w+)
|
|
197
|
+
// Next.js: /users/[id] -> /users/(\w+)
|
|
198
|
+
|
|
199
|
+
let regexPattern = pattern;
|
|
200
|
+
|
|
201
|
+
// Replace :param with (\w+)
|
|
202
|
+
regexPattern = regexPattern.replace(/:(\w+)/g, '(\\w+)');
|
|
203
|
+
|
|
204
|
+
// Replace [param] with (\w+)
|
|
205
|
+
regexPattern = regexPattern.replace(/\[(\w+)\]/g, '(\\w+)');
|
|
206
|
+
|
|
207
|
+
// Escape other special characters
|
|
208
|
+
regexPattern = regexPattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
209
|
+
|
|
210
|
+
// Restore the capture groups
|
|
211
|
+
regexPattern = regexPattern.replace(/\\\(\\\\w\+\\\)/g, '(\\w+)');
|
|
212
|
+
|
|
213
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
214
|
+
const match = target.match(regex);
|
|
215
|
+
|
|
216
|
+
return match ? { matched: true, groups: match.slice(1) } : null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* PHASE 12: Evaluate route navigation outcome
|
|
221
|
+
*
|
|
222
|
+
* @param {Object} correlation - Route correlation result
|
|
223
|
+
* @param {Object} trace - Interaction trace
|
|
224
|
+
* @param {string} beforeUrl - URL before interaction
|
|
225
|
+
* @param {string} afterUrl - URL after interaction
|
|
226
|
+
* @returns {Object} Evaluation result
|
|
227
|
+
*/
|
|
228
|
+
export function evaluateRouteNavigation(correlation, trace, beforeUrl, afterUrl) {
|
|
229
|
+
if (!correlation) {
|
|
230
|
+
return {
|
|
231
|
+
outcome: 'NO_ROUTE_MATCH',
|
|
232
|
+
confidence: 0.0,
|
|
233
|
+
reason: 'Navigation target does not match any known route',
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const { route, normalizedTarget } = correlation;
|
|
238
|
+
const sensors = trace.sensors || {};
|
|
239
|
+
const navSensor = sensors.navigation || {};
|
|
240
|
+
|
|
241
|
+
// Check URL change
|
|
242
|
+
const urlChanged = navSensor.urlChanged === true ||
|
|
243
|
+
(beforeUrl && afterUrl && beforeUrl !== afterUrl);
|
|
244
|
+
|
|
245
|
+
// Check if URL matches route
|
|
246
|
+
const afterPath = extractPathFromUrl(afterUrl);
|
|
247
|
+
const routeMatched = afterPath === route.path ||
|
|
248
|
+
afterPath === normalizedTarget ||
|
|
249
|
+
(route.isDynamic && matchDynamicPattern(afterPath, route.originalPattern));
|
|
250
|
+
|
|
251
|
+
// Check router state change (for SPAs)
|
|
252
|
+
const routerStateChanged = navSensor.routerStateChanged === true ||
|
|
253
|
+
navSensor.routeChanged === true ||
|
|
254
|
+
(navSensor.routerEvents && navSensor.routerEvents.length > 0);
|
|
255
|
+
|
|
256
|
+
// Check UI change
|
|
257
|
+
const uiChanged = sensors.uiSignals?.diff?.changed === true;
|
|
258
|
+
const domChanged = trace.after?.dom !== trace.before?.dom;
|
|
259
|
+
|
|
260
|
+
// PHASE 12: Evidence Law - require before/after + supporting signal
|
|
261
|
+
const hasEvidence = (beforeUrl && afterUrl) &&
|
|
262
|
+
(urlChanged || routerStateChanged || uiChanged || domChanged);
|
|
263
|
+
|
|
264
|
+
if (routeMatched && hasEvidence) {
|
|
265
|
+
return {
|
|
266
|
+
outcome: 'VERIFIED',
|
|
267
|
+
confidence: route.stability === ROUTE_STABILITY.STATIC ? 1.0 : 0.9,
|
|
268
|
+
reason: null,
|
|
269
|
+
evidence: {
|
|
270
|
+
urlChanged,
|
|
271
|
+
routerStateChanged,
|
|
272
|
+
uiChanged,
|
|
273
|
+
domChanged,
|
|
274
|
+
routeMatched: true,
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!routeMatched && urlChanged) {
|
|
280
|
+
return {
|
|
281
|
+
outcome: 'ROUTE_MISMATCH',
|
|
282
|
+
confidence: 0.8,
|
|
283
|
+
reason: 'Navigation occurred but target route does not match',
|
|
284
|
+
evidence: {
|
|
285
|
+
urlChanged: true,
|
|
286
|
+
expectedRoute: route.path,
|
|
287
|
+
actualPath: afterPath,
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (!hasEvidence) {
|
|
293
|
+
return {
|
|
294
|
+
outcome: 'SILENT_FAILURE',
|
|
295
|
+
confidence: correlation.confidence,
|
|
296
|
+
reason: 'Navigation promise not fulfilled - no URL change, router state change, or UI change',
|
|
297
|
+
evidence: {
|
|
298
|
+
urlChanged: false,
|
|
299
|
+
routerStateChanged: false,
|
|
300
|
+
uiChanged: false,
|
|
301
|
+
domChanged: false,
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Ambiguous case
|
|
307
|
+
if (route.stability === ROUTE_STABILITY.AMBIGUOUS) {
|
|
308
|
+
return {
|
|
309
|
+
outcome: 'SUSPECTED',
|
|
310
|
+
confidence: 0.6,
|
|
311
|
+
reason: 'Dynamic route cannot be deterministically validated',
|
|
312
|
+
evidence: {
|
|
313
|
+
routeStability: 'ambiguous',
|
|
314
|
+
urlChanged,
|
|
315
|
+
routerStateChanged,
|
|
316
|
+
uiChanged,
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
outcome: 'UNKNOWN',
|
|
323
|
+
confidence: 0.5,
|
|
324
|
+
reason: 'Route navigation outcome unclear',
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Extract path from URL
|
|
330
|
+
*/
|
|
331
|
+
function extractPathFromUrl(url) {
|
|
332
|
+
if (!url || typeof url !== 'string') return '';
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
const urlObj = new URL(url);
|
|
336
|
+
return urlObj.pathname;
|
|
337
|
+
} catch {
|
|
338
|
+
// Relative URL
|
|
339
|
+
const pathMatch = url.match(/^([^?#]+)/);
|
|
340
|
+
return pathMatch ? pathMatch[1] : url;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* PHASE 12: Build evidence for route-related finding
|
|
346
|
+
*
|
|
347
|
+
* @param {Object} correlation - Route correlation
|
|
348
|
+
* @param {Object} navigationPromise - Navigation promise from expectation
|
|
349
|
+
* @param {Object} evaluation - Route evaluation result
|
|
350
|
+
* @param {Object} trace - Interaction trace
|
|
351
|
+
* @returns {Object} Evidence object
|
|
352
|
+
*/
|
|
353
|
+
export function buildRouteEvidence(correlation, navigationPromise, evaluation, trace) {
|
|
354
|
+
const evidence = {
|
|
355
|
+
routeDefinition: {
|
|
356
|
+
path: correlation?.route?.path || null,
|
|
357
|
+
type: correlation?.route?.type || null,
|
|
358
|
+
stability: correlation?.route?.stability || null,
|
|
359
|
+
source: correlation?.route?.source || null,
|
|
360
|
+
sourceRef: correlation?.route?.sourceRef || null,
|
|
361
|
+
},
|
|
362
|
+
navigationTrigger: {
|
|
363
|
+
target: navigationPromise?.target || null,
|
|
364
|
+
method: navigationPromise?.method || null,
|
|
365
|
+
astSource: navigationPromise?.astSource || null,
|
|
366
|
+
context: navigationPromise?.context || null,
|
|
367
|
+
},
|
|
368
|
+
beforeAfter: {
|
|
369
|
+
beforeUrl: trace.before?.url || null,
|
|
370
|
+
afterUrl: trace.after?.url || null,
|
|
371
|
+
beforeScreenshot: trace.before?.screenshot || null,
|
|
372
|
+
afterScreenshot: trace.after?.screenshot || null,
|
|
373
|
+
},
|
|
374
|
+
signals: {
|
|
375
|
+
urlChanged: evaluation.evidence?.urlChanged || false,
|
|
376
|
+
routerStateChanged: evaluation.evidence?.routerStateChanged || false,
|
|
377
|
+
uiChanged: evaluation.evidence?.uiChanged || false,
|
|
378
|
+
domChanged: evaluation.evidence?.domChanged || false,
|
|
379
|
+
routeMatched: evaluation.evidence?.routeMatched || false,
|
|
380
|
+
},
|
|
381
|
+
evaluation: {
|
|
382
|
+
outcome: evaluation.outcome,
|
|
383
|
+
confidence: evaluation.confidence,
|
|
384
|
+
reason: evaluation.reason,
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
return evidence;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* PHASE 12: Check if route change is false positive
|
|
393
|
+
*
|
|
394
|
+
* @param {Object} trace - Interaction trace
|
|
395
|
+
* @param {Object} correlation - Route correlation
|
|
396
|
+
* @returns {boolean} True if should be ignored (false positive)
|
|
397
|
+
*/
|
|
398
|
+
export function isRouteChangeFalsePositive(trace, correlation) {
|
|
399
|
+
const sensors = trace.sensors || {};
|
|
400
|
+
const navSensor = sensors.navigation || {};
|
|
401
|
+
|
|
402
|
+
// Internal state-only route changes (shallow routing)
|
|
403
|
+
if (navSensor.shallowRouting === true && !navSensor.urlChanged) {
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Analytics-only navigation
|
|
408
|
+
const networkSensor = sensors.network || {};
|
|
409
|
+
const hasAnalyticsRequest = networkSensor.observedRequestUrls?.some(url =>
|
|
410
|
+
url && typeof url === 'string' && url.includes('/api/analytics')
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
if (hasAnalyticsRequest && !navSensor.urlChanged && !sensors.uiSignals?.diff?.changed) {
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
|