@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,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 15 — Confidence Helper
|
|
3
|
+
*
|
|
4
|
+
* Helper function to add unified confidence to findings
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { computeConfidenceForFinding } from '../core/confidence-engine.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* PHASE 15: Add unified confidence to a finding
|
|
11
|
+
*
|
|
12
|
+
* @param {Object} finding - Finding object
|
|
13
|
+
* @param {Object} params - Confidence computation parameters
|
|
14
|
+
* @returns {Object} Finding with unified confidence fields
|
|
15
|
+
*/
|
|
16
|
+
export function addUnifiedConfidence(finding, params) {
|
|
17
|
+
const unifiedConfidence = computeConfidenceForFinding({
|
|
18
|
+
findingType: finding.type || 'unknown',
|
|
19
|
+
expectation: params.expectation || null,
|
|
20
|
+
sensors: params.sensors || {},
|
|
21
|
+
comparisons: params.comparisons || {},
|
|
22
|
+
evidence: params.evidence || {},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Add unified confidence fields (additive only)
|
|
26
|
+
return {
|
|
27
|
+
...finding,
|
|
28
|
+
confidence: unifiedConfidence.score, // PHASE 15: Normalized 0..1
|
|
29
|
+
confidenceLevel: unifiedConfidence.level, // PHASE 15: HIGH/MEDIUM/LOW/UNPROVEN
|
|
30
|
+
confidenceReasons: unifiedConfidence.reasons, // PHASE 15: Stable reason codes
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
@@ -2,11 +2,19 @@
|
|
|
2
2
|
* Detection Engine: Core of VERAX
|
|
3
3
|
* Compares learn.json and observe.json to produce evidence-backed findings
|
|
4
4
|
* with deterministic classification and confidence calculation
|
|
5
|
+
*
|
|
6
|
+
* PHASE 11: EXPECTATION CONTINUITY
|
|
7
|
+
* - Detects journey-level stalls (individual steps OK, overall journey stalls)
|
|
8
|
+
* - Tracks interaction sequences and infers expected progression signals
|
|
9
|
+
* - Emits "journey-stall-silent-failure" findings with high-confidence context
|
|
5
10
|
*/
|
|
6
11
|
|
|
12
|
+
import JourneyStallDetector from './journey-stall-detector.js';
|
|
13
|
+
|
|
7
14
|
class DetectionEngine {
|
|
8
15
|
constructor(options = {}) {
|
|
9
16
|
this.options = options;
|
|
17
|
+
this.journeyStallDetector = new JourneyStallDetector(options.journeyStall || {});
|
|
10
18
|
}
|
|
11
19
|
|
|
12
20
|
/**
|
|
@@ -22,6 +30,7 @@ class DetectionEngine {
|
|
|
22
30
|
|
|
23
31
|
const expectations = learnData.expectations || [];
|
|
24
32
|
const observations = observeData.observations || [];
|
|
33
|
+
const traces = observeData.traces || [];
|
|
25
34
|
|
|
26
35
|
// Index observations for fast lookup
|
|
27
36
|
const observationMap = this._indexObservations(observations);
|
|
@@ -31,6 +40,10 @@ class DetectionEngine {
|
|
|
31
40
|
return this._classifyExpectation(expectation, observationMap, observations);
|
|
32
41
|
});
|
|
33
42
|
|
|
43
|
+
// PHASE 11: Detect journey-level stalls
|
|
44
|
+
const journeyStallFindings = this.journeyStallDetector.detectStalls(traces);
|
|
45
|
+
findings.push(...journeyStallFindings);
|
|
46
|
+
|
|
34
47
|
// Calculate stats
|
|
35
48
|
const stats = this._calculateStats(findings);
|
|
36
49
|
|
|
@@ -38,7 +51,11 @@ class DetectionEngine {
|
|
|
38
51
|
findings,
|
|
39
52
|
stats,
|
|
40
53
|
detectedAt: new Date().toISOString(),
|
|
41
|
-
version: '1.
|
|
54
|
+
version: '1.1.0',
|
|
55
|
+
phaseFeatures: {
|
|
56
|
+
expectationContinuity: true,
|
|
57
|
+
journeyStallDetection: true
|
|
58
|
+
}
|
|
42
59
|
};
|
|
43
60
|
}
|
|
44
61
|
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 14 — Dynamic Route Findings Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects dynamic route-related findings with proper verifiability classification
|
|
5
|
+
* and intentional skips for unverifiable routes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
classifyDynamicRoute,
|
|
10
|
+
correlateDynamicRouteNavigation,
|
|
11
|
+
buildDynamicRouteEvidence,
|
|
12
|
+
shouldSkipDynamicRoute,
|
|
13
|
+
DYNAMIC_ROUTE_VERIFIABILITY,
|
|
14
|
+
ROUTE_VERDICT,
|
|
15
|
+
} from '../core/dynamic-route-intelligence.js';
|
|
16
|
+
import { buildRouteModels } from '../core/route-intelligence.js';
|
|
17
|
+
import { computeConfidence } from './confidence-engine.js';
|
|
18
|
+
import { computeConfidenceForFinding } from '../core/confidence-engine.js';
|
|
19
|
+
import { buildAndEnforceEvidencePackage } from '../core/evidence-builder.js';
|
|
20
|
+
import { applyGuardrails } from '../core/guardrails-engine.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* PHASE 14: Detect dynamic route-related findings
|
|
24
|
+
*
|
|
25
|
+
* @param {Array} traces - Interaction traces
|
|
26
|
+
* @param {Object} manifest - Project manifest with routes and expectations
|
|
27
|
+
* @param {Array} findings - Findings array to append to
|
|
28
|
+
* @returns {Object} { findings: Array, skips: Array }
|
|
29
|
+
*/
|
|
30
|
+
export function detectDynamicRouteFindings(traces, manifest, findings) {
|
|
31
|
+
const dynamicRouteFindings = [];
|
|
32
|
+
const skips = [];
|
|
33
|
+
|
|
34
|
+
// Build route models from manifest routes
|
|
35
|
+
const routeModels = buildRouteModels(manifest.routes || []);
|
|
36
|
+
|
|
37
|
+
// Process each trace
|
|
38
|
+
for (const trace of traces) {
|
|
39
|
+
const interaction = trace.interaction || {};
|
|
40
|
+
|
|
41
|
+
// Find navigation expectations for this interaction
|
|
42
|
+
const navigationExpectations = findNavigationExpectations(manifest, interaction, trace);
|
|
43
|
+
|
|
44
|
+
for (const expectation of navigationExpectations) {
|
|
45
|
+
const navigationTarget = expectation.targetPath || expectation.expectedTarget || '';
|
|
46
|
+
|
|
47
|
+
if (!navigationTarget) continue;
|
|
48
|
+
|
|
49
|
+
// Find matching route model
|
|
50
|
+
const matchingRoute = findMatchingRoute(navigationTarget, routeModels);
|
|
51
|
+
|
|
52
|
+
if (!matchingRoute) {
|
|
53
|
+
// No route match - handled by regular route findings
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check if route is dynamic
|
|
58
|
+
const classification = classifyDynamicRoute(matchingRoute, trace);
|
|
59
|
+
|
|
60
|
+
// If route is unverifiable, add to skips
|
|
61
|
+
if (classification.verifiability === DYNAMIC_ROUTE_VERIFIABILITY.UNVERIFIABLE_DYNAMIC) {
|
|
62
|
+
const skipDecision = shouldSkipDynamicRoute(matchingRoute, trace);
|
|
63
|
+
|
|
64
|
+
skips.push({
|
|
65
|
+
type: 'dynamic_route_unverifiable',
|
|
66
|
+
interaction: {
|
|
67
|
+
type: interaction.type,
|
|
68
|
+
selector: interaction.selector,
|
|
69
|
+
label: interaction.label,
|
|
70
|
+
},
|
|
71
|
+
route: {
|
|
72
|
+
path: matchingRoute.path,
|
|
73
|
+
originalPattern: matchingRoute.originalPattern,
|
|
74
|
+
sourceRef: matchingRoute.sourceRef,
|
|
75
|
+
},
|
|
76
|
+
reason: skipDecision.reason,
|
|
77
|
+
confidence: skipDecision.confidence,
|
|
78
|
+
expectation: {
|
|
79
|
+
target: navigationTarget,
|
|
80
|
+
source: expectation.source,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Correlate navigation with dynamic route
|
|
87
|
+
const correlation = correlateDynamicRouteNavigation(expectation, matchingRoute, trace);
|
|
88
|
+
|
|
89
|
+
// If correlation indicates skip, add to skips
|
|
90
|
+
if (correlation.skip) {
|
|
91
|
+
skips.push({
|
|
92
|
+
type: 'dynamic_route_skip',
|
|
93
|
+
interaction: {
|
|
94
|
+
type: interaction.type,
|
|
95
|
+
selector: interaction.selector,
|
|
96
|
+
label: interaction.label,
|
|
97
|
+
},
|
|
98
|
+
route: {
|
|
99
|
+
path: matchingRoute.path,
|
|
100
|
+
originalPattern: matchingRoute.originalPattern,
|
|
101
|
+
sourceRef: matchingRoute.sourceRef,
|
|
102
|
+
},
|
|
103
|
+
reason: correlation.skipReason,
|
|
104
|
+
confidence: correlation.confidence,
|
|
105
|
+
expectation: {
|
|
106
|
+
target: navigationTarget,
|
|
107
|
+
source: expectation.source,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Generate finding if verdict indicates failure
|
|
114
|
+
if (correlation.verdict === ROUTE_VERDICT.SILENT_FAILURE ||
|
|
115
|
+
correlation.verdict === ROUTE_VERDICT.ROUTE_MISMATCH ||
|
|
116
|
+
(correlation.verdict === ROUTE_VERDICT.AMBIGUOUS && correlation.confidence >= 0.7)) {
|
|
117
|
+
|
|
118
|
+
// Build evidence
|
|
119
|
+
const evidence = buildDynamicRouteEvidence(expectation, matchingRoute, correlation, trace);
|
|
120
|
+
|
|
121
|
+
// PHASE 14: Evidence Law - require sufficient evidence for CONFIRMED
|
|
122
|
+
const hasSufficientEvidence = evidence.beforeAfter.beforeUrl &&
|
|
123
|
+
evidence.beforeAfter.afterUrl &&
|
|
124
|
+
(evidence.signals.urlChanged ||
|
|
125
|
+
evidence.signals.routeMatched ||
|
|
126
|
+
evidence.signals.uiFeedback !== 'FEEDBACK_MISSING' ||
|
|
127
|
+
evidence.signals.domChanged);
|
|
128
|
+
|
|
129
|
+
// PHASE 15: Compute unified confidence
|
|
130
|
+
const unifiedConfidence = computeConfidenceForFinding({
|
|
131
|
+
findingType: findingType,
|
|
132
|
+
expectation,
|
|
133
|
+
sensors: trace.sensors || {},
|
|
134
|
+
comparisons: {},
|
|
135
|
+
evidence,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Legacy confidence for backward compatibility
|
|
139
|
+
const confidence = computeConfidence({
|
|
140
|
+
findingType: 'dynamic_route_silent_failure',
|
|
141
|
+
expectation,
|
|
142
|
+
sensors: trace.sensors || {},
|
|
143
|
+
comparisons: {},
|
|
144
|
+
attemptMeta: {},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Determine severity based on evidence and verdict
|
|
148
|
+
let severity = 'SUSPECTED';
|
|
149
|
+
if (hasSufficientEvidence && correlation.verdict === ROUTE_VERDICT.SILENT_FAILURE && unifiedConfidence.score >= 0.8) {
|
|
150
|
+
severity = 'CONFIRMED';
|
|
151
|
+
} else if (correlation.verdict === ROUTE_VERDICT.ROUTE_MISMATCH && hasSufficientEvidence) {
|
|
152
|
+
severity = 'CONFIRMED';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Determine finding type
|
|
156
|
+
let findingType = 'dynamic_route_silent_failure';
|
|
157
|
+
if (correlation.verdict === ROUTE_VERDICT.ROUTE_MISMATCH) {
|
|
158
|
+
findingType = 'dynamic_route_mismatch';
|
|
159
|
+
} else if (correlation.verdict === ROUTE_VERDICT.AMBIGUOUS) {
|
|
160
|
+
findingType = 'dynamic_route_ambiguous';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const finding = {
|
|
164
|
+
type: findingType,
|
|
165
|
+
severity,
|
|
166
|
+
confidence: unifiedConfidence.score, // PHASE 15: Use unified confidence score (0..1)
|
|
167
|
+
confidenceLevel: unifiedConfidence.level, // PHASE 15: Add confidence level
|
|
168
|
+
confidenceReasons: unifiedConfidence.reasons, // PHASE 15: Add stable reason codes
|
|
169
|
+
interaction: {
|
|
170
|
+
type: interaction.type,
|
|
171
|
+
selector: interaction.selector,
|
|
172
|
+
label: interaction.label,
|
|
173
|
+
},
|
|
174
|
+
reason: correlation.reason || 'Dynamic route navigation outcome unclear',
|
|
175
|
+
evidence,
|
|
176
|
+
source: {
|
|
177
|
+
file: expectation.source?.file || null,
|
|
178
|
+
line: expectation.source?.line || null,
|
|
179
|
+
column: expectation.source?.column || null,
|
|
180
|
+
context: expectation.source?.context || null,
|
|
181
|
+
astSource: expectation.source?.astSource || null,
|
|
182
|
+
},
|
|
183
|
+
route: correlation.route,
|
|
184
|
+
expectation,
|
|
185
|
+
classification: classification,
|
|
186
|
+
classificationReason: classificationReason,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// PHASE 16: Build and enforce evidence package
|
|
190
|
+
const findingWithEvidence = buildAndEnforceEvidencePackage(finding, {
|
|
191
|
+
expectation,
|
|
192
|
+
trace,
|
|
193
|
+
evidence,
|
|
194
|
+
confidence: unifiedConfidence,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// PHASE 17: Apply guardrails (AFTER evidence builder)
|
|
198
|
+
const context = {
|
|
199
|
+
evidencePackage: findingWithEvidence.evidencePackage,
|
|
200
|
+
signals: findingWithEvidence.evidencePackage?.signals || evidence.signals || {},
|
|
201
|
+
confidenceReasons: unifiedConfidence.reasons || [],
|
|
202
|
+
promiseType: expectation?.type || null,
|
|
203
|
+
};
|
|
204
|
+
const { finding: findingWithGuardrails } = applyGuardrails(findingWithEvidence, context);
|
|
205
|
+
|
|
206
|
+
dynamicRouteFindings.push(findingWithGuardrails);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
findings: dynamicRouteFindings,
|
|
213
|
+
skips: skips,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Find navigation expectations matching the interaction
|
|
219
|
+
*/
|
|
220
|
+
function findNavigationExpectations(manifest, interaction, trace) {
|
|
221
|
+
const expectations = [];
|
|
222
|
+
|
|
223
|
+
// Check static expectations
|
|
224
|
+
if (manifest.staticExpectations) {
|
|
225
|
+
const beforeUrl = trace.before?.url || trace.sensors?.navigation?.beforeUrl || '';
|
|
226
|
+
const beforePath = extractPathFromUrl(beforeUrl);
|
|
227
|
+
|
|
228
|
+
if (beforePath) {
|
|
229
|
+
const normalizedBefore = beforePath.replace(/\/$/, '') || '/';
|
|
230
|
+
|
|
231
|
+
for (const expectation of manifest.staticExpectations) {
|
|
232
|
+
if (expectation.type !== 'navigation' && expectation.type !== 'spa_navigation') {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const normalizedFrom = (expectation.fromPath || '').replace(/\/$/, '') || '/';
|
|
237
|
+
if (normalizedFrom === normalizedBefore) {
|
|
238
|
+
const selectorHint = expectation.selectorHint || '';
|
|
239
|
+
const interactionSelector = interaction.selector || '';
|
|
240
|
+
|
|
241
|
+
if (!selectorHint || !interactionSelector ||
|
|
242
|
+
selectorHint === interactionSelector ||
|
|
243
|
+
selectorHint.includes(interactionSelector) ||
|
|
244
|
+
interactionSelector.includes(selectorHint)) {
|
|
245
|
+
expectations.push(expectation);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Check expectations from intel (AST-based)
|
|
253
|
+
if (manifest.expectations) {
|
|
254
|
+
for (const expectation of manifest.expectations) {
|
|
255
|
+
if (expectation.type === 'navigation' || expectation.type === 'spa_navigation') {
|
|
256
|
+
const selectorHint = expectation.selectorHint || '';
|
|
257
|
+
const interactionSelector = interaction.selector || '';
|
|
258
|
+
const interactionLabel = (interaction.label || '').toLowerCase();
|
|
259
|
+
const expectationLabel = (expectation.promise?.value || '').toLowerCase();
|
|
260
|
+
|
|
261
|
+
if (selectorHint === interactionSelector ||
|
|
262
|
+
(expectationLabel && interactionLabel && expectationLabel.includes(interactionLabel))) {
|
|
263
|
+
expectations.push(expectation);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return expectations;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Find matching route model for navigation target
|
|
274
|
+
*/
|
|
275
|
+
function findMatchingRoute(navigationTarget, routeModels) {
|
|
276
|
+
// Try exact match first
|
|
277
|
+
let matchedRoute = routeModels.find(r => r.path === navigationTarget);
|
|
278
|
+
|
|
279
|
+
if (matchedRoute) {
|
|
280
|
+
return matchedRoute;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Try pattern match for dynamic routes
|
|
284
|
+
for (const route of routeModels) {
|
|
285
|
+
if (route.isDynamic && route.originalPattern) {
|
|
286
|
+
if (matchDynamicPattern(navigationTarget, route.originalPattern)) {
|
|
287
|
+
return route;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Match dynamic pattern against actual path
|
|
297
|
+
*/
|
|
298
|
+
function matchDynamicPattern(actualPath, pattern) {
|
|
299
|
+
if (!actualPath || !pattern) return false;
|
|
300
|
+
|
|
301
|
+
// Convert pattern to regex
|
|
302
|
+
let regexPattern = pattern;
|
|
303
|
+
|
|
304
|
+
// Replace :param with (\w+)
|
|
305
|
+
regexPattern = regexPattern.replace(/:(\w+)/g, '(\\w+)');
|
|
306
|
+
|
|
307
|
+
// Replace [param] with (\w+)
|
|
308
|
+
regexPattern = regexPattern.replace(/\[(\w+)\]/g, '(\\w+)');
|
|
309
|
+
|
|
310
|
+
// Escape other special characters
|
|
311
|
+
regexPattern = regexPattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
312
|
+
|
|
313
|
+
// Restore the capture groups
|
|
314
|
+
regexPattern = regexPattern.replace(/\\\(\\\\w\+\\\)/g, '(\\w+)');
|
|
315
|
+
|
|
316
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
317
|
+
return regex.test(actualPath);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Extract path from URL
|
|
322
|
+
*/
|
|
323
|
+
function extractPathFromUrl(url) {
|
|
324
|
+
if (!url || typeof url !== 'string') return '';
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
const urlObj = new URL(url);
|
|
328
|
+
return urlObj.pathname;
|
|
329
|
+
} catch {
|
|
330
|
+
// Relative URL
|
|
331
|
+
const pathMatch = url.match(/^([^?#]+)/);
|
|
332
|
+
return pathMatch ? pathMatch[1] : url;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|