@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,218 @@
1
+ /**
2
+ * PHASE 12 — Route Intelligence Findings Detector
3
+ *
4
+ * Detects route-related silent failures by correlating navigation promises
5
+ * with route definitions and evaluating outcomes.
6
+ */
7
+
8
+ import {
9
+ buildRouteModels,
10
+ correlateNavigationWithRoute,
11
+ evaluateRouteNavigation,
12
+ buildRouteEvidence,
13
+ isRouteChangeFalsePositive,
14
+ } from '../core/route-intelligence.js';
15
+ import { computeConfidence } from './confidence-engine.js';
16
+ import { computeConfidenceForFinding } from '../core/confidence-engine.js';
17
+ import { buildAndEnforceEvidencePackage } from '../core/evidence-builder.js';
18
+ import { applyGuardrails } from '../core/guardrails-engine.js';
19
+
20
+ /**
21
+ * PHASE 12: Detect route-related findings
22
+ *
23
+ * @param {Array} traces - Interaction traces
24
+ * @param {Object} manifest - Project manifest with routes and expectations
25
+ * @param {Array} findings - Findings array to append to
26
+ * @returns {Array} Route-related findings
27
+ */
28
+ export function detectRouteFindings(traces, manifest, findings) {
29
+ const routeFindings = [];
30
+
31
+ // Build route models from manifest routes
32
+ const routeModels = buildRouteModels(manifest.routes || []);
33
+
34
+ // Process each trace
35
+ for (const trace of traces) {
36
+ const interaction = trace.interaction || {};
37
+ const beforeUrl = trace.before?.url || trace.sensors?.navigation?.beforeUrl || '';
38
+ const afterUrl = trace.after?.url || trace.sensors?.navigation?.afterUrl || '';
39
+
40
+ // Find navigation expectations for this interaction
41
+ const navigationExpectations = findNavigationExpectations(manifest, interaction, beforeUrl);
42
+
43
+ for (const expectation of navigationExpectations) {
44
+ const navigationTarget = expectation.targetPath || expectation.expectedTarget || '';
45
+
46
+ if (!navigationTarget) continue;
47
+
48
+ // Correlate navigation promise with route
49
+ const correlation = correlateNavigationWithRoute(navigationTarget, routeModels);
50
+
51
+ // Check for false positives
52
+ if (isRouteChangeFalsePositive(trace, correlation)) {
53
+ continue;
54
+ }
55
+
56
+ // Evaluate route navigation outcome
57
+ const evaluation = evaluateRouteNavigation(correlation, trace, beforeUrl, afterUrl);
58
+
59
+ // Generate finding if needed
60
+ if (evaluation.outcome === 'SILENT_FAILURE' ||
61
+ evaluation.outcome === 'ROUTE_MISMATCH' ||
62
+ (evaluation.outcome === 'SUSPECTED' && evaluation.confidence >= 0.6)) {
63
+
64
+ // Build evidence
65
+ const evidence = buildRouteEvidence(correlation, expectation, evaluation, trace);
66
+
67
+ // Determine finding type
68
+ let findingType = 'route_silent_failure';
69
+ let reason = evaluation.reason || 'Route navigation promise not fulfilled';
70
+
71
+ if (evaluation.outcome === 'ROUTE_MISMATCH') {
72
+ findingType = 'route_mismatch';
73
+ reason = `Navigation occurred but target route does not match. Expected: ${correlation?.route?.path}, Actual: ${evidence.beforeAfter.afterUrl}`;
74
+ } else if (evaluation.outcome === 'SUSPECTED') {
75
+ findingType = 'route_ambiguous';
76
+ reason = 'Dynamic route cannot be deterministically validated';
77
+ }
78
+
79
+ // PHASE 15: Compute unified confidence
80
+ const unifiedConfidence = computeConfidenceForFinding({
81
+ findingType: findingType,
82
+ expectation,
83
+ sensors: trace.sensors || {},
84
+ comparisons: {
85
+ urlChanged: evidence.signals.urlChanged,
86
+ domChanged: evidence.signals.domChanged,
87
+ },
88
+ evidence,
89
+ });
90
+
91
+ // PHASE 12: Evidence Law - require sufficient evidence for CONFIRMED
92
+ const hasSufficientEvidence = evidence.beforeAfter.beforeUrl &&
93
+ evidence.beforeAfter.afterUrl &&
94
+ (evidence.signals.urlChanged ||
95
+ evidence.signals.routerStateChanged ||
96
+ evidence.signals.uiChanged ||
97
+ evidence.signals.domChanged);
98
+
99
+ const severity = hasSufficientEvidence && unifiedConfidence.score >= 0.8 ? 'CONFIRMED' : 'SUSPECTED';
100
+
101
+ const finding = {
102
+ type: findingType,
103
+ severity,
104
+ confidence: unifiedConfidence.score, // PHASE 15: Use unified confidence score (0..1)
105
+ confidenceLevel: unifiedConfidence.level, // PHASE 15: Add confidence level
106
+ confidenceReasons: unifiedConfidence.reasons, // PHASE 15: Add stable reason codes
107
+ interaction: {
108
+ type: interaction.type,
109
+ selector: interaction.selector,
110
+ label: interaction.label,
111
+ },
112
+ reason,
113
+ evidence,
114
+ source: {
115
+ file: expectation.source?.file || null,
116
+ line: expectation.source?.line || null,
117
+ column: expectation.source?.column || null,
118
+ context: expectation.source?.context || null,
119
+ astSource: expectation.source?.astSource || expectation.metadata?.astSource || null,
120
+ },
121
+ };
122
+
123
+ // PHASE 16: Build and enforce evidence package
124
+ const findingWithEvidence = buildAndEnforceEvidencePackage(finding, {
125
+ expectation,
126
+ trace,
127
+ evidence,
128
+ confidence: unifiedConfidence,
129
+ });
130
+
131
+ // PHASE 17: Apply guardrails (AFTER evidence builder)
132
+ const context = {
133
+ evidencePackage: findingWithEvidence.evidencePackage,
134
+ signals: findingWithEvidence.evidencePackage?.signals || evidence.signals || {},
135
+ confidenceReasons: unifiedConfidence.reasons || [],
136
+ promiseType: expectation?.type || null,
137
+ };
138
+ const { finding: findingWithGuardrails } = applyGuardrails(findingWithEvidence, context);
139
+
140
+ routeFindings.push(findingWithGuardrails);
141
+ }
142
+ }
143
+ }
144
+
145
+ return routeFindings;
146
+ }
147
+
148
+ /**
149
+ * Find navigation expectations matching the interaction
150
+ */
151
+ function findNavigationExpectations(manifest, interaction, beforeUrl) {
152
+ const expectations = [];
153
+
154
+ // Check static expectations
155
+ if (manifest.staticExpectations) {
156
+ const beforePath = extractPathFromUrl(beforeUrl);
157
+ if (beforePath) {
158
+ const normalizedBefore = beforePath.replace(/\/$/, '') || '/';
159
+
160
+ for (const expectation of manifest.staticExpectations) {
161
+ if (expectation.type !== 'navigation' && expectation.type !== 'spa_navigation') {
162
+ continue;
163
+ }
164
+
165
+ const normalizedFrom = (expectation.fromPath || '').replace(/\/$/, '') || '/';
166
+ if (normalizedFrom === normalizedBefore) {
167
+ // Check selector match
168
+ const selectorHint = expectation.selectorHint || '';
169
+ const interactionSelector = interaction.selector || '';
170
+
171
+ if (!selectorHint || !interactionSelector ||
172
+ selectorHint === interactionSelector ||
173
+ selectorHint.includes(interactionSelector) ||
174
+ interactionSelector.includes(selectorHint)) {
175
+ expectations.push(expectation);
176
+ }
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ // Check expectations from intel (AST-based)
183
+ if (manifest.expectations) {
184
+ for (const expectation of manifest.expectations) {
185
+ if (expectation.type === 'navigation' || expectation.type === 'spa_navigation') {
186
+ // Match by selector or label
187
+ const selectorHint = expectation.selectorHint || '';
188
+ const interactionSelector = interaction.selector || '';
189
+ const interactionLabel = (interaction.label || '').toLowerCase();
190
+ const expectationLabel = (expectation.promise?.value || '').toLowerCase();
191
+
192
+ if (selectorHint === interactionSelector ||
193
+ (expectationLabel && interactionLabel && expectationLabel.includes(interactionLabel))) {
194
+ expectations.push(expectation);
195
+ }
196
+ }
197
+ }
198
+ }
199
+
200
+ return expectations;
201
+ }
202
+
203
+ /**
204
+ * Extract path from URL
205
+ */
206
+ function extractPathFromUrl(url) {
207
+ if (!url || typeof url !== 'string') return '';
208
+
209
+ try {
210
+ const urlObj = new URL(url);
211
+ return urlObj.pathname;
212
+ } catch {
213
+ // Relative URL
214
+ const pathMatch = url.match(/^([^?#]+)/);
215
+ return pathMatch ? pathMatch[1] : url;
216
+ }
217
+ }
218
+
@@ -136,8 +136,8 @@ export function mapOwnership(finding, trace = {}) {
136
136
  const findingType = finding.type || 'unknown';
137
137
  const sensors = trace.sensors || {};
138
138
  const hasNetwork = (sensors.network?.totalRequests || 0) > 0;
139
- const hasAria = sensors.aria !== undefined;
140
- const hasFocus = sensors.focus !== undefined;
139
+ const _hasAria = sensors.aria !== undefined;
140
+ const _hasFocus = sensors.focus !== undefined;
141
141
  const hasTiming = sensors.timing !== undefined;
142
142
 
143
143
  // ACCESSIBILITY: Focus, ARIA, keyboard trap failures
@@ -54,8 +54,8 @@ export function classifySkipReason(manifest, interaction, beforeUrl, validation
54
54
  const interactionSelector = interaction.selector || '';
55
55
 
56
56
  if (selectorHint && interactionSelector) {
57
- const normalizedSelectorHint = selectorHint.replace(/[\[\]()]/g, '');
58
- const normalizedInteractionSelector = interactionSelector.replace(/[\[\]()]/g, '');
57
+ const normalizedSelectorHint = selectorHint.replace(/[[\]()]/g, '');
58
+ const normalizedInteractionSelector = interactionSelector.replace(/[[\]()]/g, '');
59
59
 
60
60
  if (selectorHint === interactionSelector ||
61
61
  selectorHint.includes(interactionSelector) ||
@@ -76,8 +76,8 @@ export function classifySkipReason(manifest, interaction, beforeUrl, validation
76
76
  const interactionSelector = interaction.selector || '';
77
77
 
78
78
  if (selectorHint && interactionSelector) {
79
- const normalizedSelectorHint = selectorHint.replace(/[\[\]()]/g, '');
80
- const normalizedInteractionSelector = interactionSelector.replace(/[\[\]()]/g, '');
79
+ const normalizedSelectorHint = selectorHint.replace(/[[\]()]/g, '');
80
+ const normalizedInteractionSelector = interactionSelector.replace(/[[\]()]/g, '');
81
81
 
82
82
  if (selectorHint === interactionSelector ||
83
83
  selectorHint.includes(interactionSelector) ||
@@ -0,0 +1,207 @@
1
+ /**
2
+ * PHASE 13 — UI Feedback Findings Detector
3
+ *
4
+ * Detects UI feedback-related silent failures by correlating promises
5
+ * with feedback signals and evaluating outcomes.
6
+ */
7
+
8
+ import {
9
+ detectUIFeedbackSignals,
10
+ scoreUIFeedback,
11
+ correlatePromiseWithFeedback,
12
+ buildUIFeedbackEvidence,
13
+ FEEDBACK_SCORE,
14
+ } from '../core/ui-feedback-intelligence.js';
15
+ import { computeConfidence } from './confidence-engine.js';
16
+ import { computeConfidenceForFinding } from '../core/confidence-engine.js';
17
+ import { buildAndEnforceEvidencePackage } from '../core/evidence-builder.js';
18
+ import { applyGuardrails } from '../core/guardrails-engine.js';
19
+
20
+ /**
21
+ * PHASE 13: Detect UI feedback-related findings
22
+ *
23
+ * @param {Array} traces - Interaction traces
24
+ * @param {Object} manifest - Project manifest with expectations
25
+ * @param {Array} findings - Findings array to append to
26
+ * @returns {Array} UI feedback-related findings
27
+ */
28
+ export function detectUIFeedbackFindings(traces, manifest, findings) {
29
+ const feedbackFindings = [];
30
+
31
+ // Process each trace
32
+ for (const trace of traces) {
33
+ const interaction = trace.interaction || {};
34
+
35
+ // Find expectations for this interaction
36
+ const expectations = findExpectationsForInteraction(manifest, interaction, trace);
37
+
38
+ for (const expectation of expectations) {
39
+ // Detect UI feedback signals
40
+ const signals = detectUIFeedbackSignals(trace);
41
+
42
+ // Score feedback presence/absence
43
+ const feedbackScore = scoreUIFeedback(signals, expectation, trace);
44
+
45
+ // Correlate promise with feedback
46
+ const correlation = correlatePromiseWithFeedback(expectation, feedbackScore, trace);
47
+
48
+ // Generate finding if correlation indicates silent failure
49
+ if (correlation.outcome === 'CONFIRMED' || correlation.outcome === 'SUSPECTED') {
50
+ // Build evidence
51
+ const evidence = buildUIFeedbackEvidence(feedbackScore, correlation, trace, expectation);
52
+
53
+ // PHASE 13: Evidence Law - require sufficient evidence for CONFIRMED
54
+ const hasSufficientEvidence = evidence.beforeAfter.beforeScreenshot &&
55
+ evidence.beforeAfter.afterScreenshot &&
56
+ (evidence.feedback.signals.length > 0 ||
57
+ evidence.feedback.score === FEEDBACK_SCORE.MISSING);
58
+
59
+ // PHASE 15: Compute unified confidence
60
+ const unifiedConfidence = computeConfidenceForFinding({
61
+ findingType: findingType,
62
+ expectation,
63
+ sensors: trace.sensors || {},
64
+ comparisons: {},
65
+ evidence,
66
+ });
67
+
68
+ // Legacy confidence for backward compatibility
69
+ const confidence = computeConfidence({
70
+ findingType: 'ui_feedback_silent_failure',
71
+ expectation,
72
+ sensors: trace.sensors || {},
73
+ comparisons: {},
74
+ attemptMeta: {},
75
+ });
76
+
77
+ // Determine severity based on evidence
78
+ const severity = hasSufficientEvidence && correlation.outcome === 'CONFIRMED' && unifiedConfidence.score >= 0.8
79
+ ? 'CONFIRMED'
80
+ : 'SUSPECTED';
81
+
82
+ // Determine finding type
83
+ let findingType = 'ui_feedback_silent_failure';
84
+ if (expectation.type === 'network_action' || expectation.type === 'network') {
85
+ findingType = 'network_feedback_missing';
86
+ } else if (expectation.type === 'navigation' || expectation.type === 'spa_navigation') {
87
+ findingType = 'navigation_feedback_missing';
88
+ } else if (expectation.type === 'validation' || expectation.type === 'form_submission') {
89
+ findingType = 'validation_feedback_missing';
90
+ } else if (expectation.type === 'state_action' || expectation.type === 'state') {
91
+ findingType = 'state_feedback_missing';
92
+ }
93
+
94
+ const finding = {
95
+ type: findingType,
96
+ severity,
97
+ confidence: unifiedConfidence.score, // PHASE 15: Use unified confidence score (0..1)
98
+ confidenceLevel: unifiedConfidence.level, // PHASE 15: Add confidence level
99
+ confidenceReasons: unifiedConfidence.reasons, // PHASE 15: Add stable reason codes
100
+ interaction: {
101
+ type: interaction.type,
102
+ selector: interaction.selector,
103
+ label: interaction.label,
104
+ },
105
+ reason: correlation.reason || feedbackScore.explanation,
106
+ evidence,
107
+ source: {
108
+ file: expectation.source?.file || null,
109
+ line: expectation.source?.line || null,
110
+ column: expectation.source?.column || null,
111
+ context: expectation.source?.context || null,
112
+ astSource: expectation.source?.astSource || null,
113
+ },
114
+ };
115
+
116
+ // PHASE 16: Build and enforce evidence package
117
+ const findingWithEvidence = buildAndEnforceEvidencePackage(finding, {
118
+ expectation,
119
+ trace,
120
+ evidence,
121
+ confidence: unifiedConfidence,
122
+ });
123
+
124
+ // PHASE 17: Apply guardrails (AFTER evidence builder)
125
+ const context = {
126
+ evidencePackage: findingWithEvidence.evidencePackage,
127
+ signals: findingWithEvidence.evidencePackage?.signals || evidence.signals || {},
128
+ confidenceReasons: unifiedConfidence.reasons || [],
129
+ promiseType: expectation?.type || null,
130
+ };
131
+ const { finding: findingWithGuardrails } = applyGuardrails(findingWithEvidence, context);
132
+
133
+ feedbackFindings.push(findingWithGuardrails);
134
+ }
135
+ }
136
+ }
137
+
138
+ return feedbackFindings;
139
+ }
140
+
141
+ /**
142
+ * Find expectations matching the interaction
143
+ */
144
+ function findExpectationsForInteraction(manifest, interaction, trace) {
145
+ const expectations = [];
146
+
147
+ // Check static expectations
148
+ if (manifest.staticExpectations) {
149
+ const beforeUrl = trace.before?.url || trace.sensors?.navigation?.beforeUrl || '';
150
+ const beforePath = extractPathFromUrl(beforeUrl);
151
+
152
+ if (beforePath) {
153
+ const normalizedBefore = beforePath.replace(/\/$/, '') || '/';
154
+
155
+ for (const expectation of manifest.staticExpectations) {
156
+ const normalizedFrom = (expectation.fromPath || '').replace(/\/$/, '') || '/';
157
+ if (normalizedFrom === normalizedBefore) {
158
+ // Check selector match
159
+ const selectorHint = expectation.selectorHint || '';
160
+ const interactionSelector = interaction.selector || '';
161
+
162
+ if (!selectorHint || !interactionSelector ||
163
+ selectorHint === interactionSelector ||
164
+ selectorHint.includes(interactionSelector) ||
165
+ interactionSelector.includes(selectorHint)) {
166
+ expectations.push(expectation);
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ // Check expectations from intel (AST-based)
174
+ if (manifest.expectations) {
175
+ for (const expectation of manifest.expectations) {
176
+ // Match by selector or label
177
+ const selectorHint = expectation.selectorHint || '';
178
+ const interactionSelector = interaction.selector || '';
179
+ const interactionLabel = (interaction.label || '').toLowerCase();
180
+ const expectationLabel = (expectation.promise?.value || '').toLowerCase();
181
+
182
+ if (selectorHint === interactionSelector ||
183
+ (expectationLabel && interactionLabel && expectationLabel.includes(interactionLabel))) {
184
+ expectations.push(expectation);
185
+ }
186
+ }
187
+ }
188
+
189
+ return expectations;
190
+ }
191
+
192
+ /**
193
+ * Extract path from URL
194
+ */
195
+ function extractPathFromUrl(url) {
196
+ if (!url || typeof url !== 'string') return '';
197
+
198
+ try {
199
+ const urlObj = new URL(url);
200
+ return urlObj.pathname;
201
+ } catch {
202
+ // Relative URL
203
+ const pathMatch = url.match(/^([^?#]+)/);
204
+ return pathMatch ? pathMatch[1] : url;
205
+ }
206
+ }
207
+
@@ -14,9 +14,7 @@
14
14
  * PHASE 4: All observations include Silence lifecycle - type, trigger, evaluation status, confidence impact.
15
15
  */
16
16
 
17
- import fs from 'fs';
18
- import path from 'path';
19
- import { buildEvidenceIndex, writeEvidenceIndex } from './evidence-index.js';
17
+ import { buildEvidenceIndex } from './evidence-index.js';
20
18
  import { CANONICAL_OUTCOMES } from '../core/canonical-outcomes.js';
21
19
  import { SILENCE_TYPES, EVALUATION_STATUS } from '../core/silence-model.js';
22
20
  import { inferPromiseFromInteraction } from '../core/promise-model.js';
@@ -57,29 +55,63 @@ export function computeObservationSummary(findings, observeTruth, learnTruth, co
57
55
  );
58
56
  const skippedCount = coverage.skippedInteractions || 0;
59
57
 
60
- // Count findings by confidence (for transparency, not judgment)
58
+ // PHASE 15: Count findings by unified confidence level
61
59
  const findingsByConfidence = {
62
60
  HIGH: 0,
63
61
  MEDIUM: 0,
64
62
  LOW: 0,
65
- UNKNOWN: 0
63
+ UNPROVEN: 0 // PHASE 15: Changed from UNKNOWN to UNPROVEN
66
64
  };
67
65
  const findingsByType = {};
68
66
  const findingsByOutcome = {}; // PHASE 2: Added outcome tracking
69
67
  const findingsByPromise = {}; // PHASE 3: Added promise tracking
70
68
 
71
69
  for (const finding of (findings || [])) {
72
- const confidence = finding.confidence?.level || 'UNKNOWN';
70
+ // PHASE 15: Use unified confidence level
71
+ const confidence = finding.confidenceLevel || finding.confidence?.level || 'UNPROVEN';
73
72
  const type = finding.type || 'unknown';
74
73
  const outcome = finding.outcome || CANONICAL_OUTCOMES.SILENT_FAILURE; // Default for legacy findings
75
74
  const promiseType = finding.promise?.type || 'UNKNOWN_PROMISE'; // PHASE 3
76
75
 
77
- if (findingsByConfidence.hasOwnProperty(confidence)) {
76
+ if (Object.prototype.hasOwnProperty.call(findingsByConfidence, confidence)) {
78
77
  findingsByConfidence[confidence]++;
79
78
  }
80
79
  findingsByType[type] = (findingsByType[type] || 0) + 1;
81
80
  findingsByOutcome[outcome] = (findingsByOutcome[outcome] || 0) + 1; // PHASE 2
82
81
  findingsByPromise[promiseType] = (findingsByPromise[promiseType] || 0) + 1; // PHASE 3
82
+
83
+ // PHASE 16: Track evidence completeness
84
+ evidenceCompleteness.totalFindings++;
85
+ if (finding.evidencePackage) {
86
+ if (finding.evidencePackage.isComplete) {
87
+ evidenceCompleteness.completeEvidence++;
88
+ } else {
89
+ evidenceCompleteness.incompleteEvidence++;
90
+ }
91
+ }
92
+ if (finding.evidenceCompleteness?.downgraded) {
93
+ evidenceCompleteness.downgradedCount++;
94
+ }
95
+
96
+ // PHASE 17: Track guardrails
97
+ if (finding.guardrails) {
98
+ guardrailsSummary.totalFindingsProcessed++;
99
+ if (finding.guardrails.appliedRules) {
100
+ guardrailsSummary.appliedRulesCount += finding.guardrails.appliedRules.length;
101
+ }
102
+ if (finding.guardrails.contradictions) {
103
+ guardrailsSummary.contradictionsCount += finding.guardrails.contradictions.length;
104
+ }
105
+ if (finding.guardrails.finalDecision === 'SUSPECTED' && finding.severity === 'CONFIRMED') {
106
+ guardrailsSummary.downgradedCount++;
107
+ }
108
+ if (finding.guardrails.finalDecision === 'INFORMATIONAL') {
109
+ guardrailsSummary.informationalCount++;
110
+ }
111
+ if (finding.guardrails.recommendedStatus !== finding.severity && finding.severity === 'CONFIRMED') {
112
+ guardrailsSummary.preventedConfirmedCount++;
113
+ }
114
+ }
83
115
  }
84
116
 
85
117
  // Calculate ratios (factual, not judgmental)
@@ -96,6 +128,24 @@ export function computeObservationSummary(findings, observeTruth, learnTruth, co
96
128
  unprovenResults: unprovenTraces.length
97
129
  };
98
130
 
131
+ // PHASE 16: Track evidence completeness summary
132
+ const evidenceCompleteness = {
133
+ totalFindings: 0,
134
+ completeEvidence: 0,
135
+ incompleteEvidence: 0,
136
+ downgradedCount: 0,
137
+ };
138
+
139
+ // PHASE 17: Track guardrails summary
140
+ const guardrailsSummary = {
141
+ totalFindingsProcessed: 0,
142
+ preventedConfirmedCount: 0,
143
+ downgradedCount: 0,
144
+ informationalCount: 0,
145
+ appliedRulesCount: 0,
146
+ contradictionsCount: 0,
147
+ };
148
+
99
149
  // Build gap details
100
150
  const gapDetails = [];
101
151
  if (isBudgetExceeded) {
@@ -152,6 +202,8 @@ export function computeObservationSummary(findings, observeTruth, learnTruth, co
152
202
  discrepanciesByPromise: findingsByPromise, // PHASE 3: Promise types
153
203
  findings: findings || []
154
204
  },
205
+ evidenceCompleteness: evidenceCompleteness, // PHASE 16: Evidence completeness summary
206
+ guardrailsSummary: guardrailsSummary, // PHASE 17: Guardrails summary
155
207
  coverage: {
156
208
  pagesEvaluated,
157
209
  pagesDiscovered,
@@ -402,7 +454,7 @@ export function formatObservationSummary(observationSummary) {
402
454
  for (const finding of obs.findings.slice(0, 3)) {
403
455
  const outcome = finding.outcome ? ` [${finding.outcome}]` : '';
404
456
  const promiseInfo = finding.promise ? ` (${finding.promise.type.replace(/_PROMISE$/, '')})` : '';
405
- const confStr = finding.confidence?.level ? ` (${finding.confidence.level} confidence)` : '';
457
+ const _confStr = finding.confidence?.level ? ` (${finding.confidence.level} confidence)` : '';
406
458
  const userStmt = finding.what_happened ? `User: ${finding.what_happened}` : '';
407
459
  lines.push(` • ${finding.type}${outcome}${promiseInfo}`);
408
460
  if (userStmt) lines.push(` ${userStmt}`);
@@ -454,7 +506,7 @@ export function formatObservationSummary(observationSummary) {
454
506
  export function inferPromiseForSilence(silence) {
455
507
  if (!silence) return null;
456
508
 
457
- const { silence_type, scope, reason, context } = silence;
509
+ const { silence_type, scope: _scope, reason, context } = silence;
458
510
 
459
511
  // Navigation-related silences
460
512
  if (silence_type === SILENCE_TYPES.NAVIGATION_TIMEOUT ||