@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.
- package/README.md +14 -18
- package/bin/verax.js +7 -0
- package/package.json +3 -3
- package/src/cli/commands/baseline.js +104 -0
- package/src/cli/commands/default.js +79 -25
- package/src/cli/commands/ga.js +243 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +131 -2
- package/src/cli/commands/release-check.js +213 -0
- package/src/cli/commands/run.js +246 -35
- package/src/cli/commands/security-check.js +211 -0
- package/src/cli/commands/truth.js +114 -0
- package/src/cli/entry.js +304 -67
- package/src/cli/util/angular-component-extractor.js +179 -0
- package/src/cli/util/angular-navigation-detector.js +141 -0
- package/src/cli/util/angular-network-detector.js +161 -0
- package/src/cli/util/angular-state-detector.js +162 -0
- package/src/cli/util/ast-interactive-detector.js +546 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/determinism-runner.js +123 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/env-url.js +4 -0
- package/src/cli/util/expectation-extractor.js +369 -73
- package/src/cli/util/findings-writer.js +126 -16
- package/src/cli/util/learn-writer.js +3 -1
- package/src/cli/util/observe-writer.js +3 -1
- package/src/cli/util/paths.js +3 -12
- package/src/cli/util/project-discovery.js +3 -0
- package/src/cli/util/project-writer.js +3 -1
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +1 -0
- package/src/cli/util/svelte-navigation-detector.js +163 -0
- package/src/cli/util/svelte-network-detector.js +80 -0
- package/src/cli/util/svelte-sfc-extractor.js +147 -0
- package/src/cli/util/svelte-state-detector.js +243 -0
- package/src/cli/util/vue-navigation-detector.js +177 -0
- package/src/cli/util/vue-sfc-extractor.js +162 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/verax/cli/finding-explainer.js +56 -3
- package/src/verax/core/artifacts/registry.js +154 -0
- package/src/verax/core/artifacts/verifier.js +980 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +231 -0
- package/src/verax/core/capabilities/gates.js +499 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +137 -0
- package/src/verax/core/confidence/confidence-invariants.js +234 -0
- package/src/verax/core/confidence/confidence-report-writer.js +112 -0
- package/src/verax/core/confidence/confidence-weights.js +44 -0
- package/src/verax/core/confidence/confidence.defaults.js +65 -0
- package/src/verax/core/confidence/confidence.loader.js +79 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +484 -0
- package/src/verax/core/confidence-engine.js +486 -0
- package/src/verax/core/confidence-engine.js.backup +471 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +185 -0
- package/src/verax/core/contracts/validators.js +381 -0
- package/src/verax/core/decision-snapshot.js +30 -3
- package/src/verax/core/decisions/decision.trace.js +276 -0
- package/src/verax/core/determinism/contract-writer.js +89 -0
- package/src/verax/core/determinism/contract.js +139 -0
- package/src/verax/core/determinism/diff.js +364 -0
- package/src/verax/core/determinism/engine.js +221 -0
- package/src/verax/core/determinism/finding-identity.js +148 -0
- package/src/verax/core/determinism/normalize.js +438 -0
- package/src/verax/core/determinism/report-writer.js +92 -0
- package/src/verax/core/determinism/run-fingerprint.js +118 -0
- package/src/verax/core/dynamic-route-intelligence.js +528 -0
- package/src/verax/core/evidence/evidence-capture-service.js +307 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
- package/src/verax/core/evidence-builder.js +487 -0
- package/src/verax/core/execution-mode-context.js +77 -0
- package/src/verax/core/execution-mode-detector.js +190 -0
- package/src/verax/core/failures/exit-codes.js +86 -0
- package/src/verax/core/failures/failure-summary.js +76 -0
- package/src/verax/core/failures/failure.factory.js +225 -0
- package/src/verax/core/failures/failure.ledger.js +132 -0
- package/src/verax/core/failures/failure.types.js +196 -0
- package/src/verax/core/failures/index.js +10 -0
- package/src/verax/core/ga/ga-report-writer.js +43 -0
- package/src/verax/core/ga/ga.artifact.js +49 -0
- package/src/verax/core/ga/ga.contract.js +434 -0
- package/src/verax/core/ga/ga.enforcer.js +86 -0
- package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
- package/src/verax/core/guardrails/policy.defaults.js +210 -0
- package/src/verax/core/guardrails/policy.loader.js +83 -0
- package/src/verax/core/guardrails/policy.schema.js +110 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
- package/src/verax/core/guardrails-engine.js +505 -0
- package/src/verax/core/observe/run-timeline.js +316 -0
- package/src/verax/core/perf/perf.contract.js +186 -0
- package/src/verax/core/perf/perf.display.js +65 -0
- package/src/verax/core/perf/perf.enforcer.js +91 -0
- package/src/verax/core/perf/perf.monitor.js +209 -0
- package/src/verax/core/perf/perf.report.js +198 -0
- package/src/verax/core/pipeline-tracker.js +238 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +271 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +159 -0
- package/src/verax/core/release/reproducibility.check.js +221 -0
- package/src/verax/core/release/sbom.builder.js +283 -0
- package/src/verax/core/report/cross-index.js +192 -0
- package/src/verax/core/report/human-summary.js +222 -0
- package/src/verax/core/route-intelligence.js +419 -0
- package/src/verax/core/security/secrets.scan.js +326 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +124 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +326 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/truth/truth.certificate.js +250 -0
- package/src/verax/core/ui-feedback-intelligence.js +515 -0
- package/src/verax/detect/confidence-engine.js +628 -40
- package/src/verax/detect/confidence-helper.js +33 -0
- package/src/verax/detect/detection-engine.js +18 -1
- package/src/verax/detect/dynamic-route-findings.js +335 -0
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +3 -1
- package/src/verax/detect/findings-writer.js +141 -5
- package/src/verax/detect/index.js +229 -5
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/route-findings.js +218 -0
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/verdict-engine.js +57 -3
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/index.js +413 -45
- package/src/verax/learn/action-contract-extractor.js +682 -64
- package/src/verax/learn/route-validator.js +4 -1
- package/src/verax/observe/index.js +88 -843
- package/src/verax/observe/interaction-runner.js +25 -8
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +191 -0
- package/src/verax/observe/observe-runner.js +226 -0
- package/src/verax/observe/observers/budget-observer.js +185 -0
- package/src/verax/observe/observers/console-observer.js +102 -0
- package/src/verax/observe/observers/coverage-observer.js +107 -0
- package/src/verax/observe/observers/interaction-observer.js +471 -0
- package/src/verax/observe/observers/navigation-observer.js +132 -0
- package/src/verax/observe/observers/network-observer.js +87 -0
- package/src/verax/observe/observers/safety-observer.js +82 -0
- package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/observe/ui-signal-sensor.js +148 -2
- package/src/verax/scan-summary-writer.js +42 -8
- package/src/verax/shared/artifact-manager.js +8 -5
- package/src/verax/shared/css-spinner-rules.js +204 -0
- 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
|
+
|