@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.
Files changed (152) hide show
  1. package/README.md +14 -18
  2. package/bin/verax.js +7 -0
  3. package/package.json +3 -3
  4. package/src/cli/commands/baseline.js +104 -0
  5. package/src/cli/commands/default.js +79 -25
  6. package/src/cli/commands/ga.js +243 -0
  7. package/src/cli/commands/gates.js +95 -0
  8. package/src/cli/commands/inspect.js +131 -2
  9. package/src/cli/commands/release-check.js +213 -0
  10. package/src/cli/commands/run.js +246 -35
  11. package/src/cli/commands/security-check.js +211 -0
  12. package/src/cli/commands/truth.js +114 -0
  13. package/src/cli/entry.js +304 -67
  14. package/src/cli/util/angular-component-extractor.js +179 -0
  15. package/src/cli/util/angular-navigation-detector.js +141 -0
  16. package/src/cli/util/angular-network-detector.js +161 -0
  17. package/src/cli/util/angular-state-detector.js +162 -0
  18. package/src/cli/util/ast-interactive-detector.js +546 -0
  19. package/src/cli/util/ast-network-detector.js +603 -0
  20. package/src/cli/util/ast-usestate-detector.js +602 -0
  21. package/src/cli/util/bootstrap-guard.js +86 -0
  22. package/src/cli/util/determinism-runner.js +123 -0
  23. package/src/cli/util/determinism-writer.js +129 -0
  24. package/src/cli/util/env-url.js +4 -0
  25. package/src/cli/util/expectation-extractor.js +369 -73
  26. package/src/cli/util/findings-writer.js +126 -16
  27. package/src/cli/util/learn-writer.js +3 -1
  28. package/src/cli/util/observe-writer.js +3 -1
  29. package/src/cli/util/paths.js +3 -12
  30. package/src/cli/util/project-discovery.js +3 -0
  31. package/src/cli/util/project-writer.js +3 -1
  32. package/src/cli/util/run-resolver.js +64 -0
  33. package/src/cli/util/source-requirement.js +55 -0
  34. package/src/cli/util/summary-writer.js +1 -0
  35. package/src/cli/util/svelte-navigation-detector.js +163 -0
  36. package/src/cli/util/svelte-network-detector.js +80 -0
  37. package/src/cli/util/svelte-sfc-extractor.js +147 -0
  38. package/src/cli/util/svelte-state-detector.js +243 -0
  39. package/src/cli/util/vue-navigation-detector.js +177 -0
  40. package/src/cli/util/vue-sfc-extractor.js +162 -0
  41. package/src/cli/util/vue-state-detector.js +215 -0
  42. package/src/verax/cli/finding-explainer.js +56 -3
  43. package/src/verax/core/artifacts/registry.js +154 -0
  44. package/src/verax/core/artifacts/verifier.js +980 -0
  45. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  46. package/src/verax/core/baseline/baseline.snapshot.js +231 -0
  47. package/src/verax/core/capabilities/gates.js +499 -0
  48. package/src/verax/core/capabilities/registry.js +475 -0
  49. package/src/verax/core/confidence/confidence-compute.js +137 -0
  50. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  51. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  52. package/src/verax/core/confidence/confidence-weights.js +44 -0
  53. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  54. package/src/verax/core/confidence/confidence.loader.js +79 -0
  55. package/src/verax/core/confidence/confidence.schema.js +94 -0
  56. package/src/verax/core/confidence-engine-refactor.js +484 -0
  57. package/src/verax/core/confidence-engine.js +486 -0
  58. package/src/verax/core/confidence-engine.js.backup +471 -0
  59. package/src/verax/core/contracts/index.js +29 -0
  60. package/src/verax/core/contracts/types.js +185 -0
  61. package/src/verax/core/contracts/validators.js +381 -0
  62. package/src/verax/core/decision-snapshot.js +30 -3
  63. package/src/verax/core/decisions/decision.trace.js +276 -0
  64. package/src/verax/core/determinism/contract-writer.js +89 -0
  65. package/src/verax/core/determinism/contract.js +139 -0
  66. package/src/verax/core/determinism/diff.js +364 -0
  67. package/src/verax/core/determinism/engine.js +221 -0
  68. package/src/verax/core/determinism/finding-identity.js +148 -0
  69. package/src/verax/core/determinism/normalize.js +438 -0
  70. package/src/verax/core/determinism/report-writer.js +92 -0
  71. package/src/verax/core/determinism/run-fingerprint.js +118 -0
  72. package/src/verax/core/dynamic-route-intelligence.js +528 -0
  73. package/src/verax/core/evidence/evidence-capture-service.js +307 -0
  74. package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
  75. package/src/verax/core/evidence-builder.js +487 -0
  76. package/src/verax/core/execution-mode-context.js +77 -0
  77. package/src/verax/core/execution-mode-detector.js +190 -0
  78. package/src/verax/core/failures/exit-codes.js +86 -0
  79. package/src/verax/core/failures/failure-summary.js +76 -0
  80. package/src/verax/core/failures/failure.factory.js +225 -0
  81. package/src/verax/core/failures/failure.ledger.js +132 -0
  82. package/src/verax/core/failures/failure.types.js +196 -0
  83. package/src/verax/core/failures/index.js +10 -0
  84. package/src/verax/core/ga/ga-report-writer.js +43 -0
  85. package/src/verax/core/ga/ga.artifact.js +49 -0
  86. package/src/verax/core/ga/ga.contract.js +434 -0
  87. package/src/verax/core/ga/ga.enforcer.js +86 -0
  88. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  89. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  90. package/src/verax/core/guardrails/policy.loader.js +83 -0
  91. package/src/verax/core/guardrails/policy.schema.js +110 -0
  92. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  93. package/src/verax/core/guardrails-engine.js +505 -0
  94. package/src/verax/core/observe/run-timeline.js +316 -0
  95. package/src/verax/core/perf/perf.contract.js +186 -0
  96. package/src/verax/core/perf/perf.display.js +65 -0
  97. package/src/verax/core/perf/perf.enforcer.js +91 -0
  98. package/src/verax/core/perf/perf.monitor.js +209 -0
  99. package/src/verax/core/perf/perf.report.js +198 -0
  100. package/src/verax/core/pipeline-tracker.js +238 -0
  101. package/src/verax/core/product-definition.js +127 -0
  102. package/src/verax/core/release/provenance.builder.js +271 -0
  103. package/src/verax/core/release/release-report-writer.js +40 -0
  104. package/src/verax/core/release/release.enforcer.js +159 -0
  105. package/src/verax/core/release/reproducibility.check.js +221 -0
  106. package/src/verax/core/release/sbom.builder.js +283 -0
  107. package/src/verax/core/report/cross-index.js +192 -0
  108. package/src/verax/core/report/human-summary.js +222 -0
  109. package/src/verax/core/route-intelligence.js +419 -0
  110. package/src/verax/core/security/secrets.scan.js +326 -0
  111. package/src/verax/core/security/security-report.js +50 -0
  112. package/src/verax/core/security/security.enforcer.js +124 -0
  113. package/src/verax/core/security/supplychain.defaults.json +38 -0
  114. package/src/verax/core/security/supplychain.policy.js +326 -0
  115. package/src/verax/core/security/vuln.scan.js +265 -0
  116. package/src/verax/core/truth/truth.certificate.js +250 -0
  117. package/src/verax/core/ui-feedback-intelligence.js +515 -0
  118. package/src/verax/detect/confidence-engine.js +628 -40
  119. package/src/verax/detect/confidence-helper.js +33 -0
  120. package/src/verax/detect/detection-engine.js +18 -1
  121. package/src/verax/detect/dynamic-route-findings.js +335 -0
  122. package/src/verax/detect/expectation-chain-detector.js +417 -0
  123. package/src/verax/detect/expectation-model.js +3 -1
  124. package/src/verax/detect/findings-writer.js +141 -5
  125. package/src/verax/detect/index.js +229 -5
  126. package/src/verax/detect/journey-stall-detector.js +558 -0
  127. package/src/verax/detect/route-findings.js +218 -0
  128. package/src/verax/detect/ui-feedback-findings.js +207 -0
  129. package/src/verax/detect/verdict-engine.js +57 -3
  130. package/src/verax/detect/view-switch-correlator.js +242 -0
  131. package/src/verax/index.js +413 -45
  132. package/src/verax/learn/action-contract-extractor.js +682 -64
  133. package/src/verax/learn/route-validator.js +4 -1
  134. package/src/verax/observe/index.js +88 -843
  135. package/src/verax/observe/interaction-runner.js +25 -8
  136. package/src/verax/observe/observe-context.js +205 -0
  137. package/src/verax/observe/observe-helpers.js +191 -0
  138. package/src/verax/observe/observe-runner.js +226 -0
  139. package/src/verax/observe/observers/budget-observer.js +185 -0
  140. package/src/verax/observe/observers/console-observer.js +102 -0
  141. package/src/verax/observe/observers/coverage-observer.js +107 -0
  142. package/src/verax/observe/observers/interaction-observer.js +471 -0
  143. package/src/verax/observe/observers/navigation-observer.js +132 -0
  144. package/src/verax/observe/observers/network-observer.js +87 -0
  145. package/src/verax/observe/observers/safety-observer.js +82 -0
  146. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  147. package/src/verax/observe/ui-feedback-detector.js +742 -0
  148. package/src/verax/observe/ui-signal-sensor.js +148 -2
  149. package/src/verax/scan-summary-writer.js +42 -8
  150. package/src/verax/shared/artifact-manager.js +8 -5
  151. package/src/verax/shared/css-spinner-rules.js +204 -0
  152. package/src/verax/shared/view-switch-rules.js +208 -0
@@ -70,6 +70,32 @@ function hasUiData(uiSignals) {
70
70
  return hasAnyDelta || hasDomChange || hasVisibleChange || hasAriaChange || hasFocusChange || hasTextChange;
71
71
  }
72
72
 
73
+ /**
74
+ * Check if UIFeedback sensor contains non-trivial data (GAP 5.2).
75
+ * STRICT: Must have meaningful feedback signals captured.
76
+ */
77
+ function hasUiFeedbackData(uiFeedback) {
78
+ if (!uiFeedback || typeof uiFeedback !== 'object') return false;
79
+
80
+ // Check if overall score is present and non-zero
81
+ const hasScore = typeof uiFeedback.overallUiFeedbackScore === 'number' && uiFeedback.overallUiFeedbackScore > 0;
82
+
83
+ // Check if any signals are present
84
+ const signals = uiFeedback.signals || {};
85
+ const hasAnySignal = (
86
+ signals.domChange?.happened === true ||
87
+ signals.loading?.appeared === true ||
88
+ signals.loading?.disappeared === true ||
89
+ signals.buttonStateTransition?.happened === true ||
90
+ signals.notification?.happened === true ||
91
+ signals.navigation?.happened === true ||
92
+ signals.focusChange?.happened === true ||
93
+ signals.scrollChange?.happened === true
94
+ );
95
+
96
+ return hasScore || hasAnySignal;
97
+ }
98
+
73
99
  const BASE_SCORES = {
74
100
  network_silent_failure: 70,
75
101
  validation_silent_failure: 60, // VALIDATION INTELLIGENCE v1
@@ -80,7 +106,9 @@ const BASE_SCORES = {
80
106
  navigation_silent_failure: 75, // NAVIGATION INTELLIGENCE v2
81
107
  partial_navigation_failure: 65, // NAVIGATION INTELLIGENCE v2
82
108
  flow_silent_failure: 70, // FLOW INTELLIGENCE v1
83
- observed_break: 50 // OBSERVED expectations (runtime-derived, lower confidence)
109
+ observed_break: 50, // OBSERVED expectations (runtime-derived, lower confidence)
110
+ 'journey-stall-silent-failure': 72, // PHASE 11: Journey stalls have high base (pattern-based), can reach HIGH despite individual steps OK
111
+ 'expectation-chain-break': 78 // PHASE 12: Expectation chains from proven source - high base, increases with chain depth and break position
84
112
  };
85
113
 
86
114
  /**
@@ -101,17 +129,22 @@ function getBaseScoreFromExpectationStrength(expectationStrength) {
101
129
 
102
130
  /**
103
131
  * Main confidence computation function.
104
- * @param {Object} params - { findingType, expectation, sensors, comparisons, attemptMeta }
105
- * @returns {Object} - { score, level, explain, factors }
132
+ * GAP 5.2: Enhanced with UIFeedback integration, contradiction detection, and factor tracking.
133
+ * EXECUTION MODE: Respects confidence ceiling based on execution mode (PROJECT_SCAN vs WEB_SCAN_LIMITED).
134
+ * @param {Object} params - { findingType, expectation, sensors, comparisons, attemptMeta, executionModeCeiling }
135
+ * @param {number} params.executionModeCeiling - Optional confidence ceiling (0..1). Defaults to 1.0 (no ceiling).
136
+ * @returns {Object} - { score, level, explain, factors, contradictions, executionMode }
106
137
  */
107
- export function computeConfidence({ findingType, expectation, sensors = {}, comparisons = {}, attemptMeta = {} }) {
138
+ export function computeConfidence({ findingType, expectation, sensors = {}, comparisons = {}, attemptMeta = {}, executionModeCeiling = 1.0 }) {
108
139
  const boosts = [];
109
140
  const penalties = [];
141
+ const contradictions = []; // GAP 5.2: Track contradictions explicitly
110
142
 
111
143
  // Extract sensor data (with defaults for missing sensors)
112
144
  const networkSummary = sensors.network || {};
113
145
  const consoleSummary = sensors.console || {};
114
146
  const uiSignals = sensors.uiSignals || {};
147
+ const uiFeedback = sensors.uiFeedback || {}; // GAP 5.1: UI feedback signals
115
148
 
116
149
  // === STEP 1: DETERMINE EXPECTATION STRENGTH ===
117
150
  const expectationStrength = determineExpectationStrength(expectation);
@@ -129,7 +162,8 @@ export function computeConfidence({ findingType, expectation, sensors = {}, comp
129
162
  networkSummary,
130
163
  consoleSummary,
131
164
  uiSignals,
132
- comparisons
165
+ comparisons,
166
+ uiFeedback // GAP 5.2: Pass uiFeedback for signal extraction
133
167
  });
134
168
 
135
169
  // === STEP 3: SENSOR PRESENCE CHECK (STRICT - must contain data) ===
@@ -137,10 +171,20 @@ export function computeConfidence({ findingType, expectation, sensors = {}, comp
137
171
  const sensorsPresent = {
138
172
  network: hasNetworkData(networkSummary),
139
173
  console: hasConsoleData(consoleSummary),
140
- ui: hasUiData(uiSignals)
174
+ ui: hasUiData(uiSignals),
175
+ uiFeedback: hasUiFeedbackData(uiFeedback) // GAP 5.2: Check UIFeedback presence
141
176
  };
142
177
 
143
- const allSensorsPresent = sensorsPresent.network && sensorsPresent.console && sensorsPresent.ui;
178
+ const allSensorsPresent = sensorsPresent.network && sensorsPresent.console && sensorsPresent.ui && sensorsPresent.uiFeedback;
179
+
180
+ // === STEP 3B: DETECT CONTRADICTIONS (GAP 5.2) ===
181
+ detectContradictions({
182
+ evidenceSignals,
183
+ expectation,
184
+ findingType,
185
+ contradictions,
186
+ penalties
187
+ });
144
188
 
145
189
  // === STEP 4: COMPUTE BOOSTS AND PENALTIES (TYPE-SPECIFIC) ===
146
190
  let totalBoosts = 0;
@@ -171,6 +215,7 @@ export function computeConfidence({ findingType, expectation, sensors = {}, comp
171
215
  if (!sensorsPresent.network) missingSensors.push('network');
172
216
  if (!sensorsPresent.console) missingSensors.push('console');
173
217
  if (!sensorsPresent.ui) missingSensors.push('ui');
218
+ if (!sensorsPresent.uiFeedback) missingSensors.push('uiFeedback'); // GAP 5.2
174
219
 
175
220
  const penalty = 15;
176
221
  totalPenalties += penalty;
@@ -183,6 +228,13 @@ export function computeConfidence({ findingType, expectation, sensors = {}, comp
183
228
  penalties.push(`Expectation strength is ${expectationStrength}, not PROVEN`);
184
229
  }
185
230
 
231
+ // GAP 5.2: Apply contradiction penalties
232
+ if (contradictions.length > 0) {
233
+ const contradictionPenalty = contradictions.length * 12; // -12 per contradiction
234
+ totalPenalties += contradictionPenalty;
235
+ penalties.push(`Contradictions detected: ${contradictions.length}`);
236
+ }
237
+
186
238
  // === STEP 6: COMPUTE FINAL SCORE ===
187
239
  let score = baseScore + totalBoosts - totalPenalties;
188
240
  score = Math.max(0, Math.min(100, score)); // Clamp to [0, 100]
@@ -254,22 +306,49 @@ export function computeConfidence({ findingType, expectation, sensors = {}, comp
254
306
  boosts,
255
307
  penalties,
256
308
  attemptMeta,
257
- boundaryExplanation // Phase 3: Include boundary reasoning
309
+ boundaryExplanation, // Phase 3: Include boundary reasoning
310
+ contradictions // GAP 5.2: Include contradiction list
311
+ });
312
+
313
+ // === STEP 11: GENERATE CONFIDENCE FACTORS (GAP 5.2) ===
314
+ const factors = generateConfidenceFactors({
315
+ expectationStrength,
316
+ sensorsPresent,
317
+ evidenceSignals,
318
+ boosts,
319
+ penalties,
320
+ contradictions,
321
+ baseScore,
322
+ totalBoosts,
323
+ totalPenalties
258
324
  });
259
325
 
326
+ // === STEP 12: APPLY EXECUTION MODE CEILING ===
327
+ // If execution mode is WEB_SCAN_LIMITED, cap confidence at 0.45 (45%)
328
+ let computedScore = score / 100; // Convert from 0..100 to 0..1 (float)
329
+ const ceiledScore = Math.min(computedScore, executionModeCeiling);
330
+
331
+ // If ceiling was applied, adjust level accordingly
332
+ let ceiledLevel = level;
333
+ if (ceiledScore < computedScore) {
334
+ // Score was capped, so adjust level to match new ceiling
335
+ if (ceiledScore < 0.2) ceiledLevel = 'LOW';
336
+ else if (ceiledScore < 0.5) ceiledLevel = 'MEDIUM';
337
+ else ceiledLevel = 'HIGH';
338
+ }
339
+
260
340
  return {
261
- score: Math.round(score),
262
- level,
341
+ score: ceiledScore, // Convert from 0..100 to 0..1 (float), with ceiling applied
342
+ scorePct: Math.round(ceiledScore * 100), // Optional: 0..100 for backward compat/convenience
343
+ level: ceiledLevel,
263
344
  explain: finalExplain,
264
- factors: {
265
- expectationStrength,
266
- sensorsPresent,
267
- evidenceSignals,
268
- penalties,
269
- boosts
270
- },
345
+ factors: factors, // GAP 5.2: Structured factor breakdown
346
+ contradictions: contradictions, // GAP 5.2: Explicit contradictions
271
347
  confidenceExplanation,
272
- boundaryExplanation // Phase 3: Surface boundary reasoning in output
348
+ boundaryExplanation, // Phase 3: Surface boundary reasoning in output
349
+ // Expose raw boosts/penalties for testing and debugging
350
+ boosts: boosts,
351
+ penalties: penalties
273
352
  };
274
353
  }
275
354
 
@@ -306,16 +385,35 @@ function determineExpectationStrength(expectation = {}) {
306
385
 
307
386
  /**
308
387
  * Extract deterministic evidence signals from runtime data.
388
+ * GAP 5.2: Integrates Gap 5.1 UI Feedback signals with existing sensor data.
309
389
  */
310
- function extractEvidenceSignals({ networkSummary, consoleSummary, uiSignals, comparisons }) {
390
+ function extractEvidenceSignals({ networkSummary, consoleSummary, uiSignals, comparisons, uiFeedback }) {
391
+ // GAP 5.1: Extract UI feedback signals (6 types)
392
+ const uiFeedbackScore = uiFeedback?.overallUiFeedbackScore || 0;
393
+ const uiFeedbackSignals = uiFeedback?.signals || {};
394
+
311
395
  const signals = {
312
396
  urlChanged: comparisons?.hasUrlChange === true,
313
397
  domChanged: comparisons?.hasDomChange === true,
314
398
  screenshotChanged: comparisons?.hasVisibleChange === true,
315
399
  networkFailed: (networkSummary?.failedRequests || 0) > 0,
400
+ networkSuccess: (networkSummary?.totalRequests || 0) > 0 && (networkSummary?.failedRequests || 0) === 0,
316
401
  consoleErrors: (consoleSummary?.hasErrors === true),
317
402
  uiFeedbackDetected: hasAnyFeedback(uiSignals),
318
- slowRequests: (networkSummary?.slowRequestsCount || 0) > 0
403
+ slowRequests: (networkSummary?.slowRequestsCount || 0) > 0,
404
+
405
+ // GAP 5.1: Runtime UI feedback signals
406
+ uiFeedbackScore: uiFeedbackScore, // 0..1 overall score
407
+ uiFeedbackDomChange: uiFeedbackSignals.domChange?.happened === true,
408
+ uiFeedbackLoading: uiFeedbackSignals.loading?.appeared === true || uiFeedbackSignals.loading?.disappeared === true,
409
+ uiFeedbackButtonState: uiFeedbackSignals.buttonStateTransition?.happened === true,
410
+ uiFeedbackNotification: uiFeedbackSignals.notification?.happened === true,
411
+ uiFeedbackNavigation: uiFeedbackSignals.navigation?.happened === true,
412
+ uiFeedbackFocusChange: uiFeedbackSignals.focusChange?.happened === true,
413
+ uiFeedbackScrollChange: uiFeedbackSignals.scrollChange?.happened === true,
414
+
415
+ // Derived: Strong UI feedback = any significant signal
416
+ strongUiFeedback: uiFeedbackScore > 0.5
319
417
  };
320
418
 
321
419
  return signals;
@@ -468,6 +566,36 @@ function scoreByFindingType({
468
566
  penalties
469
567
  });
470
568
  break;
569
+
570
+ // PHASE 11: Journey stall detection
571
+ case 'journey-stall-silent-failure':
572
+ totalBoosts = scoreJourneyStall({
573
+ expectation,
574
+ evidenceSignals,
575
+ boosts,
576
+ penalties
577
+ });
578
+ totalPenalties = penalizeJourneyStall({
579
+ expectation,
580
+ evidenceSignals,
581
+ penalties
582
+ });
583
+ break;
584
+
585
+ // PHASE 12: Expectation chain breaks
586
+ case 'expectation-chain-break':
587
+ totalBoosts = scoreExpectationChainBreak({
588
+ expectation,
589
+ evidenceSignals,
590
+ boosts,
591
+ penalties
592
+ });
593
+ totalPenalties = penalizeExpectationChainBreak({
594
+ expectation,
595
+ evidenceSignals,
596
+ penalties
597
+ });
598
+ break;
471
599
  }
472
600
 
473
601
  return { totalBoosts, totalPenalties };
@@ -492,10 +620,16 @@ function scoreNetworkSilentFailure({ networkSummary: _networkSummary, consoleSum
492
620
  boosts.push('Console errors present');
493
621
  }
494
622
 
495
- // +6 if network failed AND no UI feedback
496
- if (evidenceSignals.networkFailed && !evidenceSignals.uiFeedbackDetected) {
497
- total += 6;
498
- boosts.push('Silent failure: no user feedback on network error');
623
+ // GAP 5.2: +12 if network failed AND no UI feedback (strong silent failure evidence)
624
+ if (evidenceSignals.networkFailed && !evidenceSignals.strongUiFeedback && evidenceSignals.uiFeedbackScore < 0.3) {
625
+ total += 12;
626
+ boosts.push(`Silent failure: network error with minimal UI feedback (score: ${evidenceSignals.uiFeedbackScore.toFixed(2)})`);
627
+ }
628
+
629
+ // GAP 5.2: +8 if network failed + no notification signal
630
+ if (evidenceSignals.networkFailed && !evidenceSignals.uiFeedbackNotification) {
631
+ total += 8;
632
+ boosts.push('Network failed without error notification to user');
499
633
  }
500
634
 
501
635
  return total;
@@ -504,10 +638,22 @@ function scoreNetworkSilentFailure({ networkSummary: _networkSummary, consoleSum
504
638
  function penalizeNetworkSilentFailure({ evidenceSignals, penalties }) {
505
639
  let total = 0;
506
640
 
507
- // -10 if UI feedback present (shouldn't be silent failure)
508
- if (evidenceSignals.uiFeedbackDetected) {
641
+ // GAP 5.2: -15 if strong UI feedback present (not silent)
642
+ if (evidenceSignals.strongUiFeedback) {
643
+ total += 15;
644
+ penalties.push(`Strong UI feedback detected (score: ${evidenceSignals.uiFeedbackScore.toFixed(2)}) - not silent`);
645
+ }
646
+
647
+ // GAP 5.2: -10 if notification shown
648
+ if (evidenceSignals.uiFeedbackNotification) {
509
649
  total += 10;
510
- penalties.push('UI feedback detected (suggests not silent)');
650
+ penalties.push('Error notification shown to user');
651
+ }
652
+
653
+ // GAP 5.2: -8 if moderate UI feedback (0.3-0.5)
654
+ if (evidenceSignals.uiFeedbackScore >= 0.3 && evidenceSignals.uiFeedbackScore <= 0.5) {
655
+ total += 8;
656
+ penalties.push(`Moderate UI feedback detected (score: ${evidenceSignals.uiFeedbackScore.toFixed(2)})`);
511
657
  }
512
658
 
513
659
  return total;
@@ -552,10 +698,16 @@ function scoreMissingFeedbackFailure({ networkSummary: _networkSummary, evidence
552
698
  boosts.push('Slow requests detected');
553
699
  }
554
700
 
555
- // +8 if network activity without loading feedback
556
- if (evidenceSignals.networkFailed && !evidenceSignals.uiFeedbackDetected) {
557
- total += 8;
558
- boosts.push('Network activity without user feedback');
701
+ // GAP 5.2: +12 if network activity without loading indicator
702
+ if (evidenceSignals.networkSuccess && !evidenceSignals.uiFeedbackLoading && evidenceSignals.slowRequests) {
703
+ total += 12;
704
+ boosts.push('Slow network requests without loading indicator');
705
+ }
706
+
707
+ // GAP 5.2: +10 if button state didn't change during async operation
708
+ if (evidenceSignals.networkSuccess && !evidenceSignals.uiFeedbackButtonState && evidenceSignals.slowRequests) {
709
+ total += 10;
710
+ boosts.push('Async operation without button state feedback (disabled/loading)');
559
711
  }
560
712
 
561
713
  return total;
@@ -564,12 +716,18 @@ function scoreMissingFeedbackFailure({ networkSummary: _networkSummary, evidence
564
716
  function penalizeMissingFeedbackFailure({ evidenceSignals, penalties }) {
565
717
  let total = 0;
566
718
 
567
- // -10 if loading feedback detected
568
- if (evidenceSignals.uiFeedbackDetected) {
569
- total += 10;
719
+ // GAP 5.2: -12 if loading feedback detected
720
+ if (evidenceSignals.uiFeedbackLoading) {
721
+ total += 12;
570
722
  penalties.push('Loading indicator detected');
571
723
  }
572
724
 
725
+ // GAP 5.2: -8 if button state changed
726
+ if (evidenceSignals.uiFeedbackButtonState) {
727
+ total += 8;
728
+ penalties.push('Button state transition detected');
729
+ }
730
+
573
731
  return total;
574
732
  }
575
733
 
@@ -717,13 +875,13 @@ function penalizeNavigationSilentFailure({ evidenceSignals, penalties }) {
717
875
 
718
876
  // -10 if UI feedback present (shouldn't be silent failure)
719
877
  if (evidenceSignals.uiFeedbackDetected) {
720
- total += 10;
878
+ total -= 10;
721
879
  penalties.push('UI feedback detected (suggests navigation feedback provided)');
722
880
  }
723
881
 
724
882
  // -5 if URL changed (navigation might have succeeded)
725
883
  if (evidenceSignals.urlChanged) {
726
- total += 5;
884
+ total -= 5;
727
885
  penalties.push('URL changed (navigation may have succeeded)');
728
886
  }
729
887
 
@@ -753,7 +911,7 @@ function penalizePartialNavigationFailure({ evidenceSignals, penalties }) {
753
911
 
754
912
  // -10 if UI feedback present (shouldn't be partial failure)
755
913
  if (evidenceSignals.uiFeedbackDetected) {
756
- total += 10;
914
+ total -= 10;
757
915
  penalties.push('UI feedback detected (suggests navigation feedback provided)');
758
916
  }
759
917
 
@@ -799,6 +957,7 @@ function generateExplanations(boosts, penalties, expectationStrength, _evidenceS
799
957
  * Generate confidence explanation for Phase 9: Reality Confidence & Explanation Layer.
800
958
  * Provides whyThisConfidence, whatWouldIncreaseConfidence, whatWouldReduceConfidence.
801
959
  * Phase 3: Also includes boundaryExplanation for near-threshold decisions.
960
+ * GAP 5.2: Includes contradiction handling.
802
961
  */
803
962
  function generateConfidenceExplanation({
804
963
  level,
@@ -810,7 +969,8 @@ function generateConfidenceExplanation({
810
969
  boosts,
811
970
  penalties,
812
971
  attemptMeta,
813
- boundaryExplanation = null // Phase 3: Optional boundary reasoning
972
+ boundaryExplanation = null, // Phase 3: Optional boundary reasoning
973
+ contradictions = [] // GAP 5.2: Contradiction list
814
974
  }) {
815
975
  const whyThisConfidence = [];
816
976
  const whatWouldIncreaseConfidence = [];
@@ -821,6 +981,19 @@ function generateConfidenceExplanation({
821
981
  whyThisConfidence.push(boundaryExplanation);
822
982
  }
823
983
 
984
+ // GAP 5.2: If contradictions exist, mention them first
985
+ if (contradictions.length > 0) {
986
+ const criticalCount = contradictions.filter(c => c.severity === 'critical').length;
987
+ const majorCount = contradictions.filter(c => c.severity === 'major').length;
988
+ if (criticalCount > 0) {
989
+ whyThisConfidence.push(`${criticalCount} critical contradiction(s) detected - significantly reduces confidence`);
990
+ } else if (majorCount > 0) {
991
+ whyThisConfidence.push(`${majorCount} major contradiction(s) detected - reduces confidence`);
992
+ } else {
993
+ whyThisConfidence.push(`${contradictions.length} minor contradiction(s) detected - slightly reduces confidence`);
994
+ }
995
+ }
996
+
824
997
  // WHY THIS CONFIDENCE: Explain current level
825
998
  if (level === 'HIGH') {
826
999
  whyThisConfidence.push('High confidence: expectation is proven and all sensors captured evidence');
@@ -828,7 +1001,7 @@ function generateConfidenceExplanation({
828
1001
  whyThisConfidence.push('Expectation is proven from source code');
829
1002
  }
830
1003
  if (allSensorsPresent) {
831
- whyThisConfidence.push('All sensors (network, console, UI) were active');
1004
+ whyThisConfidence.push('All sensors (network, console, UI, UIFeedback) were active');
832
1005
  }
833
1006
  if (boosts.length > 0) {
834
1007
  whyThisConfidence.push(`Strong evidence: ${boosts.length} positive signal(s)`);
@@ -845,6 +1018,7 @@ function generateConfidenceExplanation({
845
1018
  if (!sensorsPresent.network) missing.push('network');
846
1019
  if (!sensorsPresent.console) missing.push('console');
847
1020
  if (!sensorsPresent.ui) missing.push('UI');
1021
+ if (!sensorsPresent.uiFeedback) missing.push('UIFeedback');
848
1022
  whyThisConfidence.push(`Missing sensor data: ${missing.join(', ')}`);
849
1023
  }
850
1024
  if (penalties.length > 0) {
@@ -873,6 +1047,7 @@ function generateConfidenceExplanation({
873
1047
  if (!sensorsPresent.network) missing.push('network monitoring');
874
1048
  if (!sensorsPresent.console) missing.push('console error detection');
875
1049
  if (!sensorsPresent.ui) missing.push('UI change detection');
1050
+ if (!sensorsPresent.uiFeedback) missing.push('UI feedback detection');
876
1051
  whatWouldIncreaseConfidence.push(`Enable missing sensors: ${missing.join(', ')}`);
877
1052
  }
878
1053
  if (attemptMeta && !attemptMeta.repeated && level === 'LOW') {
@@ -881,6 +1056,9 @@ function generateConfidenceExplanation({
881
1056
  if (boosts.length === 0) {
882
1057
  whatWouldIncreaseConfidence.push('Add stronger evidence signals (network requests, console errors, UI changes)');
883
1058
  }
1059
+ if (contradictions.length > 0) {
1060
+ whatWouldIncreaseConfidence.push('Resolve contradictions by clarifying expected behavior or fixing detection logic');
1061
+ }
884
1062
  }
885
1063
 
886
1064
  // WHAT WOULD REDUCE CONFIDENCE
@@ -894,6 +1072,9 @@ function generateConfidenceExplanation({
894
1072
  if (boosts.length > 0) {
895
1073
  whatWouldReduceConfidence.push('If positive evidence signals disappear (network succeeds, UI feedback appears)');
896
1074
  }
1075
+ if (contradictions.length === 0) {
1076
+ whatWouldReduceConfidence.push('If contradictory evidence appears (mixed signals, conflicting feedback)');
1077
+ }
897
1078
  }
898
1079
  if (penalties.length === 0 && level === 'HIGH') {
899
1080
  whatWouldReduceConfidence.push('If uncertainty factors appear (URL changes, partial effects, missing data)');
@@ -911,7 +1092,7 @@ function generateConfidenceExplanation({
911
1092
  // ============================================================
912
1093
 
913
1094
  // PHASE 3: Export sensor validation functions for testing
914
- export { hasNetworkData, hasConsoleData, hasUiData };
1095
+ export { hasNetworkData, hasConsoleData, hasUiData, hasUiFeedbackData };
915
1096
 
916
1097
  // Detect error feedback (legacy helper)
917
1098
  function _detectErrorFeedback(uiSignals) {
@@ -931,3 +1112,410 @@ function _detectStatusFeedback(uiSignals) {
931
1112
  const after = uiSignals?.after || {};
932
1113
  return after.hasStatusSignal || after.hasLiveRegion || after.hasDialog;
933
1114
  }
1115
+
1116
+ // ============================================================
1117
+ // GAP 5.2: CONTRADICTION DETECTION
1118
+ // ============================================================
1119
+
1120
+ /**
1121
+ * Detect contradictions in evidence signals and populate contradictions array.
1122
+ * Contradictions reduce confidence by identifying conflicting signals.
1123
+ */
1124
+ function detectContradictions({ evidenceSignals, expectation, findingType, contradictions, penalties }) {
1125
+ // Contradiction 1: Network success + no UI feedback + no navigation + no DOM change + claiming silent failure
1126
+ if (
1127
+ evidenceSignals.networkSuccess &&
1128
+ !evidenceSignals.strongUiFeedback &&
1129
+ !evidenceSignals.uiFeedbackNavigation &&
1130
+ !evidenceSignals.domChanged &&
1131
+ !evidenceSignals.urlChanged &&
1132
+ findingType?.includes('silent_failure')
1133
+ ) {
1134
+ contradictions.push({
1135
+ type: 'network_success_no_feedback',
1136
+ details: 'Network succeeded but no UI feedback, navigation, or DOM change detected - possible silent success or deferred update',
1137
+ severity: 'major'
1138
+ });
1139
+ }
1140
+
1141
+ // Contradiction 2: UI feedback shows explicit error/notification but finding claims "silent"
1142
+ if (
1143
+ (evidenceSignals.uiFeedbackNotification || evidenceSignals.consoleErrors) &&
1144
+ !evidenceSignals.strongUiFeedback &&
1145
+ findingType?.includes('silent')
1146
+ ) {
1147
+ contradictions.push({
1148
+ type: 'error_feedback_present',
1149
+ details: 'Error notifications or console errors present but UI feedback score is low - may not be truly "silent"',
1150
+ severity: 'minor'
1151
+ });
1152
+ }
1153
+
1154
+ // Contradiction 3: Strong UI feedback but claiming silent failure
1155
+ if (evidenceSignals.strongUiFeedback && findingType?.includes('silent_failure')) {
1156
+ contradictions.push({
1157
+ type: 'strong_feedback_silent_claim',
1158
+ details: `Strong UI feedback detected (score: ${evidenceSignals.uiFeedbackScore.toFixed(2)}) contradicts silent failure claim`,
1159
+ severity: 'critical'
1160
+ });
1161
+ }
1162
+
1163
+ // Contradiction 4: Navigation occurred but claiming missing action
1164
+ if (
1165
+ (evidenceSignals.urlChanged || evidenceSignals.uiFeedbackNavigation) &&
1166
+ (findingType === 'missing_network_action' || findingType === 'missing_state_action')
1167
+ ) {
1168
+ contradictions.push({
1169
+ type: 'navigation_with_missing_action',
1170
+ details: 'Navigation detected but claiming missing action - action may have fired differently',
1171
+ severity: 'major'
1172
+ });
1173
+ }
1174
+
1175
+ // Contradiction 5: DOM changed significantly but UI feedback score is zero
1176
+ if (
1177
+ evidenceSignals.domChanged &&
1178
+ evidenceSignals.uiFeedbackScore === 0 &&
1179
+ (expectation?.promise?.kind === 'network' || expectation?.promise?.kind === 'state')
1180
+ ) {
1181
+ contradictions.push({
1182
+ type: 'dom_change_no_ui_feedback',
1183
+ details: 'DOM changed but UI feedback detection missed it - detection may be too conservative',
1184
+ severity: 'minor'
1185
+ });
1186
+ }
1187
+
1188
+ // Contradiction 6: Multiple conflicting signals (network failed + strong feedback + no console errors)
1189
+ if (
1190
+ evidenceSignals.networkFailed &&
1191
+ evidenceSignals.strongUiFeedback &&
1192
+ !evidenceSignals.consoleErrors &&
1193
+ findingType?.includes('silent')
1194
+ ) {
1195
+ contradictions.push({
1196
+ type: 'mixed_signals',
1197
+ details: 'Network failed but strong UI feedback present without console errors - user likely informed',
1198
+ severity: 'major'
1199
+ });
1200
+ }
1201
+ }
1202
+
1203
+ // ============================================================
1204
+ // GAP 5.2: CONFIDENCE FACTORS GENERATION
1205
+ // ============================================================
1206
+
1207
+ /**
1208
+ * Generate structured confidence factors with weights, values, and rationales.
1209
+ * Each factor explains how it contributes to the final confidence score.
1210
+ */
1211
+ function generateConfidenceFactors({
1212
+ expectationStrength,
1213
+ sensorsPresent,
1214
+ evidenceSignals,
1215
+ boosts,
1216
+ penalties,
1217
+ contradictions,
1218
+ baseScore,
1219
+ totalBoosts,
1220
+ totalPenalties
1221
+ }) {
1222
+ const factors = [];
1223
+
1224
+ // Factor 1: Expectation strength (weight: high)
1225
+ factors.push({
1226
+ key: 'expectation_strength',
1227
+ weight: 0.25,
1228
+ value: expectationStrength,
1229
+ rationale: expectationStrength === 'PROVEN'
1230
+ ? 'Expectation is proven from source code analysis'
1231
+ : `Expectation strength is ${expectationStrength}, not from proven source`,
1232
+ impact: expectationStrength === 'PROVEN' ? 'positive' : 'negative'
1233
+ });
1234
+
1235
+ // Factor 2: Sensor availability (weight: high)
1236
+ const sensorCount = Object.values(sensorsPresent).filter(Boolean).length;
1237
+ const sensorTotal = Object.keys(sensorsPresent).length;
1238
+ factors.push({
1239
+ key: 'sensor_availability',
1240
+ weight: 0.20,
1241
+ value: `${sensorCount}/${sensorTotal}`,
1242
+ rationale: sensorCount === sensorTotal
1243
+ ? 'All sensors active and captured data'
1244
+ : `Only ${sensorCount} of ${sensorTotal} sensors captured data`,
1245
+ impact: sensorCount === sensorTotal ? 'positive' : 'negative'
1246
+ });
1247
+
1248
+ // Factor 3: UI feedback score (weight: medium) - GAP 5.2
1249
+ if (sensorsPresent.uiFeedback) {
1250
+ factors.push({
1251
+ key: 'ui_feedback_score',
1252
+ weight: 0.18,
1253
+ value: evidenceSignals.uiFeedbackScore.toFixed(2),
1254
+ rationale: evidenceSignals.uiFeedbackScore > 0.5
1255
+ ? `Strong UI feedback detected (score: ${evidenceSignals.uiFeedbackScore.toFixed(2)}) - user likely received feedback`
1256
+ : evidenceSignals.uiFeedbackScore > 0
1257
+ ? `Moderate UI feedback detected (score: ${evidenceSignals.uiFeedbackScore.toFixed(2)}) - some user feedback present`
1258
+ : 'No UI feedback detected - potential silent failure',
1259
+ impact: evidenceSignals.uiFeedbackScore > 0.5 ? 'negative' : 'positive'
1260
+ });
1261
+ }
1262
+
1263
+ // Factor 4: Network evidence (weight: medium)
1264
+ if (sensorsPresent.network) {
1265
+ const networkValue = evidenceSignals.networkFailed ? 'failed' : evidenceSignals.networkSuccess ? 'success' : 'none';
1266
+ factors.push({
1267
+ key: 'network_evidence',
1268
+ weight: 0.15,
1269
+ value: networkValue,
1270
+ rationale: evidenceSignals.networkFailed
1271
+ ? 'Network requests failed - strong evidence of failure'
1272
+ : evidenceSignals.networkSuccess
1273
+ ? 'Network requests succeeded - may not be a failure'
1274
+ : 'No network activity detected',
1275
+ impact: evidenceSignals.networkFailed ? 'positive' : evidenceSignals.networkSuccess ? 'negative' : 'neutral'
1276
+ });
1277
+ }
1278
+
1279
+ // Factor 5: Observable changes (weight: low)
1280
+ const observableChanges = [
1281
+ evidenceSignals.domChanged && 'DOM',
1282
+ evidenceSignals.urlChanged && 'URL',
1283
+ evidenceSignals.screenshotChanged && 'visual'
1284
+ ].filter(Boolean);
1285
+ factors.push({
1286
+ key: 'observable_changes',
1287
+ weight: 0.12,
1288
+ value: observableChanges.length > 0 ? observableChanges.join(', ') : 'none',
1289
+ rationale: observableChanges.length > 0
1290
+ ? `Observable changes detected: ${observableChanges.join(', ')} - user likely saw something`
1291
+ : 'No observable changes detected - potential silent failure',
1292
+ impact: observableChanges.length > 0 ? 'negative' : 'positive'
1293
+ });
1294
+
1295
+ // Factor 6: Contradictions (weight: penalty) - GAP 5.2
1296
+ if (contradictions.length > 0) {
1297
+ const criticalCount = contradictions.filter(c => c.severity === 'critical').length;
1298
+ const majorCount = contradictions.filter(c => c.severity === 'major').length;
1299
+ const minorCount = contradictions.filter(c => c.severity === 'minor').length;
1300
+
1301
+ factors.push({
1302
+ key: 'contradictions',
1303
+ weight: 0.10, // Non-negative weight; negativity represented via impact field
1304
+ value: `${contradictions.length} (${criticalCount}C/${majorCount}M/${minorCount}m)`,
1305
+ rationale: `Contradictory evidence detected: ${criticalCount} critical, ${majorCount} major, ${minorCount} minor - reduces confidence`,
1306
+ impact: 'negative' // Impact field indicates this reduces confidence
1307
+ });
1308
+ }
1309
+
1310
+ // Factor 7: Score composition (informational)
1311
+ factors.push({
1312
+ key: 'score_composition',
1313
+ weight: 1.0,
1314
+ value: `${baseScore} + ${totalBoosts} - ${totalPenalties} = ${Math.max(0, Math.min(100, baseScore + totalBoosts - totalPenalties))}`,
1315
+ rationale: `Base score: ${baseScore}, Boosts: +${totalBoosts}, Penalties: -${totalPenalties}`,
1316
+ impact: 'neutral'
1317
+ });
1318
+
1319
+ return factors;
1320
+ }
1321
+ // ============================================================
1322
+ // PHASE 11: JOURNEY STALL CONFIDENCE SCORING
1323
+ // ============================================================
1324
+
1325
+ /**
1326
+ * Score journey stall findings.
1327
+ *
1328
+ * Individual steps work (low confidence on each), but pattern across
1329
+ * sequence clearly shows stall (high journey-level confidence possible).
1330
+ */
1331
+ function scoreJourneyStall({ expectation, evidenceSignals, boosts, penalties }) {
1332
+ let total = 0;
1333
+
1334
+ // Extract journey context
1335
+ const journeyEvidence = expectation?.evidence?.journeyContext || {};
1336
+ const stallPoints = expectation?.evidence?.stallPoints || [];
1337
+
1338
+ // +15 for multiple stall points (clear pattern)
1339
+ if (stallPoints.length >= 2) {
1340
+ total += 15;
1341
+ boosts.push(`Multiple stall points detected (${stallPoints.length}) - clear pattern`);
1342
+ }
1343
+
1344
+ // +12 if stall severity is CRITICAL
1345
+ const hasCritical = stallPoints.some(sp => sp.severity === 'CRITICAL');
1346
+ if (hasCritical) {
1347
+ total += 12;
1348
+ boosts.push('Critical severity stall points detected');
1349
+ }
1350
+
1351
+ // +10 if no navigation (classic stall indicator)
1352
+ const noNavStalls = stallPoints.filter(sp => sp.reasons.includes('no_navigation'));
1353
+ if (noNavStalls.length > 0) {
1354
+ total += 10;
1355
+ boosts.push(`Expected navigation blocked: ${noNavStalls.length} instances`);
1356
+ }
1357
+
1358
+ // +8 if no new actionable UI (user stuck with same options)
1359
+ const noUiStalls = stallPoints.filter(sp => sp.reasons.includes('no_new_actionable_ui'));
1360
+ if (noUiStalls.length > 0) {
1361
+ total += 8;
1362
+ boosts.push(`No new interactive elements: ${noUiStalls.length} instances`);
1363
+ }
1364
+
1365
+ // +8 if DOM stagnation across sequence
1366
+ const noDomStalls = stallPoints.filter(sp => sp.reasons.includes('no_dom_progression'));
1367
+ if (noDomStalls.length > 0) {
1368
+ total += 8;
1369
+ boosts.push(`DOM content unchanged: ${noDomStalls.length} instances`);
1370
+ }
1371
+
1372
+ // +10 if long sequence before stall (shows user persisted through multiple steps)
1373
+ const sequenceLength = journeyEvidence?.totalInteractions || 0;
1374
+ if (sequenceLength >= 5) {
1375
+ total += 10;
1376
+ boosts.push(`Long journey sequence (${sequenceLength} interactions) before stall`);
1377
+ }
1378
+
1379
+ // +8 for URL stagnation (stayed on same page despite actions)
1380
+ const urlProgression = journeyEvidence?.urlProgression || [];
1381
+ if (urlProgression.length <= 1 && sequenceLength >= 3) {
1382
+ total += 8;
1383
+ boosts.push(`URL unchanged across ${sequenceLength} interactions - clear stall`);
1384
+ }
1385
+
1386
+ return total;
1387
+ }
1388
+
1389
+ /**
1390
+ * Penalize journey stall findings.
1391
+ *
1392
+ * Reduce confidence if any step in journey clearly failed.
1393
+ */
1394
+ function penalizeJourneyStall({ expectation, evidenceSignals, penalties }) {
1395
+ let total = 0;
1396
+
1397
+ const stallPoints = expectation?.evidence?.stallPoints || [];
1398
+
1399
+ // -20 if low severity stalls (might not be real blocking issue)
1400
+ const lowSeverity = stallPoints.filter(sp => sp.severity === 'LOW').length;
1401
+ if (lowSeverity === stallPoints.length && stallPoints.length > 0) {
1402
+ total -= 20;
1403
+ penalties.push('All stall points are LOW severity - weak pattern');
1404
+ }
1405
+
1406
+ // -15 if only 1 stall point (might be coincidence)
1407
+ if (stallPoints.length === 1) {
1408
+ total -= 15;
1409
+ penalties.push('Only single stall point - weak evidence of pattern');
1410
+ }
1411
+
1412
+ // -12 if URL did progress (navigation worked)
1413
+ const urlProgression = expectation?.evidence?.journeyContext?.urlProgression || [];
1414
+ if (urlProgression.length > 1) {
1415
+ total -= 12;
1416
+ penalties.push(`URL progressed (${urlProgression.length} distinct pages) - not complete stall`);
1417
+ }
1418
+
1419
+ // -10 if strong UI feedback detected (user might be informed)
1420
+ if (evidenceSignals?.strongUiFeedback) {
1421
+ total -= 10;
1422
+ penalties.push('Strong UI feedback score detected - not silent');
1423
+ }
1424
+
1425
+ // -8 if no navigation expectation violations (unexpected navigation stall)
1426
+ const navStalls = stallPoints.filter(sp => sp.reasons.includes('no_navigation')).length;
1427
+ const totalReasons = stallPoints.reduce((sum, sp) => sum + sp.reasons.length, 0);
1428
+
1429
+ if (navStalls === 0 && totalReasons > 0) {
1430
+ total -= 8;
1431
+ penalties.push('No navigation stalls - stall is not navigation-related');
1432
+ }
1433
+
1434
+ return total;
1435
+ }
1436
+
1437
+ // ============================================================
1438
+ // PHASE 12: EXPECTATION CHAIN BREAK SCORING
1439
+ // ============================================================
1440
+
1441
+ /**
1442
+ * Score expectation chain breaks.
1443
+ *
1444
+ * Chains of proven expectations breaking mid-sequence is high-confidence evidence
1445
+ * of a root cause failure.
1446
+ */
1447
+ function scoreExpectationChainBreak({ expectation, evidenceSignals, boosts, penalties: _penalties }) {
1448
+ let total = 0;
1449
+
1450
+ const chainEvidence = expectation?.evidence || {};
1451
+ const chainLength = chainEvidence.chainLength || 0;
1452
+ const fulfilledSteps = chainEvidence.fulfilledSteps || 0;
1453
+ const brokenStepIndex = chainEvidence.brokenStepIndex || 0;
1454
+
1455
+ // +15 if chain is long (3+ steps proven before break)
1456
+ if (chainLength >= 3) {
1457
+ total += 15;
1458
+ boosts.push(`Long expectation chain (${chainLength} steps) - deep pattern proof`);
1459
+ }
1460
+
1461
+ // +12 if break occurs late in chain (past 50%)
1462
+ const breakDepth = brokenStepIndex / Math.max(1, chainLength);
1463
+ if (breakDepth >= 0.5 && brokenStepIndex > 0) {
1464
+ total += 12;
1465
+ boosts.push(`Chain broke late (step ${brokenStepIndex + 1}/${chainLength}) - proves path was valid`);
1466
+ }
1467
+
1468
+ // +10 if multiple steps fulfilled before break (shows pattern works initially)
1469
+ if (fulfilledSteps >= 2) {
1470
+ total += 10;
1471
+ boosts.push(`Multiple proven steps (${fulfilledSteps}) completed before failure - clear causality`);
1472
+ }
1473
+
1474
+ // +8 if first step was proven (entry point to chain is valid)
1475
+ if (fulfilledSteps > 0) {
1476
+ total += 8;
1477
+ boosts.push('Chain entry point was fulfilled - break is at downstream step');
1478
+ }
1479
+
1480
+ return total;
1481
+ }
1482
+
1483
+ /**
1484
+ * Penalize expectation chain breaks.
1485
+ *
1486
+ * Reduce confidence if chain evidence is weak or incomplete.
1487
+ */
1488
+ function penalizeExpectationChainBreak({ expectation, evidenceSignals, penalties }) {
1489
+ let total = 0;
1490
+
1491
+ const chainEvidence = expectation?.evidence || {};
1492
+ const chainLength = chainEvidence.chainLength || 0;
1493
+ const fulfilledSteps = chainEvidence.fulfilledSteps || 0;
1494
+ const brokenStepIndex = chainEvidence.brokenStepIndex || 0;
1495
+
1496
+ // -15 if chain is too short (2 steps might be coincidence)
1497
+ if (chainLength <= 2) {
1498
+ total -= 15;
1499
+ penalties.push('Short chain (2 steps) - may be coincidence');
1500
+ }
1501
+
1502
+ // -12 if break occurs very early (step 1)
1503
+ if (brokenStepIndex <= 1) {
1504
+ total -= 12;
1505
+ penalties.push('Chain broke at entry point - not a chain break pattern');
1506
+ }
1507
+
1508
+ // -10 if no steps were fulfilled (trace doesn't support chain)
1509
+ if (fulfilledSteps === 0) {
1510
+ total -= 10;
1511
+ penalties.push('No chain steps were fulfilled - chain pattern not evident');
1512
+ }
1513
+
1514
+ // -8 if strong UI feedback present (user might have been informed of issue)
1515
+ if (evidenceSignals?.strongUiFeedback) {
1516
+ total -= 8;
1517
+ penalties.push('Strong UI feedback detected - not silent');
1518
+ }
1519
+
1520
+ return total;
1521
+ }