@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,72 @@
1
+ /**
2
+ *
3
+ * Formats and prints standardized CLI output for VERAX runs.
4
+ *
5
+ * Output format (decision-oriented, minimal):
6
+ * - ✔ VERAX finished
7
+ * - ✔ 14 interactions tested
8
+ * - ✖ 3 user problems found (2 HIGH, 1 MEDIUM)
9
+ * - → Open .verax/SUMMARY.md
10
+ */
11
+
12
+ /**
13
+ * Format console output for a completed run
14
+ *
15
+ * @param {Object} stats - Run statistics
16
+ * @param {number} stats.flowsScanned - Public flows scanned
17
+ * @param {number} stats.silentFailures - Silent failures detected
18
+ * @param {string} stats.outDir - Output directory path
19
+ * @returns {string} Formatted output (multiple lines)
20
+ */
21
+ export function formatConsoleOutput(stats) {
22
+ const lines = [];
23
+
24
+ const flowsScanned = stats?.flowsScanned || 0;
25
+ const silentFailures = stats?.silentFailures || 0;
26
+ const outDir = stats?.outDir || '.verax';
27
+
28
+ // Line 1: Scan complete summary
29
+ const flowsStr = `${flowsScanned} flow${flowsScanned !== 1 ? 's' : ''}`;
30
+ const failuresStr = `${silentFailures} silent failure${silentFailures !== 1 ? 's' : ''}`;
31
+ lines.push(`✓ Scan complete — ${flowsStr} checked, ${failuresStr} found`);
32
+
33
+ // Line 2: Point to SUMMARY.md
34
+ lines.push(`→ See ${outDir}/SUMMARY.md for details`);
35
+
36
+ // Line 3: Mention output directory
37
+ lines.push('');
38
+ lines.push(`Output saved to ${outDir}/`);
39
+ lines.push(` • SUMMARY.md — Human-readable findings`);
40
+ lines.push(` • REPORT.json — Machine-readable results`);
41
+
42
+ return lines.join('\n');
43
+ }
44
+
45
+ /**
46
+ * Print formatted console output to stdout
47
+ */
48
+ export function printConsoleOutput(stats) {
49
+ const output = formatConsoleOutput(stats);
50
+ console.log(output);
51
+ return output;
52
+ }
53
+
54
+ /**
55
+ * Format and print error output when fatal error occurs
56
+ *
57
+ * @param {string} message - Error message
58
+ * @param {boolean} [_debug] - If true, include more details; if false, minimal
59
+ */
60
+ export function formatErrorOutput(message, _debug = false) {
61
+ // Single-line error message with [ERROR] prefix
62
+ return `[ERROR] ${message}`;
63
+ }
64
+
65
+ /**
66
+ * Print error output
67
+ */
68
+ export function printErrorOutput(message, _debug = false) {
69
+ const output = formatErrorOutput(message, _debug);
70
+ console.error(output);
71
+ return output;
72
+ }
@@ -75,6 +75,14 @@ export async function detectFindings(learnData, observeData, projectPath, onProg
75
75
  findings,
76
76
  stats,
77
77
  detectedAt: new Date().toISOString(),
78
+ enforcement: {
79
+ evidenceLawEnforced: true,
80
+ contractVersion: 1,
81
+ timestamp: new Date().toISOString(),
82
+ droppedCount: 0,
83
+ downgradedCount: 0,
84
+ downgrades: []
85
+ }
78
86
  };
79
87
  }
80
88
 
@@ -100,7 +108,9 @@ function getObservationForExpectation(expectation, observationMap) {
100
108
  }
101
109
 
102
110
  function classificationIcon(classification) {
103
- switch (classification) {
111
+ // Handle both old format and new taxonomy format
112
+ const baseClassification = classification.split(':')[0];
113
+ switch (baseClassification) {
104
114
  case 'observed':
105
115
  return '✓';
106
116
  case 'silent-failure':
@@ -115,7 +125,9 @@ function classificationIcon(classification) {
115
125
  }
116
126
 
117
127
  function findingStatKey(classification) {
118
- switch (classification) {
128
+ // Handle both old format and new taxonomy format
129
+ const baseClassification = classification.split(':')[0];
130
+ switch (baseClassification) {
119
131
  case 'silent-failure':
120
132
  return 'silentFailures';
121
133
  case 'observed':
@@ -131,6 +143,8 @@ function findingStatKey(classification) {
131
143
 
132
144
  /**
133
145
  * Classify a single expectation according to deterministic rules.
146
+ * EVIDENCE GATE: silent-failure REQUIRES evidence.
147
+ * OUTCOME BINDING: Use attempt.cause to provide precise taxonomy.
134
148
  */
135
149
  function classifyExpectation(expectation, observation) {
136
150
  const finding = {
@@ -148,13 +162,15 @@ function classifyExpectation(expectation, observation) {
148
162
  const attempted = Boolean(observation?.attempted);
149
163
  const observed = observation?.observed === true;
150
164
  const reason = observation?.reason || null;
165
+ const cause = observation?.cause || null; // NEW: precise cause from planner
151
166
 
152
167
  const evidence = normalizeEvidence(observation?.evidenceFiles || []);
153
168
  finding.evidence = evidence;
154
169
 
155
170
  const evidenceSignals = analyzeEvidenceSignals(observation, evidence);
171
+ const hasAnyEvidence = evidence.length > 0 || evidenceSignals.hasDomChange;
156
172
 
157
- // 1) observed
173
+ // 1) observed (success)
158
174
  if (observed) {
159
175
  finding.classification = 'observed';
160
176
  finding.reason = 'Expectation observed at runtime';
@@ -163,8 +179,8 @@ function classifyExpectation(expectation, observation) {
163
179
  return finding;
164
180
  }
165
181
 
166
- // 2) coverage-gap (not attempted or explicitly blocked)
167
- if (!attempted || isSafetySkip(reason)) {
182
+ // 2) coverage-gap (not attempted or safety skip)
183
+ if (!attempted) {
168
184
  finding.classification = 'coverage-gap';
169
185
  finding.reason = reason || 'No observation attempt recorded';
170
186
  finding.impact = 'LOW';
@@ -172,29 +188,48 @@ function classifyExpectation(expectation, observation) {
172
188
  return finding;
173
189
  }
174
190
 
175
- // 3) silent-failure (attempted, observed === false, no safety skip)
176
- if (attempted && observation?.observed === false && !isSafetySkip(reason)) {
177
- finding.classification = 'silent-failure';
178
- finding.reason = reason || 'Expected behavior not observed';
179
- finding.impact = getImpact(expectation);
180
- finding.confidence = calculateConfidence(expectation, evidenceSignals, 'silent-failure');
181
- return finding;
182
- }
183
-
184
- // 4) unproven (attempted, ambiguous evidence)
191
+ // 3) Attempted but not observed - apply EVIDENCE GATE + OUTCOME BINDING
185
192
  if (attempted && !observed) {
186
- finding.classification = 'unproven';
187
- finding.reason = reason || 'Attempted but evidence insufficient';
188
- finding.impact = 'MEDIUM';
189
- finding.confidence = calculateConfidence(expectation, evidenceSignals, 'unproven');
193
+ // CRITICAL: Evidence Gate - silent-failure REQUIRES evidence
194
+ if (!hasAnyEvidence) {
195
+ // NO EVIDENCE → cannot prove silence → coverage-gap or unproven
196
+ if (isSafetySkip(reason)) {
197
+ finding.classification = 'coverage-gap';
198
+ finding.reason = reason || 'Blocked or skipped for safety';
199
+ finding.impact = 'LOW';
200
+ finding.confidence = 0;
201
+ } else {
202
+ finding.classification = 'unproven';
203
+ finding.reason = reason || 'Attempted but no evidence captured';
204
+ finding.impact = 'MEDIUM';
205
+ finding.confidence = 0;
206
+ }
207
+ return finding;
208
+ }
209
+
210
+ // HAS EVIDENCE → can classify as silent-failure with PRECISE taxonomy
211
+ let taxonomy = 'no-change'; // default
212
+
213
+ if (cause) {
214
+ // Use the cause from interaction planner (most precise)
215
+ taxonomy = cause; // 'not-found' | 'blocked' | 'prevented-submit' | 'timeout' | 'no-change' | 'error'
216
+ } else {
217
+ // Fallback to signal-based detection (legacy)
218
+ taxonomy = determineSilenceTaxonomy(reason, evidenceSignals);
219
+ }
220
+
221
+ finding.classification = `silent-failure:${taxonomy}`;
222
+ finding.reason = reason || `Expected behavior not observed (${taxonomy})`;
223
+ finding.impact = getImpact(expectation);
224
+ finding.confidence = calculateConfidenceFromEvidence(evidenceSignals);
190
225
  return finding;
191
226
  }
192
227
 
193
- // 5) informational fallback
228
+ // 4) Fallback
194
229
  finding.classification = 'informational';
195
230
  finding.reason = reason || 'No classification rule matched';
196
231
  finding.impact = 'LOW';
197
- finding.confidence = calculateConfidence(expectation, evidenceSignals, 'informational');
232
+ finding.confidence = 0;
198
233
  return finding;
199
234
  }
200
235
 
@@ -206,32 +241,61 @@ function isSafetySkip(reason) {
206
241
  }
207
242
 
208
243
  /**
209
- * Deterministic confidence calculation.
210
- * Screenshots + network + DOM change => >=0.8
211
- * Screenshots only => ~0.6
212
- * Weak signals => <0.5
244
+ * Determine silence taxonomy based on reason and evidence signals.
245
+ * Returns: no-change | blocked | not-found | timeout | prevented-submit
213
246
  */
214
- function calculateConfidence(expectation, evidenceSignals, classification) {
215
- if (classification === 'observed') return 1.0;
216
- if (classification === 'coverage-gap') return 0;
247
+ function determineSilenceTaxonomy(reason, evidenceSignals) {
248
+ if (!reason) {
249
+ // No explicit reason - check evidence
250
+ if (evidenceSignals.hasScreenshots || evidenceSignals.hasDomChange) {
251
+ return 'no-change'; // Evidence exists but no observed change
252
+ }
253
+ return 'no-change';
254
+ }
217
255
 
218
- const { hasScreenshots, hasNetworkLogs, hasDomChange } = evidenceSignals;
256
+ const lower = reason.toLowerCase();
219
257
 
220
- if (classification === 'silent-failure') {
221
- if (hasScreenshots && hasNetworkLogs && hasDomChange) return 0.85;
222
- if (hasScreenshots && hasNetworkLogs) return 0.75;
223
- if (hasScreenshots && hasDomChange) return 0.7;
224
- if (hasScreenshots) return 0.6;
225
- if (hasNetworkLogs || hasDomChange) return 0.5;
226
- return 0.4; // weak signals, attempted but no evidence
258
+ // Check for specific conditions
259
+ if (lower.includes('timeout') || lower.includes('timed out')) {
260
+ return 'timeout';
227
261
  }
228
-
229
- if (classification === 'unproven') {
230
- if (hasScreenshots || hasNetworkLogs || hasDomChange) return 0.45;
231
- return 0.3;
262
+ if (lower.includes('not found') || lower.includes('element not found') || lower.includes('selector not found') || lower.includes('not-found')) {
263
+ return 'not-found';
264
+ }
265
+ if (lower.includes('blocked') || lower.includes('not-interactable') || lower.includes('interactable')) {
266
+ return 'blocked';
232
267
  }
268
+ if (lower.includes('prevented') || lower.includes('prevented-submit') || lower.includes('submit-prevented')) {
269
+ return 'prevented-submit';
270
+ }
271
+ if (lower.includes('no matching event') || lower.includes('no change') || lower.includes('no-change')) {
272
+ return 'no-change';
273
+ }
274
+
275
+ // Default to no-change if evidence exists
276
+ return 'no-change';
277
+ }
278
+
279
+ /**
280
+ * Calculate confidence from evidence ONLY.
281
+ * No evidence = 0 confidence.
282
+ */
283
+ function calculateConfidenceFromEvidence(evidenceSignals) {
284
+ const { hasScreenshots, hasNetworkLogs, hasDomChange } = evidenceSignals;
233
285
 
234
- return 0.3;
286
+ // Multiple strong signals
287
+ if (hasScreenshots && hasNetworkLogs && hasDomChange) return 0.85;
288
+ if (hasScreenshots && hasNetworkLogs) return 0.75;
289
+ if (hasScreenshots && hasDomChange) return 0.7;
290
+
291
+ // Single strong signal
292
+ if (hasScreenshots) return 0.6;
293
+
294
+ // Weak signals
295
+ if (hasNetworkLogs || hasDomChange) return 0.5;
296
+
297
+ // No evidence
298
+ return 0;
235
299
  }
236
300
 
237
301
  function analyzeEvidenceSignals(observation, evidence) {
@@ -0,0 +1,124 @@
1
+ /**
2
+ * PHASE 18 — Determinism Runner
3
+ *
4
+ * Wraps a scan execution to run it multiple times and compare results.
5
+ */
6
+
7
+ import { resolve } from 'path';
8
+ import { readFileSync, existsSync } from 'fs';
9
+ import { runDeterminismCheck } from '../../verax/core/determinism/engine.js';
10
+ import { verifyRun } from '../../verax/core/artifacts/verifier.js';
11
+ import { writeDeterminismReport } from './determinism-writer.js';
12
+
13
+ /**
14
+ * PHASE 18: Run scan with determinism checking
15
+ *
16
+ * @param {Function} scanFn - Function that executes a scan and returns { runId }
17
+ * @param {Object} options - Options
18
+ * @param {number} options.runs - Number of runs (default: 2)
19
+ * @param {string} options.out - Output directory
20
+ * @returns {Promise<Object>} Determinism check results
21
+ */
22
+ export async function runWithDeterminism(scanFn, options = { runs: 2, out: '.verax' }) {
23
+ const { runs = 2, out = '.verax' } = options;
24
+
25
+ // Wrap scan function to return artifact paths
26
+ const runFn = async (runConfig) => {
27
+ const result = await scanFn({ ...options, ...runConfig });
28
+
29
+ // Extract artifact paths from result
30
+ const artifactPaths = {};
31
+ if (result.runId) {
32
+ const runDir = resolve(out, 'runs', result.runId);
33
+
34
+ // Map artifact keys to paths
35
+ const artifactMap = {
36
+ findings: resolve(runDir, 'findings.json'),
37
+ runStatus: resolve(runDir, 'run.status.json'),
38
+ summary: resolve(runDir, 'summary.json'),
39
+ learn: resolve(runDir, 'learn.json'),
40
+ observe: resolve(runDir, 'observe.json'),
41
+ };
42
+
43
+ for (const [key, path] of Object.entries(artifactMap)) {
44
+ if (existsSync(path)) {
45
+ artifactPaths[key] = path;
46
+ }
47
+ }
48
+ }
49
+
50
+ return {
51
+ runId: result.runId,
52
+ artifactPaths,
53
+ };
54
+ };
55
+
56
+ // PHASE 25: Load run fingerprints from run.meta.json
57
+ const loadRunFingerprints = async (runMeta) => {
58
+ if (runMeta.runId) {
59
+ const runDir = resolve(out, 'runs', runMeta.runId);
60
+ const metaPath = resolve(runDir, 'run.meta.json');
61
+ if (existsSync(metaPath)) {
62
+ try {
63
+ const metaContent = readFileSync(metaPath, 'utf-8');
64
+ // @ts-expect-error - readFileSync with encoding returns string
65
+ const meta = JSON.parse(metaContent);
66
+ return meta.runFingerprint || null;
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+ }
72
+ return null;
73
+ };
74
+
75
+ // Run determinism check
76
+ const determinismResult = await runDeterminismCheck(runFn, {
77
+ runs,
78
+ config: options,
79
+ normalize: true,
80
+ });
81
+
82
+ // PHASE 25: Load run fingerprints for each run
83
+ for (const runMeta of determinismResult.runsMeta) {
84
+ runMeta.runFingerprint = await loadRunFingerprints(runMeta);
85
+ }
86
+
87
+ // Verify each run
88
+ const verificationResults = [];
89
+ for (const runMeta of determinismResult.runsMeta) {
90
+ if (runMeta.runId) {
91
+ const runDir = resolve(out, 'runs', runMeta.runId);
92
+ if (existsSync(runDir)) {
93
+ try {
94
+ const { getArtifactVersions } = await import('../../verax/core/artifacts/registry.js');
95
+ const verification = verifyRun(runDir, getArtifactVersions());
96
+ verificationResults.push({
97
+ runId: runMeta.runId,
98
+ verification,
99
+ });
100
+ } catch (error) {
101
+ // Verification failed
102
+ verificationResults.push({
103
+ runId: runMeta.runId,
104
+ verification: { ok: false, errors: [error.message] },
105
+ });
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ // Write determinism report
112
+ const reportPath = await writeDeterminismReport(
113
+ determinismResult,
114
+ verificationResults,
115
+ out
116
+ );
117
+
118
+ return {
119
+ ...determinismResult,
120
+ verificationResults,
121
+ reportPath,
122
+ };
123
+ }
124
+
@@ -0,0 +1,129 @@
1
+ /**
2
+ * PHASE 18 — Determinism Report Writer
3
+ *
4
+ * Writes determinism check results to artifact.
5
+ */
6
+
7
+ import { resolve } from 'path';
8
+ import { mkdirSync, writeFileSync } from 'fs';
9
+ import { ARTIFACT_REGISTRY, getArtifactVersions } from '../../verax/core/artifacts/registry.js'; // eslint-disable-line no-unused-vars
10
+
11
+ /**
12
+ * PHASE 18: Write determinism report
13
+ * PHASE 25: Enhanced with runFingerprint, contract validation, and structured verdicts
14
+ *
15
+ * @param {Object} determinismResult - Result from runDeterminismCheck
16
+ * @param {Array} verificationResults - Verification results for each run
17
+ * @param {string} outDir - Output directory
18
+ * @returns {Promise<string>} Path to determinism report
19
+ */
20
+ export async function writeDeterminismReport(determinismResult, verificationResults, outDir) {
21
+ const reportsDir = resolve(outDir, 'determinism');
22
+ mkdirSync(reportsDir, { recursive: true });
23
+
24
+ const reportId = `determinism-${Date.now()}`;
25
+ const reportPath = resolve(reportsDir, `${reportId}.json`);
26
+
27
+ // PHASE 25: Check run fingerprints
28
+ const runFingerprints = [];
29
+ const runFingerprintMismatches = [];
30
+ for (const runMeta of determinismResult.runsMeta) {
31
+ if (runMeta.runFingerprint) {
32
+ runFingerprints.push(runMeta.runFingerprint);
33
+ }
34
+ }
35
+ if (runFingerprints.length > 1) {
36
+ const firstFingerprint = runFingerprints[0];
37
+ for (let i = 1; i < runFingerprints.length; i++) {
38
+ if (runFingerprints[i] !== firstFingerprint) {
39
+ runFingerprintMismatches.push({
40
+ runIndex: i + 1,
41
+ expected: firstFingerprint,
42
+ actual: runFingerprints[i]
43
+ });
44
+ }
45
+ }
46
+ }
47
+
48
+ // PHASE 25: Check verifier errors
49
+ const verifierErrors = [];
50
+ for (const verification of verificationResults) {
51
+ if (verification.verification && !verification.verification.ok) {
52
+ verifierErrors.push({
53
+ runId: verification.runId,
54
+ errors: verification.verification.errors || [],
55
+ verdictStatus: verification.verification.verdictStatus
56
+ });
57
+ }
58
+ }
59
+
60
+ // PHASE 25: Determine final verdict with expected/unexpected distinction
61
+ let finalVerdict = determinismResult.verdict;
62
+ if (runFingerprintMismatches.length > 0) {
63
+ finalVerdict = 'NON_DETERMINISTIC_UNEXPECTED';
64
+ } else if (verifierErrors.length > 0) {
65
+ finalVerdict = 'NON_DETERMINISTIC_UNEXPECTED';
66
+ } else if (determinismResult.adaptiveEvents && determinismResult.adaptiveEvents.length > 0) {
67
+ finalVerdict = 'NON_DETERMINISTIC_EXPECTED';
68
+ } else if (determinismResult.diffs && determinismResult.diffs.length > 0) {
69
+ const blockerDiffs = determinismResult.diffs.filter(d => d.severity === 'BLOCKER');
70
+ if (blockerDiffs.length > 0) {
71
+ finalVerdict = 'NON_DETERMINISTIC_UNEXPECTED';
72
+ } else {
73
+ finalVerdict = 'NON_DETERMINISTIC_EXPECTED';
74
+ }
75
+ }
76
+
77
+ // PHASE 25: Build top reasons
78
+ const topReasons = [];
79
+ if (runFingerprintMismatches.length > 0) {
80
+ topReasons.push({ code: 'RUN_FINGERPRINT_MISMATCH', count: runFingerprintMismatches.length });
81
+ }
82
+ if (verifierErrors.length > 0) {
83
+ topReasons.push({ code: 'VERIFIER_ERRORS_DETECTED', count: verifierErrors.length });
84
+ }
85
+ if (determinismResult.adaptiveEvents && determinismResult.adaptiveEvents.length > 0) {
86
+ topReasons.push({ code: 'EXPECTED_ADAPTIVE_BEHAVIOR', count: determinismResult.adaptiveEvents.length });
87
+ }
88
+ if (determinismResult.diffs && determinismResult.diffs.length > 0) {
89
+ const blockerCount = determinismResult.diffs.filter(d => d.severity === 'BLOCKER').length;
90
+ if (blockerCount > 0) {
91
+ topReasons.push({ code: 'ARTIFACT_DIFF_DETECTED', count: blockerCount });
92
+ }
93
+ }
94
+
95
+ const report = {
96
+ version: 2, // PHASE 25: Bump version
97
+ contractVersion: 1,
98
+ artifactVersions: getArtifactVersions(),
99
+ generatedAt: new Date().toISOString(),
100
+ verdict: finalVerdict,
101
+ summary: {
102
+ ...determinismResult.summary,
103
+ runFingerprintMismatches: runFingerprintMismatches.length,
104
+ verifierErrors: verifierErrors.length,
105
+ topReasons: topReasons.slice(0, 10)
106
+ },
107
+ runsMeta: determinismResult.runsMeta,
108
+ runFingerprints,
109
+ runFingerprintMismatches,
110
+ verificationResults,
111
+ verifierErrors,
112
+ normalizationRulesApplied: [
113
+ 'Stripped timestamps (startedAt, completedAt, detectedAt, etc.)',
114
+ 'Stripped runId fields',
115
+ 'Normalized absolute paths to relative',
116
+ 'Normalized floating scores to 3 decimals',
117
+ 'Sorted arrays by stable keys',
118
+ 'Normalized evidence paths but preserved presence/absence',
119
+ ],
120
+ diffs: determinismResult.diffs,
121
+ adaptiveEvents: determinismResult.adaptiveEvents || [],
122
+ stabilityScore: determinismResult.summary.stabilityScore,
123
+ };
124
+
125
+ writeFileSync(reportPath, JSON.stringify(report, null, 2) + '\n');
126
+
127
+ return reportPath;
128
+ }
129
+