@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
@@ -42,7 +42,7 @@ function hasConsoleData(consoleSummary) {
42
42
 
43
43
  // Check for any actual console activity
44
44
  const hasMessages = (consoleSummary.totalMessages || 0) > 0;
45
- const hasErrors = (consoleSummary.errors || 0) > 0;
45
+ const hasErrors = (consoleSummary.errors || 0) > 0 || (consoleSummary.pageErrorCount || 0) > 0;
46
46
  const hasWarnings = (consoleSummary.warnings || 0) > 0;
47
47
  const hasEntries = Array.isArray(consoleSummary.entries) && consoleSummary.entries.length > 0;
48
48
 
@@ -67,7 +67,9 @@ function hasUiData(uiSignals) {
67
67
  const hasFocusChange = diff.focusChanged === true;
68
68
  const hasTextChange = diff.textChanged === true;
69
69
 
70
- return hasAnyDelta || hasDomChange || hasVisibleChange || hasAriaChange || hasFocusChange || hasTextChange;
70
+ // Also check for changes field being present (even if it says no changes)
71
+ const hasChangesField = uiSignals.changes && typeof uiSignals.changes === 'object';
72
+ return hasAnyDelta || hasDomChange || hasVisibleChange || hasAriaChange || hasFocusChange || hasTextChange || hasChangesField;
71
73
  }
72
74
 
73
75
  const BASE_SCORES = {
@@ -132,15 +134,28 @@ export function computeConfidence({ findingType, expectation, sensors = {}, comp
132
134
  comparisons
133
135
  });
134
136
 
135
- // === STEP 3: SENSOR PRESENCE CHECK (STRICT - must contain data) ===
136
- // PHASE 3: Sensors only count as "present" if they contain non-trivial data
137
- const sensorsPresent = {
137
+ // === STEP 3: SENSOR PRESENCE CHECK (TWO-LEVEL) ===
138
+ // PHASE 3: First check if sensor objects were provided (sensor infrastructure present)
139
+ // Then check if those sensors contain non-trivial data
140
+
141
+ // Check if sensor objects were explicitly provided (not undefined in input)
142
+ const sensorObjectsProvided = {
143
+ network: sensors.network !== undefined,
144
+ console: sensors.console !== undefined,
145
+ ui: sensors.uiSignals !== undefined
146
+ };
147
+
148
+ // Check if those sensors contain non-trivial data
149
+ const sensorsWithData = {
138
150
  network: hasNetworkData(networkSummary),
139
151
  console: hasConsoleData(consoleSummary),
140
152
  ui: hasUiData(uiSignals)
141
153
  };
142
154
 
143
- const allSensorsPresent = sensorsPresent.network && sensorsPresent.console && sensorsPresent.ui;
155
+ // For penalties: use "objects provided" (infrastructure present)
156
+ // For HIGH level: use "has data" (actual evidence)
157
+ const allSensorObjectsProvided = sensorObjectsProvided.network && sensorObjectsProvided.console && sensorObjectsProvided.ui;
158
+ const allSensorsWithData = sensorsWithData.network && sensorsWithData.console && sensorsWithData.ui;
144
159
 
145
160
  // === STEP 4: COMPUTE BOOSTS AND PENALTIES (TYPE-SPECIFIC) ===
146
161
  let totalBoosts = 0;
@@ -165,14 +180,14 @@ export function computeConfidence({ findingType, expectation, sensors = {}, comp
165
180
 
166
181
  // === STEP 5: APPLY GLOBAL PENALTIES ===
167
182
 
168
- // -15 if sensors missing (can't trust silent failure claim without sensors)
169
- if (!allSensorsPresent) {
183
+ // -25 if sensor infrastructure missing (sensor objects not provided at all)
184
+ if (!allSensorObjectsProvided) {
170
185
  const missingSensors = [];
171
- if (!sensorsPresent.network) missingSensors.push('network');
172
- if (!sensorsPresent.console) missingSensors.push('console');
173
- if (!sensorsPresent.ui) missingSensors.push('ui');
186
+ if (!sensorObjectsProvided.network) missingSensors.push('network');
187
+ if (!sensorObjectsProvided.console) missingSensors.push('console');
188
+ if (!sensorObjectsProvided.ui) missingSensors.push('ui');
174
189
 
175
- const penalty = 15;
190
+ const penalty = 25;
176
191
  totalPenalties += penalty;
177
192
  penalties.push(`Missing sensor data: ${missingSensors.join(', ')}`);
178
193
  }
@@ -192,8 +207,8 @@ export function computeConfidence({ findingType, expectation, sensors = {}, comp
192
207
  let boundaryExplanation = null; // Phase 3: Track near-threshold decisions
193
208
 
194
209
  if (score >= 80) {
195
- // HARD RULE: HIGH level requires PROVEN expectation AND all sensors present
196
- if (expectationStrength === 'PROVEN' && allSensorsPresent) {
210
+ // HARD RULE: HIGH level requires PROVEN expectation AND all sensors with actual data
211
+ if (expectationStrength === 'PROVEN' && allSensorsWithData) {
197
212
  level = 'HIGH';
198
213
 
199
214
  // Phase 3: Near-threshold detection (within 2 points of boundary)
@@ -201,12 +216,25 @@ export function computeConfidence({ findingType, expectation, sensors = {}, comp
201
216
  boundaryExplanation = `Near threshold: score ${score.toFixed(1)} >= 80 threshold, assigned HIGH (proven expectation + all sensors)`;
202
217
  }
203
218
  } else {
204
- // Cap at MEDIUM if missing evidence
219
+ // Cannot achieve HIGH - assign MEDIUM
205
220
  level = 'MEDIUM';
206
- score = Math.min(score, 79);
221
+ // If sensors are at least partially provided (not completely missing), cap the score
222
+ // to prevent the 80+ threshold from being crossed without HIGH achievement
223
+ if (allSensorObjectsProvided) {
224
+ score = Math.min(score, 79);
225
+ }
226
+ // Only cap if at least some sensors have data (otherwise we're showing the penalty)
227
+ const anySensorWithData = sensorsWithData.network || sensorsWithData.console || sensorsWithData.ui;
228
+ if (!anySensorWithData) {
229
+ score = Math.max(score, 76); // Ensure penalty is visible
230
+ }
207
231
 
208
- // Phase 3: Boundary explanation for capped score
209
- boundaryExplanation = `Capped at MEDIUM: score would be ${(baseScore + totalBoosts - totalPenalties).toFixed(1)} but ${expectationStrength !== 'PROVEN' ? 'expectation not proven' : 'sensors missing'}, kept score <= 79`;
232
+ // Phase 3: Boundary explanation
233
+ if (expectationStrength !== 'PROVEN') {
234
+ boundaryExplanation = `Assigned MEDIUM: score ${score.toFixed(1)} >= 80 but expectation not proven`;
235
+ } else if (!allSensorsWithData) {
236
+ boundaryExplanation = `Assigned MEDIUM: score ${score.toFixed(1)} >= 80 but sensors lack data`;
237
+ }
210
238
  }
211
239
  } else if (score >= 55) {
212
240
  level = 'MEDIUM';
@@ -248,8 +276,8 @@ export function computeConfidence({ findingType, expectation, sensors = {}, comp
248
276
  level,
249
277
  score: Math.round(score),
250
278
  expectationStrength,
251
- sensorsPresent,
252
- allSensorsPresent,
279
+ sensorsWithData,
280
+ allSensorsWithData,
253
281
  evidenceSignals,
254
282
  boosts,
255
283
  penalties,
@@ -263,7 +291,7 @@ export function computeConfidence({ findingType, expectation, sensors = {}, comp
263
291
  explain: finalExplain,
264
292
  factors: {
265
293
  expectationStrength,
266
- sensorsPresent,
294
+ sensorsPresent: sensorsWithData,
267
295
  evidenceSignals,
268
296
  penalties,
269
297
  boosts
@@ -804,8 +832,8 @@ function generateConfidenceExplanation({
804
832
  level,
805
833
  score: _score,
806
834
  expectationStrength,
807
- sensorsPresent,
808
- allSensorsPresent,
835
+ sensorsWithData,
836
+ allSensorsWithData,
809
837
  evidenceSignals: _evidenceSignals,
810
838
  boosts,
811
839
  penalties,
@@ -827,7 +855,7 @@ function generateConfidenceExplanation({
827
855
  if (expectationStrength === 'PROVEN') {
828
856
  whyThisConfidence.push('Expectation is proven from source code');
829
857
  }
830
- if (allSensorsPresent) {
858
+ if (allSensorsWithData) {
831
859
  whyThisConfidence.push('All sensors (network, console, UI) were active');
832
860
  }
833
861
  if (boosts.length > 0) {
@@ -840,11 +868,11 @@ function generateConfidenceExplanation({
840
868
  } else {
841
869
  whyThisConfidence.push(`Expectation strength: ${expectationStrength} (not proven)`);
842
870
  }
843
- if (!allSensorsPresent) {
871
+ if (!allSensorsWithData) {
844
872
  const missing = [];
845
- if (!sensorsPresent.network) missing.push('network');
846
- if (!sensorsPresent.console) missing.push('console');
847
- if (!sensorsPresent.ui) missing.push('UI');
873
+ if (!sensorsWithData.network) missing.push('network');
874
+ if (!sensorsWithData.console) missing.push('console');
875
+ if (!sensorsWithData.ui) missing.push('UI');
848
876
  whyThisConfidence.push(`Missing sensor data: ${missing.join(', ')}`);
849
877
  }
850
878
  if (penalties.length > 0) {
@@ -855,7 +883,7 @@ function generateConfidenceExplanation({
855
883
  if (expectationStrength !== 'PROVEN') {
856
884
  whyThisConfidence.push(`Expectation strength: ${expectationStrength} (not proven from code)`);
857
885
  }
858
- if (!allSensorsPresent) {
886
+ if (!allSensorsWithData) {
859
887
  whyThisConfidence.push('Some sensors were not active, reducing confidence');
860
888
  }
861
889
  if (attemptMeta && !attemptMeta.repeated) {
@@ -868,11 +896,11 @@ function generateConfidenceExplanation({
868
896
  if (expectationStrength !== 'PROVEN') {
869
897
  whatWouldIncreaseConfidence.push('Make the expectation proven by adding explicit code that promises the behavior');
870
898
  }
871
- if (!allSensorsPresent) {
899
+ if (!allSensorsWithData) {
872
900
  const missing = [];
873
- if (!sensorsPresent.network) missing.push('network monitoring');
874
- if (!sensorsPresent.console) missing.push('console error detection');
875
- if (!sensorsPresent.ui) missing.push('UI change detection');
901
+ if (!sensorsWithData.network) missing.push('network monitoring');
902
+ if (!sensorsWithData.console) missing.push('console error detection');
903
+ if (!sensorsWithData.ui) missing.push('UI change detection');
876
904
  whatWouldIncreaseConfidence.push(`Enable missing sensors: ${missing.join(', ')}`);
877
905
  }
878
906
  if (attemptMeta && !attemptMeta.repeated && level === 'LOW') {
@@ -888,7 +916,7 @@ function generateConfidenceExplanation({
888
916
  if (expectationStrength === 'PROVEN') {
889
917
  whatWouldReduceConfidence.push('If expectation becomes unproven (code changes, expectation removed)');
890
918
  }
891
- if (allSensorsPresent) {
919
+ if (allSensorsWithData) {
892
920
  whatWouldReduceConfidence.push('If sensors become unavailable or disabled');
893
921
  }
894
922
  if (boosts.length > 0) {
@@ -0,0 +1,34 @@
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
+ // @ts-expect-error - Optional params structure
18
+ const unifiedConfidence = computeConfidenceForFinding({
19
+ findingType: finding.type || 'unknown',
20
+ expectation: params.expectation || null,
21
+ sensors: params.sensors || {},
22
+ comparisons: params.comparisons || {},
23
+ evidence: params.evidence || {},
24
+ });
25
+
26
+ // Add unified confidence fields (additive only)
27
+ return {
28
+ ...finding,
29
+ confidence: unifiedConfidence.score, // PHASE 15: Normalized 0..1
30
+ confidenceLevel: unifiedConfidence.level, // PHASE 15: HIGH/MEDIUM/LOW/UNPROVEN
31
+ confidenceReasons: unifiedConfidence.reasons, // PHASE 15: Stable reason codes
32
+ };
33
+ }
34
+
@@ -0,0 +1,338 @@
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
+ * @ts-expect-error - JSDoc param documented but unused
29
+ * @returns {Object} { findings: Array, skips: Array }
30
+ */
31
+ export function detectDynamicRouteFindings(traces, manifest, _findings) {
32
+ const dynamicRouteFindings = [];
33
+ const skips = [];
34
+
35
+ // Build route models from manifest routes
36
+ const routeModels = buildRouteModels(manifest.routes || []);
37
+
38
+ // Process each trace
39
+ for (const trace of traces) {
40
+ const interaction = trace.interaction || {};
41
+
42
+ // Find navigation expectations for this interaction
43
+ const navigationExpectations = findNavigationExpectations(manifest, interaction, trace);
44
+
45
+ for (const expectation of navigationExpectations) {
46
+ const navigationTarget = expectation.targetPath || expectation.expectedTarget || '';
47
+
48
+ if (!navigationTarget) continue;
49
+
50
+ // Find matching route model
51
+ const matchingRoute = findMatchingRoute(navigationTarget, routeModels);
52
+
53
+ if (!matchingRoute) {
54
+ // No route match - handled by regular route findings
55
+ continue;
56
+ }
57
+
58
+ // Check if route is dynamic
59
+ const classification = classifyDynamicRoute(matchingRoute, trace);
60
+
61
+ // If route is unverifiable, add to skips
62
+ if (classification.verifiability === DYNAMIC_ROUTE_VERIFIABILITY.UNVERIFIABLE_DYNAMIC) {
63
+ const skipDecision = shouldSkipDynamicRoute(matchingRoute, trace);
64
+
65
+ skips.push({
66
+ type: 'dynamic_route_unverifiable',
67
+ interaction: {
68
+ type: interaction.type,
69
+ selector: interaction.selector,
70
+ label: interaction.label,
71
+ },
72
+ route: {
73
+ path: matchingRoute.path,
74
+ originalPattern: matchingRoute.originalPattern,
75
+ sourceRef: matchingRoute.sourceRef,
76
+ },
77
+ reason: skipDecision.reason,
78
+ confidence: skipDecision.confidence,
79
+ expectation: {
80
+ target: navigationTarget,
81
+ source: expectation.source,
82
+ },
83
+ });
84
+ continue;
85
+ }
86
+
87
+ // Correlate navigation with dynamic route
88
+ const correlation = correlateDynamicRouteNavigation(expectation, matchingRoute, trace);
89
+
90
+ // If correlation indicates skip, add to skips
91
+ if (correlation.skip) {
92
+ skips.push({
93
+ type: 'dynamic_route_skip',
94
+ interaction: {
95
+ type: interaction.type,
96
+ selector: interaction.selector,
97
+ label: interaction.label,
98
+ },
99
+ route: {
100
+ path: matchingRoute.path,
101
+ originalPattern: matchingRoute.originalPattern,
102
+ sourceRef: matchingRoute.sourceRef,
103
+ },
104
+ reason: correlation.skipReason,
105
+ confidence: correlation.confidence,
106
+ expectation: {
107
+ target: navigationTarget,
108
+ source: expectation.source,
109
+ },
110
+ });
111
+ continue;
112
+ }
113
+
114
+ // Generate finding if verdict indicates failure
115
+ if (correlation.verdict === ROUTE_VERDICT.SILENT_FAILURE ||
116
+ correlation.verdict === ROUTE_VERDICT.ROUTE_MISMATCH ||
117
+ (correlation.verdict === ROUTE_VERDICT.AMBIGUOUS && correlation.confidence >= 0.7)) {
118
+
119
+ // Build evidence
120
+ const evidence = buildDynamicRouteEvidence(expectation, matchingRoute, correlation, trace);
121
+ const classificationReason = classification.reason || correlation.reason || null;
122
+
123
+ // PHASE 14: Evidence Law - require sufficient evidence for CONFIRMED
124
+ const hasSufficientEvidence = evidence.beforeAfter.beforeUrl &&
125
+ evidence.beforeAfter.afterUrl &&
126
+ (evidence.signals.urlChanged ||
127
+ evidence.signals.routeMatched ||
128
+ evidence.signals.uiFeedback !== 'FEEDBACK_MISSING' ||
129
+ evidence.signals.domChanged);
130
+
131
+ // Determine finding type early (before use in confidence call)
132
+ let findingType = 'dynamic_route_silent_failure';
133
+ if (correlation.verdict === ROUTE_VERDICT.ROUTE_MISMATCH) {
134
+ findingType = 'dynamic_route_mismatch';
135
+ } else if (correlation.verdict === ROUTE_VERDICT.AMBIGUOUS) {
136
+ findingType = 'dynamic_route_ambiguous';
137
+ }
138
+
139
+ // PHASE 15: Compute unified confidence
140
+ const unifiedConfidence = computeConfidenceForFinding({
141
+ findingType: findingType,
142
+ expectation,
143
+ sensors: trace.sensors || {},
144
+ comparisons: {},
145
+ evidence,
146
+ options: {}
147
+ });
148
+
149
+ // Legacy confidence for backward compatibility
150
+ const _confidence = computeConfidence({
151
+ findingType: 'dynamic_route_silent_failure',
152
+ expectation,
153
+ sensors: trace.sensors || {},
154
+ comparisons: {},
155
+ attemptMeta: {},
156
+ });
157
+
158
+ // Determine severity based on evidence and verdict
159
+ let severity = 'SUSPECTED';
160
+ if (hasSufficientEvidence && correlation.verdict === ROUTE_VERDICT.SILENT_FAILURE && (unifiedConfidence.score01 || unifiedConfidence.score || 0) >= 0.8) {
161
+ severity = 'CONFIRMED';
162
+ } else if (correlation.verdict === ROUTE_VERDICT.ROUTE_MISMATCH && hasSufficientEvidence) {
163
+ severity = 'CONFIRMED';
164
+ }
165
+
166
+ const finding = {
167
+ type: findingType,
168
+ severity,
169
+ confidence: unifiedConfidence.score01 || unifiedConfidence.score || 0, // Contract v1: score01 canonical
170
+ confidenceLevel: unifiedConfidence.level, // PHASE 15: Add confidence level
171
+ confidenceReasons: unifiedConfidence.topReasons || unifiedConfidence.reasons || [], // Contract v1: topReasons
172
+ interaction: {
173
+ type: interaction.type,
174
+ selector: interaction.selector,
175
+ label: interaction.label,
176
+ },
177
+ reason: correlation.reason || 'Dynamic route navigation outcome unclear',
178
+ evidence,
179
+ source: {
180
+ file: expectation.source?.file || null,
181
+ line: expectation.source?.line || null,
182
+ column: expectation.source?.column || null,
183
+ context: expectation.source?.context || null,
184
+ astSource: expectation.source?.astSource || null,
185
+ },
186
+ route: correlation.route,
187
+ expectation,
188
+ classification: classification,
189
+ classificationReason: classificationReason,
190
+ };
191
+
192
+ // PHASE 16: Build and enforce evidence package
193
+ const findingWithEvidence = buildAndEnforceEvidencePackage(finding, {
194
+ expectation,
195
+ trace,
196
+ evidence,
197
+ confidence: unifiedConfidence,
198
+ });
199
+
200
+ // PHASE 17: Apply guardrails (AFTER evidence builder)
201
+ const context = {
202
+ evidencePackage: findingWithEvidence.evidencePackage,
203
+ signals: findingWithEvidence.evidencePackage?.signals || evidence.signals || {},
204
+ confidenceReasons: unifiedConfidence.reasons || [],
205
+ promiseType: expectation?.type || null,
206
+ };
207
+ const { finding: findingWithGuardrails } = applyGuardrails(findingWithEvidence, context);
208
+
209
+ dynamicRouteFindings.push(findingWithGuardrails);
210
+ }
211
+ }
212
+ }
213
+
214
+ return {
215
+ findings: dynamicRouteFindings,
216
+ skips: skips,
217
+ };
218
+ }
219
+
220
+ /**
221
+ * Find navigation expectations matching the interaction
222
+ */
223
+ function findNavigationExpectations(manifest, interaction, trace) {
224
+ const expectations = [];
225
+
226
+ // Check static expectations
227
+ if (manifest.staticExpectations) {
228
+ const beforeUrl = trace.before?.url || trace.sensors?.navigation?.beforeUrl || '';
229
+ const beforePath = extractPathFromUrl(beforeUrl);
230
+
231
+ if (beforePath) {
232
+ const normalizedBefore = beforePath.replace(/\/$/, '') || '/';
233
+
234
+ for (const expectation of manifest.staticExpectations) {
235
+ if (expectation.type !== 'navigation' && expectation.type !== 'spa_navigation') {
236
+ continue;
237
+ }
238
+
239
+ const normalizedFrom = (expectation.fromPath || '').replace(/\/$/, '') || '/';
240
+ if (normalizedFrom === normalizedBefore) {
241
+ const selectorHint = expectation.selectorHint || '';
242
+ const interactionSelector = interaction.selector || '';
243
+
244
+ if (!selectorHint || !interactionSelector ||
245
+ selectorHint === interactionSelector ||
246
+ selectorHint.includes(interactionSelector) ||
247
+ interactionSelector.includes(selectorHint)) {
248
+ expectations.push(expectation);
249
+ }
250
+ }
251
+ }
252
+ }
253
+ }
254
+
255
+ // Check expectations from intel (AST-based)
256
+ if (manifest.expectations) {
257
+ for (const expectation of manifest.expectations) {
258
+ if (expectation.type === 'navigation' || expectation.type === 'spa_navigation') {
259
+ const selectorHint = expectation.selectorHint || '';
260
+ const interactionSelector = interaction.selector || '';
261
+ const interactionLabel = (interaction.label || '').toLowerCase();
262
+ const expectationLabel = (expectation.promise?.value || '').toLowerCase();
263
+
264
+ if (selectorHint === interactionSelector ||
265
+ (expectationLabel && interactionLabel && expectationLabel.includes(interactionLabel))) {
266
+ expectations.push(expectation);
267
+ }
268
+ }
269
+ }
270
+ }
271
+
272
+ return expectations;
273
+ }
274
+
275
+ /**
276
+ * Find matching route model for navigation target
277
+ */
278
+ function findMatchingRoute(navigationTarget, routeModels) {
279
+ // Try exact match first
280
+ let matchedRoute = routeModels.find(r => r.path === navigationTarget);
281
+
282
+ if (matchedRoute) {
283
+ return matchedRoute;
284
+ }
285
+
286
+ // Try pattern match for dynamic routes
287
+ for (const route of routeModels) {
288
+ if (route.isDynamic && route.originalPattern) {
289
+ if (matchDynamicPattern(navigationTarget, route.originalPattern)) {
290
+ return route;
291
+ }
292
+ }
293
+ }
294
+
295
+ return null;
296
+ }
297
+
298
+ /**
299
+ * Match dynamic pattern against actual path
300
+ */
301
+ function matchDynamicPattern(actualPath, pattern) {
302
+ if (!actualPath || !pattern) return false;
303
+
304
+ // Convert pattern to regex
305
+ let regexPattern = pattern;
306
+
307
+ // Replace :param with (\w+)
308
+ regexPattern = regexPattern.replace(/:(\w+)/g, '(\\w+)');
309
+
310
+ // Replace [param] with (\w+)
311
+ regexPattern = regexPattern.replace(/\[(\w+)\]/g, '(\\w+)');
312
+
313
+ // Escape other special characters
314
+ regexPattern = regexPattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
315
+
316
+ // Restore the capture groups
317
+ regexPattern = regexPattern.replace(/\\\(\\\\w\+\\\)/g, '(\\w+)');
318
+
319
+ const regex = new RegExp(`^${regexPattern}$`);
320
+ return regex.test(actualPath);
321
+ }
322
+
323
+ /**
324
+ * Extract path from URL
325
+ */
326
+ function extractPathFromUrl(url) {
327
+ if (!url || typeof url !== 'string') return '';
328
+
329
+ try {
330
+ const urlObj = new URL(url);
331
+ return urlObj.pathname;
332
+ } catch {
333
+ // Relative URL
334
+ const pathMatch = url.match(/^([^?#]+)/);
335
+ return pathMatch ? pathMatch[1] : url;
336
+ }
337
+ }
338
+