@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.
Files changed (217) hide show
  1. package/README.md +14 -18
  2. package/bin/verax.js +7 -0
  3. package/package.json +15 -5
  4. package/src/cli/commands/baseline.js +104 -0
  5. package/src/cli/commands/default.js +323 -111
  6. package/src/cli/commands/doctor.js +36 -4
  7. package/src/cli/commands/ga.js +243 -0
  8. package/src/cli/commands/gates.js +95 -0
  9. package/src/cli/commands/inspect.js +131 -2
  10. package/src/cli/commands/release-check.js +213 -0
  11. package/src/cli/commands/run.js +498 -103
  12. package/src/cli/commands/security-check.js +211 -0
  13. package/src/cli/commands/truth.js +114 -0
  14. package/src/cli/entry.js +305 -68
  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 +546 -0
  20. package/src/cli/util/ast-network-detector.js +603 -0
  21. package/src/cli/util/ast-usestate-detector.js +602 -0
  22. package/src/cli/util/bootstrap-guard.js +86 -0
  23. package/src/cli/util/detection-engine.js +4 -3
  24. package/src/cli/util/determinism-runner.js +123 -0
  25. package/src/cli/util/determinism-writer.js +129 -0
  26. package/src/cli/util/env-url.js +4 -0
  27. package/src/cli/util/events.js +76 -0
  28. package/src/cli/util/expectation-extractor.js +380 -74
  29. package/src/cli/util/findings-writer.js +126 -15
  30. package/src/cli/util/learn-writer.js +3 -1
  31. package/src/cli/util/observation-engine.js +69 -23
  32. package/src/cli/util/observe-writer.js +3 -1
  33. package/src/cli/util/paths.js +6 -14
  34. package/src/cli/util/project-discovery.js +23 -0
  35. package/src/cli/util/project-writer.js +3 -1
  36. package/src/cli/util/redact.js +2 -2
  37. package/src/cli/util/run-resolver.js +64 -0
  38. package/src/cli/util/runtime-budget.js +147 -0
  39. package/src/cli/util/source-requirement.js +55 -0
  40. package/src/cli/util/summary-writer.js +13 -1
  41. package/src/cli/util/svelte-navigation-detector.js +163 -0
  42. package/src/cli/util/svelte-network-detector.js +80 -0
  43. package/src/cli/util/svelte-sfc-extractor.js +147 -0
  44. package/src/cli/util/svelte-state-detector.js +243 -0
  45. package/src/cli/util/vue-navigation-detector.js +177 -0
  46. package/src/cli/util/vue-sfc-extractor.js +162 -0
  47. package/src/cli/util/vue-state-detector.js +215 -0
  48. package/src/types/global.d.ts +28 -0
  49. package/src/types/ts-ast.d.ts +24 -0
  50. package/src/verax/cli/doctor.js +2 -2
  51. package/src/verax/cli/finding-explainer.js +56 -3
  52. package/src/verax/cli/init.js +1 -1
  53. package/src/verax/cli/url-safety.js +12 -2
  54. package/src/verax/cli/wizard.js +13 -2
  55. package/src/verax/core/artifacts/registry.js +154 -0
  56. package/src/verax/core/artifacts/verifier.js +980 -0
  57. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  58. package/src/verax/core/baseline/baseline.snapshot.js +231 -0
  59. package/src/verax/core/budget-engine.js +1 -1
  60. package/src/verax/core/capabilities/gates.js +499 -0
  61. package/src/verax/core/capabilities/registry.js +475 -0
  62. package/src/verax/core/confidence/confidence-compute.js +137 -0
  63. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  64. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  65. package/src/verax/core/confidence/confidence-weights.js +44 -0
  66. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  67. package/src/verax/core/confidence/confidence.loader.js +79 -0
  68. package/src/verax/core/confidence/confidence.schema.js +94 -0
  69. package/src/verax/core/confidence-engine-refactor.js +484 -0
  70. package/src/verax/core/confidence-engine.js +486 -0
  71. package/src/verax/core/confidence-engine.js.backup +471 -0
  72. package/src/verax/core/contracts/index.js +29 -0
  73. package/src/verax/core/contracts/types.js +185 -0
  74. package/src/verax/core/contracts/validators.js +381 -0
  75. package/src/verax/core/decision-snapshot.js +31 -4
  76. package/src/verax/core/decisions/decision.trace.js +276 -0
  77. package/src/verax/core/determinism/contract-writer.js +89 -0
  78. package/src/verax/core/determinism/contract.js +139 -0
  79. package/src/verax/core/determinism/diff.js +364 -0
  80. package/src/verax/core/determinism/engine.js +221 -0
  81. package/src/verax/core/determinism/finding-identity.js +148 -0
  82. package/src/verax/core/determinism/normalize.js +438 -0
  83. package/src/verax/core/determinism/report-writer.js +92 -0
  84. package/src/verax/core/determinism/run-fingerprint.js +118 -0
  85. package/src/verax/core/determinism-model.js +35 -6
  86. package/src/verax/core/dynamic-route-intelligence.js +528 -0
  87. package/src/verax/core/evidence/evidence-capture-service.js +307 -0
  88. package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
  89. package/src/verax/core/evidence-builder.js +487 -0
  90. package/src/verax/core/execution-mode-context.js +77 -0
  91. package/src/verax/core/execution-mode-detector.js +190 -0
  92. package/src/verax/core/failures/exit-codes.js +86 -0
  93. package/src/verax/core/failures/failure-summary.js +76 -0
  94. package/src/verax/core/failures/failure.factory.js +225 -0
  95. package/src/verax/core/failures/failure.ledger.js +132 -0
  96. package/src/verax/core/failures/failure.types.js +196 -0
  97. package/src/verax/core/failures/index.js +10 -0
  98. package/src/verax/core/ga/ga-report-writer.js +43 -0
  99. package/src/verax/core/ga/ga.artifact.js +49 -0
  100. package/src/verax/core/ga/ga.contract.js +434 -0
  101. package/src/verax/core/ga/ga.enforcer.js +86 -0
  102. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  103. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  104. package/src/verax/core/guardrails/policy.loader.js +83 -0
  105. package/src/verax/core/guardrails/policy.schema.js +110 -0
  106. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  107. package/src/verax/core/guardrails-engine.js +505 -0
  108. package/src/verax/core/incremental-store.js +15 -7
  109. package/src/verax/core/observe/run-timeline.js +316 -0
  110. package/src/verax/core/perf/perf.contract.js +186 -0
  111. package/src/verax/core/perf/perf.display.js +65 -0
  112. package/src/verax/core/perf/perf.enforcer.js +91 -0
  113. package/src/verax/core/perf/perf.monitor.js +209 -0
  114. package/src/verax/core/perf/perf.report.js +198 -0
  115. package/src/verax/core/pipeline-tracker.js +238 -0
  116. package/src/verax/core/product-definition.js +127 -0
  117. package/src/verax/core/release/provenance.builder.js +271 -0
  118. package/src/verax/core/release/release-report-writer.js +40 -0
  119. package/src/verax/core/release/release.enforcer.js +159 -0
  120. package/src/verax/core/release/reproducibility.check.js +221 -0
  121. package/src/verax/core/release/sbom.builder.js +283 -0
  122. package/src/verax/core/replay-validator.js +4 -4
  123. package/src/verax/core/replay.js +1 -1
  124. package/src/verax/core/report/cross-index.js +192 -0
  125. package/src/verax/core/report/human-summary.js +222 -0
  126. package/src/verax/core/route-intelligence.js +419 -0
  127. package/src/verax/core/security/secrets.scan.js +326 -0
  128. package/src/verax/core/security/security-report.js +50 -0
  129. package/src/verax/core/security/security.enforcer.js +124 -0
  130. package/src/verax/core/security/supplychain.defaults.json +38 -0
  131. package/src/verax/core/security/supplychain.policy.js +326 -0
  132. package/src/verax/core/security/vuln.scan.js +265 -0
  133. package/src/verax/core/silence-impact.js +1 -1
  134. package/src/verax/core/silence-model.js +9 -7
  135. package/src/verax/core/truth/truth.certificate.js +250 -0
  136. package/src/verax/core/ui-feedback-intelligence.js +515 -0
  137. package/src/verax/detect/comparison.js +8 -3
  138. package/src/verax/detect/confidence-engine.js +645 -57
  139. package/src/verax/detect/confidence-helper.js +33 -0
  140. package/src/verax/detect/detection-engine.js +19 -2
  141. package/src/verax/detect/dynamic-route-findings.js +335 -0
  142. package/src/verax/detect/evidence-index.js +15 -65
  143. package/src/verax/detect/expectation-chain-detector.js +417 -0
  144. package/src/verax/detect/expectation-model.js +56 -3
  145. package/src/verax/detect/explanation-helpers.js +1 -1
  146. package/src/verax/detect/finding-detector.js +2 -2
  147. package/src/verax/detect/findings-writer.js +149 -20
  148. package/src/verax/detect/flow-detector.js +4 -4
  149. package/src/verax/detect/index.js +265 -15
  150. package/src/verax/detect/interactive-findings.js +3 -4
  151. package/src/verax/detect/journey-stall-detector.js +558 -0
  152. package/src/verax/detect/route-findings.js +218 -0
  153. package/src/verax/detect/signal-mapper.js +2 -2
  154. package/src/verax/detect/skip-classifier.js +4 -4
  155. package/src/verax/detect/ui-feedback-findings.js +207 -0
  156. package/src/verax/detect/verdict-engine.js +61 -9
  157. package/src/verax/detect/view-switch-correlator.js +242 -0
  158. package/src/verax/flow/flow-engine.js +3 -2
  159. package/src/verax/flow/flow-spec.js +1 -2
  160. package/src/verax/index.js +413 -33
  161. package/src/verax/intel/effect-detector.js +1 -1
  162. package/src/verax/intel/index.js +2 -2
  163. package/src/verax/intel/route-extractor.js +3 -3
  164. package/src/verax/intel/vue-navigation-extractor.js +81 -18
  165. package/src/verax/intel/vue-router-extractor.js +4 -2
  166. package/src/verax/learn/action-contract-extractor.js +684 -66
  167. package/src/verax/learn/ast-contract-extractor.js +53 -1
  168. package/src/verax/learn/index.js +36 -2
  169. package/src/verax/learn/manifest-writer.js +28 -14
  170. package/src/verax/learn/route-extractor.js +1 -1
  171. package/src/verax/learn/route-validator.js +12 -8
  172. package/src/verax/learn/state-extractor.js +1 -1
  173. package/src/verax/learn/static-extractor-navigation.js +1 -1
  174. package/src/verax/learn/static-extractor-validation.js +2 -2
  175. package/src/verax/learn/static-extractor.js +8 -7
  176. package/src/verax/learn/ts-contract-resolver.js +14 -12
  177. package/src/verax/observe/browser.js +22 -3
  178. package/src/verax/observe/console-sensor.js +2 -2
  179. package/src/verax/observe/expectation-executor.js +2 -1
  180. package/src/verax/observe/focus-sensor.js +1 -1
  181. package/src/verax/observe/human-driver.js +29 -10
  182. package/src/verax/observe/index.js +92 -844
  183. package/src/verax/observe/interaction-discovery.js +27 -15
  184. package/src/verax/observe/interaction-runner.js +31 -14
  185. package/src/verax/observe/loading-sensor.js +6 -0
  186. package/src/verax/observe/navigation-sensor.js +1 -1
  187. package/src/verax/observe/observe-context.js +205 -0
  188. package/src/verax/observe/observe-helpers.js +191 -0
  189. package/src/verax/observe/observe-runner.js +226 -0
  190. package/src/verax/observe/observers/budget-observer.js +185 -0
  191. package/src/verax/observe/observers/console-observer.js +102 -0
  192. package/src/verax/observe/observers/coverage-observer.js +107 -0
  193. package/src/verax/observe/observers/interaction-observer.js +471 -0
  194. package/src/verax/observe/observers/navigation-observer.js +132 -0
  195. package/src/verax/observe/observers/network-observer.js +87 -0
  196. package/src/verax/observe/observers/safety-observer.js +82 -0
  197. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  198. package/src/verax/observe/settle.js +1 -0
  199. package/src/verax/observe/state-sensor.js +8 -4
  200. package/src/verax/observe/state-ui-sensor.js +7 -1
  201. package/src/verax/observe/traces-writer.js +27 -16
  202. package/src/verax/observe/ui-feedback-detector.js +742 -0
  203. package/src/verax/observe/ui-signal-sensor.js +155 -2
  204. package/src/verax/scan-summary-writer.js +46 -9
  205. package/src/verax/shared/artifact-manager.js +9 -6
  206. package/src/verax/shared/budget-profiles.js +2 -2
  207. package/src/verax/shared/caching.js +1 -1
  208. package/src/verax/shared/config-loader.js +1 -2
  209. package/src/verax/shared/css-spinner-rules.js +204 -0
  210. package/src/verax/shared/dynamic-route-utils.js +12 -6
  211. package/src/verax/shared/retry-policy.js +1 -6
  212. package/src/verax/shared/root-artifacts.js +1 -1
  213. package/src/verax/shared/view-switch-rules.js +208 -0
  214. package/src/verax/shared/zip-artifacts.js +1 -0
  215. package/src/verax/validate/context-validator.js +1 -1
  216. package/src/verax/observe/index.js.backup +0 -1
  217. package/src/verax/validate/context-validator.js.bak +0 -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.0.0'
54
+ version: '1.1.0',
55
+ phaseFeatures: {
56
+ expectationContinuity: true,
57
+ journeyStallDetection: true
58
+ }
42
59
  };
43
60
  }
44
61
 
@@ -77,7 +94,7 @@ class DetectionEngine {
77
94
  * Classify a single expectation against observations
78
95
  * @private
79
96
  */
80
- _classifyExpectation(expectation, observationMap, allObservations) {
97
+ _classifyExpectation(expectation, observationMap, _allObservations) {
81
98
  const expectationId = expectation.id;
82
99
  const promise = expectation.promise || {};
83
100
 
@@ -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
+
@@ -8,18 +8,15 @@
8
8
  * with scope='evidence', reason='evidence_missing', preserving full context.
9
9
  */
10
10
 
11
- import fs from 'fs';
12
- import path from 'path';
11
+ // fs and path imports removed - currently unused
13
12
 
14
13
  /**
15
14
  * Build evidence index from observation traces with validation.
16
15
  *
17
16
  * @param {Array} traces - Observation traces from observe phase
18
- * @param {string|null} projectDir - Project directory for file validation (null to skip validation)
19
- * @param {Object|null} silenceTracker - Silence tracker instance (null to skip silence tracking)
20
17
  * @returns {Object} { evidenceIndex, expectationEvidenceMap, findingEvidenceMap }
21
18
  */
22
- export function buildEvidenceIndex(traces, projectDir = null, silenceTracker = null) {
19
+ export function buildEvidenceIndex(traces, _projectDir = null, _silenceTracker = null) {
23
20
  const evidenceIndex = [];
24
21
  const expectationEvidenceMap = new Map();
25
22
  const findingEvidenceMap = new Map();
@@ -29,13 +26,13 @@ export function buildEvidenceIndex(traces, projectDir = null, silenceTracker = n
29
26
  return { evidenceIndex, expectationEvidenceMap, findingEvidenceMap };
30
27
  }
31
28
 
32
- // Use fs/path for evidence validation if projectDir provided
33
- let existsSync = null;
34
- let resolvePath = null;
35
- if (projectDir && fs && path) {
36
- existsSync = fs.existsSync;
37
- resolvePath = path.resolve;
38
- }
29
+ // Use fs/path for evidence validation if projectDir provided (currently unused)
30
+ // let existsSync = null;
31
+ // let resolvePath = null;
32
+ // if (projectDir && fs && path) {
33
+ // existsSync = fs.existsSync;
34
+ // resolvePath = path.resolve;
35
+ // }
39
36
 
40
37
  for (const trace of traces) {
41
38
  // Prefer modern trace schema: trace.before/trace.after
@@ -44,56 +41,7 @@ export function buildEvidenceIndex(traces, projectDir = null, silenceTracker = n
44
41
  let beforeScreenshot = trace.before?.screenshot ?? trace.evidence?.beforeScreenshot ?? null;
45
42
  let afterScreenshot = trace.after?.screenshot ?? trace.evidence?.afterScreenshot ?? null;
46
43
 
47
- // PHASE 3: Validate evidence file existence
48
- if (existsSync && resolvePath && projectDir) {
49
- const veraxDir = resolvePath(projectDir, '.veraxverax');
50
-
51
- // Check beforeScreenshot exists
52
- if (beforeScreenshot) {
53
- const beforePath = resolvePath(veraxDir, 'observe', beforeScreenshot);
54
- const fileExists = existsSync(beforePath);
55
-
56
- if (!fileExists) {
57
- // Track missing evidence as silence
58
- if (silenceTracker) {
59
- silenceTracker.record({
60
- scope: 'evidence',
61
- reason: 'evidence_missing',
62
- description: `Screenshot evidence file not found: ${beforeScreenshot}`,
63
- context: {
64
- expectationId: trace.expectationId,
65
- interaction: trace.interaction?.label,
66
- expectedPath: beforePath
67
- },
68
- impact: 'incomplete_check'
69
- });
70
- }
71
- beforeScreenshot = null; // Remove invalid evidence reference
72
- }
73
- }
74
-
75
- // Check afterScreenshot exists
76
- if (afterScreenshot) {
77
- const afterPath = resolvePath(veraxDir, 'observe', afterScreenshot);
78
- if (!existsSync(afterPath)) {
79
- // Track missing evidence as silence
80
- if (silenceTracker) {
81
- silenceTracker.record({
82
- scope: 'evidence',
83
- reason: 'evidence_missing',
84
- description: `Screenshot evidence file not found: ${afterScreenshot}`,
85
- context: {
86
- expectationId: trace.expectationId,
87
- interaction: trace.interaction?.label,
88
- expectedPath: afterPath
89
- },
90
- impact: 'incomplete_check'
91
- });
92
- }
93
- afterScreenshot = null; // Remove invalid evidence reference
94
- }
95
- }
96
- }
44
+ // PHASE 3: Evidence file validation removed - screenshots stored in .verax/runs/<runId>/evidence/
97
45
 
98
46
  const entry = {
99
47
  id: `ev-${id}`,
@@ -135,12 +83,14 @@ export function buildEvidenceIndex(traces, projectDir = null, silenceTracker = n
135
83
  * @param {string} findingsPath - Path to findings.json
136
84
  * @returns {Promise<string>} Path to written evidence-index.json
137
85
  */
138
- export async function writeEvidenceIndex(projectDir, evidenceIndex, tracesPath, findingsPath, runDirOpt = null) {
86
+ export async function writeEvidenceIndex(projectDir, evidenceIndex, tracesPath, findingsPath, runDirOpt) {
139
87
  const { resolve } = await import('path');
140
88
  const { mkdirSync, writeFileSync } = await import('fs');
141
89
 
142
- // Prefer canonical run directory when provided
143
- const artifactsDir = runDirOpt ? resolve(runDirOpt) : resolve(projectDir, '.veraxverax', 'artifacts');
90
+ if (!runDirOpt) {
91
+ throw new Error('runDirOpt is required');
92
+ }
93
+ const artifactsDir = resolve(runDirOpt, 'evidence');
144
94
  mkdirSync(artifactsDir, { recursive: true });
145
95
 
146
96
  const evidenceIndexPath = resolve(artifactsDir, 'evidence-index.json');