@veraxhq/verax 0.2.1 → 0.3.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 (152) hide show
  1. package/README.md +14 -18
  2. package/bin/verax.js +7 -0
  3. package/package.json +3 -3
  4. package/src/cli/commands/baseline.js +104 -0
  5. package/src/cli/commands/default.js +79 -25
  6. package/src/cli/commands/ga.js +243 -0
  7. package/src/cli/commands/gates.js +95 -0
  8. package/src/cli/commands/inspect.js +131 -2
  9. package/src/cli/commands/release-check.js +213 -0
  10. package/src/cli/commands/run.js +246 -35
  11. package/src/cli/commands/security-check.js +211 -0
  12. package/src/cli/commands/truth.js +114 -0
  13. package/src/cli/entry.js +304 -67
  14. package/src/cli/util/angular-component-extractor.js +179 -0
  15. package/src/cli/util/angular-navigation-detector.js +141 -0
  16. package/src/cli/util/angular-network-detector.js +161 -0
  17. package/src/cli/util/angular-state-detector.js +162 -0
  18. package/src/cli/util/ast-interactive-detector.js +546 -0
  19. package/src/cli/util/ast-network-detector.js +603 -0
  20. package/src/cli/util/ast-usestate-detector.js +602 -0
  21. package/src/cli/util/bootstrap-guard.js +86 -0
  22. package/src/cli/util/determinism-runner.js +123 -0
  23. package/src/cli/util/determinism-writer.js +129 -0
  24. package/src/cli/util/env-url.js +4 -0
  25. package/src/cli/util/expectation-extractor.js +369 -73
  26. package/src/cli/util/findings-writer.js +126 -16
  27. package/src/cli/util/learn-writer.js +3 -1
  28. package/src/cli/util/observe-writer.js +3 -1
  29. package/src/cli/util/paths.js +3 -12
  30. package/src/cli/util/project-discovery.js +3 -0
  31. package/src/cli/util/project-writer.js +3 -1
  32. package/src/cli/util/run-resolver.js +64 -0
  33. package/src/cli/util/source-requirement.js +55 -0
  34. package/src/cli/util/summary-writer.js +1 -0
  35. package/src/cli/util/svelte-navigation-detector.js +163 -0
  36. package/src/cli/util/svelte-network-detector.js +80 -0
  37. package/src/cli/util/svelte-sfc-extractor.js +147 -0
  38. package/src/cli/util/svelte-state-detector.js +243 -0
  39. package/src/cli/util/vue-navigation-detector.js +177 -0
  40. package/src/cli/util/vue-sfc-extractor.js +162 -0
  41. package/src/cli/util/vue-state-detector.js +215 -0
  42. package/src/verax/cli/finding-explainer.js +56 -3
  43. package/src/verax/core/artifacts/registry.js +154 -0
  44. package/src/verax/core/artifacts/verifier.js +980 -0
  45. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  46. package/src/verax/core/baseline/baseline.snapshot.js +231 -0
  47. package/src/verax/core/capabilities/gates.js +499 -0
  48. package/src/verax/core/capabilities/registry.js +475 -0
  49. package/src/verax/core/confidence/confidence-compute.js +137 -0
  50. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  51. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  52. package/src/verax/core/confidence/confidence-weights.js +44 -0
  53. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  54. package/src/verax/core/confidence/confidence.loader.js +79 -0
  55. package/src/verax/core/confidence/confidence.schema.js +94 -0
  56. package/src/verax/core/confidence-engine-refactor.js +484 -0
  57. package/src/verax/core/confidence-engine.js +486 -0
  58. package/src/verax/core/confidence-engine.js.backup +471 -0
  59. package/src/verax/core/contracts/index.js +29 -0
  60. package/src/verax/core/contracts/types.js +185 -0
  61. package/src/verax/core/contracts/validators.js +381 -0
  62. package/src/verax/core/decision-snapshot.js +30 -3
  63. package/src/verax/core/decisions/decision.trace.js +276 -0
  64. package/src/verax/core/determinism/contract-writer.js +89 -0
  65. package/src/verax/core/determinism/contract.js +139 -0
  66. package/src/verax/core/determinism/diff.js +364 -0
  67. package/src/verax/core/determinism/engine.js +221 -0
  68. package/src/verax/core/determinism/finding-identity.js +148 -0
  69. package/src/verax/core/determinism/normalize.js +438 -0
  70. package/src/verax/core/determinism/report-writer.js +92 -0
  71. package/src/verax/core/determinism/run-fingerprint.js +118 -0
  72. package/src/verax/core/dynamic-route-intelligence.js +528 -0
  73. package/src/verax/core/evidence/evidence-capture-service.js +307 -0
  74. package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
  75. package/src/verax/core/evidence-builder.js +487 -0
  76. package/src/verax/core/execution-mode-context.js +77 -0
  77. package/src/verax/core/execution-mode-detector.js +190 -0
  78. package/src/verax/core/failures/exit-codes.js +86 -0
  79. package/src/verax/core/failures/failure-summary.js +76 -0
  80. package/src/verax/core/failures/failure.factory.js +225 -0
  81. package/src/verax/core/failures/failure.ledger.js +132 -0
  82. package/src/verax/core/failures/failure.types.js +196 -0
  83. package/src/verax/core/failures/index.js +10 -0
  84. package/src/verax/core/ga/ga-report-writer.js +43 -0
  85. package/src/verax/core/ga/ga.artifact.js +49 -0
  86. package/src/verax/core/ga/ga.contract.js +434 -0
  87. package/src/verax/core/ga/ga.enforcer.js +86 -0
  88. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  89. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  90. package/src/verax/core/guardrails/policy.loader.js +83 -0
  91. package/src/verax/core/guardrails/policy.schema.js +110 -0
  92. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  93. package/src/verax/core/guardrails-engine.js +505 -0
  94. package/src/verax/core/observe/run-timeline.js +316 -0
  95. package/src/verax/core/perf/perf.contract.js +186 -0
  96. package/src/verax/core/perf/perf.display.js +65 -0
  97. package/src/verax/core/perf/perf.enforcer.js +91 -0
  98. package/src/verax/core/perf/perf.monitor.js +209 -0
  99. package/src/verax/core/perf/perf.report.js +198 -0
  100. package/src/verax/core/pipeline-tracker.js +238 -0
  101. package/src/verax/core/product-definition.js +127 -0
  102. package/src/verax/core/release/provenance.builder.js +271 -0
  103. package/src/verax/core/release/release-report-writer.js +40 -0
  104. package/src/verax/core/release/release.enforcer.js +159 -0
  105. package/src/verax/core/release/reproducibility.check.js +221 -0
  106. package/src/verax/core/release/sbom.builder.js +283 -0
  107. package/src/verax/core/report/cross-index.js +192 -0
  108. package/src/verax/core/report/human-summary.js +222 -0
  109. package/src/verax/core/route-intelligence.js +419 -0
  110. package/src/verax/core/security/secrets.scan.js +326 -0
  111. package/src/verax/core/security/security-report.js +50 -0
  112. package/src/verax/core/security/security.enforcer.js +124 -0
  113. package/src/verax/core/security/supplychain.defaults.json +38 -0
  114. package/src/verax/core/security/supplychain.policy.js +326 -0
  115. package/src/verax/core/security/vuln.scan.js +265 -0
  116. package/src/verax/core/truth/truth.certificate.js +250 -0
  117. package/src/verax/core/ui-feedback-intelligence.js +515 -0
  118. package/src/verax/detect/confidence-engine.js +628 -40
  119. package/src/verax/detect/confidence-helper.js +33 -0
  120. package/src/verax/detect/detection-engine.js +18 -1
  121. package/src/verax/detect/dynamic-route-findings.js +335 -0
  122. package/src/verax/detect/expectation-chain-detector.js +417 -0
  123. package/src/verax/detect/expectation-model.js +3 -1
  124. package/src/verax/detect/findings-writer.js +141 -5
  125. package/src/verax/detect/index.js +229 -5
  126. package/src/verax/detect/journey-stall-detector.js +558 -0
  127. package/src/verax/detect/route-findings.js +218 -0
  128. package/src/verax/detect/ui-feedback-findings.js +207 -0
  129. package/src/verax/detect/verdict-engine.js +57 -3
  130. package/src/verax/detect/view-switch-correlator.js +242 -0
  131. package/src/verax/index.js +413 -45
  132. package/src/verax/learn/action-contract-extractor.js +682 -64
  133. package/src/verax/learn/route-validator.js +4 -1
  134. package/src/verax/observe/index.js +88 -843
  135. package/src/verax/observe/interaction-runner.js +25 -8
  136. package/src/verax/observe/observe-context.js +205 -0
  137. package/src/verax/observe/observe-helpers.js +191 -0
  138. package/src/verax/observe/observe-runner.js +226 -0
  139. package/src/verax/observe/observers/budget-observer.js +185 -0
  140. package/src/verax/observe/observers/console-observer.js +102 -0
  141. package/src/verax/observe/observers/coverage-observer.js +107 -0
  142. package/src/verax/observe/observers/interaction-observer.js +471 -0
  143. package/src/verax/observe/observers/navigation-observer.js +132 -0
  144. package/src/verax/observe/observers/network-observer.js +87 -0
  145. package/src/verax/observe/observers/safety-observer.js +82 -0
  146. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  147. package/src/verax/observe/ui-feedback-detector.js +742 -0
  148. package/src/verax/observe/ui-signal-sensor.js +148 -2
  149. package/src/verax/scan-summary-writer.js +42 -8
  150. package/src/verax/shared/artifact-manager.js +8 -5
  151. package/src/verax/shared/css-spinner-rules.js +204 -0
  152. package/src/verax/shared/view-switch-rules.js +208 -0
@@ -0,0 +1,221 @@
1
+ /**
2
+ * PHASE 21.7 — Reproducibility Check
3
+ *
4
+ * Verifies that same commit + same policies = same hashes.
5
+ * Difference = NON_REPRODUCIBLE (BLOCKING for GA).
6
+ */
7
+
8
+ import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
9
+ import { resolve } from 'path';
10
+ import { createHash } from 'crypto';
11
+ import { execSync } from 'child_process';
12
+
13
+ /**
14
+ * Get current git commit
15
+ *
16
+ * @param {string} projectDir - Project directory
17
+ * @returns {string|null} Commit hash or null
18
+ */
19
+ function getGitCommit(projectDir) {
20
+ try {
21
+ const result = execSync('git rev-parse HEAD', {
22
+ cwd: projectDir,
23
+ encoding: 'utf-8',
24
+ stdio: ['ignore', 'pipe', 'ignore']
25
+ });
26
+ return result.trim();
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Get policy hashes
34
+ *
35
+ * @param {string} projectDir - Project directory
36
+ * @returns {Promise<Object>} Policy hashes
37
+ */
38
+ async function getPolicyHashes(projectDir) {
39
+ try {
40
+ const { loadGuardrailsPolicy } = await import('../guardrails/policy.loader.js');
41
+ const { loadConfidencePolicy } = await import('../confidence/confidence.loader.js');
42
+
43
+ const guardrails = loadGuardrailsPolicy(null, projectDir);
44
+ const confidence = loadConfidencePolicy(null, projectDir);
45
+
46
+ const guardrailsHash = createHash('sha256')
47
+ .update(JSON.stringify(guardrails, null, 0))
48
+ .digest('hex');
49
+
50
+ const confidenceHash = createHash('sha256')
51
+ .update(JSON.stringify(confidence, null, 0))
52
+ .digest('hex');
53
+
54
+ return {
55
+ guardrails: guardrailsHash,
56
+ confidence: confidenceHash
57
+ };
58
+ } catch {
59
+ return {
60
+ guardrails: null,
61
+ confidence: null
62
+ };
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Get artifact hashes
68
+ *
69
+ * @param {string} projectDir - Project directory
70
+ * @returns {Object} Artifact hashes
71
+ */
72
+ function getArtifactHashes(projectDir) {
73
+ const hashes = {};
74
+
75
+ // Hash key files
76
+ const keyFiles = [
77
+ 'package.json',
78
+ 'bin/verax.js',
79
+ 'src/cli/entry.js'
80
+ ];
81
+
82
+ for (const file of keyFiles) {
83
+ const filePath = resolve(projectDir, file);
84
+ if (existsSync(filePath)) {
85
+ try {
86
+ const content = readFileSync(filePath);
87
+ hashes[file] = createHash('sha256').update(content).digest('hex');
88
+ } catch {
89
+ hashes[file] = null;
90
+ }
91
+ } else {
92
+ hashes[file] = null;
93
+ }
94
+ }
95
+
96
+ return hashes;
97
+ }
98
+
99
+ /**
100
+ * Load previous reproducibility report
101
+ *
102
+ * @param {string} projectDir - Project directory
103
+ * @returns {Object|null} Previous report or null
104
+ */
105
+ function loadPreviousReport(projectDir) {
106
+ const reportPath = resolve(projectDir, 'release', 'reproducibility.report.json');
107
+ if (!existsSync(reportPath)) {
108
+ return null;
109
+ }
110
+
111
+ try {
112
+ const content = readFileSync(reportPath, 'utf-8');
113
+ return JSON.parse(content);
114
+ } catch {
115
+ return null;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Check reproducibility
121
+ *
122
+ * @param {string} projectDir - Project directory
123
+ * @returns {Promise<Object>} Reproducibility check result
124
+ */
125
+ export async function checkReproducibility(projectDir) {
126
+ const gitCommit = getGitCommit(projectDir);
127
+ const policyHashes = await getPolicyHashes(projectDir);
128
+ const artifactHashes = getArtifactHashes(projectDir);
129
+
130
+ const current = {
131
+ gitCommit,
132
+ policies: policyHashes,
133
+ artifacts: artifactHashes,
134
+ checkedAt: new Date().toISOString()
135
+ };
136
+
137
+ const previous = loadPreviousReport(projectDir);
138
+
139
+ let reproducible = true;
140
+ const differences = [];
141
+
142
+ if (previous) {
143
+ // Compare with previous build
144
+ if (previous.gitCommit !== gitCommit) {
145
+ reproducible = false;
146
+ differences.push({
147
+ type: 'git_commit',
148
+ previous: previous.gitCommit,
149
+ current: gitCommit,
150
+ message: 'Git commit changed'
151
+ });
152
+ }
153
+
154
+ if (previous.policies?.guardrails !== policyHashes.guardrails) {
155
+ reproducible = false;
156
+ differences.push({
157
+ type: 'guardrails_policy',
158
+ previous: previous.policies?.guardrails,
159
+ current: policyHashes.guardrails,
160
+ message: 'Guardrails policy changed'
161
+ });
162
+ }
163
+
164
+ if (previous.policies?.confidence !== policyHashes.confidence) {
165
+ reproducible = false;
166
+ differences.push({
167
+ type: 'confidence_policy',
168
+ previous: previous.policies?.confidence,
169
+ current: policyHashes.confidence,
170
+ message: 'Confidence policy changed'
171
+ });
172
+ }
173
+
174
+ // Compare artifact hashes
175
+ for (const [file, hash] of Object.entries(artifactHashes)) {
176
+ if (previous.artifacts?.[file] !== hash) {
177
+ reproducible = false;
178
+ differences.push({
179
+ type: 'artifact',
180
+ file,
181
+ previous: previous.artifacts?.[file],
182
+ current: hash,
183
+ message: `Artifact ${file} changed`
184
+ });
185
+ }
186
+ }
187
+ }
188
+
189
+ const verdict = reproducible ? 'REPRODUCIBLE' : 'NON_REPRODUCIBLE';
190
+
191
+ const report = {
192
+ verdict,
193
+ reproducible,
194
+ differences,
195
+ current,
196
+ previous: previous || null,
197
+ checkedAt: new Date().toISOString()
198
+ };
199
+
200
+ return report;
201
+ }
202
+
203
+ /**
204
+ * Write reproducibility report
205
+ *
206
+ * @param {string} projectDir - Project directory
207
+ * @param {Object} report - Reproducibility report
208
+ * @returns {string} Path to written file
209
+ */
210
+ export function writeReproducibilityReport(projectDir, report) {
211
+ const outputDir = resolve(projectDir, 'release');
212
+ if (!existsSync(outputDir)) {
213
+ mkdirSync(outputDir, { recursive: true });
214
+ }
215
+
216
+ const outputPath = resolve(outputDir, 'reproducibility.report.json');
217
+ writeFileSync(outputPath, JSON.stringify(report, null, 2), 'utf-8');
218
+
219
+ return outputPath;
220
+ }
221
+
@@ -0,0 +1,283 @@
1
+ /**
2
+ * PHASE 21.7 — SBOM Builder
3
+ *
4
+ * Generates Software Bill of Materials (SBOM) in CycloneDX format.
5
+ * Missing SBOM = BLOCKING.
6
+ */
7
+
8
+ import { readFileSync, existsSync, writeFileSync, mkdirSync, readdirSync } from 'fs';
9
+ import { resolve } from 'path';
10
+ import { createHash } from 'crypto';
11
+ import { execSync } from 'child_process';
12
+
13
+ /**
14
+ * Get package.json dependencies
15
+ *
16
+ * @param {string} projectDir - Project directory
17
+ * @returns {Object} Dependencies object
18
+ */
19
+ function getDependencies(projectDir) {
20
+ try {
21
+ const pkgPath = resolve(projectDir, 'package.json');
22
+ if (!existsSync(pkgPath)) {
23
+ return { dependencies: {}, devDependencies: {} };
24
+ }
25
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
26
+ return {
27
+ dependencies: pkg.dependencies || {},
28
+ devDependencies: pkg.devDependencies || {}
29
+ };
30
+ } catch {
31
+ return { dependencies: {}, devDependencies: {} };
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Get transitive dependencies from node_modules
37
+ *
38
+ * @param {string} projectDir - Project directory
39
+ * @returns {Array} Array of package info
40
+ */
41
+ function getTransitiveDependencies(projectDir) {
42
+ const packages = [];
43
+ const nodeModulesPath = resolve(projectDir, 'node_modules');
44
+
45
+ if (!existsSync(nodeModulesPath)) {
46
+ return packages;
47
+ }
48
+
49
+ try {
50
+ // Use npm ls to get dependency tree
51
+ const result = execSync('npm ls --all --json', {
52
+ cwd: projectDir,
53
+ encoding: 'utf-8',
54
+ stdio: ['ignore', 'pipe', 'ignore']
55
+ });
56
+
57
+ const tree = JSON.parse(result);
58
+
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
+ if (tree.dependencies) {
80
+ traverseDeps(tree.dependencies);
81
+ }
82
+ } catch {
83
+ // Fallback: scan node_modules directory
84
+ try {
85
+ const entries = readdirSync(nodeModulesPath, { withFileTypes: true });
86
+
87
+ for (const entry of entries) {
88
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
89
+ const pkgPath = resolve(nodeModulesPath, entry.name, 'package.json');
90
+ if (existsSync(pkgPath)) {
91
+ try {
92
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
93
+ packages.push({
94
+ name: pkg.name || entry.name,
95
+ version: pkg.version || 'unknown',
96
+ parent: null
97
+ });
98
+ } catch {
99
+ // Skip invalid packages
100
+ }
101
+ }
102
+ }
103
+ }
104
+ } catch {
105
+ // If scanning fails, return empty
106
+ }
107
+ }
108
+
109
+ return packages;
110
+ }
111
+
112
+ /**
113
+ * Get license from package.json
114
+ *
115
+ * @param {string} packagePath - Path to package.json
116
+ * @returns {string|null} License or null
117
+ */
118
+ function getPackageLicense(packagePath) {
119
+ try {
120
+ if (!existsSync(packagePath)) {
121
+ return null;
122
+ }
123
+ const pkg = JSON.parse(readFileSync(packagePath, 'utf-8'));
124
+ if (typeof pkg.license === 'string') {
125
+ return pkg.license;
126
+ } else if (pkg.license && pkg.license.type) {
127
+ return pkg.license.type;
128
+ }
129
+ return null;
130
+ } catch {
131
+ return null;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Compute integrity hash for a package
137
+ *
138
+ * @param {string} projectDir - Project directory
139
+ * @param {string} packageName - Package name
140
+ * @returns {string|null} SHA256 hash or null
141
+ */
142
+ function getPackageIntegrity(projectDir, packageName) {
143
+ try {
144
+ const packagePath = resolve(projectDir, 'node_modules', packageName);
145
+ if (!existsSync(packagePath)) {
146
+ return null;
147
+ }
148
+
149
+ // Hash the package.json as a proxy for package integrity
150
+ const pkgPath = resolve(packagePath, 'package.json');
151
+ if (existsSync(pkgPath)) {
152
+ const content = readFileSync(pkgPath);
153
+ return createHash('sha256').update(content).digest('hex');
154
+ }
155
+
156
+ return null;
157
+ } catch {
158
+ return null;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Build SBOM in CycloneDX format
164
+ *
165
+ * @param {string} projectDir - Project directory
166
+ * @returns {Promise<Object>} SBOM object
167
+ */
168
+ export async function buildSBOM(projectDir) {
169
+ const pkgPath = resolve(projectDir, 'package.json');
170
+ if (!existsSync(pkgPath)) {
171
+ throw new Error('Cannot build SBOM: package.json not found');
172
+ }
173
+
174
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
175
+ const deps = getDependencies(projectDir);
176
+ const transitive = getTransitiveDependencies(projectDir);
177
+
178
+ // Build components list
179
+ const components = [];
180
+
181
+ // Add main package
182
+ components.push({
183
+ type: 'application',
184
+ name: pkg.name || 'unknown',
185
+ version: pkg.version || 'unknown',
186
+ purl: `pkg:npm/${pkg.name}@${pkg.version}`,
187
+ licenses: pkg.license ? [{ license: { id: pkg.license } }] : []
188
+ });
189
+
190
+ // Add direct dependencies
191
+ for (const [name, version] of Object.entries(deps.dependencies)) {
192
+ const integrity = getPackageIntegrity(projectDir, name);
193
+ const license = getPackageLicense(resolve(projectDir, 'node_modules', name, 'package.json'));
194
+
195
+ components.push({
196
+ type: 'library',
197
+ name,
198
+ version: version.replace(/^[\^~]/, ''),
199
+ purl: `pkg:npm/${name}@${version.replace(/^[\^~]/, '')}`,
200
+ hashes: integrity ? [{ alg: 'SHA-256', content: integrity }] : [],
201
+ licenses: license ? [{ license: { id: license } }] : []
202
+ });
203
+ }
204
+
205
+ // Add dev dependencies (marked as development)
206
+ for (const [name, version] of Object.entries(deps.devDependencies)) {
207
+ const integrity = getPackageIntegrity(projectDir, name);
208
+ const license = getPackageLicense(resolve(projectDir, 'node_modules', name, 'package.json'));
209
+
210
+ components.push({
211
+ type: 'library',
212
+ name,
213
+ version: version.replace(/^[\^~]/, ''),
214
+ purl: `pkg:npm/${name}@${version.replace(/^[\^~]/, '')}`,
215
+ hashes: integrity ? [{ alg: 'SHA-256', content: integrity }] : [],
216
+ licenses: license ? [{ license: { id: license } }] : [],
217
+ scope: 'development'
218
+ });
219
+ }
220
+
221
+ // Add transitive dependencies (simplified - in production, use proper dependency resolution)
222
+ const transitiveMap = new Map();
223
+ for (const trans of transitive) {
224
+ const key = `${trans.name}@${trans.version}`;
225
+ if (!transitiveMap.has(key)) {
226
+ transitiveMap.set(key, trans);
227
+
228
+ const integrity = getPackageIntegrity(projectDir, trans.name);
229
+ const license = getPackageLicense(resolve(projectDir, 'node_modules', trans.name, 'package.json'));
230
+
231
+ components.push({
232
+ type: 'library',
233
+ name: trans.name,
234
+ version: trans.version,
235
+ purl: `pkg:npm/${trans.name}@${trans.version}`,
236
+ hashes: integrity ? [{ alg: 'SHA-256', content: integrity }] : [],
237
+ licenses: license ? [{ license: { id: license } }] : []
238
+ });
239
+ }
240
+ }
241
+
242
+ const sbom = {
243
+ bomFormat: 'CycloneDX',
244
+ specVersion: '1.4',
245
+ version: 1,
246
+ metadata: {
247
+ timestamp: new Date().toISOString(),
248
+ tools: [{
249
+ vendor: 'VERAX',
250
+ name: 'SBOM Builder',
251
+ version: '1.0.0'
252
+ }],
253
+ component: {
254
+ type: 'application',
255
+ name: pkg.name || 'unknown',
256
+ version: pkg.version || 'unknown'
257
+ }
258
+ },
259
+ components: components
260
+ };
261
+
262
+ return sbom;
263
+ }
264
+
265
+ /**
266
+ * Write SBOM to file
267
+ *
268
+ * @param {string} projectDir - Project directory
269
+ * @param {Object} sbom - SBOM object
270
+ * @returns {string} Path to written file
271
+ */
272
+ export function writeSBOM(projectDir, sbom) {
273
+ const outputDir = resolve(projectDir, 'release');
274
+ if (!existsSync(outputDir)) {
275
+ mkdirSync(outputDir, { recursive: true });
276
+ }
277
+
278
+ const outputPath = resolve(outputDir, 'sbom.json');
279
+ writeFileSync(outputPath, JSON.stringify(sbom, null, 2), 'utf-8');
280
+
281
+ return outputPath;
282
+ }
283
+
@@ -0,0 +1,192 @@
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 } from 'fs';
8
+ import { resolve, 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
+ const findings = JSON.parse(readFileSync(findingsPath, 'utf-8'));
38
+ const evidenceIndex = loadArtifact(runDir, 'evidence.index.json');
39
+ const decisionTrace = loadArtifact(runDir, 'decisions.trace.json');
40
+ const timeline = loadArtifact(runDir, 'run.timeline.json');
41
+ const failureLedger = loadArtifact(runDir, 'failure.ledger.json');
42
+ const performanceReport = loadArtifact(runDir, 'performance.report.json');
43
+
44
+ if (!Array.isArray(findings.findings)) {
45
+ return {
46
+ runId,
47
+ findings: {},
48
+ summary: { total: 0 },
49
+ generatedAt: new Date().toISOString()
50
+ };
51
+ }
52
+
53
+ for (const finding of findings.findings) {
54
+ const findingId = finding.findingId || finding.id || `finding-${Object.keys(index).length}`;
55
+
56
+ const entry = {
57
+ findingId,
58
+ type: finding.type || null,
59
+ status: finding.severity || finding.status || null,
60
+
61
+ // Evidence files
62
+ evidence: {
63
+ packageId: finding.evidencePackage?.id || null,
64
+ files: finding.evidencePackage?.files || [],
65
+ isComplete: finding.evidencePackage?.isComplete || false,
66
+ beforeScreenshot: finding.evidence?.before || null,
67
+ afterScreenshot: finding.evidence?.after || null
68
+ },
69
+
70
+ // Confidence reasons
71
+ confidence: {
72
+ level: finding.confidenceLevel || null,
73
+ score: finding.confidence !== undefined ? finding.confidence : null,
74
+ reasons: finding.confidenceReasons || [],
75
+ trace: decisionTrace?.findings?.find(t => t.findingId === findingId)?.confidence || null
76
+ },
77
+
78
+ // Guardrails rules
79
+ guardrails: {
80
+ applied: finding.guardrails?.appliedRules?.map(r => ({
81
+ id: r.id || r,
82
+ category: r.category || null,
83
+ action: r.action || null
84
+ })) || [],
85
+ finalDecision: finding.guardrails?.finalDecision || null,
86
+ contradictions: finding.guardrails?.contradictions || [],
87
+ trace: decisionTrace?.findings?.find(t => t.findingId === findingId)?.guardrails || null
88
+ },
89
+
90
+ // Failures (if any related)
91
+ failures: failureLedger?.failures?.filter(f =>
92
+ f.context?.findingId === findingId ||
93
+ f.message?.includes(findingId)
94
+ ).map(f => ({
95
+ code: f.code,
96
+ message: f.message,
97
+ severity: f.severity,
98
+ timestamp: f.timestamp
99
+ })) || [],
100
+
101
+ // Performance impacts (if any)
102
+ performance: performanceReport?.violations?.some(v =>
103
+ v.message?.includes(findingId)
104
+ ) ? {
105
+ impacted: true,
106
+ violations: performanceReport.violations.filter(v =>
107
+ v.message?.includes(findingId)
108
+ )
109
+ } : null,
110
+
111
+ // Timeline entries
112
+ timeline: timeline?.events?.filter(e =>
113
+ e.data?.findingId === findingId ||
114
+ (e.event === 'guardrails_applied' && e.data?.findingId === findingId) ||
115
+ (e.event === 'evidence_enforced' && e.data?.findingId === findingId)
116
+ ).map(e => ({
117
+ timestamp: e.timestamp,
118
+ phase: e.phase,
119
+ event: e.event,
120
+ data: e.data
121
+ })) || []
122
+ };
123
+
124
+ index[findingId] = entry;
125
+ }
126
+
127
+ return {
128
+ runId,
129
+ findings: index,
130
+ summary: {
131
+ total: Object.keys(index).length,
132
+ withEvidence: Object.values(index).filter(e => e.evidence.files.length > 0).length,
133
+ withGuardrails: Object.values(index).filter(e => e.guardrails.applied.length > 0).length,
134
+ withFailures: Object.values(index).filter(e => e.failures.length > 0).length,
135
+ withTimeline: Object.values(index).filter(e => e.timeline.length > 0).length
136
+ },
137
+ generatedAt: new Date().toISOString()
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Load artifact JSON
143
+ */
144
+ function loadArtifact(runDir, filename) {
145
+ const path = resolve(runDir, filename);
146
+ if (!existsSync(path)) {
147
+ return null;
148
+ }
149
+ try {
150
+ return JSON.parse(readFileSync(path, 'utf-8'));
151
+ } catch {
152
+ return null;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Write cross-index to file
158
+ *
159
+ * @param {string} projectDir - Project directory
160
+ * @param {string} runId - Run ID
161
+ * @param {Object} index - Cross-index
162
+ * @returns {string} Path to written file
163
+ */
164
+ export function writeCrossIndex(projectDir, runId, index) {
165
+ const runDir = resolve(projectDir, '.verax', 'runs', runId);
166
+ const outputPath = resolve(runDir, 'artifacts.index.json');
167
+ writeFileSync(outputPath, JSON.stringify(index, null, 2), 'utf-8');
168
+ return outputPath;
169
+ }
170
+
171
+ /**
172
+ * Load cross-index from file
173
+ *
174
+ * @param {string} projectDir - Project directory
175
+ * @param {string} runId - Run ID
176
+ * @returns {Object|null} Cross-index or null
177
+ */
178
+ export function loadCrossIndex(projectDir, runId) {
179
+ const runDir = resolve(projectDir, '.verax', 'runs', runId);
180
+ const indexPath = resolve(runDir, 'artifacts.index.json');
181
+
182
+ if (!existsSync(indexPath)) {
183
+ return null;
184
+ }
185
+
186
+ try {
187
+ return JSON.parse(readFileSync(indexPath, 'utf-8'));
188
+ } catch {
189
+ return null;
190
+ }
191
+ }
192
+