@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,308 @@
1
+ /**
2
+ * EVIDENCE CAPTURE RELIABILITY LAYER
3
+ *
4
+ * Centralized evidence capture service with retries and failure tracking.
5
+ * Ensures evidence capture is resilient and failures are never silent.
6
+ */
7
+
8
+ import { captureScreenshot } from '../../observe/evidence-capture.js';
9
+ import { captureDomSignature } from '../../observe/dom-signature.js';
10
+
11
+ /**
12
+ * Evidence capture failure reason codes (stable)
13
+ */
14
+ export const EVIDENCE_CAPTURE_FAILURE_CODES = {
15
+ SCREENSHOT_FAILED: 'EVIDENCE_SCREENSHOT_FAILED',
16
+ SCREENSHOT_TIMEOUT: 'EVIDENCE_SCREENSHOT_TIMEOUT',
17
+ DOM_SIGNATURE_FAILED: 'EVIDENCE_DOM_SIGNATURE_FAILED',
18
+ URL_CAPTURE_FAILED: 'EVIDENCE_URL_CAPTURE_FAILED',
19
+ UISIGNALS_CAPTURE_FAILED: 'EVIDENCE_UISIGNALS_CAPTURE_FAILED',
20
+ NETWORK_CAPTURE_FAILED: 'EVIDENCE_NETWORK_CAPTURE_FAILED',
21
+ UNKNOWN_ERROR: 'EVIDENCE_UNKNOWN_ERROR'
22
+ };
23
+
24
+ /**
25
+ * Evidence capture stage codes (stable)
26
+ */
27
+ export const EVIDENCE_CAPTURE_STAGE = {
28
+ BEFORE_SCREENSHOT: 'BEFORE_SCREENSHOT',
29
+ AFTER_SCREENSHOT: 'AFTER_SCREENSHOT',
30
+ DOM_SIGNATURE: 'DOM_SIGNATURE',
31
+ URL: 'URL',
32
+ UISIGNALS: 'UISIGNALS',
33
+ NETWORK: 'NETWORK'
34
+ };
35
+
36
+ /**
37
+ * EvidenceCaptureFailure object (structured failure record)
38
+ */
39
+ export class EvidenceCaptureFailure {
40
+ constructor(stage, reasonCode, reason, stackSummary = null, attemptCount = 1) {
41
+ this.stage = stage;
42
+ this.reasonCode = reasonCode;
43
+ this.reason = reason;
44
+ this.stackSummary = stackSummary || this._extractStackSummary(new Error().stack);
45
+ this.attemptCount = attemptCount;
46
+ this.timestamp = new Date().toISOString();
47
+ }
48
+
49
+ _extractStackSummary(stack) {
50
+ if (!stack) return null;
51
+ const lines = stack.split('\n').slice(0, 5);
52
+ return lines.join('\n');
53
+ }
54
+
55
+ toJSON() {
56
+ return {
57
+ stage: this.stage,
58
+ reasonCode: this.reasonCode,
59
+ reason: this.reason,
60
+ stackSummary: this.stackSummary,
61
+ attemptCount: this.attemptCount,
62
+ timestamp: this.timestamp
63
+ };
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Screenshot capture with retries
69
+ *
70
+ * @param {Object} page - Playwright page object
71
+ * @param {string} filepath - Path to save screenshot
72
+ * @param {Object} options - Options { maxRetries: 2, retryDelayMs: 100 }
73
+ * @returns {Promise<{ success: boolean, filepath: string | null, failure: EvidenceCaptureFailure | null }>}
74
+ */
75
+ export async function captureScreenshotWithRetry(page, filepath, options = {}) {
76
+ const maxRetries = options.maxRetries || 2;
77
+ const retryDelayMs = options.retryDelayMs || 100;
78
+
79
+ let lastError = null;
80
+ let attemptCount = 0;
81
+
82
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
83
+ attemptCount = attempt + 1;
84
+ try {
85
+ await captureScreenshot(page, filepath);
86
+ // Verify file was created
87
+ const { existsSync } = await import('fs');
88
+ if (existsSync(filepath)) {
89
+ return { success: true, filepath, failure: null };
90
+ }
91
+ throw new Error('Screenshot file was not created');
92
+ } catch (error) {
93
+ lastError = error;
94
+
95
+ // If not last attempt, wait before retry
96
+ if (attempt < maxRetries) {
97
+ await new Promise(resolve => setTimeout(resolve, retryDelayMs));
98
+ }
99
+ }
100
+ }
101
+
102
+ // All retries failed
103
+ const failure = new EvidenceCaptureFailure(
104
+ filepath.includes('before') ? EVIDENCE_CAPTURE_STAGE.BEFORE_SCREENSHOT : EVIDENCE_CAPTURE_STAGE.AFTER_SCREENSHOT,
105
+ lastError?.message?.includes('timeout') ? EVIDENCE_CAPTURE_FAILURE_CODES.SCREENSHOT_TIMEOUT : EVIDENCE_CAPTURE_FAILURE_CODES.SCREENSHOT_FAILED,
106
+ lastError?.message || 'Screenshot capture failed after retries',
107
+ lastError?.stack,
108
+ attemptCount
109
+ );
110
+
111
+ return { success: false, filepath: null, failure };
112
+ }
113
+
114
+ /**
115
+ * Capture DOM signature with error handling
116
+ *
117
+ * @param {Object} page - Playwright page object
118
+ * @returns {Promise<{ success: boolean, domSignature: string | null, failure: EvidenceCaptureFailure | null }>}
119
+ */
120
+ export async function captureDomSignatureSafe(page) {
121
+ try {
122
+ const domSignature = await captureDomSignature(page);
123
+ // @ts-expect-error - digest returns string
124
+ return { success: true, domSignature, failure: null };
125
+ } catch (error) {
126
+ const failure = new EvidenceCaptureFailure(
127
+ EVIDENCE_CAPTURE_STAGE.DOM_SIGNATURE,
128
+ EVIDENCE_CAPTURE_FAILURE_CODES.DOM_SIGNATURE_FAILED,
129
+ error.message || 'DOM signature capture failed',
130
+ error.stack
131
+ );
132
+ return { success: false, domSignature: null, failure };
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Capture URL with error handling
138
+ *
139
+ * @param {Object} page - Playwright page object
140
+ * @returns {Promise<{ success: boolean, url: string | null, failure: EvidenceCaptureFailure | null }>}
141
+ */
142
+ export async function captureUrlSafe(page) {
143
+ try {
144
+ const url = page.url();
145
+ return { success: true, url, failure: null };
146
+ } catch (error) {
147
+ const failure = new EvidenceCaptureFailure(
148
+ EVIDENCE_CAPTURE_STAGE.URL,
149
+ EVIDENCE_CAPTURE_FAILURE_CODES.URL_CAPTURE_FAILED,
150
+ error.message || 'URL capture failed',
151
+ error.stack
152
+ );
153
+ return { success: false, url: null, failure };
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Capture UI signals snapshot with error handling
159
+ *
160
+ * @param {Object} uiSignalSensor - UISignalSensor instance
161
+ * @param {Object} page - Playwright page object
162
+ * @param {number} interactionTime - Optional interaction timestamp
163
+ * @param {Object} beforeSnapshot - Optional before snapshot
164
+ * @returns {Promise<{ success: boolean, uiSignals: Object | null, failure: EvidenceCaptureFailure | null }>}
165
+ */
166
+ export async function captureUiSignalsSafe(uiSignalSensor, page, interactionTime = null, beforeSnapshot = null) {
167
+ try {
168
+ const uiSignals = await uiSignalSensor.snapshot(page, interactionTime, beforeSnapshot);
169
+ return { success: true, uiSignals, failure: null };
170
+ } catch (error) {
171
+ const failure = new EvidenceCaptureFailure(
172
+ EVIDENCE_CAPTURE_STAGE.UISIGNALS,
173
+ EVIDENCE_CAPTURE_FAILURE_CODES.UISIGNALS_CAPTURE_FAILED,
174
+ error.message || 'UI signals capture failed',
175
+ error.stack
176
+ );
177
+ return { success: false, uiSignals: null, failure };
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Capture network snapshot with error handling
183
+ *
184
+ * @param {Object} networkSensor - NetworkSensor instance
185
+ * @param {string} windowId - Network window ID
186
+ * @returns {Promise<{ success: boolean, networkSummary: Object | null, failure: EvidenceCaptureFailure | null }>}
187
+ */
188
+ export async function captureNetworkSafe(networkSensor, windowId) {
189
+ try {
190
+ const networkSummary = networkSensor.stopWindow(windowId);
191
+ return { success: true, networkSummary, failure: null };
192
+ } catch (error) {
193
+ const failure = new EvidenceCaptureFailure(
194
+ EVIDENCE_CAPTURE_STAGE.NETWORK,
195
+ EVIDENCE_CAPTURE_FAILURE_CODES.NETWORK_CAPTURE_FAILED,
196
+ error.message || 'Network capture failed',
197
+ error.stack
198
+ );
199
+ return { success: false, networkSummary: null, failure };
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Comprehensive evidence capture for a finding
205
+ *
206
+ * @param {Object} params - Capture parameters
207
+ * @param {Object} params.page - Playwright page object
208
+ * @param {string} params.beforeScreenshotPath - Before screenshot path
209
+ * @param {string} params.afterScreenshotPath - After screenshot path
210
+ * @param {Object} params.uiSignalSensor - UISignalSensor instance
211
+ * @param {Object} params.networkSensor - NetworkSensor instance
212
+ * @param {string} params.networkWindowId - Network window ID
213
+ * @param {number} params.interactionTime - Interaction timestamp
214
+ * @param {Object} params.beforeSnapshot - Before snapshot
215
+ * @returns {Promise<{ evidence: Object, failures: Array<EvidenceCaptureFailure> }>}
216
+ */
217
+ export async function captureEvidenceComprehensive(params) {
218
+ const {
219
+ page,
220
+ beforeScreenshotPath,
221
+ afterScreenshotPath,
222
+ uiSignalSensor,
223
+ networkSensor,
224
+ networkWindowId,
225
+ interactionTime,
226
+ beforeSnapshot
227
+ } = params;
228
+
229
+ const failures = [];
230
+ const evidence = {
231
+ before: {},
232
+ after: {},
233
+ signals: {}
234
+ };
235
+
236
+ // Capture before screenshot
237
+ if (beforeScreenshotPath) {
238
+ const beforeScreenshot = await captureScreenshotWithRetry(page, beforeScreenshotPath);
239
+ if (beforeScreenshot.success) {
240
+ evidence.before.screenshot = beforeScreenshotPath;
241
+ } else {
242
+ failures.push(beforeScreenshot.failure);
243
+ }
244
+ }
245
+
246
+ // Capture after screenshot
247
+ if (afterScreenshotPath) {
248
+ const afterScreenshot = await captureScreenshotWithRetry(page, afterScreenshotPath);
249
+ if (afterScreenshot.success) {
250
+ evidence.after.screenshot = afterScreenshotPath;
251
+ } else {
252
+ failures.push(afterScreenshot.failure);
253
+ }
254
+ }
255
+
256
+ // Capture URLs
257
+ const beforeUrl = await captureUrlSafe(page);
258
+ if (beforeUrl.success) {
259
+ evidence.before.url = beforeUrl.url;
260
+ } else {
261
+ failures.push(beforeUrl.failure);
262
+ }
263
+
264
+ const afterUrl = await captureUrlSafe(page);
265
+ if (afterUrl.success) {
266
+ evidence.after.url = afterUrl.url;
267
+ } else {
268
+ failures.push(afterUrl.failure);
269
+ }
270
+
271
+ // Capture DOM signatures
272
+ const beforeDom = await captureDomSignatureSafe(page);
273
+ if (beforeDom.success) {
274
+ evidence.before.domSignature = beforeDom.domSignature;
275
+ } else {
276
+ failures.push(beforeDom.failure);
277
+ }
278
+
279
+ const afterDom = await captureDomSignatureSafe(page);
280
+ if (afterDom.success) {
281
+ evidence.after.domSignature = afterDom.domSignature;
282
+ } else {
283
+ failures.push(afterDom.failure);
284
+ }
285
+
286
+ // Capture UI signals
287
+ if (uiSignalSensor) {
288
+ const uiSignals = await captureUiSignalsSafe(uiSignalSensor, page, interactionTime, beforeSnapshot);
289
+ if (uiSignals.success) {
290
+ evidence.signals.uiSignals = uiSignals.uiSignals;
291
+ } else {
292
+ failures.push(uiSignals.failure);
293
+ }
294
+ }
295
+
296
+ // Capture network
297
+ if (networkSensor && networkWindowId) {
298
+ const network = await captureNetworkSafe(networkSensor, networkWindowId);
299
+ if (network.success) {
300
+ evidence.signals.network = network.networkSummary;
301
+ } else {
302
+ failures.push(network.failure);
303
+ }
304
+ }
305
+
306
+ return { evidence, failures };
307
+ }
308
+
@@ -0,0 +1,166 @@
1
+ /**
2
+ * EVIDENCE INTENT LEDGER
3
+ *
4
+ * Audit trail of evidence capture attempts per finding.
5
+ * Records what evidence was required, what was captured, and what failed.
6
+ * This is NOT proof; it is an audit trail proving we attempted to capture evidence.
7
+ */
8
+
9
+ import { writeFileSync, readFileSync, existsSync } from 'fs';
10
+ import { resolve } from 'path';
11
+ import { EVIDENCE_CAPTURE_STAGE, EVIDENCE_CAPTURE_FAILURE_CODES as _EVIDENCE_CAPTURE_FAILURE_CODES } from './evidence-capture-service.js';
12
+
13
+ /**
14
+ * Required evidence fields for CONFIRMED findings (stable)
15
+ */
16
+ export const REQUIRED_EVIDENCE_FIELDS = [
17
+ 'trigger.source',
18
+ 'before.screenshot',
19
+ 'after.screenshot',
20
+ 'before.url',
21
+ 'after.url',
22
+ 'action.interaction',
23
+ 'signals.network',
24
+ 'signals.uiSignals'
25
+ ];
26
+
27
+ /**
28
+ * Build evidence intent entry for a finding
29
+ *
30
+ * @param {Object} finding - Finding object
31
+ * @param {Object} evidencePackage - Evidence package
32
+ * @param {Array<Object>} captureFailures - Array of EvidenceCaptureFailure objects
33
+ * @returns {Object} Evidence intent entry
34
+ */
35
+ export function buildEvidenceIntentEntry(finding, evidencePackage, captureFailures = []) {
36
+ const findingIdentity = finding.findingId || finding.id || `finding-${Date.now()}`;
37
+
38
+ // Determine required fields based on finding severity
39
+ const requiredFields = finding.severity === 'CONFIRMED' || finding.status === 'CONFIRMED'
40
+ ? REQUIRED_EVIDENCE_FIELDS
41
+ : [];
42
+
43
+ // Check capture outcomes for each required field
44
+ const captureOutcomes = {};
45
+ const missingFields = [];
46
+
47
+ for (const field of requiredFields) {
48
+ const [section, key] = field.split('.');
49
+
50
+ let captured = false;
51
+ let failure = null;
52
+
53
+ // Check if field exists in evidence package
54
+ if (section === 'trigger' && evidencePackage.trigger?.[key]) {
55
+ captured = true;
56
+ } else if (section === 'before' && evidencePackage.before?.[key]) {
57
+ captured = true;
58
+ } else if (section === 'after' && evidencePackage.after?.[key]) {
59
+ captured = true;
60
+ } else if (section === 'action' && evidencePackage.action?.[key]) {
61
+ captured = true;
62
+ } else if (section === 'signals' && evidencePackage.signals?.[key]) {
63
+ captured = true;
64
+ }
65
+
66
+ // Check if there was a capture failure for this field
67
+ if (!captured) {
68
+ const fieldFailure = captureFailures.find(f => {
69
+ if (field.includes('screenshot') && f.stage.includes('SCREENSHOT')) return true;
70
+ if (field.includes('domSignature') && f.stage === EVIDENCE_CAPTURE_STAGE.DOM_SIGNATURE) return true;
71
+ if (field.includes('url') && f.stage === EVIDENCE_CAPTURE_STAGE.URL) return true;
72
+ if (field.includes('uiSignals') && f.stage === EVIDENCE_CAPTURE_STAGE.UISIGNALS) return true;
73
+ if (field.includes('network') && f.stage === EVIDENCE_CAPTURE_STAGE.NETWORK) return true;
74
+ return false;
75
+ });
76
+
77
+ if (fieldFailure) {
78
+ failure = {
79
+ stage: fieldFailure.stage,
80
+ reasonCode: fieldFailure.reasonCode,
81
+ reason: fieldFailure.reason
82
+ };
83
+ }
84
+
85
+ missingFields.push(field);
86
+ }
87
+
88
+ captureOutcomes[field] = {
89
+ required: true,
90
+ captured,
91
+ failure
92
+ };
93
+ }
94
+
95
+ return {
96
+ findingIdentity,
97
+ findingType: finding.type || 'finding',
98
+ severity: finding.severity || finding.status || 'SUSPECTED',
99
+ requiredFields,
100
+ captureOutcomes,
101
+ missingFields,
102
+ evidencePackageComplete: evidencePackage.isComplete === true,
103
+ timestamp: new Date().toISOString()
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Write evidence intent ledger
109
+ *
110
+ * @param {string} runDir - Run directory
111
+ * @param {Array<Object>} findings - Array of findings with evidence packages
112
+ * @param {Map<string, Array<Object>>} captureFailuresMap - Map of findingId -> capture failures
113
+ * @returns {string} Path to written evidence.intent.json
114
+ */
115
+ export function writeEvidenceIntentLedger(runDir, findings, captureFailuresMap = new Map()) {
116
+ const intentPath = resolve(runDir, 'evidence.intent.json');
117
+
118
+ // Build intent entries (deterministic ordering by findingIdentity)
119
+ const entries = [];
120
+
121
+ for (const finding of findings || []) {
122
+ const findingIdentity = finding.findingId || finding.id || `finding-${findings.indexOf(finding)}`;
123
+ const captureFailures = captureFailuresMap.get(findingIdentity) || [];
124
+ const evidencePackage = finding.evidencePackage || {};
125
+
126
+ const entry = buildEvidenceIntentEntry(finding, evidencePackage, captureFailures);
127
+ entries.push(entry);
128
+ }
129
+
130
+ // Sort by findingIdentity for deterministic ordering
131
+ entries.sort((a, b) => a.findingIdentity.localeCompare(b.findingIdentity));
132
+
133
+ const ledger = {
134
+ version: 1,
135
+ generatedAt: new Date().toISOString(),
136
+ totalFindings: entries.length,
137
+ entries
138
+ };
139
+
140
+ writeFileSync(intentPath, JSON.stringify(ledger, null, 2) + '\n');
141
+
142
+ return intentPath;
143
+ }
144
+
145
+ /**
146
+ * Read evidence intent ledger
147
+ *
148
+ * @param {string} runDir - Run directory
149
+ * @returns {Object | null} Evidence intent ledger or null if not found
150
+ */
151
+ export function readEvidenceIntentLedger(runDir) {
152
+ const intentPath = resolve(runDir, 'evidence.intent.json');
153
+
154
+ if (!existsSync(intentPath)) {
155
+ return null;
156
+ }
157
+
158
+ try {
159
+ const content = readFileSync(intentPath, 'utf-8');
160
+ // @ts-expect-error - readFileSync with encoding returns string
161
+ return JSON.parse(content);
162
+ } catch (error) {
163
+ return null;
164
+ }
165
+ }
166
+