@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
@@ -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
@@ -0,0 +1,195 @@
1
+ /**
2
+ * PHASE 21.10 — Artifact Cross-Index
3
+ *
4
+ * Builds cross-index linking findingId to all related artifacts.
5
+ */
6
+
7
+ import { readFileSync, existsSync, writeFileSync, readdirSync as _readdirSync } from 'fs';
8
+ import { resolve, relative as _relative } from 'path';
9
+
10
+ /**
11
+ * Build cross-index
12
+ *
13
+ * @param {string} projectDir - Project directory
14
+ * @param {string} runId - Run ID
15
+ * @returns {Object} Cross-index
16
+ */
17
+ export function buildCrossIndex(projectDir, runId) {
18
+ const runDir = resolve(projectDir, '.verax', 'runs', runId);
19
+
20
+ if (!existsSync(runDir)) {
21
+ return null;
22
+ }
23
+
24
+ const index = {};
25
+
26
+ // Load findings
27
+ const findingsPath = resolve(runDir, 'findings.json');
28
+ if (!existsSync(findingsPath)) {
29
+ return {
30
+ runId,
31
+ findings: {},
32
+ summary: { total: 0 },
33
+ generatedAt: new Date().toISOString()
34
+ };
35
+ }
36
+
37
+ // @ts-expect-error - readFileSync with encoding returns string
38
+ const findings = JSON.parse(readFileSync(findingsPath, 'utf-8'));
39
+ const _evidenceIndex = loadArtifact(runDir, 'evidence.index.json');
40
+ const decisionTrace = loadArtifact(runDir, 'decisions.trace.json');
41
+ const timeline = loadArtifact(runDir, 'run.timeline.json');
42
+ const failureLedger = loadArtifact(runDir, 'failure.ledger.json');
43
+ const performanceReport = loadArtifact(runDir, 'performance.report.json');
44
+
45
+ if (!Array.isArray(findings.findings)) {
46
+ return {
47
+ runId,
48
+ findings: {},
49
+ summary: { total: 0 },
50
+ generatedAt: new Date().toISOString()
51
+ };
52
+ }
53
+
54
+ for (const finding of findings.findings) {
55
+ const findingId = finding.findingId || finding.id || `finding-${Object.keys(index).length}`;
56
+
57
+ const entry = {
58
+ findingId,
59
+ type: finding.type || null,
60
+ status: finding.severity || finding.status || null,
61
+
62
+ // Evidence files
63
+ evidence: {
64
+ packageId: finding.evidencePackage?.id || null,
65
+ files: finding.evidencePackage?.files || [],
66
+ isComplete: finding.evidencePackage?.isComplete || false,
67
+ beforeScreenshot: finding.evidence?.before || null,
68
+ afterScreenshot: finding.evidence?.after || null
69
+ },
70
+
71
+ // Confidence reasons
72
+ confidence: {
73
+ level: finding.confidenceLevel || null,
74
+ score: finding.confidence !== undefined ? finding.confidence : null,
75
+ reasons: finding.confidenceReasons || [],
76
+ trace: decisionTrace?.findings?.find(t => t.findingId === findingId)?.confidence || null
77
+ },
78
+
79
+ // Guardrails rules
80
+ guardrails: {
81
+ applied: finding.guardrails?.appliedRules?.map(r => ({
82
+ id: r.id || r,
83
+ category: r.category || null,
84
+ action: r.action || null
85
+ })) || [],
86
+ finalDecision: finding.guardrails?.finalDecision || null,
87
+ contradictions: finding.guardrails?.contradictions || [],
88
+ trace: decisionTrace?.findings?.find(t => t.findingId === findingId)?.guardrails || null
89
+ },
90
+
91
+ // Failures (if any related)
92
+ failures: failureLedger?.failures?.filter(f =>
93
+ f.context?.findingId === findingId ||
94
+ f.message?.includes(findingId)
95
+ ).map(f => ({
96
+ code: f.code,
97
+ message: f.message,
98
+ severity: f.severity,
99
+ timestamp: f.timestamp
100
+ })) || [],
101
+
102
+ // Performance impacts (if any)
103
+ performance: performanceReport?.violations?.some(v =>
104
+ v.message?.includes(findingId)
105
+ ) ? {
106
+ impacted: true,
107
+ violations: performanceReport.violations.filter(v =>
108
+ v.message?.includes(findingId)
109
+ )
110
+ } : null,
111
+
112
+ // Timeline entries
113
+ timeline: timeline?.events?.filter(e =>
114
+ e.data?.findingId === findingId ||
115
+ (e.event === 'guardrails_applied' && e.data?.findingId === findingId) ||
116
+ (e.event === 'evidence_enforced' && e.data?.findingId === findingId)
117
+ ).map(e => ({
118
+ timestamp: e.timestamp,
119
+ phase: e.phase,
120
+ event: e.event,
121
+ data: e.data
122
+ })) || []
123
+ };
124
+
125
+ index[findingId] = entry;
126
+ }
127
+
128
+ return {
129
+ runId,
130
+ findings: index,
131
+ summary: {
132
+ total: Object.keys(index).length,
133
+ withEvidence: Object.values(index).filter(e => e.evidence.files.length > 0).length,
134
+ withGuardrails: Object.values(index).filter(e => e.guardrails.applied.length > 0).length,
135
+ withFailures: Object.values(index).filter(e => e.failures.length > 0).length,
136
+ withTimeline: Object.values(index).filter(e => e.timeline.length > 0).length
137
+ },
138
+ generatedAt: new Date().toISOString()
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Load artifact JSON
144
+ */
145
+ function loadArtifact(runDir, filename) {
146
+ const path = resolve(runDir, filename);
147
+ if (!existsSync(path)) {
148
+ return null;
149
+ }
150
+ try {
151
+ // @ts-expect-error - readFileSync with encoding returns string
152
+ return JSON.parse(readFileSync(path, 'utf-8'));
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Write cross-index to file
160
+ *
161
+ * @param {string} projectDir - Project directory
162
+ * @param {string} runId - Run ID
163
+ * @param {Object} index - Cross-index
164
+ * @returns {string} Path to written file
165
+ */
166
+ export function writeCrossIndex(projectDir, runId, index) {
167
+ const runDir = resolve(projectDir, '.verax', 'runs', runId);
168
+ const outputPath = resolve(runDir, 'artifacts.index.json');
169
+ writeFileSync(outputPath, JSON.stringify(index, null, 2), 'utf-8');
170
+ return outputPath;
171
+ }
172
+
173
+ /**
174
+ * Load cross-index from file
175
+ *
176
+ * @param {string} projectDir - Project directory
177
+ * @param {string} runId - Run ID
178
+ * @returns {Object|null} Cross-index or null
179
+ */
180
+ export function loadCrossIndex(projectDir, runId) {
181
+ const runDir = resolve(projectDir, '.verax', 'runs', runId);
182
+ const indexPath = resolve(runDir, 'artifacts.index.json');
183
+
184
+ if (!existsSync(indexPath)) {
185
+ return null;
186
+ }
187
+
188
+ try {
189
+ // @ts-expect-error - readFileSync with encoding returns string
190
+ return JSON.parse(readFileSync(indexPath, 'utf-8'));
191
+ } catch {
192
+ return null;
193
+ }
194
+ }
195
+
@@ -0,0 +1,362 @@
1
+ /**
2
+ * PHASE 21.10 — Human Summary
3
+ *
4
+ * Generates human-readable summary for Enterprise UX.
5
+ * Clear, direct, no marketing.
6
+ */
7
+
8
+ import { readFileSync, existsSync } from 'fs';
9
+ import { resolve } from 'path';
10
+
11
+ /**
12
+ * Load artifact JSON
13
+ */
14
+ function loadArtifact(runDir, filename) {
15
+ const path = resolve(runDir, filename);
16
+ if (!existsSync(path)) {
17
+ return null;
18
+ }
19
+ try {
20
+ // @ts-expect-error - readFileSync with encoding returns string
21
+ return JSON.parse(readFileSync(path, 'utf-8'));
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Generate human summary
29
+ *
30
+ * @param {string} projectDir - Project directory
31
+ * @param {string} runId - Run ID
32
+ * @returns {Promise<Object>} Human summary
33
+ */
34
+ export async function generateHumanSummary(projectDir, runId) {
35
+ const runDir = resolve(projectDir, '.verax', 'runs', runId);
36
+
37
+ if (!existsSync(runDir)) {
38
+ return null;
39
+ }
40
+
41
+ const summary = loadArtifact(runDir, 'summary.json');
42
+ const findings = loadArtifact(runDir, 'findings.json');
43
+ const determinism = loadArtifact(runDir, 'decisions.json');
44
+ const performanceReport = loadArtifact(runDir, 'performance.report.json');
45
+
46
+ // Security reports are in release/ directory (project root)
47
+ // Use projectDir parameter directly (already resolved)
48
+ const releaseDir = resolve(projectDir, 'release');
49
+ const securitySecrets = loadArtifact(releaseDir, 'security.secrets.report.json');
50
+ const securityVuln = loadArtifact(releaseDir, 'security.vuln.report.json');
51
+
52
+ const gaStatus = loadArtifact(runDir, 'ga.status.json');
53
+
54
+ if (!summary) {
55
+ return null;
56
+ }
57
+
58
+ const findingsArray = Array.isArray(findings?.findings) ? findings.findings : [];
59
+ const confirmedFindings = findingsArray.filter(f => (f.severity || f.status) === 'CONFIRMED');
60
+ const suspectedFindings = findingsArray.filter(f => (f.severity || f.status) === 'SUSPECTED');
61
+
62
+ // What VERAX is confident about
63
+ const confident = {
64
+ findings: confirmedFindings.length,
65
+ message: confirmedFindings.length > 0
66
+ ? `${confirmedFindings.length} finding(s) with complete evidence`
67
+ : 'No findings with complete evidence',
68
+ details: confirmedFindings.map(f => ({
69
+ type: f.type,
70
+ outcome: f.outcome,
71
+ confidence: f.confidenceLevel || 'UNKNOWN'
72
+ }))
73
+ };
74
+
75
+ // What VERAX is NOT confident about
76
+ const notConfident = {
77
+ findings: suspectedFindings.length,
78
+ message: suspectedFindings.length > 0
79
+ ? `${suspectedFindings.length} finding(s) with incomplete evidence (SUSPECTED)`
80
+ : 'No findings with incomplete evidence',
81
+ details: suspectedFindings.map(f => ({
82
+ type: f.type,
83
+ outcome: f.outcome,
84
+ confidence: f.confidenceLevel || 'UNKNOWN',
85
+ missingEvidence: f.evidencePackage?.isComplete === false
86
+ }))
87
+ };
88
+
89
+ // Why some things were skipped
90
+ const skips = [];
91
+ if (summary.truth?.observe?.skips) {
92
+ for (const skip of summary.truth.observe.skips) {
93
+ skips.push({
94
+ reason: skip.reason || skip.code || 'UNKNOWN',
95
+ count: skip.count || 1,
96
+ message: skip.message || `Skipped: ${skip.reason || skip.code}`
97
+ });
98
+ }
99
+ }
100
+
101
+ // Determinism verdict
102
+ let determinismVerdict = 'UNKNOWN';
103
+ if (determinism) {
104
+ try {
105
+ // @ts-expect-error - Dynamic import path
106
+ const { DecisionRecorder } = await import('../../../core/determinism-model.js');
107
+ const recorder = DecisionRecorder.fromExport(determinism);
108
+ // @ts-expect-error - Dynamic import path
109
+ const { computeDeterminismVerdict } = await import('../../../core/determinism/contract.js');
110
+ const verdict = computeDeterminismVerdict(recorder);
111
+ determinismVerdict = verdict.verdict;
112
+ } catch {
113
+ determinismVerdict = summary.determinism?.verdict || 'UNKNOWN';
114
+ }
115
+ } else if (summary.determinism) {
116
+ determinismVerdict = summary.determinism.verdict || 'UNKNOWN';
117
+ }
118
+
119
+ // Performance verdict
120
+ const performanceVerdict = performanceReport?.verdict || 'UNKNOWN';
121
+ const performanceOk = performanceReport?.ok !== false;
122
+
123
+ // Security verdict
124
+ const securityOk = !securitySecrets?.hasSecrets &&
125
+ !securityVuln?.blocking &&
126
+ (securitySecrets !== null || securityVuln !== null); // At least one report exists
127
+
128
+ // GA verdict
129
+ const gaReady = gaStatus?.gaReady === true;
130
+ const gaVerdict = gaReady ? 'GA-READY' : (gaStatus ? 'GA-BLOCKED' : 'UNKNOWN');
131
+
132
+ return {
133
+ runId,
134
+ whatWeKnow: {
135
+ confident: confident,
136
+ notConfident: notConfident,
137
+ skips: skips.length > 0 ? {
138
+ total: skips.reduce((sum, s) => sum + s.count, 0),
139
+ reasons: skips
140
+ } : null
141
+ },
142
+ verdicts: {
143
+ determinism: {
144
+ verdict: determinismVerdict,
145
+ message: determinismVerdict === 'DETERMINISTIC'
146
+ ? 'Run was reproducible (same inputs = same outputs)'
147
+ : determinismVerdict === 'NON_DETERMINISTIC'
148
+ ? 'Run was not reproducible (adaptive events detected)'
149
+ : 'Determinism not evaluated'
150
+ },
151
+ performance: {
152
+ verdict: performanceVerdict,
153
+ ok: performanceOk,
154
+ message: performanceOk
155
+ ? 'Performance within budget'
156
+ : performanceReport?.violations?.length > 0
157
+ ? `${performanceReport.violations.length} BLOCKING performance violation(s)`
158
+ : 'Performance not evaluated'
159
+ },
160
+ security: {
161
+ ok: securityOk,
162
+ message: securityOk
163
+ ? 'Security baseline passed'
164
+ : securitySecrets?.hasSecrets
165
+ ? 'Secrets detected'
166
+ : securityVuln?.blocking
167
+ ? 'Critical vulnerabilities detected'
168
+ : 'Security not evaluated'
169
+ },
170
+ ga: {
171
+ verdict: gaVerdict,
172
+ ready: gaReady,
173
+ message: gaReady
174
+ ? 'GA-READY: All gates passed'
175
+ : gaStatus
176
+ ? `GA-BLOCKED: ${gaStatus.blockers?.length || 0} blocker(s)`
177
+ : 'GA not evaluated'
178
+ }
179
+ },
180
+ generatedAt: new Date().toISOString()
181
+ };
182
+ }
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
+
279
+ /**
280
+ * Format human summary for CLI display
281
+ *
282
+ * @param {Object} summary - Human summary
283
+ * @returns {string} Formatted string
284
+ */
285
+ export function formatHumanSummary(summary) {
286
+ if (!summary) {
287
+ return 'Summary: Not available';
288
+ }
289
+
290
+ const lines = [];
291
+ lines.push('\n' + '='.repeat(80));
292
+ lines.push('HUMAN SUMMARY');
293
+ lines.push('='.repeat(80));
294
+
295
+ // What we know
296
+ lines.push('\nWhat VERAX is confident about:');
297
+ lines.push(` ${summary.whatWeKnow.confident.message}`);
298
+
299
+ lines.push('\nWhat VERAX is NOT confident about:');
300
+ lines.push(` ${summary.whatWeKnow.notConfident.message}`);
301
+
302
+ if (summary.whatWeKnow.skips) {
303
+ lines.push('\nWhy some things were skipped:');
304
+ for (const skip of summary.whatWeKnow.skips.reasons) {
305
+ lines.push(` - ${skip.message} (${skip.count}x)`);
306
+ }
307
+ }
308
+
309
+ // Verdicts
310
+ lines.push('\nVerdicts:');
311
+ lines.push(` Determinism: ${summary.verdicts.determinism.verdict} - ${summary.verdicts.determinism.message}`);
312
+ lines.push(` Performance: ${summary.verdicts.performance.verdict} - ${summary.verdicts.performance.message}`);
313
+ lines.push(` Security: ${summary.verdicts.security.ok ? 'OK' : 'BLOCKED'} - ${summary.verdicts.security.message}`);
314
+ lines.push(` GA: ${summary.verdicts.ga.verdict} - ${summary.verdicts.ga.message}`);
315
+
316
+ lines.push('='.repeat(80) + '\n');
317
+
318
+ return lines.join('\n');
319
+ }
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
+