@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
@@ -0,0 +1,361 @@
1
+ /**
2
+ * Problem Aggregator
3
+ *
4
+ * Groups raw findings into decision-level problems for executive review.
5
+ *
6
+ * RULES:
7
+ * - Deterministic grouping (no ML, no heuristics)
8
+ * - Every problem references underlying findings
9
+ * - Raw findings are preserved (never hidden)
10
+ * - Evidence is deduplicated across group
11
+ */
12
+
13
+ import { basename } from 'path';
14
+
15
+ /**
16
+ * Aggregate findings into decision-level problems
17
+ *
18
+ * @param {Array} findings - Raw findings from detection
19
+ * @param {Object} manifest - Project manifest
20
+ * @returns {Array} Array of ProblemGroup objects
21
+ */
22
+ export function aggregateProblems(findings, manifest) {
23
+ if (!findings || findings.length === 0) {
24
+ return [];
25
+ }
26
+
27
+ // Group findings by page/route
28
+ const byPage = groupByPage(findings);
29
+
30
+ // Within each page, group by user intent
31
+ const problems = [];
32
+
33
+ for (const [page, pageFindings] of Object.entries(byPage)) {
34
+ const intentGroups = groupByIntent(pageFindings);
35
+
36
+ for (const [intent, intentFindings] of Object.entries(intentGroups)) {
37
+ // Create a problem group
38
+ const problem = createProblemGroup(page, intent, intentFindings, manifest);
39
+ if (problem) {
40
+ problems.push(problem);
41
+ }
42
+ }
43
+ }
44
+
45
+ // Sort by impact (HIGH > MEDIUM > LOW) then confidence (HIGH > LOW)
46
+ problems.sort((a, b) => {
47
+ const impactOrder = { HIGH: 3, MEDIUM: 2, LOW: 1, UNKNOWN: 0 };
48
+ const impactDiff = impactOrder[b.impact] - impactOrder[a.impact];
49
+ if (impactDiff !== 0) return impactDiff;
50
+ return b.confidence - a.confidence;
51
+ });
52
+
53
+ return problems;
54
+ }
55
+
56
+ /**
57
+ * Group findings by page/route
58
+ */
59
+ function groupByPage(findings) {
60
+ const groups = {};
61
+
62
+ for (const finding of findings) {
63
+ const page = extractPage(finding);
64
+ if (!groups[page]) {
65
+ groups[page] = [];
66
+ }
67
+ groups[page].push(finding);
68
+ }
69
+
70
+ return groups;
71
+ }
72
+
73
+ /**
74
+ * Extract page identifier from finding
75
+ */
76
+ function extractPage(finding) {
77
+ if (!finding.source || !finding.source.file) {
78
+ return 'unknown';
79
+ }
80
+
81
+ // Extract filename without extension
82
+ const file = finding.source.file;
83
+ const filename = basename(file, '.jsx').replace(/\.js$/, '');
84
+
85
+ // Map common patterns to routes
86
+ if (filename.match(/dashboard/i)) return 'dashboard';
87
+ if (filename.match(/profile/i)) return 'profile';
88
+ if (filename.match(/settings/i)) return 'settings';
89
+ if (filename.match(/home|index/i)) return 'home';
90
+ if (filename.match(/login|auth/i)) return 'auth';
91
+
92
+ return filename.toLowerCase();
93
+ }
94
+
95
+ /**
96
+ * Group findings by user intent
97
+ */
98
+ function groupByIntent(findings) {
99
+ const groups = {};
100
+
101
+ for (const finding of findings) {
102
+ const intent = inferIntent(finding);
103
+ if (!groups[intent]) {
104
+ groups[intent] = [];
105
+ }
106
+ groups[intent].push(finding);
107
+ }
108
+
109
+ return groups;
110
+ }
111
+
112
+ /**
113
+ * Infer user intent from finding
114
+ */
115
+ function inferIntent(finding) {
116
+ const promise = finding.promise || {};
117
+ const type = finding.type;
118
+ const source = finding.source || {};
119
+ const file = source.file || '';
120
+
121
+ // Form submission
122
+ if (type === 'form' || promise.kind === 'submit') {
123
+ return 'save';
124
+ }
125
+
126
+ // Navigation
127
+ if (type === 'navigation' || promise.kind === 'navigate') {
128
+ return 'navigate';
129
+ }
130
+
131
+ // Auth actions - explicit button clicks
132
+ if (promise.kind === 'click' && promise.value && promise.value.match(/login|logout|signin|signout/i)) {
133
+ return 'auth';
134
+ }
135
+
136
+ // State changes - be more specific about what kind
137
+ if (type === 'state' || promise.kind === 'state_mutation') {
138
+ const value = promise.value || '';
139
+
140
+ // Loading/saving state - usually indicates stuck loading indicator
141
+ if (value.match(/loading|saving|submitting/i) || file.match(/dashboard/i)) {
142
+ return 'loading_feedback';
143
+ }
144
+
145
+ // Auth-related state
146
+ if (value.match(/auth|user|login/i) || file.match(/auth|profile/i)) {
147
+ // If in Profile.jsx specifically, it's conditional UI bug
148
+ if (file.match(/profile/i)) {
149
+ return 'conditional_ui';
150
+ }
151
+ return 'auth_state';
152
+ }
153
+
154
+ // Generic state update
155
+ return 'state_update';
156
+ }
157
+
158
+ // Feedback/validation
159
+ if (type === 'feedback' || promise.kind === 'validation') {
160
+ return 'validation';
161
+ }
162
+
163
+ // Button clicks
164
+ if (promise.kind === 'click') {
165
+ return 'interact';
166
+ }
167
+
168
+ return 'other';
169
+ }
170
+
171
+ /**
172
+ * Create a problem group from findings
173
+ */
174
+ function createProblemGroup(page, intent, findings, _manifest) {
175
+ if (findings.length === 0) {
176
+ return null;
177
+ }
178
+
179
+ // Generate problem ID
180
+ const id = `problem_${page}_${intent}`;
181
+
182
+ // Determine title based on intent
183
+ const title = generateTitle(page, intent, findings);
184
+
185
+ // Aggregate impact (highest wins)
186
+ const impact = aggregateImpact(findings);
187
+
188
+ // Aggregate confidence (average)
189
+ const confidence = aggregateConfidence(findings);
190
+
191
+ // Deduplicate evidence
192
+ const evidence = deduplicateEvidence(findings);
193
+
194
+ // Extract finding IDs
195
+ const findingIds = findings.map(f => f.id);
196
+
197
+ // Generate explanation
198
+ const explanation = generateExplanation(page, intent, findings);
199
+
200
+ return {
201
+ id,
202
+ title,
203
+ page,
204
+ userIntent: intent,
205
+ impact,
206
+ confidence,
207
+ findings: findingIds,
208
+ findingCount: findings.length,
209
+ evidence,
210
+ whatUserTried: explanation.whatUserTried,
211
+ whatWasExpected: explanation.whatWasExpected,
212
+ whatActuallyHappened: explanation.whatActuallyHappened,
213
+ whyItMatters: explanation.whyItMatters
214
+ };
215
+ }
216
+
217
+ /**
218
+ * Generate human-readable title for problem
219
+ */
220
+ function generateTitle(page, intent, findings) {
221
+ const pageName = page.charAt(0).toUpperCase() + page.slice(1);
222
+
223
+ const intentMap = {
224
+ navigate: `${pageName} page doesn't load`,
225
+ save: `${pageName} form submission fails silently`,
226
+ auth: `${pageName} authentication fails silently`,
227
+ auth_state: `${pageName} authentication state doesn't update UI`,
228
+ conditional_ui: `${pageName} conditional UI doesn't update after state change`,
229
+ loading_feedback: `${pageName} loading state never resolves`,
230
+ state_update: `${pageName} state changes don't update UI`,
231
+ validation: `${pageName} validation feedback missing`,
232
+ interact: `${pageName} interactive elements don't work`,
233
+ other: `${pageName} has broken functionality`
234
+ };
235
+
236
+ return intentMap[intent] || `${pageName} has ${findings.length} silent failures`;
237
+ }
238
+
239
+ /**
240
+ * Aggregate impact across findings (highest wins)
241
+ */
242
+ function aggregateImpact(findings) {
243
+ const impactOrder = { HIGH: 3, MEDIUM: 2, LOW: 1, UNKNOWN: 0 };
244
+ let maxImpact = 'UNKNOWN';
245
+ let maxValue = 0;
246
+
247
+ for (const finding of findings) {
248
+ const impact = finding.impact || 'UNKNOWN';
249
+ const value = impactOrder[impact] || 0;
250
+ if (value > maxValue) {
251
+ maxValue = value;
252
+ maxImpact = impact;
253
+ }
254
+ }
255
+
256
+ return maxImpact;
257
+ }
258
+
259
+ /**
260
+ * Aggregate confidence across findings (average)
261
+ */
262
+ function aggregateConfidence(findings) {
263
+ if (findings.length === 0) return 0;
264
+
265
+ const sum = findings.reduce((acc, f) => acc + (f.confidence || 0.5), 0);
266
+ return Math.round((sum / findings.length) * 100) / 100;
267
+ }
268
+
269
+ /**
270
+ * Deduplicate evidence across findings
271
+ */
272
+ function deduplicateEvidence(findings) {
273
+ const evidenceMap = new Map();
274
+
275
+ for (const finding of findings) {
276
+ if (!finding.evidence) continue;
277
+
278
+ for (const ev of finding.evidence) {
279
+ const key = `${ev.type}:${ev.path || 'none'}`;
280
+ if (!evidenceMap.has(key)) {
281
+ evidenceMap.set(key, ev);
282
+ }
283
+ }
284
+ }
285
+
286
+ return Array.from(evidenceMap.values());
287
+ }
288
+
289
+ /**
290
+ * Generate explanation for problem
291
+ */
292
+ function generateExplanation(page, intent, findings) {
293
+ const pageName = page.charAt(0).toUpperCase() + page.slice(1);
294
+ const count = findings.length;
295
+
296
+ // Build explanation based on intent
297
+ const explanations = {
298
+ navigate: {
299
+ whatUserTried: `Navigate to ${pageName} page`,
300
+ whatWasExpected: 'Page content loads and displays',
301
+ whatActuallyHappened: `URL changed but page content did not render (${count} state mutations stuck)`,
302
+ whyItMatters: 'Users cannot access the intended page or see its content'
303
+ },
304
+ save: {
305
+ whatUserTried: `Submit form on ${pageName} page`,
306
+ whatWasExpected: 'Form submits and shows success feedback (message, redirect, or toast)',
307
+ whatActuallyHappened: `Form submitted but no feedback was provided to user (${count} related issues)`,
308
+ whyItMatters: 'Users don\'t know if their data was saved or if they should retry'
309
+ },
310
+ auth: {
311
+ whatUserTried: `Log in or log out using ${pageName} page buttons`,
312
+ whatWasExpected: 'Button click triggers authentication action with visual feedback',
313
+ whatActuallyHappened: `Button clicks produced no observable effect (${count} buttons don't work)`,
314
+ whyItMatters: 'Users cannot complete authentication workflows'
315
+ },
316
+ auth_state: {
317
+ whatUserTried: `Authenticate using ${pageName} page`,
318
+ whatWasExpected: 'Authentication state changes update UI accordingly',
319
+ whatActuallyHappened: `Auth state changed but UI did not reflect changes (${count} state mutations)`,
320
+ whyItMatters: 'Users cannot tell their authentication status'
321
+ },
322
+ conditional_ui: {
323
+ whatUserTried: `Interact with conditional UI on ${pageName} page`,
324
+ whatWasExpected: 'UI elements appear/disappear based on state (e.g., Login button hides after login)',
325
+ whatActuallyHappened: `State changed but conditional UI did not update (${count} stale elements)`,
326
+ whyItMatters: 'Users see UI elements that should be hidden or vice versa, causing confusion'
327
+ },
328
+ loading_feedback: {
329
+ whatUserTried: `Interact with ${pageName} page`,
330
+ whatWasExpected: 'Loading indicator appears then resolves when content is ready',
331
+ whatActuallyHappened: `Loading states were set but never cleared (${count} stuck states)`,
332
+ whyItMatters: 'Users see perpetual loading spinners or incomplete UI'
333
+ },
334
+ state_update: {
335
+ whatUserTried: `Interact with ${pageName} page elements`,
336
+ whatWasExpected: 'UI updates to reflect new state',
337
+ whatActuallyHappened: `State changed but UI did not update (${count} stale UI elements)`,
338
+ whyItMatters: 'Users see outdated information that doesn\'t match actual state'
339
+ },
340
+ validation: {
341
+ whatUserTried: `Submit invalid data on ${pageName} form`,
342
+ whatWasExpected: 'Validation errors display clearly',
343
+ whatActuallyHappened: `Validation feedback elements missing (${count} issues)`,
344
+ whyItMatters: 'Users cannot correct their mistakes or understand what went wrong'
345
+ },
346
+ interact: {
347
+ whatUserTried: `Click buttons or interact with elements on ${pageName}`,
348
+ whatWasExpected: 'Interactive elements respond with visible feedback',
349
+ whatActuallyHappened: `${count} interactive elements produced no observable effect`,
350
+ whyItMatters: 'Users cannot complete intended actions or workflows'
351
+ },
352
+ other: {
353
+ whatUserTried: `Use ${pageName} page functionality`,
354
+ whatWasExpected: 'Actions produce visible results',
355
+ whatActuallyHappened: `${count} actions failed silently with no user feedback`,
356
+ whyItMatters: 'Core functionality is broken but fails without error messages'
357
+ }
358
+ };
359
+
360
+ return explanations[intent] || explanations.other;
361
+ }
@@ -0,0 +1,219 @@
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 { computeConfidenceForFinding } from '../core/confidence-engine.js';
16
+ import { buildAndEnforceEvidencePackage } from '../core/evidence-builder.js';
17
+ import { applyGuardrails } from '../core/guardrails-engine.js';
18
+
19
+ /**
20
+ * PHASE 12: Detect route-related findings
21
+ *
22
+ * @param {Array} traces - Interaction traces
23
+ * @param {Object} manifest - Project manifest with routes and expectations
24
+ * @param {Array} _findings - Findings array to append to
25
+ * @ts-expect-error - JSDoc param documented but unused
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
+ options: {}
90
+ });
91
+
92
+ // PHASE 12: Evidence Law - require sufficient evidence for CONFIRMED
93
+ const hasSufficientEvidence = evidence.beforeAfter.beforeUrl &&
94
+ evidence.beforeAfter.afterUrl &&
95
+ (evidence.signals.urlChanged ||
96
+ evidence.signals.routerStateChanged ||
97
+ evidence.signals.uiChanged ||
98
+ evidence.signals.domChanged);
99
+
100
+ const severity = hasSufficientEvidence && (unifiedConfidence.score01 || unifiedConfidence.score || 0) >= 0.8 ? 'CONFIRMED' : 'SUSPECTED';
101
+
102
+ const finding = {
103
+ type: findingType,
104
+ severity,
105
+ confidence: unifiedConfidence.score01 || unifiedConfidence.score || 0, // Contract v1: score01 canonical
106
+ confidenceLevel: unifiedConfidence.level, // PHASE 15: Add confidence level
107
+ confidenceReasons: unifiedConfidence.topReasons || unifiedConfidence.reasons || [], // Contract v1: topReasons
108
+ interaction: {
109
+ type: interaction.type,
110
+ selector: interaction.selector,
111
+ label: interaction.label,
112
+ },
113
+ reason,
114
+ evidence,
115
+ source: {
116
+ file: expectation.source?.file || null,
117
+ line: expectation.source?.line || null,
118
+ column: expectation.source?.column || null,
119
+ context: expectation.source?.context || null,
120
+ astSource: expectation.source?.astSource || expectation.metadata?.astSource || null,
121
+ },
122
+ };
123
+
124
+ // PHASE 16: Build and enforce evidence package
125
+ const findingWithEvidence = buildAndEnforceEvidencePackage(finding, {
126
+ expectation,
127
+ trace,
128
+ evidence,
129
+ confidence: unifiedConfidence,
130
+ });
131
+
132
+ // PHASE 17: Apply guardrails (AFTER evidence builder)
133
+ const context = {
134
+ evidencePackage: findingWithEvidence.evidencePackage,
135
+ signals: findingWithEvidence.evidencePackage?.signals || evidence.signals || {},
136
+ confidenceReasons: unifiedConfidence.reasons || [],
137
+ promiseType: expectation?.type || null,
138
+ };
139
+ const { finding: findingWithGuardrails } = applyGuardrails(findingWithEvidence, context);
140
+
141
+ routeFindings.push(findingWithGuardrails);
142
+ }
143
+ }
144
+ }
145
+
146
+ return routeFindings;
147
+ }
148
+
149
+ /**
150
+ * Find navigation expectations matching the interaction
151
+ */
152
+ function findNavigationExpectations(manifest, interaction, beforeUrl) {
153
+ const expectations = [];
154
+
155
+ // Check static expectations
156
+ if (manifest.staticExpectations) {
157
+ const beforePath = extractPathFromUrl(beforeUrl);
158
+ if (beforePath) {
159
+ const normalizedBefore = beforePath.replace(/\/$/, '') || '/';
160
+
161
+ for (const expectation of manifest.staticExpectations) {
162
+ if (expectation.type !== 'navigation' && expectation.type !== 'spa_navigation') {
163
+ continue;
164
+ }
165
+
166
+ const normalizedFrom = (expectation.fromPath || '').replace(/\/$/, '') || '/';
167
+ if (normalizedFrom === normalizedBefore) {
168
+ // Check selector match
169
+ const selectorHint = expectation.selectorHint || '';
170
+ const interactionSelector = interaction.selector || '';
171
+
172
+ if (!selectorHint || !interactionSelector ||
173
+ selectorHint === interactionSelector ||
174
+ selectorHint.includes(interactionSelector) ||
175
+ interactionSelector.includes(selectorHint)) {
176
+ expectations.push(expectation);
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+
183
+ // Check expectations from intel (AST-based)
184
+ if (manifest.expectations) {
185
+ for (const expectation of manifest.expectations) {
186
+ if (expectation.type === 'navigation' || expectation.type === 'spa_navigation') {
187
+ // Match by selector or label
188
+ const selectorHint = expectation.selectorHint || '';
189
+ const interactionSelector = interaction.selector || '';
190
+ const interactionLabel = (interaction.label || '').toLowerCase();
191
+ const expectationLabel = (expectation.promise?.value || '').toLowerCase();
192
+
193
+ if (selectorHint === interactionSelector ||
194
+ (expectationLabel && interactionLabel && expectationLabel.includes(interactionLabel))) {
195
+ expectations.push(expectation);
196
+ }
197
+ }
198
+ }
199
+ }
200
+
201
+ return expectations;
202
+ }
203
+
204
+ /**
205
+ * Extract path from URL
206
+ */
207
+ function extractPathFromUrl(url) {
208
+ if (!url || typeof url !== 'string') return '';
209
+
210
+ try {
211
+ const urlObj = new URL(url);
212
+ return urlObj.pathname;
213
+ } catch {
214
+ // Relative URL
215
+ const pathMatch = url.match(/^([^?#]+)/);
216
+ return pathMatch ? pathMatch[1] : url;
217
+ }
218
+ }
219
+