@veraxhq/verax 0.1.0 → 0.2.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 (126) hide show
  1. package/README.md +123 -88
  2. package/bin/verax.js +11 -452
  3. package/package.json +14 -36
  4. package/src/cli/commands/default.js +523 -0
  5. package/src/cli/commands/doctor.js +165 -0
  6. package/src/cli/commands/inspect.js +109 -0
  7. package/src/cli/commands/run.js +402 -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 +296 -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 +34 -0
  14. package/src/cli/util/expectation-extractor.js +378 -0
  15. package/src/cli/util/findings-writer.js +31 -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 +366 -0
  19. package/src/cli/util/observe-writer.js +25 -0
  20. package/src/cli/util/paths.js +29 -0
  21. package/src/cli/util/project-discovery.js +277 -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/summary-writer.js +32 -0
  26. package/src/verax/cli/ci-summary.js +35 -0
  27. package/src/verax/cli/context-explanation.js +89 -0
  28. package/src/verax/cli/doctor.js +277 -0
  29. package/src/verax/cli/error-normalizer.js +154 -0
  30. package/src/verax/cli/explain-output.js +105 -0
  31. package/src/verax/cli/finding-explainer.js +130 -0
  32. package/src/verax/cli/init.js +237 -0
  33. package/src/verax/cli/run-overview.js +163 -0
  34. package/src/verax/cli/url-safety.js +101 -0
  35. package/src/verax/cli/wizard.js +98 -0
  36. package/src/verax/cli/zero-findings-explainer.js +57 -0
  37. package/src/verax/cli/zero-interaction-explainer.js +127 -0
  38. package/src/verax/core/action-classifier.js +86 -0
  39. package/src/verax/core/budget-engine.js +218 -0
  40. package/src/verax/core/canonical-outcomes.js +157 -0
  41. package/src/verax/core/decision-snapshot.js +335 -0
  42. package/src/verax/core/determinism-model.js +403 -0
  43. package/src/verax/core/incremental-store.js +237 -0
  44. package/src/verax/core/invariants.js +356 -0
  45. package/src/verax/core/promise-model.js +230 -0
  46. package/src/verax/core/replay-validator.js +350 -0
  47. package/src/verax/core/replay.js +222 -0
  48. package/src/verax/core/run-id.js +175 -0
  49. package/src/verax/core/run-manifest.js +99 -0
  50. package/src/verax/core/silence-impact.js +369 -0
  51. package/src/verax/core/silence-model.js +521 -0
  52. package/src/verax/detect/comparison.js +2 -34
  53. package/src/verax/detect/confidence-engine.js +764 -329
  54. package/src/verax/detect/detection-engine.js +293 -0
  55. package/src/verax/detect/evidence-index.js +177 -0
  56. package/src/verax/detect/expectation-model.js +194 -172
  57. package/src/verax/detect/explanation-helpers.js +187 -0
  58. package/src/verax/detect/finding-detector.js +450 -0
  59. package/src/verax/detect/findings-writer.js +44 -8
  60. package/src/verax/detect/flow-detector.js +366 -0
  61. package/src/verax/detect/index.js +172 -286
  62. package/src/verax/detect/interactive-findings.js +613 -0
  63. package/src/verax/detect/signal-mapper.js +308 -0
  64. package/src/verax/detect/verdict-engine.js +563 -0
  65. package/src/verax/evidence-index-writer.js +61 -0
  66. package/src/verax/index.js +90 -14
  67. package/src/verax/intel/effect-detector.js +368 -0
  68. package/src/verax/intel/handler-mapper.js +249 -0
  69. package/src/verax/intel/index.js +281 -0
  70. package/src/verax/intel/route-extractor.js +280 -0
  71. package/src/verax/intel/ts-program.js +256 -0
  72. package/src/verax/intel/vue-navigation-extractor.js +579 -0
  73. package/src/verax/intel/vue-router-extractor.js +323 -0
  74. package/src/verax/learn/action-contract-extractor.js +335 -101
  75. package/src/verax/learn/ast-contract-extractor.js +95 -5
  76. package/src/verax/learn/flow-extractor.js +172 -0
  77. package/src/verax/learn/manifest-writer.js +97 -47
  78. package/src/verax/learn/project-detector.js +40 -0
  79. package/src/verax/learn/route-extractor.js +27 -96
  80. package/src/verax/learn/state-extractor.js +212 -0
  81. package/src/verax/learn/static-extractor-navigation.js +114 -0
  82. package/src/verax/learn/static-extractor-validation.js +88 -0
  83. package/src/verax/learn/static-extractor.js +112 -4
  84. package/src/verax/learn/truth-assessor.js +24 -21
  85. package/src/verax/observe/aria-sensor.js +211 -0
  86. package/src/verax/observe/browser.js +10 -5
  87. package/src/verax/observe/console-sensor.js +1 -17
  88. package/src/verax/observe/domain-boundary.js +10 -1
  89. package/src/verax/observe/expectation-executor.js +512 -0
  90. package/src/verax/observe/flow-matcher.js +143 -0
  91. package/src/verax/observe/focus-sensor.js +196 -0
  92. package/src/verax/observe/human-driver.js +643 -275
  93. package/src/verax/observe/index.js +908 -27
  94. package/src/verax/observe/index.js.backup +1 -0
  95. package/src/verax/observe/interaction-discovery.js +365 -14
  96. package/src/verax/observe/interaction-runner.js +563 -198
  97. package/src/verax/observe/loading-sensor.js +139 -0
  98. package/src/verax/observe/navigation-sensor.js +255 -0
  99. package/src/verax/observe/network-sensor.js +55 -7
  100. package/src/verax/observe/observed-expectation-deriver.js +186 -0
  101. package/src/verax/observe/observed-expectation.js +305 -0
  102. package/src/verax/observe/page-frontier.js +234 -0
  103. package/src/verax/observe/settle.js +37 -17
  104. package/src/verax/observe/state-sensor.js +389 -0
  105. package/src/verax/observe/timing-sensor.js +228 -0
  106. package/src/verax/observe/traces-writer.js +61 -20
  107. package/src/verax/observe/ui-signal-sensor.js +136 -17
  108. package/src/verax/scan-summary-writer.js +77 -15
  109. package/src/verax/shared/artifact-manager.js +110 -8
  110. package/src/verax/shared/budget-profiles.js +136 -0
  111. package/src/verax/shared/ci-detection.js +39 -0
  112. package/src/verax/shared/config-loader.js +170 -0
  113. package/src/verax/shared/dynamic-route-utils.js +218 -0
  114. package/src/verax/shared/expectation-coverage.js +44 -0
  115. package/src/verax/shared/expectation-prover.js +81 -0
  116. package/src/verax/shared/expectation-tracker.js +201 -0
  117. package/src/verax/shared/expectations-writer.js +60 -0
  118. package/src/verax/shared/first-run.js +44 -0
  119. package/src/verax/shared/progress-reporter.js +171 -0
  120. package/src/verax/shared/retry-policy.js +14 -1
  121. package/src/verax/shared/root-artifacts.js +49 -0
  122. package/src/verax/shared/scan-budget.js +86 -0
  123. package/src/verax/shared/url-normalizer.js +162 -0
  124. package/src/verax/shared/zip-artifacts.js +65 -0
  125. package/src/verax/validate/context-validator.js +244 -0
  126. package/src/verax/validate/context-validator.js.bak +0 -0
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Detection Engine: Core of VERAX
3
+ * Compares learn.json and observe.json to produce evidence-backed findings
4
+ * with deterministic classification and confidence calculation
5
+ */
6
+
7
+ class DetectionEngine {
8
+ constructor(options = {}) {
9
+ this.options = options;
10
+ }
11
+
12
+ /**
13
+ * Detect findings by comparing expectations with observations
14
+ * @param {Object} learnData - The learn.json output
15
+ * @param {Object} observeData - The observe.json output
16
+ * @returns {Object} Findings with classifications
17
+ */
18
+ detect(learnData, observeData) {
19
+ if (!learnData || !observeData) {
20
+ throw new Error('Both learnData and observeData are required');
21
+ }
22
+
23
+ const expectations = learnData.expectations || [];
24
+ const observations = observeData.observations || [];
25
+
26
+ // Index observations for fast lookup
27
+ const observationMap = this._indexObservations(observations);
28
+
29
+ // Generate one finding per expectation
30
+ const findings = expectations.map((expectation) => {
31
+ return this._classifyExpectation(expectation, observationMap, observations);
32
+ });
33
+
34
+ // Calculate stats
35
+ const stats = this._calculateStats(findings);
36
+
37
+ return {
38
+ findings,
39
+ stats,
40
+ detectedAt: new Date().toISOString(),
41
+ version: '1.0.0'
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Index observations by expectation ID and promise for fast lookup
47
+ * @private
48
+ */
49
+ _indexObservations(observations) {
50
+ const map = {
51
+ byId: {},
52
+ byPromise: {}
53
+ };
54
+
55
+ observations.forEach((obs) => {
56
+ // Index by expectation ID if available
57
+ if (obs.expectationId) {
58
+ if (!map.byId[obs.expectationId]) {
59
+ map.byId[obs.expectationId] = [];
60
+ }
61
+ map.byId[obs.expectationId].push(obs);
62
+ }
63
+
64
+ // Index by promise kind for coverage gap detection
65
+ if (obs.promise && obs.promise.kind) {
66
+ if (!map.byPromise[obs.promise.kind]) {
67
+ map.byPromise[obs.promise.kind] = [];
68
+ }
69
+ map.byPromise[obs.promise.kind].push(obs);
70
+ }
71
+ });
72
+
73
+ return map;
74
+ }
75
+
76
+ /**
77
+ * Classify a single expectation against observations
78
+ * @private
79
+ */
80
+ _classifyExpectation(expectation, observationMap, allObservations) {
81
+ const expectationId = expectation.id;
82
+ const promise = expectation.promise || {};
83
+
84
+ // Find direct observations for this expectation
85
+ const directObservations = observationMap.byId[expectationId] || [];
86
+
87
+ // Determine classification
88
+ let classification = 'informational'; // default
89
+ let matchedObservation = null;
90
+ let evidence = [];
91
+
92
+ if (directObservations.length > 0) {
93
+ // We have observations for this expectation
94
+ const obs = directObservations[0];
95
+ matchedObservation = obs;
96
+
97
+ if (obs.observed === true) {
98
+ classification = 'observed';
99
+ evidence = obs.evidence || [];
100
+ } else if (obs.attempted === true) {
101
+ classification = 'silent-failure';
102
+ evidence = obs.evidence || [];
103
+ } else {
104
+ classification = 'unproven';
105
+ evidence = obs.evidence || [];
106
+ }
107
+ } else {
108
+ // No direct observations - check if we attempted this type of observation
109
+ const relevantObservations =
110
+ observationMap.byPromise[promise.kind] || [];
111
+ const wasAttempted = relevantObservations.some((obs) => obs.attempted);
112
+
113
+ if (wasAttempted) {
114
+ // We attempted this type but didn't get this specific promise
115
+ classification = 'silent-failure';
116
+ } else {
117
+ // We never attempted to observe this type of promise
118
+ classification = 'coverage-gap';
119
+ }
120
+ }
121
+
122
+ // Calculate deterministic confidence
123
+ const confidence = this._calculateConfidence(
124
+ expectation,
125
+ matchedObservation,
126
+ classification
127
+ );
128
+
129
+ // Determine impact
130
+ const impact = this._determineImpact(expectation, classification);
131
+
132
+ return {
133
+ id: expectation.id,
134
+ expectation: expectation,
135
+ classification,
136
+ confidence,
137
+ impact,
138
+ evidence,
139
+ matched: matchedObservation || null,
140
+ summary: this._generateSummary(expectation, classification),
141
+ details: {
142
+ expectationType: expectation.type,
143
+ promiseKind: promise.kind,
144
+ promiseValue: promise.value,
145
+ attemptedObservations: observationMap.byPromise[promise.kind]
146
+ ? observationMap.byPromise[promise.kind].length
147
+ : 0
148
+ }
149
+ };
150
+ }
151
+
152
+ /**
153
+ * Calculate deterministic confidence score (0-1)
154
+ * Based on: expectation confidence, observation evidence, classification
155
+ * @private
156
+ */
157
+ _calculateConfidence(expectation, observation, classification) {
158
+ const baseConfidence = expectation.confidence || 0.5;
159
+
160
+ let multiplier = 1.0;
161
+ switch (classification) {
162
+ case 'observed':
163
+ // High confidence - we saw it
164
+ multiplier = 1.0;
165
+ break;
166
+ case 'silent-failure':
167
+ // Medium-high - we looked for it but didn't see it
168
+ multiplier = 0.75;
169
+ break;
170
+ case 'coverage-gap':
171
+ // Lower - we didn't even try to look
172
+ multiplier = 0.5;
173
+ break;
174
+ case 'unproven':
175
+ // Low - we tried but evidence insufficient
176
+ multiplier = 0.25;
177
+ break;
178
+ default:
179
+ multiplier = 0.5;
180
+ }
181
+
182
+ // If we have evidence, boost confidence slightly
183
+ if (observation && observation.evidence && observation.evidence.length > 0) {
184
+ multiplier = Math.min(1.0, multiplier + 0.1);
185
+ }
186
+
187
+ const confidence = baseConfidence * multiplier;
188
+ return Math.round(confidence * 100) / 100; // Round to 2 decimals
189
+ }
190
+
191
+ /**
192
+ * Determine impact level based on expectation criticality
193
+ * @private
194
+ */
195
+ _determineImpact(expectation, classification) {
196
+ // If explicitly marked as critical
197
+ if (expectation.critical === true) {
198
+ return 'HIGH';
199
+ }
200
+
201
+ // If silent-failure on a navigation or network call, it's high impact
202
+ if (
203
+ classification === 'silent-failure' &&
204
+ (expectation.type === 'navigation' || expectation.type === 'network')
205
+ ) {
206
+ return 'HIGH';
207
+ }
208
+
209
+ // State mutations are typically medium impact
210
+ if (expectation.type === 'state') {
211
+ return 'MEDIUM';
212
+ }
213
+
214
+ // Coverage gaps are lower impact (we didn't even try)
215
+ if (classification === 'coverage-gap') {
216
+ return 'LOW';
217
+ }
218
+
219
+ return 'MEDIUM';
220
+ }
221
+
222
+ /**
223
+ * Generate human-readable summary
224
+ * @private
225
+ */
226
+ _generateSummary(expectation, classification) {
227
+ const type = expectation.type || 'unknown';
228
+ const promise = expectation.promise || {};
229
+ const value = promise.value || 'unknown';
230
+
231
+ const summaries = {
232
+ 'silent-failure': `Silent failure: ${type} "${value}" was learned but never observed during testing`,
233
+ 'observed': `Success: ${type} "${value}" was verified during testing`,
234
+ 'coverage-gap': `Coverage gap: ${type} "${value}" was never attempted during testing`,
235
+ 'unproven': `Unproven: ${type} "${value}" was attempted but insufficient evidence`,
236
+ 'informational': `Info: ${type} "${value}" was analyzed`
237
+ };
238
+
239
+ return summaries[classification] || summaries.informational;
240
+ }
241
+
242
+ /**
243
+ * Calculate statistics across all findings
244
+ * @private
245
+ */
246
+ _calculateStats(findings) {
247
+ const stats = {
248
+ total: findings.length,
249
+ byClassification: {},
250
+ byImpact: {},
251
+ averageConfidence: 0,
252
+ highConfidence: 0,
253
+ mediumConfidence: 0,
254
+ lowConfidence: 0
255
+ };
256
+
257
+ // Initialize counters
258
+ ['silent-failure', 'observed', 'coverage-gap', 'unproven', 'informational'].forEach(
259
+ (c) => {
260
+ stats.byClassification[c] = 0;
261
+ }
262
+ );
263
+ ['HIGH', 'MEDIUM', 'LOW'].forEach((i) => {
264
+ stats.byImpact[i] = 0;
265
+ });
266
+
267
+ // Count and aggregate
268
+ let totalConfidence = 0;
269
+ findings.forEach((finding) => {
270
+ stats.byClassification[finding.classification]++;
271
+ stats.byImpact[finding.impact]++;
272
+
273
+ totalConfidence += finding.confidence;
274
+
275
+ if (finding.confidence >= 0.8) {
276
+ stats.highConfidence++;
277
+ } else if (finding.confidence >= 0.5) {
278
+ stats.mediumConfidence++;
279
+ } else {
280
+ stats.lowConfidence++;
281
+ }
282
+ });
283
+
284
+ stats.averageConfidence =
285
+ findings.length > 0
286
+ ? Math.round((totalConfidence / findings.length) * 100) / 100
287
+ : 0;
288
+
289
+ return stats;
290
+ }
291
+ }
292
+
293
+ module.exports = DetectionEngine;
@@ -0,0 +1,177 @@
1
+ /**
2
+ * EVIDENCE INDEX MODULE
3
+ *
4
+ * Builds and validates evidence index from observation traces.
5
+ * Handles screenshot validation and missing evidence tracking via silence system.
6
+ *
7
+ * SILENCE INTEGRATION: Missing screenshot files are tracked as silence entries
8
+ * with scope='evidence', reason='evidence_missing', preserving full context.
9
+ */
10
+
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+
14
+ /**
15
+ * Build evidence index from observation traces with validation.
16
+ *
17
+ * @param {Array} traces - Observation traces from observe phase
18
+ * @param {string|null} projectDir - Project directory for file validation (null to skip validation)
19
+ * @param {Object|null} silenceTracker - Silence tracker instance (null to skip silence tracking)
20
+ * @returns {Object} { evidenceIndex, expectationEvidenceMap, findingEvidenceMap }
21
+ */
22
+ export function buildEvidenceIndex(traces, projectDir = null, silenceTracker = null) {
23
+ const evidenceIndex = [];
24
+ const expectationEvidenceMap = new Map();
25
+ const findingEvidenceMap = new Map();
26
+ let id = 1;
27
+
28
+ if (!Array.isArray(traces)) {
29
+ return { evidenceIndex, expectationEvidenceMap, findingEvidenceMap };
30
+ }
31
+
32
+ // Use fs/path for evidence validation if projectDir provided
33
+ let existsSync = null;
34
+ let resolvePath = null;
35
+ if (projectDir && fs && path) {
36
+ existsSync = fs.existsSync;
37
+ resolvePath = path.resolve;
38
+ }
39
+
40
+ for (const trace of traces) {
41
+ // Prefer modern trace schema: trace.before/trace.after
42
+ const beforeUrl = trace.before?.url ?? trace.evidence?.beforeUrl ?? null;
43
+ const afterUrl = trace.after?.url ?? trace.evidence?.afterUrl ?? null;
44
+ let beforeScreenshot = trace.before?.screenshot ?? trace.evidence?.beforeScreenshot ?? null;
45
+ let afterScreenshot = trace.after?.screenshot ?? trace.evidence?.afterScreenshot ?? null;
46
+
47
+ // PHASE 3: Validate evidence file existence
48
+ if (existsSync && resolvePath && projectDir) {
49
+ const veraxDir = resolvePath(projectDir, '.veraxverax');
50
+
51
+ // Check beforeScreenshot exists
52
+ if (beforeScreenshot) {
53
+ const beforePath = resolvePath(veraxDir, 'observe', beforeScreenshot);
54
+ const fileExists = existsSync(beforePath);
55
+
56
+ if (!fileExists) {
57
+ // Track missing evidence as silence
58
+ if (silenceTracker) {
59
+ silenceTracker.record({
60
+ scope: 'evidence',
61
+ reason: 'evidence_missing',
62
+ description: `Screenshot evidence file not found: ${beforeScreenshot}`,
63
+ context: {
64
+ expectationId: trace.expectationId,
65
+ interaction: trace.interaction?.label,
66
+ expectedPath: beforePath
67
+ },
68
+ impact: 'incomplete_check'
69
+ });
70
+ }
71
+ beforeScreenshot = null; // Remove invalid evidence reference
72
+ }
73
+ }
74
+
75
+ // Check afterScreenshot exists
76
+ if (afterScreenshot) {
77
+ const afterPath = resolvePath(veraxDir, 'observe', afterScreenshot);
78
+ if (!existsSync(afterPath)) {
79
+ // Track missing evidence as silence
80
+ if (silenceTracker) {
81
+ silenceTracker.record({
82
+ scope: 'evidence',
83
+ reason: 'evidence_missing',
84
+ description: `Screenshot evidence file not found: ${afterScreenshot}`,
85
+ context: {
86
+ expectationId: trace.expectationId,
87
+ interaction: trace.interaction?.label,
88
+ expectedPath: afterPath
89
+ },
90
+ impact: 'incomplete_check'
91
+ });
92
+ }
93
+ afterScreenshot = null; // Remove invalid evidence reference
94
+ }
95
+ }
96
+ }
97
+
98
+ const entry = {
99
+ id: `ev-${id}`,
100
+ expectationId: trace.expectationId || null,
101
+ interaction: trace.interaction ? { ...trace.interaction } : null,
102
+ resultType: trace.resultType || 'UNKNOWN',
103
+ evidence: {
104
+ beforeUrl,
105
+ afterUrl,
106
+ beforeScreenshot,
107
+ afterScreenshot
108
+ }
109
+ };
110
+
111
+ evidenceIndex.push(entry);
112
+
113
+ if (trace.expectationId) {
114
+ expectationEvidenceMap.set(trace.expectationId, entry.id);
115
+ }
116
+
117
+ const selector = trace.interaction?.selector;
118
+ if (selector) {
119
+ findingEvidenceMap.set(selector, entry.id);
120
+ }
121
+
122
+ id++;
123
+ }
124
+
125
+ return { evidenceIndex, expectationEvidenceMap, findingEvidenceMap };
126
+ }
127
+
128
+ /**
129
+ * Write evidence index to artifacts directory.
130
+ * Maps findingId/expectationId to evidence paths (screenshots, traces, network logs).
131
+ *
132
+ * @param {string} projectDir - Project root directory
133
+ * @param {Array} evidenceIndex - Evidence index from buildEvidenceIndex
134
+ * @param {string} tracesPath - Path to observation-traces.json
135
+ * @param {string} findingsPath - Path to findings.json
136
+ * @returns {Promise<string>} Path to written evidence-index.json
137
+ */
138
+ export async function writeEvidenceIndex(projectDir, evidenceIndex, tracesPath, findingsPath, runDirOpt = null) {
139
+ const { resolve } = await import('path');
140
+ const { mkdirSync, writeFileSync } = await import('fs');
141
+
142
+ // Prefer canonical run directory when provided
143
+ const artifactsDir = runDirOpt ? resolve(runDirOpt) : resolve(projectDir, '.veraxverax', 'artifacts');
144
+ mkdirSync(artifactsDir, { recursive: true });
145
+
146
+ const evidenceIndexPath = resolve(artifactsDir, 'evidence-index.json');
147
+
148
+ // Build evidence index with full paths
149
+ const index = {
150
+ version: 1,
151
+ generatedAt: new Date().toISOString(),
152
+ tracesPath: tracesPath,
153
+ findingsPath: findingsPath,
154
+ evidence: evidenceIndex.map(entry => ({
155
+ id: entry.id,
156
+ expectationId: entry.expectationId,
157
+ interaction: entry.interaction ? {
158
+ type: entry.interaction.type,
159
+ selector: entry.interaction.selector,
160
+ label: entry.interaction.label
161
+ } : null,
162
+ resultType: entry.resultType,
163
+ expectationOutcome: entry.expectationOutcome,
164
+ evidence: {
165
+ beforeUrl: entry.evidence.beforeUrl,
166
+ afterUrl: entry.evidence.afterUrl,
167
+ beforeScreenshot: entry.evidence.beforeScreenshot,
168
+ afterScreenshot: entry.evidence.afterScreenshot,
169
+ traceFile: tracesPath // All traces are in the same file
170
+ }
171
+ }))
172
+ };
173
+
174
+ writeFileSync(evidenceIndexPath, JSON.stringify(index, null, 2) + '\n');
175
+
176
+ return evidenceIndexPath;
177
+ }