@veraxhq/verax 0.1.0 → 0.2.1

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 (135) hide show
  1. package/README.md +123 -88
  2. package/bin/verax.js +11 -452
  3. package/package.json +24 -36
  4. package/src/cli/commands/default.js +681 -0
  5. package/src/cli/commands/doctor.js +197 -0
  6. package/src/cli/commands/inspect.js +109 -0
  7. package/src/cli/commands/run.js +586 -0
  8. package/src/cli/entry.js +196 -0
  9. package/src/cli/util/atomic-write.js +37 -0
  10. package/src/cli/util/detection-engine.js +297 -0
  11. package/src/cli/util/env-url.js +33 -0
  12. package/src/cli/util/errors.js +44 -0
  13. package/src/cli/util/events.js +110 -0
  14. package/src/cli/util/expectation-extractor.js +388 -0
  15. package/src/cli/util/findings-writer.js +32 -0
  16. package/src/cli/util/idgen.js +87 -0
  17. package/src/cli/util/learn-writer.js +39 -0
  18. package/src/cli/util/observation-engine.js +412 -0
  19. package/src/cli/util/observe-writer.js +25 -0
  20. package/src/cli/util/paths.js +30 -0
  21. package/src/cli/util/project-discovery.js +297 -0
  22. package/src/cli/util/project-writer.js +26 -0
  23. package/src/cli/util/redact.js +128 -0
  24. package/src/cli/util/run-id.js +30 -0
  25. package/src/cli/util/runtime-budget.js +147 -0
  26. package/src/cli/util/summary-writer.js +43 -0
  27. package/src/types/global.d.ts +28 -0
  28. package/src/types/ts-ast.d.ts +24 -0
  29. package/src/verax/cli/ci-summary.js +35 -0
  30. package/src/verax/cli/context-explanation.js +89 -0
  31. package/src/verax/cli/doctor.js +277 -0
  32. package/src/verax/cli/error-normalizer.js +154 -0
  33. package/src/verax/cli/explain-output.js +105 -0
  34. package/src/verax/cli/finding-explainer.js +130 -0
  35. package/src/verax/cli/init.js +237 -0
  36. package/src/verax/cli/run-overview.js +163 -0
  37. package/src/verax/cli/url-safety.js +111 -0
  38. package/src/verax/cli/wizard.js +109 -0
  39. package/src/verax/cli/zero-findings-explainer.js +57 -0
  40. package/src/verax/cli/zero-interaction-explainer.js +127 -0
  41. package/src/verax/core/action-classifier.js +86 -0
  42. package/src/verax/core/budget-engine.js +218 -0
  43. package/src/verax/core/canonical-outcomes.js +157 -0
  44. package/src/verax/core/decision-snapshot.js +335 -0
  45. package/src/verax/core/determinism-model.js +432 -0
  46. package/src/verax/core/incremental-store.js +245 -0
  47. package/src/verax/core/invariants.js +356 -0
  48. package/src/verax/core/promise-model.js +230 -0
  49. package/src/verax/core/replay-validator.js +350 -0
  50. package/src/verax/core/replay.js +222 -0
  51. package/src/verax/core/run-id.js +175 -0
  52. package/src/verax/core/run-manifest.js +99 -0
  53. package/src/verax/core/silence-impact.js +369 -0
  54. package/src/verax/core/silence-model.js +523 -0
  55. package/src/verax/detect/comparison.js +7 -34
  56. package/src/verax/detect/confidence-engine.js +764 -329
  57. package/src/verax/detect/detection-engine.js +293 -0
  58. package/src/verax/detect/evidence-index.js +127 -0
  59. package/src/verax/detect/expectation-model.js +241 -168
  60. package/src/verax/detect/explanation-helpers.js +187 -0
  61. package/src/verax/detect/finding-detector.js +450 -0
  62. package/src/verax/detect/findings-writer.js +41 -12
  63. package/src/verax/detect/flow-detector.js +366 -0
  64. package/src/verax/detect/index.js +200 -288
  65. package/src/verax/detect/interactive-findings.js +612 -0
  66. package/src/verax/detect/signal-mapper.js +308 -0
  67. package/src/verax/detect/skip-classifier.js +4 -4
  68. package/src/verax/detect/verdict-engine.js +561 -0
  69. package/src/verax/evidence-index-writer.js +61 -0
  70. package/src/verax/flow/flow-engine.js +3 -2
  71. package/src/verax/flow/flow-spec.js +1 -2
  72. package/src/verax/index.js +103 -15
  73. package/src/verax/intel/effect-detector.js +368 -0
  74. package/src/verax/intel/handler-mapper.js +249 -0
  75. package/src/verax/intel/index.js +281 -0
  76. package/src/verax/intel/route-extractor.js +280 -0
  77. package/src/verax/intel/ts-program.js +256 -0
  78. package/src/verax/intel/vue-navigation-extractor.js +642 -0
  79. package/src/verax/intel/vue-router-extractor.js +325 -0
  80. package/src/verax/learn/action-contract-extractor.js +338 -104
  81. package/src/verax/learn/ast-contract-extractor.js +148 -6
  82. package/src/verax/learn/flow-extractor.js +172 -0
  83. package/src/verax/learn/index.js +36 -2
  84. package/src/verax/learn/manifest-writer.js +122 -58
  85. package/src/verax/learn/project-detector.js +40 -0
  86. package/src/verax/learn/route-extractor.js +28 -97
  87. package/src/verax/learn/route-validator.js +8 -7
  88. package/src/verax/learn/state-extractor.js +212 -0
  89. package/src/verax/learn/static-extractor-navigation.js +114 -0
  90. package/src/verax/learn/static-extractor-validation.js +88 -0
  91. package/src/verax/learn/static-extractor.js +119 -10
  92. package/src/verax/learn/truth-assessor.js +24 -21
  93. package/src/verax/learn/ts-contract-resolver.js +14 -12
  94. package/src/verax/observe/aria-sensor.js +211 -0
  95. package/src/verax/observe/browser.js +30 -6
  96. package/src/verax/observe/console-sensor.js +2 -18
  97. package/src/verax/observe/domain-boundary.js +10 -1
  98. package/src/verax/observe/expectation-executor.js +513 -0
  99. package/src/verax/observe/flow-matcher.js +143 -0
  100. package/src/verax/observe/focus-sensor.js +196 -0
  101. package/src/verax/observe/human-driver.js +660 -273
  102. package/src/verax/observe/index.js +910 -26
  103. package/src/verax/observe/interaction-discovery.js +378 -15
  104. package/src/verax/observe/interaction-runner.js +562 -197
  105. package/src/verax/observe/loading-sensor.js +145 -0
  106. package/src/verax/observe/navigation-sensor.js +255 -0
  107. package/src/verax/observe/network-sensor.js +55 -7
  108. package/src/verax/observe/observed-expectation-deriver.js +186 -0
  109. package/src/verax/observe/observed-expectation.js +305 -0
  110. package/src/verax/observe/page-frontier.js +234 -0
  111. package/src/verax/observe/settle.js +38 -17
  112. package/src/verax/observe/state-sensor.js +393 -0
  113. package/src/verax/observe/state-ui-sensor.js +7 -1
  114. package/src/verax/observe/timing-sensor.js +228 -0
  115. package/src/verax/observe/traces-writer.js +73 -21
  116. package/src/verax/observe/ui-signal-sensor.js +143 -17
  117. package/src/verax/scan-summary-writer.js +80 -15
  118. package/src/verax/shared/artifact-manager.js +111 -9
  119. package/src/verax/shared/budget-profiles.js +136 -0
  120. package/src/verax/shared/caching.js +1 -1
  121. package/src/verax/shared/ci-detection.js +39 -0
  122. package/src/verax/shared/config-loader.js +169 -0
  123. package/src/verax/shared/dynamic-route-utils.js +224 -0
  124. package/src/verax/shared/expectation-coverage.js +44 -0
  125. package/src/verax/shared/expectation-prover.js +81 -0
  126. package/src/verax/shared/expectation-tracker.js +201 -0
  127. package/src/verax/shared/expectations-writer.js +60 -0
  128. package/src/verax/shared/first-run.js +44 -0
  129. package/src/verax/shared/progress-reporter.js +171 -0
  130. package/src/verax/shared/retry-policy.js +9 -1
  131. package/src/verax/shared/root-artifacts.js +49 -0
  132. package/src/verax/shared/scan-budget.js +86 -0
  133. package/src/verax/shared/url-normalizer.js +162 -0
  134. package/src/verax/shared/zip-artifacts.js +66 -0
  135. package/src/verax/validate/context-validator.js +244 -0
@@ -0,0 +1,157 @@
1
+ /**
2
+ * CANONICAL OUTCOME TAXONOMY
3
+ *
4
+ * Single source of truth for all outcome classifications in VERAX.
5
+ * Every interaction outcome, silence, gap, and finding must map to exactly ONE of these types.
6
+ *
7
+ * NO free-form or implicit outcome types allowed.
8
+ * NO semantic drift between outputs.
9
+ */
10
+
11
+ /**
12
+ * Canonical Outcome Types - Mutually Exclusive Categories
13
+ */
14
+ export const CANONICAL_OUTCOMES = {
15
+ // Code/user interaction broke (promise unmet, expectation failed)
16
+ SILENT_FAILURE: 'SILENT_FAILURE',
17
+
18
+ // Evaluation was not completed due to time/interaction/data limits (not a failure, just incomplete coverage)
19
+ COVERAGE_GAP: 'COVERAGE_GAP',
20
+
21
+ // Interaction executed but outcome cannot be asserted (ambiguity: SPA fallback, no DOM change signal, missing sensor)
22
+ UNPROVEN_INTERACTION: 'UNPROVEN_INTERACTION',
23
+
24
+ // Intentionally prevented (destructive action, external navigation, safety policy)
25
+ SAFETY_BLOCK: 'SAFETY_BLOCK',
26
+
27
+ // No problem detected; for informational artifacts (e.g., expectations without matching interactions)
28
+ INFORMATIONAL: 'INFORMATIONAL'
29
+ };
30
+
31
+ /**
32
+ * Outcome Definitions - For documentation and validation
33
+ */
34
+ export const OUTCOME_DEFINITIONS = {
35
+ SILENT_FAILURE: {
36
+ title: 'Silent Failure',
37
+ description: 'User action executed, code ran, but expected observable change did not occur and no error was presented to user',
38
+ example: 'User clicks "Save" → page loads without spinning wheel → data is not saved but user receives no error message',
39
+ implication: 'Gap between user expectation and actual system behavior'
40
+ },
41
+
42
+ COVERAGE_GAP: {
43
+ title: 'Coverage Gap',
44
+ description: 'Interaction or expectation discovered but not evaluated due to scan budget limit (time, page count, interaction count)',
45
+ example: '500 pages discovered but scan budget allows only 100 pages → 400 pages not evaluated',
46
+ implication: 'Incomplete scan; behaviors on skipped interactions are unknown'
47
+ },
48
+
49
+ UNPROVEN_INTERACTION: {
50
+ title: 'Unproven Interaction',
51
+ description: 'Interaction executed but outcome ambiguous: no expectation matched, or observable change is ambiguous, or expectation-driven path failed',
52
+ example: 'User clicks SPA link → no observable change (SPA routing ambiguous) → cannot assert if routing worked or if feature is broken',
53
+ implication: 'Behavior is unknown; cannot determine if code matches reality'
54
+ },
55
+
56
+ SAFETY_BLOCK: {
57
+ title: 'Safety Block',
58
+ description: 'Interaction prevented intentionally: destructive text, external navigation, or safety policy blocks execution',
59
+ example: 'User clicks "Delete Account" → blocked by safety policy → behavior untested',
60
+ implication: 'Intentional limitation; not a failure, just a boundary'
61
+ },
62
+
63
+ INFORMATIONAL: {
64
+ title: 'Informational',
65
+ description: 'Metadata or artifact without direct outcome status (e.g., expectation defined but no matching interaction found)',
66
+ example: 'Manifest defines logout expectation → but no logout interaction discovered on site',
67
+ implication: 'Context for interpretation but not a test failure or success'
68
+ }
69
+ };
70
+
71
+ /**
72
+ * Map silence reason codes to canonical outcomes
73
+ *
74
+ * Used by SilenceTracker and verdict-engine to classify silence entries
75
+ * with explicit outcome types.
76
+ */
77
+ export function mapSilenceReasonToOutcome(reason) {
78
+ // Timeouts, failures → SILENT_FAILURE (promise unmet)
79
+ if (reason.includes('timeout') || reason.includes('failed') || reason.includes('error')) {
80
+ return CANONICAL_OUTCOMES.SILENT_FAILURE;
81
+ }
82
+
83
+ // Budget/limit exceeded → COVERAGE_GAP (incomplete scan)
84
+ if (reason.includes('budget') || reason.includes('limit') || reason.includes('exceeded')) {
85
+ return CANONICAL_OUTCOMES.COVERAGE_GAP;
86
+ }
87
+
88
+ // Safety/destructive/external → SAFETY_BLOCK (intentional)
89
+ if (reason.includes('destructive') || reason.includes('external') || reason.includes('unsafe') || reason.includes('safety')) {
90
+ return CANONICAL_OUTCOMES.SAFETY_BLOCK;
91
+ }
92
+
93
+ // Incremental reuse → COVERAGE_GAP (unchanged, so gap in new scan)
94
+ if (reason.includes('incremental')) {
95
+ return CANONICAL_OUTCOMES.COVERAGE_GAP;
96
+ }
97
+
98
+ // No expectation found → INFORMATIONAL (metadata, not a test result)
99
+ if (reason.includes('no_expectation')) {
100
+ return CANONICAL_OUTCOMES.INFORMATIONAL;
101
+ }
102
+
103
+ // Discovery error (selector mismatch, link not found) → COVERAGE_GAP (could not evaluate)
104
+ if (reason.includes('discovery') || reason.includes('no_matching_selector')) {
105
+ return CANONICAL_OUTCOMES.COVERAGE_GAP;
106
+ }
107
+
108
+ // Sensor unavailable → COVERAGE_GAP (incomplete data)
109
+ if (reason.includes('sensor')) {
110
+ return CANONICAL_OUTCOMES.COVERAGE_GAP;
111
+ }
112
+
113
+ // Default: treat as coverage gap if unknown
114
+ return CANONICAL_OUTCOMES.COVERAGE_GAP;
115
+ }
116
+
117
+ /**
118
+ * Map finding type to canonical outcome
119
+ *
120
+ * Used by finding-detector.js to classify findings explicitly
121
+ */
122
+ export function mapFindingTypeToOutcome(findingType) {
123
+ // All forms of silent failure → SILENT_FAILURE
124
+ if (findingType.includes('silent_failure') ||
125
+ findingType.includes('unobserved') ||
126
+ findingType.includes('observed_break')) {
127
+ return CANONICAL_OUTCOMES.SILENT_FAILURE;
128
+ }
129
+
130
+ // Default to SILENT_FAILURE for findings (they represent observed problems)
131
+ return CANONICAL_OUTCOMES.SILENT_FAILURE;
132
+ }
133
+
134
+ /**
135
+ * Validate that an outcome is canonical
136
+ */
137
+ export function isValidOutcome(outcome) {
138
+ return Object.values(CANONICAL_OUTCOMES).includes(outcome);
139
+ }
140
+
141
+ /**
142
+ * Format outcome for human display
143
+ */
144
+ export function formatOutcomeForDisplay(outcome) {
145
+ if (!isValidOutcome(outcome)) {
146
+ throw new Error(`Invalid outcome type: ${outcome}`);
147
+ }
148
+
149
+ const definition = OUTCOME_DEFINITIONS[outcome];
150
+ return {
151
+ type: outcome,
152
+ title: definition.title,
153
+ description: definition.description
154
+ };
155
+ }
156
+
157
+ export default CANONICAL_OUTCOMES;
@@ -0,0 +1,335 @@
1
+ /**
2
+ * PHASE 7 — DECISION SNAPSHOT
3
+ *
4
+ * Computes a top-level decision snapshot that answers 6 mandatory questions:
5
+ *
6
+ * 1. Do we have confirmed SILENT FAILURES?
7
+ * 2. Where exactly are they?
8
+ * 3. How severe are they (user-impact-wise)?
9
+ * 4. What did VERAX NOT verify?
10
+ * 5. Why was it not verified?
11
+ * 6. How much confidence do we have in the findings?
12
+ *
13
+ * Rules:
14
+ * - Derived ONLY from existing data (findings, silences, coverage)
15
+ * - No new detection logic
16
+ * - No subjective language
17
+ * - No recommendations or advice
18
+ */
19
+
20
+ /**
21
+ * Severity levels (user-impact-based, not technical)
22
+ */
23
+ export const SEVERITY = {
24
+ CRITICAL_USER_BLOCKER: 'critical_user_blocker', // Prevents user from completing core task
25
+ FLOW_BREAKING: 'flow_breaking', // Breaks expected navigation/state flow
26
+ DEGRADING: 'degrading', // Reduces functionality but doesn't block
27
+ INFORMATIONAL: 'informational' // Observable but no clear user impact
28
+ };
29
+
30
+ /**
31
+ * Classify finding severity based on promise type and outcome
32
+ * @param {Object} finding - Finding object
33
+ * @returns {string} - Severity level from SEVERITY enum
34
+ */
35
+ function classifyFindingSeverity(finding) {
36
+ const promiseType = finding.promiseType || finding.type;
37
+ const outcome = finding.outcome;
38
+
39
+ // Navigation promises that fail are flow-breaking
40
+ if (promiseType === 'navigation' && outcome === 'broken') {
41
+ return SEVERITY.FLOW_BREAKING;
42
+ }
43
+
44
+ // Network actions that fail are critical (forms, submissions)
45
+ if (promiseType === 'networkAction' && outcome === 'broken') {
46
+ return SEVERITY.CRITICAL_USER_BLOCKER;
47
+ }
48
+
49
+ // State actions that fail are degrading
50
+ if (promiseType === 'stateAction' && outcome === 'broken') {
51
+ return SEVERITY.DEGRADING;
52
+ }
53
+
54
+ // Auth failures are critical user blockers
55
+ if (promiseType === 'authentication' && outcome === 'broken') {
56
+ return SEVERITY.CRITICAL_USER_BLOCKER;
57
+ }
58
+
59
+ // Timeout/unknown outcomes are degrading (cannot confirm impact)
60
+ if (outcome === 'timeout' || outcome === 'unknown') {
61
+ return SEVERITY.DEGRADING;
62
+ }
63
+
64
+ // Default to informational if unclear
65
+ return SEVERITY.INFORMATIONAL;
66
+ }
67
+
68
+ /**
69
+ * Compute confidence score (0.0 - 1.0) based on silences and coverage
70
+ * @param {Object} detectTruth - Detect phase truth
71
+ * @param {Object} observeTruth - Observe phase truth
72
+ * @returns {Object} - Confidence assessment
73
+ */
74
+ function computeConfidence(detectTruth, observeTruth) {
75
+ const totalInteractions = observeTruth?.interactionsObserved || 0;
76
+ const analyzed = detectTruth?.interactionsAnalyzed || 0;
77
+ const skipped = detectTruth?.skips?.total || 0;
78
+ const timeouts = observeTruth?.timeoutsCount || 0;
79
+ const coverageGaps = detectTruth?.coverageGapsCount || 0;
80
+
81
+ // Coverage ratio: how much was actually verified
82
+ const coverageRatio = totalInteractions > 0 ? analyzed / totalInteractions : 0;
83
+
84
+ // Silence penalty: reduce confidence for unknown outcomes
85
+ const silencePenalty = (skipped + timeouts + coverageGaps) / Math.max(totalInteractions, 1);
86
+
87
+ // Base confidence from coverage
88
+ let confidenceScore = coverageRatio;
89
+
90
+ // Apply silence penalty (max 50% reduction)
91
+ confidenceScore = confidenceScore * (1 - Math.min(silencePenalty * 0.5, 0.5));
92
+
93
+ // Clamp to 0-1 range
94
+ confidenceScore = Math.max(0, Math.min(1, confidenceScore));
95
+
96
+ // Classify confidence level
97
+ let confidenceLevel = 'very_low';
98
+ if (confidenceScore >= 0.9) {
99
+ confidenceLevel = 'high';
100
+ } else if (confidenceScore >= 0.7) {
101
+ confidenceLevel = 'medium';
102
+ } else if (confidenceScore >= 0.5) {
103
+ confidenceLevel = 'low';
104
+ }
105
+
106
+ return {
107
+ score: confidenceScore,
108
+ level: confidenceLevel,
109
+ coverageRatio,
110
+ silencePenalty,
111
+ factors: {
112
+ totalInteractions,
113
+ analyzed,
114
+ skipped,
115
+ timeouts,
116
+ coverageGaps
117
+ }
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Extract unverified items with reasons
123
+ * @param {Object} detectTruth - Detect phase truth
124
+ * @param {Object} observeTruth - Observe phase truth
125
+ * @returns {Array} - List of unverified items with reasons
126
+ */
127
+ function extractUnverified(detectTruth, observeTruth) {
128
+ const unverified = [];
129
+
130
+ // Coverage gaps (expectations not evaluated)
131
+ const coverageGaps = detectTruth?.coverageGapsCount || 0;
132
+ if (coverageGaps > 0) {
133
+ unverified.push({
134
+ category: 'expectations',
135
+ count: coverageGaps,
136
+ reason: 'budget_exceeded'
137
+ });
138
+ }
139
+
140
+ // Skipped interactions by reason
141
+ const skips = detectTruth?.skips?.reasons || [];
142
+ for (const skipReason of skips) {
143
+ unverified.push({
144
+ category: 'interactions',
145
+ count: skipReason.count,
146
+ reason: skipReason.code
147
+ });
148
+ }
149
+
150
+ // Timeouts
151
+ const timeouts = observeTruth?.timeoutsCount || 0;
152
+ if (timeouts > 0) {
153
+ unverified.push({
154
+ category: 'interactions',
155
+ count: timeouts,
156
+ reason: 'timeout'
157
+ });
158
+ }
159
+
160
+ // External navigations blocked
161
+ const externalBlocked = observeTruth?.externalNavigationBlockedCount || 0;
162
+ if (externalBlocked > 0) {
163
+ unverified.push({
164
+ category: 'navigations',
165
+ count: externalBlocked,
166
+ reason: 'external_blocked'
167
+ });
168
+ }
169
+
170
+ return unverified;
171
+ }
172
+
173
+ /**
174
+ * Compute decision snapshot from scan results
175
+ * @param {Array} findings - Array of findings
176
+ * @param {Object} detectTruth - Detect phase truth
177
+ * @param {Object} observeTruth - Observe phase truth
178
+ * @param {Object} _silences - Silence data (unused parameter, kept for API compatibility)
179
+ * @returns {Object} - Decision snapshot answering 6 mandatory questions
180
+ */
181
+ export function computeDecisionSnapshot(findings, detectTruth, observeTruth, _silences) {
182
+ // Question 1: Do we have confirmed SILENT FAILURES?
183
+ const confirmedFailures = findings.filter(f =>
184
+ f.outcome === 'broken' || f.type === 'silent_failure'
185
+ );
186
+ const hasConfirmedFailures = confirmedFailures.length > 0;
187
+
188
+ // Question 2: Where exactly are they?
189
+ const failureLocations = confirmedFailures.map(f => ({
190
+ type: f.promiseType || f.type,
191
+ fromPath: f.fromPath,
192
+ toPath: f.toPath,
193
+ selector: f.interaction?.selector,
194
+ description: f.description || f.reason
195
+ }));
196
+
197
+ // Question 3: How severe are they (user-impact-wise)?
198
+ const severityCounts = {
199
+ [SEVERITY.CRITICAL_USER_BLOCKER]: 0,
200
+ [SEVERITY.FLOW_BREAKING]: 0,
201
+ [SEVERITY.DEGRADING]: 0,
202
+ [SEVERITY.INFORMATIONAL]: 0
203
+ };
204
+
205
+ const failuresBySeverity = confirmedFailures.map(f => {
206
+ const severity = classifyFindingSeverity(f);
207
+ severityCounts[severity]++;
208
+ return {
209
+ severity,
210
+ finding: f
211
+ };
212
+ });
213
+
214
+ // Question 4: What did VERAX NOT verify?
215
+ const unverified = extractUnverified(detectTruth, observeTruth);
216
+ const totalUnverified = unverified.reduce((sum, u) => sum + u.count, 0);
217
+
218
+ // Question 5: Why was it not verified?
219
+ const unverifiedReasons = {};
220
+ for (const item of unverified) {
221
+ if (!unverifiedReasons[item.reason]) {
222
+ unverifiedReasons[item.reason] = 0;
223
+ }
224
+ unverifiedReasons[item.reason] += item.count;
225
+ }
226
+
227
+ // Question 6: How much confidence do we have in the findings?
228
+ const confidence = computeConfidence(detectTruth, observeTruth);
229
+
230
+ return {
231
+ // Question 1
232
+ hasConfirmedFailures,
233
+ confirmedFailureCount: confirmedFailures.length,
234
+
235
+ // Question 2
236
+ failureLocations,
237
+
238
+ // Question 3
239
+ severityCounts,
240
+ failuresBySeverity: failuresBySeverity.map(f => ({
241
+ severity: f.severity,
242
+ type: f.finding.promiseType || f.finding.type,
243
+ description: f.finding.description || f.finding.reason,
244
+ fromPath: f.finding.fromPath
245
+ })),
246
+
247
+ // Question 4
248
+ totalUnverified,
249
+ unverifiedByCategory: unverified.reduce((acc, u) => {
250
+ if (!acc[u.category]) {
251
+ acc[u.category] = 0;
252
+ }
253
+ acc[u.category] += u.count;
254
+ return acc;
255
+ }, {}),
256
+
257
+ // Question 5
258
+ unverifiedReasons,
259
+ unverifiedDetails: unverified,
260
+
261
+ // Question 6
262
+ confidence: {
263
+ level: confidence.level,
264
+ score: confidence.score,
265
+ coverageRatio: confidence.coverageRatio,
266
+ factors: confidence.factors
267
+ }
268
+ };
269
+ }
270
+
271
+ /**
272
+ * Format decision snapshot for human reading
273
+ * @param {Object} snapshot - Decision snapshot
274
+ * @returns {string} - Formatted text
275
+ */
276
+ export function formatDecisionSnapshot(snapshot) {
277
+ const lines = [];
278
+
279
+ lines.push('=== DECISION SNAPSHOT ===');
280
+ lines.push('');
281
+
282
+ // Question 1: Confirmed failures?
283
+ lines.push(`1. CONFIRMED SILENT FAILURES: ${snapshot.hasConfirmedFailures ? 'YES' : 'NO'}`);
284
+ lines.push(` Count: ${snapshot.confirmedFailureCount}`);
285
+ lines.push('');
286
+
287
+ // Question 2: Where?
288
+ if (snapshot.failureLocations.length > 0) {
289
+ lines.push('2. FAILURE LOCATIONS:');
290
+ for (const loc of snapshot.failureLocations.slice(0, 5)) {
291
+ lines.push(` - ${loc.type}: ${loc.fromPath} → ${loc.toPath || 'unknown'}`);
292
+ if (loc.selector) {
293
+ lines.push(` Selector: ${loc.selector}`);
294
+ }
295
+ }
296
+ if (snapshot.failureLocations.length > 5) {
297
+ lines.push(` ... and ${snapshot.failureLocations.length - 5} more`);
298
+ }
299
+ } else {
300
+ lines.push('2. FAILURE LOCATIONS: None');
301
+ }
302
+ lines.push('');
303
+
304
+ // Question 3: How severe?
305
+ lines.push('3. SEVERITY BREAKDOWN:');
306
+ lines.push(` Critical user blockers: ${snapshot.severityCounts.critical_user_blocker}`);
307
+ lines.push(` Flow-breaking issues: ${snapshot.severityCounts.flow_breaking}`);
308
+ lines.push(` Degrading issues: ${snapshot.severityCounts.degrading}`);
309
+ lines.push(` Informational: ${snapshot.severityCounts.informational}`);
310
+ lines.push('');
311
+
312
+ // Question 4: What NOT verified?
313
+ lines.push('4. NOT VERIFIED:');
314
+ lines.push(` Total: ${snapshot.totalUnverified}`);
315
+ for (const [category, count] of Object.entries(snapshot.unverifiedByCategory)) {
316
+ lines.push(` - ${category}: ${count}`);
317
+ }
318
+ lines.push('');
319
+
320
+ // Question 5: Why not verified?
321
+ lines.push('5. REASONS NOT VERIFIED:');
322
+ for (const [reason, count] of Object.entries(snapshot.unverifiedReasons)) {
323
+ lines.push(` - ${reason}: ${count}`);
324
+ }
325
+ lines.push('');
326
+
327
+ // Question 6: Confidence?
328
+ lines.push('6. CONFIDENCE IN FINDINGS:');
329
+ lines.push(` Level: ${snapshot.confidence.level.toUpperCase()}`);
330
+ lines.push(` Score: ${(snapshot.confidence.score * 100).toFixed(1)}%`);
331
+ lines.push(` Coverage: ${(snapshot.confidence.coverageRatio * 100).toFixed(1)}% of interactions verified`);
332
+ lines.push('');
333
+
334
+ return lines.join('\n');
335
+ }