@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
@@ -0,0 +1,164 @@
1
+ /**
2
+ * PHASE 21.7 — Release Enforcer
3
+ *
4
+ * Hard lock: blocks publish/release without GA-READY + Provenance + SBOM + Reproducible.
5
+ */
6
+
7
+ import { existsSync, readFileSync } from 'fs';
8
+ import { resolve } from 'path';
9
+ import { checkGAStatus } from '../ga/ga.enforcer.js';
10
+ import { findLatestRunId } from '../../../cli/util/run-resolver.js';
11
+ import { FAILURE_CODE } from '../failures/failure.types.js';
12
+ import { isBaselineFrozen, enforceBaseline } from '../baseline/baseline.enforcer.js';
13
+ import { FailureLedger } from '../failures/failure.ledger.js';
14
+
15
+ /**
16
+ * Check if release is allowed
17
+ *
18
+ * @param {string} projectDir - Project directory
19
+ * @param {string} operation - Operation name (publish, release, tag)
20
+ * @throws {Error} If release is blocked
21
+ */
22
+ export async function enforceReleaseReadiness(projectDir, operation = 'release') {
23
+ const blockers = [];
24
+
25
+ // 1. Check GA status
26
+ try {
27
+ const runId = findLatestRunId(projectDir);
28
+ if (!runId) {
29
+ blockers.push('No runs found. Run a scan and verify GA readiness first.');
30
+ } else {
31
+ const gaCheck = checkGAStatus(projectDir, runId);
32
+ if (!gaCheck.ready) {
33
+ const blockerMessages = gaCheck.status?.blockers?.map(b => b.message).join('; ') || 'GA not ready';
34
+ blockers.push(`GA-BLOCKED: ${blockerMessages}`);
35
+ }
36
+ }
37
+ } catch (error) {
38
+ blockers.push(`GA check failed: ${error.message}`);
39
+ }
40
+
41
+ // 2. Check Provenance
42
+ const provenancePath = resolve(projectDir, 'release', 'release.provenance.json');
43
+ if (!existsSync(provenancePath)) {
44
+ blockers.push('Provenance not found. Run "verax release:check" first.');
45
+ } else {
46
+ try {
47
+ // @ts-expect-error - readFileSync with encoding returns string
48
+ const provenance = JSON.parse(readFileSync(provenancePath, 'utf-8'));
49
+ if (provenance.git?.dirty) {
50
+ blockers.push('Provenance indicates dirty git repository');
51
+ }
52
+ if (provenance.gaStatus !== 'GA-READY') {
53
+ blockers.push(`Provenance GA status is ${provenance.gaStatus}, not GA-READY`);
54
+ }
55
+ } catch (error) {
56
+ blockers.push(`Invalid provenance: ${error.message}`);
57
+ }
58
+ }
59
+
60
+ // 3. Check SBOM
61
+ const sbomPath = resolve(projectDir, 'release', 'sbom.json');
62
+ if (!existsSync(sbomPath)) {
63
+ blockers.push('SBOM not found. Run "verax release:check" first.');
64
+ } else {
65
+ try {
66
+ // @ts-expect-error - readFileSync with encoding returns string
67
+ const sbom = JSON.parse(readFileSync(sbomPath, 'utf-8'));
68
+ if (!sbom.bomFormat || !sbom.components || !Array.isArray(sbom.components) || sbom.components.length === 0) {
69
+ blockers.push('Invalid or empty SBOM');
70
+ }
71
+ } catch (error) {
72
+ blockers.push(`Invalid SBOM: ${error.message}`);
73
+ }
74
+ }
75
+
76
+ // 4. Check Reproducibility
77
+ const reproducibilityPath = resolve(projectDir, 'release', 'reproducibility.report.json');
78
+ if (!existsSync(reproducibilityPath)) {
79
+ blockers.push('Reproducibility report not found. Run "verax release:check" first.');
80
+ } else {
81
+ try {
82
+ // @ts-expect-error - readFileSync with encoding returns string
83
+ const report = JSON.parse(readFileSync(reproducibilityPath, 'utf-8'));
84
+ if (report.verdict !== 'REPRODUCIBLE') {
85
+ const differences = report.differences?.map(d => d.message).join('; ') || 'Build is not reproducible';
86
+ blockers.push(`NON_REPRODUCIBLE: ${differences}`);
87
+ }
88
+ } catch (error) {
89
+ blockers.push(`Invalid reproducibility report: ${error.message}`);
90
+ }
91
+ }
92
+
93
+ // 5. Check Security (PHASE 21.8)
94
+ const secretsPath = resolve(projectDir, 'release', 'security.secrets.report.json');
95
+ const vulnPath = resolve(projectDir, 'release', 'security.vuln.report.json');
96
+ const supplyChainPath = resolve(projectDir, 'release', 'security.supplychain.report.json');
97
+
98
+ if (!existsSync(secretsPath) || !existsSync(vulnPath) || !existsSync(supplyChainPath)) {
99
+ blockers.push('Security reports not found. Run "verax security:check" first.');
100
+ } else {
101
+ try {
102
+ // Check secrets
103
+ // @ts-expect-error - readFileSync with encoding returns string
104
+ const secretsReport = JSON.parse(readFileSync(secretsPath, 'utf-8'));
105
+ if (secretsReport.hasSecrets) {
106
+ blockers.push(`SECURITY-BLOCKED: Secrets detected (${secretsReport.summary.total} finding(s))`);
107
+ }
108
+
109
+ // Check vulnerabilities
110
+ // @ts-expect-error - readFileSync with encoding returns string
111
+ const vulnReport = JSON.parse(readFileSync(vulnPath, 'utf-8'));
112
+ if (vulnReport.blocking) {
113
+ blockers.push(`SECURITY-BLOCKED: Critical/High vulnerabilities detected (${vulnReport.summary.critical + vulnReport.summary.high} total)`);
114
+ }
115
+
116
+ // Check supply-chain
117
+ // @ts-expect-error - readFileSync with encoding returns string
118
+ const supplyChainReport = JSON.parse(readFileSync(supplyChainPath, 'utf-8'));
119
+ if (!supplyChainReport.ok) {
120
+ blockers.push(`SECURITY-BLOCKED: Supply-chain violations (${supplyChainReport.summary.totalViolations} violation(s))`);
121
+ }
122
+ } catch (error) {
123
+ blockers.push(`Security check failed: ${error.message}`);
124
+ }
125
+ }
126
+
127
+ // 6. Check Performance (PHASE 21.9) - BLOCKING perf violations block release
128
+ const runId = findLatestRunId(projectDir);
129
+ if (runId) {
130
+ try {
131
+ const { checkPerformanceStatus } = await import('../perf/perf.enforcer.js');
132
+ const perfCheck = checkPerformanceStatus(projectDir, runId);
133
+
134
+ if (perfCheck.exists && !perfCheck.ok) {
135
+ blockers.push(`PERFORMANCE-BLOCKED: ${perfCheck.blockers.join('; ')}`);
136
+ }
137
+ } catch (error) {
138
+ // Performance check failure is not a blocker (may be from old runs)
139
+ }
140
+ }
141
+
142
+ // 7. Baseline Freeze Enforcement (PHASE 21.11)
143
+ // After GA, baseline must be frozen and unchanged
144
+ if (isBaselineFrozen(projectDir)) {
145
+ const failureLedger = new FailureLedger(projectDir, runId || 'unknown');
146
+ const baselineCheck = enforceBaseline(projectDir, failureLedger);
147
+ if (baselineCheck.blocked) {
148
+ blockers.push(`BASELINE-DRIFT: ${baselineCheck.message}. Changes to core contracts/policies after GA require MAJOR version bump and baseline regeneration.`);
149
+ }
150
+ }
151
+
152
+ // If any blockers, throw error
153
+ if (blockers.length > 0) {
154
+ const message = `Cannot ${operation}: RELEASE-BLOCKED. ${blockers.join('; ')}`;
155
+ const error = new Error(message);
156
+ /** @type {any} */
157
+ const e = error;
158
+ e.code = FAILURE_CODE.INTERNAL_UNEXPECTED_ERROR;
159
+ e.component = 'release.enforcer';
160
+ e.context = { operation, blockers };
161
+ throw error;
162
+ }
163
+ }
164
+
@@ -0,0 +1,222 @@
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
+ // @ts-expect-error - readFileSync with encoding returns string
114
+ return JSON.parse(content);
115
+ } catch {
116
+ return null;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Check reproducibility
122
+ *
123
+ * @param {string} projectDir - Project directory
124
+ * @returns {Promise<Object>} Reproducibility check result
125
+ */
126
+ export async function checkReproducibility(projectDir) {
127
+ const gitCommit = getGitCommit(projectDir);
128
+ const policyHashes = await getPolicyHashes(projectDir);
129
+ const artifactHashes = getArtifactHashes(projectDir);
130
+
131
+ const current = {
132
+ gitCommit,
133
+ policies: policyHashes,
134
+ artifacts: artifactHashes,
135
+ checkedAt: new Date().toISOString()
136
+ };
137
+
138
+ const previous = loadPreviousReport(projectDir);
139
+
140
+ let reproducible = true;
141
+ const differences = [];
142
+
143
+ if (previous) {
144
+ // Compare with previous build
145
+ if (previous.gitCommit !== gitCommit) {
146
+ reproducible = false;
147
+ differences.push({
148
+ type: 'git_commit',
149
+ previous: previous.gitCommit,
150
+ current: gitCommit,
151
+ message: 'Git commit changed'
152
+ });
153
+ }
154
+
155
+ if (previous.policies?.guardrails !== policyHashes.guardrails) {
156
+ reproducible = false;
157
+ differences.push({
158
+ type: 'guardrails_policy',
159
+ previous: previous.policies?.guardrails,
160
+ current: policyHashes.guardrails,
161
+ message: 'Guardrails policy changed'
162
+ });
163
+ }
164
+
165
+ if (previous.policies?.confidence !== policyHashes.confidence) {
166
+ reproducible = false;
167
+ differences.push({
168
+ type: 'confidence_policy',
169
+ previous: previous.policies?.confidence,
170
+ current: policyHashes.confidence,
171
+ message: 'Confidence policy changed'
172
+ });
173
+ }
174
+
175
+ // Compare artifact hashes
176
+ for (const [file, hash] of Object.entries(artifactHashes)) {
177
+ if (previous.artifacts?.[file] !== hash) {
178
+ reproducible = false;
179
+ differences.push({
180
+ type: 'artifact',
181
+ file,
182
+ previous: previous.artifacts?.[file],
183
+ current: hash,
184
+ message: `Artifact ${file} changed`
185
+ });
186
+ }
187
+ }
188
+ }
189
+
190
+ const verdict = reproducible ? 'REPRODUCIBLE' : 'NON_REPRODUCIBLE';
191
+
192
+ const report = {
193
+ verdict,
194
+ reproducible,
195
+ differences,
196
+ current,
197
+ previous: previous || null,
198
+ checkedAt: new Date().toISOString()
199
+ };
200
+
201
+ return report;
202
+ }
203
+
204
+ /**
205
+ * Write reproducibility report
206
+ *
207
+ * @param {string} projectDir - Project directory
208
+ * @param {Object} report - Reproducibility report
209
+ * @returns {string} Path to written file
210
+ */
211
+ export function writeReproducibilityReport(projectDir, report) {
212
+ const outputDir = resolve(projectDir, 'release');
213
+ if (!existsSync(outputDir)) {
214
+ mkdirSync(outputDir, { recursive: true });
215
+ }
216
+
217
+ const outputPath = resolve(outputDir, 'reproducibility.report.json');
218
+ writeFileSync(outputPath, JSON.stringify(report, null, 2), 'utf-8');
219
+
220
+ return outputPath;
221
+ }
222
+
@@ -0,0 +1,292 @@
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
+ // @ts-expect-error - readFileSync with encoding returns string
26
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
27
+ return {
28
+ dependencies: pkg.dependencies || {},
29
+ devDependencies: pkg.devDependencies || {}
30
+ };
31
+ } catch {
32
+ return { dependencies: {}, devDependencies: {} };
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Helper to traverse dependency tree recursively
38
+ */
39
+ function traverseDepsHelper(deps, packages, parent = null) {
40
+ if (!deps || typeof deps !== 'object') {
41
+ return;
42
+ }
43
+
44
+ for (const [name, info] of Object.entries(deps)) {
45
+ if (info && typeof info === 'object' && info.version) {
46
+ packages.push({
47
+ name,
48
+ version: info.version,
49
+ parent: parent || null
50
+ });
51
+
52
+ if (info.dependencies) {
53
+ traverseDepsHelper(info.dependencies, packages, name);
54
+ }
55
+ }
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Get transitive dependencies from node_modules
61
+ *
62
+ * @param {string} projectDir - Project directory
63
+ * @returns {Array} Array of package info
64
+ */
65
+ function getTransitiveDependencies(projectDir) {
66
+ const packages = [];
67
+ const nodeModulesPath = resolve(projectDir, 'node_modules');
68
+
69
+ if (!existsSync(nodeModulesPath)) {
70
+ return packages;
71
+ }
72
+
73
+ try {
74
+ // Use npm ls to get dependency tree (with timeout to prevent hanging)
75
+ const result = execSync('npm ls --all --json', {
76
+ cwd: projectDir,
77
+ encoding: 'utf-8',
78
+ stdio: ['ignore', 'pipe', 'ignore'],
79
+ timeout: 5000 // 5 second timeout
80
+ });
81
+
82
+ const tree = JSON.parse(result);
83
+
84
+ if (tree.dependencies) {
85
+ traverseDepsHelper(tree.dependencies, packages);
86
+ }
87
+ } catch {
88
+ // Fallback: scan node_modules directory
89
+ try {
90
+ const entries = readdirSync(nodeModulesPath, { withFileTypes: true });
91
+
92
+ for (const entry of entries) {
93
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
94
+ const pkgPath = resolve(nodeModulesPath, entry.name, 'package.json');
95
+ if (existsSync(pkgPath)) {
96
+ try {
97
+ // @ts-expect-error - readFileSync with encoding returns string
98
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
99
+ packages.push({
100
+ name: pkg.name || entry.name,
101
+ version: pkg.version || 'unknown',
102
+ parent: null
103
+ });
104
+ } catch {
105
+ // Skip invalid packages
106
+ }
107
+ }
108
+ }
109
+ }
110
+ } catch {
111
+ // If scanning fails, return empty
112
+ }
113
+ }
114
+
115
+ return packages;
116
+ }
117
+
118
+ /**
119
+ * Get license from package.json
120
+ *
121
+ * @param {string} packagePath - Path to package.json
122
+ * @returns {string|null} License or null
123
+ */
124
+ function getPackageLicense(packagePath) {
125
+ try {
126
+ if (!existsSync(packagePath)) {
127
+ return null;
128
+ }
129
+ // @ts-expect-error - readFileSync with encoding returns string
130
+ const pkg = JSON.parse(readFileSync(packagePath, 'utf-8'));
131
+ if (typeof pkg.license === 'string') {
132
+ return pkg.license;
133
+ } else if (pkg.license && pkg.license.type) {
134
+ return pkg.license.type;
135
+ }
136
+ return null;
137
+ } catch {
138
+ return null;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Compute integrity hash for a package
144
+ *
145
+ * @param {string} projectDir - Project directory
146
+ * @param {string} packageName - Package name
147
+ * @returns {string|null} SHA256 hash or null
148
+ */
149
+ function getPackageIntegrity(projectDir, packageName) {
150
+ try {
151
+ const packagePath = resolve(projectDir, 'node_modules', packageName);
152
+ if (!existsSync(packagePath)) {
153
+ return null;
154
+ }
155
+
156
+ // Hash the package.json as a proxy for package integrity
157
+ const pkgPath = resolve(packagePath, 'package.json');
158
+ if (existsSync(pkgPath)) {
159
+ const content = readFileSync(pkgPath);
160
+ // @ts-expect-error - digest returns string
161
+ return createHash('sha256').update(content).digest('hex');
162
+ }
163
+
164
+ return null;
165
+ } catch {
166
+ return null;
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Build SBOM in CycloneDX format
172
+ *
173
+ * @param {string} projectDir - Project directory
174
+ * @returns {Promise<Object>} SBOM object
175
+ */
176
+ export async function buildSBOM(projectDir) {
177
+ const pkgPath = resolve(projectDir, 'package.json');
178
+ if (!existsSync(pkgPath)) {
179
+ throw new Error('Cannot build SBOM: package.json not found');
180
+ }
181
+
182
+ // @ts-expect-error - readFileSync with encoding returns string
183
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
184
+ const deps = getDependencies(projectDir);
185
+ const transitive = getTransitiveDependencies(projectDir);
186
+
187
+ // Build components list
188
+ const components = [];
189
+
190
+ // Add main package
191
+ components.push({
192
+ type: 'application',
193
+ name: pkg.name || 'unknown',
194
+ version: pkg.version || 'unknown',
195
+ purl: `pkg:npm/${pkg.name}@${pkg.version}`,
196
+ licenses: pkg.license ? [{ license: { id: pkg.license } }] : []
197
+ });
198
+
199
+ // Add direct dependencies
200
+ for (const [name, version] of Object.entries(deps.dependencies)) {
201
+ const integrity = getPackageIntegrity(projectDir, name);
202
+ const license = getPackageLicense(resolve(projectDir, 'node_modules', name, 'package.json'));
203
+
204
+ components.push({
205
+ type: 'library',
206
+ name,
207
+ version: version.replace(/^[\^~]/, ''),
208
+ purl: `pkg:npm/${name}@${version.replace(/^[\^~]/, '')}`,
209
+ hashes: integrity ? [{ alg: 'SHA-256', content: integrity }] : [],
210
+ licenses: license ? [{ license: { id: license } }] : []
211
+ });
212
+ }
213
+
214
+ // Add dev dependencies (marked as development)
215
+ for (const [name, version] of Object.entries(deps.devDependencies)) {
216
+ const integrity = getPackageIntegrity(projectDir, name);
217
+ const license = getPackageLicense(resolve(projectDir, 'node_modules', name, 'package.json'));
218
+
219
+ components.push({
220
+ type: 'library',
221
+ name,
222
+ version: version.replace(/^[\^~]/, ''),
223
+ purl: `pkg:npm/${name}@${version.replace(/^[\^~]/, '')}`,
224
+ hashes: integrity ? [{ alg: 'SHA-256', content: integrity }] : [],
225
+ licenses: license ? [{ license: { id: license } }] : [],
226
+ scope: 'development'
227
+ });
228
+ }
229
+
230
+ // Add transitive dependencies (simplified - in production, use proper dependency resolution)
231
+ const transitiveMap = new Map();
232
+ for (const trans of transitive) {
233
+ const key = `${trans.name}@${trans.version}`;
234
+ if (!transitiveMap.has(key)) {
235
+ transitiveMap.set(key, trans);
236
+
237
+ const integrity = getPackageIntegrity(projectDir, trans.name);
238
+ const license = getPackageLicense(resolve(projectDir, 'node_modules', trans.name, 'package.json'));
239
+
240
+ components.push({
241
+ type: 'library',
242
+ name: trans.name,
243
+ version: trans.version,
244
+ purl: `pkg:npm/${trans.name}@${trans.version}`,
245
+ hashes: integrity ? [{ alg: 'SHA-256', content: integrity }] : [],
246
+ licenses: license ? [{ license: { id: license } }] : []
247
+ });
248
+ }
249
+ }
250
+
251
+ const sbom = {
252
+ bomFormat: 'CycloneDX',
253
+ specVersion: '1.4',
254
+ version: 1,
255
+ metadata: {
256
+ timestamp: new Date().toISOString(),
257
+ tools: [{
258
+ vendor: 'VERAX',
259
+ name: 'SBOM Builder',
260
+ version: '1.0.0'
261
+ }],
262
+ component: {
263
+ type: 'application',
264
+ name: pkg.name || 'unknown',
265
+ version: pkg.version || 'unknown'
266
+ }
267
+ },
268
+ components: components
269
+ };
270
+
271
+ return sbom;
272
+ }
273
+
274
+ /**
275
+ * Write SBOM to file
276
+ *
277
+ * @param {string} projectDir - Project directory
278
+ * @param {Object} sbom - SBOM object
279
+ * @returns {string} Path to written file
280
+ */
281
+ export function writeSBOM(projectDir, sbom) {
282
+ const outputDir = resolve(projectDir, 'release');
283
+ if (!existsSync(outputDir)) {
284
+ mkdirSync(outputDir, { recursive: true });
285
+ }
286
+
287
+ const outputPath = resolve(outputDir, 'sbom.json');
288
+ writeFileSync(outputPath, JSON.stringify(sbom, null, 2), 'utf-8');
289
+
290
+ return outputPath;
291
+ }
292
+
@@ -292,7 +292,9 @@ export function validateReplay(baselinePath, currentPath) {
292
292
  throw new Error(`Current decisions not found: ${currentPath}`);
293
293
  }
294
294
 
295
+ // @ts-expect-error - readFileSync with encoding returns string
295
296
  const baseline = JSON.parse(fs.readFileSync(baselinePath, 'utf-8'));
297
+ // @ts-expect-error - readFileSync with encoding returns string
296
298
  const current = JSON.parse(fs.readFileSync(currentPath, 'utf-8'));
297
299
 
298
300
  return compareReplayDecisions(baseline, current);