@veraxhq/verax 0.3.0 → 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 (191) hide show
  1. package/README.md +28 -20
  2. package/bin/verax.js +11 -18
  3. package/package.json +28 -7
  4. package/src/cli/commands/baseline.js +1 -2
  5. package/src/cli/commands/default.js +72 -81
  6. package/src/cli/commands/doctor.js +29 -0
  7. package/src/cli/commands/ga.js +3 -0
  8. package/src/cli/commands/gates.js +1 -1
  9. package/src/cli/commands/inspect.js +6 -133
  10. package/src/cli/commands/release-check.js +2 -0
  11. package/src/cli/commands/run.js +74 -246
  12. package/src/cli/commands/security-check.js +2 -1
  13. package/src/cli/commands/truth.js +0 -1
  14. package/src/cli/entry.js +82 -309
  15. package/src/cli/util/angular-component-extractor.js +2 -2
  16. package/src/cli/util/angular-navigation-detector.js +2 -2
  17. package/src/cli/util/ast-interactive-detector.js +4 -6
  18. package/src/cli/util/ast-network-detector.js +3 -3
  19. package/src/cli/util/ast-promise-extractor.js +581 -0
  20. package/src/cli/util/ast-usestate-detector.js +3 -3
  21. package/src/cli/util/atomic-write.js +12 -1
  22. package/src/cli/util/console-reporter.js +72 -0
  23. package/src/cli/util/detection-engine.js +105 -41
  24. package/src/cli/util/determinism-runner.js +2 -1
  25. package/src/cli/util/determinism-writer.js +1 -1
  26. package/src/cli/util/digest-engine.js +359 -0
  27. package/src/cli/util/dom-diff.js +226 -0
  28. package/src/cli/util/env-url.js +0 -4
  29. package/src/cli/util/evidence-engine.js +287 -0
  30. package/src/cli/util/expectation-extractor.js +217 -367
  31. package/src/cli/util/findings-writer.js +19 -126
  32. package/src/cli/util/framework-detector.js +572 -0
  33. package/src/cli/util/idgen.js +1 -1
  34. package/src/cli/util/interaction-planner.js +529 -0
  35. package/src/cli/util/learn-writer.js +2 -2
  36. package/src/cli/util/ledger-writer.js +110 -0
  37. package/src/cli/util/monorepo-resolver.js +162 -0
  38. package/src/cli/util/observation-engine.js +127 -278
  39. package/src/cli/util/observe-writer.js +2 -2
  40. package/src/cli/util/paths.js +12 -3
  41. package/src/cli/util/project-discovery.js +284 -3
  42. package/src/cli/util/project-writer.js +2 -2
  43. package/src/cli/util/run-id.js +23 -27
  44. package/src/cli/util/run-result.js +778 -0
  45. package/src/cli/util/selector-resolver.js +235 -0
  46. package/src/cli/util/summary-writer.js +2 -1
  47. package/src/cli/util/svelte-navigation-detector.js +3 -3
  48. package/src/cli/util/svelte-sfc-extractor.js +0 -1
  49. package/src/cli/util/svelte-state-detector.js +1 -2
  50. package/src/cli/util/trust-activation-integration.js +496 -0
  51. package/src/cli/util/trust-activation-wrapper.js +85 -0
  52. package/src/cli/util/trust-integration-hooks.js +164 -0
  53. package/src/cli/util/types.js +153 -0
  54. package/src/cli/util/url-validation.js +40 -0
  55. package/src/cli/util/vue-navigation-detector.js +4 -3
  56. package/src/cli/util/vue-sfc-extractor.js +1 -2
  57. package/src/cli/util/vue-state-detector.js +1 -1
  58. package/src/types/fs-augment.d.ts +23 -0
  59. package/src/types/global.d.ts +137 -0
  60. package/src/types/internal-types.d.ts +35 -0
  61. package/src/verax/cli/finding-explainer.js +3 -56
  62. package/src/verax/cli/init.js +4 -18
  63. package/src/verax/core/action-classifier.js +4 -3
  64. package/src/verax/core/artifacts/registry.js +0 -15
  65. package/src/verax/core/artifacts/verifier.js +18 -8
  66. package/src/verax/core/baseline/baseline.snapshot.js +2 -0
  67. package/src/verax/core/capabilities/gates.js +7 -1
  68. package/src/verax/core/confidence/confidence-compute.js +14 -7
  69. package/src/verax/core/confidence/confidence.loader.js +1 -0
  70. package/src/verax/core/confidence-engine-refactor.js +8 -3
  71. package/src/verax/core/confidence-engine.js +162 -23
  72. package/src/verax/core/contracts/types.js +1 -0
  73. package/src/verax/core/contracts/validators.js +79 -4
  74. package/src/verax/core/decision-snapshot.js +3 -30
  75. package/src/verax/core/decisions/decision.trace.js +2 -0
  76. package/src/verax/core/determinism/contract-writer.js +2 -2
  77. package/src/verax/core/determinism/contract.js +1 -1
  78. package/src/verax/core/determinism/diff.js +42 -1
  79. package/src/verax/core/determinism/engine.js +7 -6
  80. package/src/verax/core/determinism/finding-identity.js +3 -2
  81. package/src/verax/core/determinism/normalize.js +32 -4
  82. package/src/verax/core/determinism/report-writer.js +1 -0
  83. package/src/verax/core/determinism/run-fingerprint.js +7 -2
  84. package/src/verax/core/dynamic-route-intelligence.js +8 -7
  85. package/src/verax/core/evidence/evidence-capture-service.js +1 -0
  86. package/src/verax/core/evidence/evidence-intent-ledger.js +2 -1
  87. package/src/verax/core/evidence-builder.js +2 -2
  88. package/src/verax/core/execution-mode-context.js +1 -1
  89. package/src/verax/core/execution-mode-detector.js +5 -3
  90. package/src/verax/core/failures/exit-codes.js +39 -37
  91. package/src/verax/core/failures/failure-summary.js +1 -1
  92. package/src/verax/core/failures/failure.factory.js +3 -3
  93. package/src/verax/core/failures/failure.ledger.js +3 -2
  94. package/src/verax/core/ga/ga.artifact.js +1 -1
  95. package/src/verax/core/ga/ga.contract.js +3 -2
  96. package/src/verax/core/ga/ga.enforcer.js +1 -0
  97. package/src/verax/core/guardrails/policy.loader.js +1 -0
  98. package/src/verax/core/guardrails/truth-reconciliation.js +1 -1
  99. package/src/verax/core/guardrails-engine.js +2 -2
  100. package/src/verax/core/incremental-store.js +1 -0
  101. package/src/verax/core/integrity/budget.js +138 -0
  102. package/src/verax/core/integrity/determinism.js +342 -0
  103. package/src/verax/core/integrity/integrity.js +208 -0
  104. package/src/verax/core/integrity/poisoning.js +108 -0
  105. package/src/verax/core/integrity/transaction.js +140 -0
  106. package/src/verax/core/observe/run-timeline.js +2 -0
  107. package/src/verax/core/perf/perf.report.js +2 -0
  108. package/src/verax/core/pipeline-tracker.js +5 -0
  109. package/src/verax/core/release/provenance.builder.js +73 -214
  110. package/src/verax/core/release/release.enforcer.js +14 -9
  111. package/src/verax/core/release/reproducibility.check.js +1 -0
  112. package/src/verax/core/release/sbom.builder.js +32 -23
  113. package/src/verax/core/replay-validator.js +2 -0
  114. package/src/verax/core/replay.js +4 -0
  115. package/src/verax/core/report/cross-index.js +6 -3
  116. package/src/verax/core/report/human-summary.js +141 -1
  117. package/src/verax/core/route-intelligence.js +4 -3
  118. package/src/verax/core/run-id.js +6 -3
  119. package/src/verax/core/run-manifest.js +4 -3
  120. package/src/verax/core/security/secrets.scan.js +10 -7
  121. package/src/verax/core/security/security.enforcer.js +4 -0
  122. package/src/verax/core/security/supplychain.policy.js +9 -1
  123. package/src/verax/core/security/vuln.scan.js +2 -2
  124. package/src/verax/core/truth/truth.certificate.js +3 -1
  125. package/src/verax/core/ui-feedback-intelligence.js +12 -46
  126. package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
  127. package/src/verax/detect/confidence-engine.js +100 -660
  128. package/src/verax/detect/confidence-helper.js +1 -0
  129. package/src/verax/detect/detection-engine.js +1 -18
  130. package/src/verax/detect/dynamic-route-findings.js +17 -14
  131. package/src/verax/detect/expectation-chain-detector.js +1 -1
  132. package/src/verax/detect/expectation-model.js +3 -5
  133. package/src/verax/detect/failure-cause-inference.js +293 -0
  134. package/src/verax/detect/findings-writer.js +126 -166
  135. package/src/verax/detect/flow-detector.js +2 -2
  136. package/src/verax/detect/form-silent-failure.js +98 -0
  137. package/src/verax/detect/index.js +51 -234
  138. package/src/verax/detect/invariants-enforcer.js +147 -0
  139. package/src/verax/detect/journey-stall-detector.js +4 -4
  140. package/src/verax/detect/navigation-silent-failure.js +82 -0
  141. package/src/verax/detect/problem-aggregator.js +361 -0
  142. package/src/verax/detect/route-findings.js +7 -6
  143. package/src/verax/detect/summary-writer.js +477 -0
  144. package/src/verax/detect/test-failure-cause-inference.js +314 -0
  145. package/src/verax/detect/ui-feedback-findings.js +18 -18
  146. package/src/verax/detect/verdict-engine.js +3 -57
  147. package/src/verax/detect/view-switch-correlator.js +2 -2
  148. package/src/verax/flow/flow-engine.js +2 -1
  149. package/src/verax/flow/flow-spec.js +0 -6
  150. package/src/verax/index.js +48 -412
  151. package/src/verax/intel/ts-program.js +1 -0
  152. package/src/verax/intel/vue-navigation-extractor.js +3 -0
  153. package/src/verax/learn/action-contract-extractor.js +67 -682
  154. package/src/verax/learn/ast-contract-extractor.js +1 -1
  155. package/src/verax/learn/flow-extractor.js +1 -0
  156. package/src/verax/learn/project-detector.js +5 -0
  157. package/src/verax/learn/react-router-extractor.js +2 -0
  158. package/src/verax/learn/route-validator.js +1 -4
  159. package/src/verax/learn/source-instrumenter.js +1 -0
  160. package/src/verax/learn/state-extractor.js +2 -1
  161. package/src/verax/learn/static-extractor.js +1 -0
  162. package/src/verax/observe/coverage-gaps.js +132 -0
  163. package/src/verax/observe/expectation-handler.js +126 -0
  164. package/src/verax/observe/incremental-skip.js +46 -0
  165. package/src/verax/observe/index.js +735 -84
  166. package/src/verax/observe/interaction-executor.js +192 -0
  167. package/src/verax/observe/interaction-runner.js +782 -530
  168. package/src/verax/observe/network-firewall.js +86 -0
  169. package/src/verax/observe/observation-builder.js +169 -0
  170. package/src/verax/observe/observe-context.js +1 -1
  171. package/src/verax/observe/observe-helpers.js +2 -1
  172. package/src/verax/observe/observe-runner.js +28 -24
  173. package/src/verax/observe/observers/budget-observer.js +3 -3
  174. package/src/verax/observe/observers/console-observer.js +4 -4
  175. package/src/verax/observe/observers/coverage-observer.js +4 -4
  176. package/src/verax/observe/observers/interaction-observer.js +3 -3
  177. package/src/verax/observe/observers/navigation-observer.js +4 -4
  178. package/src/verax/observe/observers/network-observer.js +4 -4
  179. package/src/verax/observe/observers/safety-observer.js +1 -1
  180. package/src/verax/observe/observers/ui-feedback-observer.js +4 -4
  181. package/src/verax/observe/page-traversal.js +138 -0
  182. package/src/verax/observe/snapshot-ops.js +94 -0
  183. package/src/verax/observe/ui-signal-sensor.js +2 -148
  184. package/src/verax/scan-summary-writer.js +10 -42
  185. package/src/verax/shared/artifact-manager.js +30 -13
  186. package/src/verax/shared/caching.js +1 -0
  187. package/src/verax/shared/expectation-tracker.js +1 -0
  188. package/src/verax/shared/zip-artifacts.js +6 -0
  189. package/src/verax/core/confidence-engine.js.backup +0 -471
  190. package/src/verax/shared/config-loader.js +0 -169
  191. /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
@@ -22,6 +22,7 @@ function getDependencies(projectDir) {
22
22
  if (!existsSync(pkgPath)) {
23
23
  return { dependencies: {}, devDependencies: {} };
24
24
  }
25
+ // @ts-expect-error - readFileSync with encoding returns string
25
26
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
26
27
  return {
27
28
  dependencies: pkg.dependencies || {},
@@ -32,6 +33,29 @@ function getDependencies(projectDir) {
32
33
  }
33
34
  }
34
35
 
36
+ /**
37
+ * Helper to traverse dependency tree recursively
38
+ */
39
+ function traverseDepsHelper(deps, packages, parent = null) {
40
+ if (!deps || typeof deps !== 'object') {
41
+ return;
42
+ }
43
+
44
+ for (const [name, info] of Object.entries(deps)) {
45
+ if (info && typeof info === 'object' && info.version) {
46
+ packages.push({
47
+ name,
48
+ version: info.version,
49
+ parent: parent || null
50
+ });
51
+
52
+ if (info.dependencies) {
53
+ traverseDepsHelper(info.dependencies, packages, name);
54
+ }
55
+ }
56
+ }
57
+ }
58
+
35
59
  /**
36
60
  * Get transitive dependencies from node_modules
37
61
  *
@@ -47,37 +71,18 @@ function getTransitiveDependencies(projectDir) {
47
71
  }
48
72
 
49
73
  try {
50
- // Use npm ls to get dependency tree
74
+ // Use npm ls to get dependency tree (with timeout to prevent hanging)
51
75
  const result = execSync('npm ls --all --json', {
52
76
  cwd: projectDir,
53
77
  encoding: 'utf-8',
54
- stdio: ['ignore', 'pipe', 'ignore']
78
+ stdio: ['ignore', 'pipe', 'ignore'],
79
+ timeout: 5000 // 5 second timeout
55
80
  });
56
81
 
57
82
  const tree = JSON.parse(result);
58
83
 
59
- function traverseDeps(deps, parent = null) {
60
- if (!deps || typeof deps !== 'object') {
61
- return;
62
- }
63
-
64
- for (const [name, info] of Object.entries(deps)) {
65
- if (info && typeof info === 'object' && info.version) {
66
- packages.push({
67
- name,
68
- version: info.version,
69
- parent: parent || null
70
- });
71
-
72
- if (info.dependencies) {
73
- traverseDeps(info.dependencies, name);
74
- }
75
- }
76
- }
77
- }
78
-
79
84
  if (tree.dependencies) {
80
- traverseDeps(tree.dependencies);
85
+ traverseDepsHelper(tree.dependencies, packages);
81
86
  }
82
87
  } catch {
83
88
  // Fallback: scan node_modules directory
@@ -89,6 +94,7 @@ function getTransitiveDependencies(projectDir) {
89
94
  const pkgPath = resolve(nodeModulesPath, entry.name, 'package.json');
90
95
  if (existsSync(pkgPath)) {
91
96
  try {
97
+ // @ts-expect-error - readFileSync with encoding returns string
92
98
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
93
99
  packages.push({
94
100
  name: pkg.name || entry.name,
@@ -120,6 +126,7 @@ function getPackageLicense(packagePath) {
120
126
  if (!existsSync(packagePath)) {
121
127
  return null;
122
128
  }
129
+ // @ts-expect-error - readFileSync with encoding returns string
123
130
  const pkg = JSON.parse(readFileSync(packagePath, 'utf-8'));
124
131
  if (typeof pkg.license === 'string') {
125
132
  return pkg.license;
@@ -150,6 +157,7 @@ function getPackageIntegrity(projectDir, packageName) {
150
157
  const pkgPath = resolve(packagePath, 'package.json');
151
158
  if (existsSync(pkgPath)) {
152
159
  const content = readFileSync(pkgPath);
160
+ // @ts-expect-error - digest returns string
153
161
  return createHash('sha256').update(content).digest('hex');
154
162
  }
155
163
 
@@ -171,6 +179,7 @@ export async function buildSBOM(projectDir) {
171
179
  throw new Error('Cannot build SBOM: package.json not found');
172
180
  }
173
181
 
182
+ // @ts-expect-error - readFileSync with encoding returns string
174
183
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
175
184
  const deps = getDependencies(projectDir);
176
185
  const transitive = getTransitiveDependencies(projectDir);
@@ -292,7 +292,9 @@ export function validateReplay(baselinePath, currentPath) {
292
292
  throw new Error(`Current decisions not found: ${currentPath}`);
293
293
  }
294
294
 
295
+ // @ts-expect-error - readFileSync with encoding returns string
295
296
  const baseline = JSON.parse(fs.readFileSync(baselinePath, 'utf-8'));
297
+ // @ts-expect-error - readFileSync with encoding returns string
296
298
  const current = JSON.parse(fs.readFileSync(currentPath, 'utf-8'));
297
299
 
298
300
  return compareReplayDecisions(baseline, current);
@@ -32,6 +32,7 @@ export function loadRunArtifacts(runDir) {
32
32
  }
33
33
 
34
34
  try {
35
+ // @ts-expect-error - readFileSync with encoding returns string
35
36
  artifacts.runManifest = JSON.parse(readFileSync(runManifestPath, 'utf-8'));
36
37
  } catch (error) {
37
38
  throw new Error(`Failed to parse run manifest: ${error.message}`);
@@ -44,6 +45,7 @@ export function loadRunArtifacts(runDir) {
44
45
  const tracesPath = join(runDir, 'traces.json');
45
46
  if (existsSync(tracesPath)) {
46
47
  try {
48
+ // @ts-expect-error - readFileSync with encoding returns string
47
49
  artifacts.traces = JSON.parse(readFileSync(tracesPath, 'utf-8'));
48
50
 
49
51
  // Verify hash
@@ -80,6 +82,7 @@ export function loadRunArtifacts(runDir) {
80
82
  const findingsPath = join(runDir, 'findings.json');
81
83
  if (existsSync(findingsPath)) {
82
84
  try {
85
+ // @ts-expect-error - readFileSync with encoding returns string
83
86
  artifacts.findings = JSON.parse(readFileSync(findingsPath, 'utf-8'));
84
87
 
85
88
  // Verify hash
@@ -109,6 +112,7 @@ export function loadRunArtifacts(runDir) {
109
112
  const manifestPath = join(runDir, 'manifest.json');
110
113
  if (existsSync(manifestPath)) {
111
114
  try {
115
+ // @ts-expect-error - readFileSync with encoding returns string
112
116
  artifacts.manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
113
117
 
114
118
  // Verify hash
@@ -4,8 +4,8 @@
4
4
  * Builds cross-index linking findingId to all related artifacts.
5
5
  */
6
6
 
7
- import { readFileSync, existsSync, writeFileSync, readdirSync } from 'fs';
8
- import { resolve, relative } from 'path';
7
+ import { readFileSync, existsSync, writeFileSync, readdirSync as _readdirSync } from 'fs';
8
+ import { resolve, relative as _relative } from 'path';
9
9
 
10
10
  /**
11
11
  * Build cross-index
@@ -34,8 +34,9 @@ export function buildCrossIndex(projectDir, runId) {
34
34
  };
35
35
  }
36
36
 
37
+ // @ts-expect-error - readFileSync with encoding returns string
37
38
  const findings = JSON.parse(readFileSync(findingsPath, 'utf-8'));
38
- const evidenceIndex = loadArtifact(runDir, 'evidence.index.json');
39
+ const _evidenceIndex = loadArtifact(runDir, 'evidence.index.json');
39
40
  const decisionTrace = loadArtifact(runDir, 'decisions.trace.json');
40
41
  const timeline = loadArtifact(runDir, 'run.timeline.json');
41
42
  const failureLedger = loadArtifact(runDir, 'failure.ledger.json');
@@ -147,6 +148,7 @@ function loadArtifact(runDir, filename) {
147
148
  return null;
148
149
  }
149
150
  try {
151
+ // @ts-expect-error - readFileSync with encoding returns string
150
152
  return JSON.parse(readFileSync(path, 'utf-8'));
151
153
  } catch {
152
154
  return null;
@@ -184,6 +186,7 @@ export function loadCrossIndex(projectDir, runId) {
184
186
  }
185
187
 
186
188
  try {
189
+ // @ts-expect-error - readFileSync with encoding returns string
187
190
  return JSON.parse(readFileSync(indexPath, 'utf-8'));
188
191
  } catch {
189
192
  return null;
@@ -17,6 +17,7 @@ function loadArtifact(runDir, filename) {
17
17
  return null;
18
18
  }
19
19
  try {
20
+ // @ts-expect-error - readFileSync with encoding returns string
20
21
  return JSON.parse(readFileSync(path, 'utf-8'));
21
22
  } catch {
22
23
  return null;
@@ -28,7 +29,7 @@ function loadArtifact(runDir, filename) {
28
29
  *
29
30
  * @param {string} projectDir - Project directory
30
31
  * @param {string} runId - Run ID
31
- * @returns {Object} Human summary
32
+ * @returns {Promise<Object>} Human summary
32
33
  */
33
34
  export async function generateHumanSummary(projectDir, runId) {
34
35
  const runDir = resolve(projectDir, '.verax', 'runs', runId);
@@ -101,8 +102,10 @@ export async function generateHumanSummary(projectDir, runId) {
101
102
  let determinismVerdict = 'UNKNOWN';
102
103
  if (determinism) {
103
104
  try {
105
+ // @ts-expect-error - Dynamic import path
104
106
  const { DecisionRecorder } = await import('../../../core/determinism-model.js');
105
107
  const recorder = DecisionRecorder.fromExport(determinism);
108
+ // @ts-expect-error - Dynamic import path
106
109
  const { computeDeterminismVerdict } = await import('../../../core/determinism/contract.js');
107
110
  const verdict = computeDeterminismVerdict(recorder);
108
111
  determinismVerdict = verdict.verdict;
@@ -178,6 +181,101 @@ export async function generateHumanSummary(projectDir, runId) {
178
181
  };
179
182
  }
180
183
 
184
+ /**
185
+ * Format finding with Human-Readable Report Contract v1
186
+ * Six-line template per finding
187
+ */
188
+ function formatFinding(finding, index) {
189
+ const lines = [];
190
+ lines.push(`\nFinding #${index + 1}`);
191
+
192
+ // Summary: what the user did (handle both old and new format)
193
+ const summary = finding.what_happened ||
194
+ (finding.interaction ? `User interacted with ${finding.interaction.target || 'element'}` : '') ||
195
+ (finding.promise ? `Expected ${finding.promise.kind}: ${finding.promise.value}` : '') ||
196
+ 'Interaction occurred';
197
+ lines.push(` Summary: ${summary}`);
198
+
199
+ // Expected: what user expected (handle both old and new format)
200
+ const expected = finding.what_was_expected ||
201
+ (finding.promise ? `${finding.promise.kind} ${finding.promise.value}` : '') ||
202
+ 'Expected behavior not specified';
203
+ lines.push(` Expected: ${expected}`);
204
+
205
+ // Observed: what actually happened (handle both old and new format)
206
+ const observed = finding.what_was_observed ||
207
+ finding.reason ||
208
+ (finding.classification ? `${finding.classification}` : '') ||
209
+ 'Actual behavior not specified';
210
+ lines.push(` Observed: ${observed}`);
211
+
212
+ // Evidence (before) - Use interaction ID or finding ID as stable reference
213
+ const beforeId = finding.interaction?.sequenceId
214
+ ? `UI#${finding.interaction.sequenceId}`
215
+ : finding.id ? `REF#${finding.id}`
216
+ : 'UI#?';
217
+ lines.push(` Evidence (before): ${beforeId}`);
218
+
219
+ // Evidence (after) - Use evidence package references if available
220
+ const afterIds = [];
221
+ if (finding.evidencePackage?.evidence?.dom) afterIds.push('DOM#' + (finding.interaction?.sequenceId || finding.id || '?'));
222
+ if (finding.evidencePackage?.evidence?.network) afterIds.push('NET#' + (finding.interaction?.sequenceId || finding.id || '?'));
223
+ if (finding.evidencePackage?.evidence?.console) afterIds.push('LOG#' + (finding.interaction?.sequenceId || finding.id || '?'));
224
+ const afterId = afterIds.length > 0
225
+ ? afterIds.join(', ')
226
+ : finding.id ? `REF#${finding.id}`
227
+ : 'UI#?';
228
+ lines.push(` Evidence (after): ${afterId}`);
229
+
230
+ // Why this matters - Neutral impact statement
231
+ const impact = finding.signals?.impact || finding.impact || 'UNKNOWN';
232
+ const userRisk = finding.signals?.userRisk || 'affects user workflow';
233
+ lines.push(` Why this matters: ${impact} impact, ${userRisk}`);
234
+
235
+ return lines.join('\n');
236
+ }
237
+
238
+ /**
239
+ * Format coverage transparency block
240
+ */
241
+ function formatCoverage(summary) {
242
+ const lines = [];
243
+ lines.push('\n' + '='.repeat(80));
244
+ lines.push('COVERAGE');
245
+ lines.push('='.repeat(80));
246
+
247
+ // Tested interactions (confirmed + suspected findings)
248
+ const testedCount = summary.whatWeKnow.confident.findings + summary.whatWeKnow.notConfident.findings;
249
+ lines.push(`\nTested interactions: ${testedCount}`);
250
+ if (testedCount > 0) {
251
+ lines.push(` ${summary.whatWeKnow.confident.findings} with complete evidence`);
252
+ lines.push(` ${summary.whatWeKnow.notConfident.findings} with incomplete evidence`);
253
+ }
254
+
255
+ // Skipped interactions (aggregated by canonical enum)
256
+ if (summary.whatWeKnow.skips && summary.whatWeKnow.skips.canonicalReasons) {
257
+ lines.push(`\nSkipped interactions: ${summary.whatWeKnow.skips.total}`);
258
+ for (const skip of summary.whatWeKnow.skips.canonicalReasons) {
259
+ lines.push(` ${skip.canonical}: ${skip.count}`);
260
+ }
261
+ } else if (summary.whatWeKnow.skips) {
262
+ // Backward compatibility: use raw reasons if canonical not available
263
+ lines.push(`\nSkipped interactions: ${summary.whatWeKnow.skips.total}`);
264
+ for (const skip of summary.whatWeKnow.skips.reasons) {
265
+ lines.push(` ${skip.code || skip.reason}: ${skip.count}`);
266
+ }
267
+ } else {
268
+ lines.push(`\nSkipped interactions: 0`);
269
+ }
270
+
271
+ // Coverage disclaimer
272
+ lines.push('');
273
+ lines.push('Coverage indicates what was observed in this run; it does not guarantee absence of issues.');
274
+ lines.push('='.repeat(80));
275
+
276
+ return lines.join('\n');
277
+ }
278
+
181
279
  /**
182
280
  * Format human summary for CLI display
183
281
  *
@@ -220,3 +318,45 @@ export function formatHumanSummary(summary) {
220
318
  return lines.join('\n');
221
319
  }
222
320
 
321
+ /**
322
+ * Format detailed findings report with Human-Readable Report Contract v1
323
+ * Adds six-line finding summaries + coverage transparency
324
+ *
325
+ * @param {string} projectDir - Project directory
326
+ * @param {string} runId - Run ID
327
+ * @returns {Promise<string>} Formatted report
328
+ */
329
+ export async function formatFindingsReport(projectDir, runId) {
330
+ const runDir = resolve(projectDir, '.verax', 'runs', runId);
331
+ const findings = loadArtifact(runDir, 'findings.json');
332
+ const summary = await generateHumanSummary(projectDir, runId);
333
+
334
+ if (!findings || !summary) {
335
+ return 'Findings report not available';
336
+ }
337
+
338
+ const lines = [];
339
+ lines.push('\n' + '='.repeat(80));
340
+ lines.push('FINDINGS REPORT (Human-Readable Contract v1)');
341
+ lines.push('='.repeat(80));
342
+
343
+ const findingsArray = findings.findings || [];
344
+
345
+ if (findingsArray.length === 0) {
346
+ lines.push('\nNo findings detected');
347
+ } else {
348
+ lines.push(`\nTotal findings: ${findingsArray.length}`);
349
+
350
+ // Format each finding with 6-line template
351
+ for (let i = 0; i < findingsArray.length; i++) {
352
+ lines.push(formatFinding(findingsArray[i], i));
353
+ }
354
+ }
355
+
356
+ // Add coverage transparency block
357
+ lines.push(formatCoverage(summary));
358
+ lines.push('='.repeat(80) + '\n');
359
+
360
+ return lines.join('\n');
361
+ }
362
+
@@ -8,7 +8,7 @@
8
8
  * - Provides evidence for route-related findings
9
9
  */
10
10
 
11
- import { normalizeDynamicRoute, isDynamicPath, normalizeNavigationTarget } from '../shared/dynamic-route-utils.js';
11
+ import { normalizeDynamicRoute as _normalizeDynamicRoute, isDynamicPath, normalizeNavigationTarget } from '../shared/dynamic-route-utils.js';
12
12
 
13
13
  /**
14
14
  * Route Taxonomy
@@ -392,10 +392,11 @@ export function buildRouteEvidence(correlation, navigationPromise, evaluation, t
392
392
  * PHASE 12: Check if route change is false positive
393
393
  *
394
394
  * @param {Object} trace - Interaction trace
395
- * @param {Object} correlation - Route correlation
395
+ * @param {Object} _correlation - Route correlation
396
+ * @ts-expect-error - JSDoc param documented but unused
396
397
  * @returns {boolean} True if should be ignored (false positive)
397
398
  */
398
- export function isRouteChangeFalsePositive(trace, correlation) {
399
+ export function isRouteChangeFalsePositive(trace, _correlation) {
399
400
  const sensors = trace.sensors || {};
400
401
  const navSensor = sensors.navigation || {};
401
402
 
@@ -20,6 +20,7 @@ const __dirname = dirname(__filename);
20
20
  export function getVeraxVersion() {
21
21
  try {
22
22
  const packagePath = join(__dirname, '..', '..', '..', 'package.json');
23
+ // @ts-expect-error - readFileSync with encoding returns string
23
24
  const pkg = JSON.parse(readFileSync(packagePath, 'utf-8'));
24
25
  return pkg.version || '0.1.0';
25
26
  } catch (error) {
@@ -35,7 +36,7 @@ export function getVeraxVersion() {
35
36
  *
36
37
  * @param {Object} params - Run parameters
37
38
  * @param {string} params.url - Target URL
38
- * @param {Object} params.safetyFlags - Safety flags (allowWrites, allowRiskyActions, allowCrossOrigin)
39
+ * @param {Object} params.safetyFlags - Safety flags (allowRiskyActions, allowCrossOrigin)
39
40
  * @param {string} params.baseOrigin - Base origin
40
41
  * @param {Object} params.scanBudget - Scan budget configuration
41
42
  * @param {string} params.manifestPath - Optional manifest path
@@ -44,11 +45,11 @@ export function getVeraxVersion() {
44
45
  export function generateRunId(params) {
45
46
  const { url, safetyFlags = {}, baseOrigin, scanBudget, manifestPath = null } = params;
46
47
 
47
- // Sort flags deterministically
48
+ // Sort flags deterministically (allowWrites permanently false - constitutional)
48
49
  const sortedFlags = {
49
50
  allowCrossOrigin: safetyFlags.allowCrossOrigin || false,
50
51
  allowRiskyActions: safetyFlags.allowRiskyActions || false,
51
- allowWrites: safetyFlags.allowWrites || false
52
+ allowWrites: false // CONSTITUTIONAL: Always false (read-only mode)
52
53
  };
53
54
 
54
55
  // Create deterministic representation
@@ -81,6 +82,7 @@ export function generateRunId(params) {
81
82
  const hash = createHash('sha256').update(configString).digest('hex');
82
83
 
83
84
  // Return first 16 chars for readability (still collision-resistant)
85
+ // @ts-expect-error - digest returns string
84
86
  return hash.substring(0, 16);
85
87
  }
86
88
 
@@ -146,6 +148,7 @@ export function getExpectedArtifacts(projectDir, runId) {
146
148
  export function computeFileHash(filePath) {
147
149
  try {
148
150
  const content = readFileSync(filePath);
151
+ // @ts-expect-error - digest returns string
149
152
  return createHash('sha256').update(content).digest('hex');
150
153
  } catch (error) {
151
154
  return null;
@@ -34,13 +34,13 @@ export function createRunManifest(projectDir, runId, params) {
34
34
  url,
35
35
  baseOrigin,
36
36
  flags: {
37
- allowWrites: safetyFlags.allowWrites || false,
37
+ allowWrites: false, // CONSTITUTIONAL: Always false (read-only mode)
38
38
  allowRiskyActions: safetyFlags.allowRiskyActions || false,
39
39
  allowCrossOrigin: safetyFlags.allowCrossOrigin || false
40
40
  },
41
41
  safeMode: {
42
- enabled: !safetyFlags.allowWrites || !safetyFlags.allowRiskyActions || !safetyFlags.allowCrossOrigin,
43
- writesBlocked: !safetyFlags.allowWrites,
42
+ enabled: true, // Always enabled (constitutional enforcement)
43
+ writesBlocked: true, // CONSTITUTIONAL: Always blocked
44
44
  riskyActionsBlocked: !safetyFlags.allowRiskyActions,
45
45
  crossOriginBlocked: !safetyFlags.allowCrossOrigin
46
46
  },
@@ -89,6 +89,7 @@ export function updateRunManifestHashes(projectDir, runId, hashes) {
89
89
  const runManifestPath = `${runDir}/run-manifest.json`;
90
90
 
91
91
  try {
92
+ // @ts-expect-error - readFileSync with encoding returns string
92
93
  const runManifest = JSON.parse(readFileSync(runManifestPath, 'utf-8'));
93
94
  runManifest.artifactHashes = hashes;
94
95
  runManifest.endTime = new Date().toISOString();
@@ -15,13 +15,13 @@ import { createHash } from 'crypto';
15
15
  */
16
16
  const SECRET_PATTERNS = [
17
17
  // API Keys
18
- { pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi, type: 'API_KEY', severity: 'CRITICAL' },
19
- { pattern: /(?:api[_-]?token|api_token)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi, type: 'API_TOKEN', severity: 'CRITICAL' },
18
+ { pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"]?([a-zA-Z0-9_-]{20,})['"]?/gi, type: 'API_KEY', severity: 'CRITICAL' },
19
+ { pattern: /(?:api[_-]?token|api_token)\s*[:=]\s*['"]?([a-zA-Z0-9_-]{20,})['"]?/gi, type: 'API_TOKEN', severity: 'CRITICAL' },
20
20
 
21
21
  // Tokens
22
- { pattern: /(?:bearer|token)\s+([a-zA-Z0-9_\-\.]{20,})/gi, type: 'BEARER_TOKEN', severity: 'CRITICAL' },
23
- { pattern: /(?:access[_-]?token|access_token)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi, type: 'ACCESS_TOKEN', severity: 'CRITICAL' },
24
- { pattern: /(?:refresh[_-]?token|refresh_token)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi, type: 'REFRESH_TOKEN', severity: 'HIGH' },
22
+ { pattern: /(?:bearer|token)\s+([a-zA-Z0-9._-]{20,})/gi, type: 'BEARER_TOKEN', severity: 'CRITICAL' },
23
+ { pattern: /(?:access[_-]?token|access_token)\s*[:=]\s*['"]?([a-zA-Z0-9_-]{20,})['"]?/gi, type: 'ACCESS_TOKEN', severity: 'CRITICAL' },
24
+ { pattern: /(?:refresh[_-]?token|refresh_token)\s*[:=]\s*['"]?([a-zA-Z0-9_-]{20,})['"]?/gi, type: 'REFRESH_TOKEN', severity: 'HIGH' },
25
25
 
26
26
  // Private Keys
27
27
  { pattern: /-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----/gi, type: 'PRIVATE_KEY', severity: 'CRITICAL' },
@@ -34,10 +34,10 @@ const SECRET_PATTERNS = [
34
34
 
35
35
  // GitHub
36
36
  { pattern: /ghp_[a-zA-Z0-9]{36}/gi, type: 'GITHUB_TOKEN', severity: 'CRITICAL' },
37
- { pattern: /github[_-]?token\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi, type: 'GITHUB_TOKEN', severity: 'CRITICAL' },
37
+ { pattern: /github[_-]?token\s*[:=]\s*['"]?([a-zA-Z0-9_-]{20,})['"]?/gi, type: 'GITHUB_TOKEN', severity: 'CRITICAL' },
38
38
 
39
39
  // Generic secrets
40
- { pattern: /(?:secret|password|pwd|passwd)\s*[:=]\s*['"]?([a-zA-Z0-9_\-\.@]{12,})['"]?/gi, type: 'SECRET', severity: 'HIGH' },
40
+ { pattern: /(?:secret|password|pwd|passwd)\s*[:=]\s*['"]?([a-zA-Z0-9._@-]{12,})['"]?/gi, type: 'SECRET', severity: 'HIGH' },
41
41
 
42
42
  // .env files
43
43
  { pattern: /\.env/, type: 'ENV_FILE', severity: 'HIGH', fileOnly: true }
@@ -119,6 +119,7 @@ function scanFileContent(content, filePath, projectDir) {
119
119
  file: relPath,
120
120
  line: content.substring(0, match.index).split('\n').length,
121
121
  match: match[0].substring(0, 50), // Truncate for safety
122
+ // @ts-expect-error - digest returns string
122
123
  hash: createHash('sha256').update(match[0]).digest('hex').substring(0, 16)
123
124
  });
124
125
  }
@@ -196,6 +197,7 @@ function scanGitHistory(projectDir) {
196
197
 
197
198
  const fileFindings = scanFileContent(content, resolve(projectDir, file), projectDir);
198
199
  for (const finding of fileFindings) {
200
+ // @ts-expect-error - Dynamic finding property
199
201
  finding.source = 'git_history';
200
202
  }
201
203
  findings.push(...fileFindings);
@@ -235,6 +237,7 @@ function scanArtifacts(projectDir) {
235
237
  const content = readFileSync(fullPath, 'utf-8');
236
238
  const fileFindings = scanFileContent(content, fullPath, projectDir);
237
239
  for (const finding of fileFindings) {
240
+ // @ts-expect-error - Dynamic finding property
238
241
  finding.source = 'artifacts';
239
242
  }
240
243
  findings.push(...fileFindings);
@@ -27,6 +27,7 @@ export function checkSecurityStatus(projectDir) {
27
27
 
28
28
  if (existsSync(unifiedPath)) {
29
29
  try {
30
+ // @ts-expect-error - readFileSync with encoding returns string
30
31
  const unifiedReport = JSON.parse(readFileSync(unifiedPath, 'utf-8'));
31
32
  status.exists = true;
32
33
  status.ok = unifiedReport.securityOk || false;
@@ -63,18 +64,21 @@ export function checkSecurityStatus(projectDir) {
63
64
 
64
65
  try {
65
66
  // Check secrets
67
+ // @ts-expect-error - readFileSync with encoding returns string
66
68
  const secretsReport = JSON.parse(readFileSync(secretsPath, 'utf-8'));
67
69
  if (secretsReport.hasSecrets) {
68
70
  status.blockers.push(`Secrets detected: ${secretsReport.summary.total} finding(s)`);
69
71
  }
70
72
 
71
73
  // Check vulnerabilities
74
+ // @ts-expect-error - readFileSync with encoding returns string
72
75
  const vulnReport = JSON.parse(readFileSync(vulnPath, 'utf-8'));
73
76
  if (vulnReport.blocking) {
74
77
  status.blockers.push(`Critical/High vulnerabilities: ${vulnReport.summary.critical + vulnReport.summary.high} total`);
75
78
  }
76
79
 
77
80
  // Check supply-chain
81
+ // @ts-expect-error - readFileSync with encoding returns string
78
82
  const supplyChainReport = JSON.parse(readFileSync(supplyChainPath, 'utf-8'));
79
83
  if (!supplyChainReport.ok) {
80
84
  status.blockers.push(`Supply-chain violations: ${supplyChainReport.summary.totalViolations} violation(s)`);
@@ -9,7 +9,10 @@
9
9
 
10
10
  import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
11
11
  import { resolve } from 'path';
12
- import DEFAULT_POLICY from './supplychain.defaults.json' with { type: 'json' };
12
+
13
+ const DEFAULT_POLICY = JSON.parse(
14
+ readFileSync(new URL('./supplychain.defaults.json', import.meta.url), 'utf-8')
15
+ );
13
16
 
14
17
  /**
15
18
  * Load supply-chain policy
@@ -22,6 +25,7 @@ export function loadSupplyChainPolicy(projectDir) {
22
25
 
23
26
  if (existsSync(customPath)) {
24
27
  try {
28
+ // @ts-expect-error - readFileSync with encoding returns string
25
29
  const custom = JSON.parse(readFileSync(customPath, 'utf-8'));
26
30
  // Merge with defaults (custom overrides)
27
31
  return {
@@ -64,6 +68,7 @@ function getDependencies(projectDir) {
64
68
  if (!existsSync(pkgPath)) {
65
69
  return { dependencies: {}, devDependencies: {} };
66
70
  }
71
+ // @ts-expect-error - readFileSync with encoding returns string
67
72
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
68
73
  return {
69
74
  dependencies: pkg.dependencies || {},
@@ -87,6 +92,7 @@ function getPackageLicense(projectDir, packageName) {
87
92
  if (!existsSync(pkgPath)) {
88
93
  return null;
89
94
  }
95
+ // @ts-expect-error - readFileSync with encoding returns string
90
96
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
91
97
 
92
98
  if (typeof pkg.license === 'string') {
@@ -118,6 +124,7 @@ function checkIntegrityHashes(projectDir) {
118
124
  }
119
125
 
120
126
  try {
127
+ // @ts-expect-error - readFileSync with encoding returns string
121
128
  const lock = JSON.parse(readFileSync(lockPath, 'utf-8'));
122
129
  const packages = lock.packages || {};
123
130
  let total = 0;
@@ -164,6 +171,7 @@ function checkPostinstallScripts(projectDir) {
164
171
  const pkgPath = resolve(nodeModulesPath, entry.name, 'package.json');
165
172
  if (existsSync(pkgPath)) {
166
173
  try {
174
+ // @ts-expect-error - readFileSync with encoding returns string
167
175
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
168
176
  if (pkg.scripts) {
169
177
  if (pkg.scripts.postinstall || pkg.scripts.preinstall || pkg.scripts.install) {
@@ -5,7 +5,7 @@
5
5
  * HIGH/CRITICAL = BLOCKING, MEDIUM = WARNING (configurable).
6
6
  */
7
7
 
8
- import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
8
+ import { readFileSync as _readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
9
9
  import { resolve } from 'path';
10
10
  import { execSync } from 'child_process';
11
11
 
@@ -110,7 +110,7 @@ function parseVulnerabilities(auditResults) {
110
110
  * @param {boolean} options.requireOSV - Require OSV scanner (default: false)
111
111
  * @returns {Promise<Object>} Scan results
112
112
  */
113
- export async function scanVulnerabilities(projectDir, options = {}) {
113
+ export async function scanVulnerabilities(projectDir, options = { blockMedium: false, requireOSV: false }) {
114
114
  const { blockMedium = false, requireOSV = false } = options;
115
115
 
116
116
  // Check OSV availability
@@ -18,6 +18,7 @@ function loadArtifact(runDir, filename) {
18
18
  return null;
19
19
  }
20
20
  try {
21
+ // @ts-expect-error - readFileSync with encoding returns string
21
22
  return JSON.parse(readFileSync(path, 'utf-8'));
22
23
  } catch {
23
24
  return null;
@@ -29,7 +30,7 @@ function loadArtifact(runDir, filename) {
29
30
  *
30
31
  * @param {string} projectDir - Project directory
31
32
  * @param {string} runId - Run ID
32
- * @returns {Object} Truth certificate
33
+ * @returns {Promise<Object>} Truth certificate
33
34
  */
34
35
  export async function generateTruthCertificate(projectDir, runId) {
35
36
  const runDir = resolve(projectDir, '.verax', 'runs', runId);
@@ -242,6 +243,7 @@ export function loadTruthCertificate(projectDir, runId) {
242
243
  }
243
244
 
244
245
  try {
246
+ // @ts-expect-error - readFileSync with encoding returns string
245
247
  return JSON.parse(readFileSync(certPath, 'utf-8'));
246
248
  } catch {
247
249
  return null;