@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,127 @@
1
+ /**
2
+ * VERAX Product Definition - Locked in Code
3
+ *
4
+ * Single source of truth for what VERAX is, does, and doesn't do.
5
+ * This definition is wired into CLI help, documentation, and assertions throughout the codebase.
6
+ *
7
+ * CRITICAL: This is not marketing copy. This is the operational contract.
8
+ */
9
+
10
+ export const VERAX_PRODUCT_DEFINITION = {
11
+ // What VERAX is: one-liner
12
+ oneLiner: 'A forensic observation engine that detects silent user failures by comparing what your code promises with what users can actually observe.',
13
+
14
+ // Does: explicit capabilities
15
+ does: [
16
+ 'Observes real websites in real browsers using Playwright',
17
+ 'Reads source code to extract explicit expectations (navigation, network calls, state changes)',
18
+ 'Compares code-derived expectations with observed browser behavior',
19
+ 'Reports gaps between promise and reality with concrete evidence',
20
+ 'Assigns confidence levels (HIGH/MEDIUM/LOW) based on evidence strength',
21
+ 'Runs locally on developer machines or in CI/CD pipelines',
22
+ 'Produces forensic artifacts: findings, traces, screenshots, network logs',
23
+ 'Requires and validates source code as the source of truth for expectations',
24
+ 'Enforces Evidence Law: findings cannot be CONFIRMED without sufficient evidence'
25
+ ],
26
+
27
+ // Does NOT: explicit limitations
28
+ doesNot: [
29
+ 'Guess intent - only analyzes explicit code promises',
30
+ 'Detect dynamic routes (e.g., /user/${id}) - skipped intentionally',
31
+ 'Replace QA or automated tests - complements them',
32
+ 'Monitor production traffic',
33
+ 'Support every framework - only documented frameworks (React, Next.js, Vue, static HTML)',
34
+ 'Detect every bug - only gaps backed by explicit code promises',
35
+ 'Operate as a hosted or public-website scanner - runs locally with your repository',
36
+ 'Run without source code - requires local access to codebase',
37
+ 'Create CONFIRMED findings without evidence - Evidence Law is mandatory'
38
+ ],
39
+
40
+ // Success conditions: when does a VERAX run succeed?
41
+ successConditions: [
42
+ 'Run executes without crashing',
43
+ 'At least one expectation is extracted from source code',
44
+ 'At least one interaction is discovered in the browser',
45
+ 'Findings are generated from expectations vs observations',
46
+ 'All findings satisfy contracts (have evidence, confidence, signals)',
47
+ 'All CONFIRMED findings have substantive evidence (per Evidence Law)',
48
+ 'Artifacts are written to standard locations (.verax/runs/<runId>/)'
49
+ ],
50
+
51
+ // Failure conditions: when does a VERAX run fail?
52
+ failureConditions: [
53
+ 'No source code found or readable',
54
+ 'No expectations extracted from source code',
55
+ 'URL is unreachable or site fails to load',
56
+ 'No interactions discovered in the browser',
57
+ 'Critical invariants violated (e.g., findings with missing required fields)',
58
+ 'A CONFIRMED finding violates Evidence Law (has insufficient evidence)'
59
+ ],
60
+
61
+ // The Evidence Law: most critical rule
62
+ evidenceLaw: {
63
+ statement: 'A finding cannot be marked CONFIRMED without sufficient evidence.',
64
+ definition: 'Substantive evidence means at least one of: DOM changes, URL changes, network requests, state mutations, or concrete sensor data.',
65
+ enforcement: 'If a finding is marked CONFIRMED but lacks evidence, it must be downgraded to SUSPECTED or dropped.',
66
+ rationale: 'VERAX exists to surface real gaps backed by observable signals. Unsubstantiated claims are guesses, not forensic findings.'
67
+ },
68
+
69
+ // Local source code requirement
70
+ sourceCodeRequirement: {
71
+ statement: 'VERAX requires local access to source code. It is not a public website scanner.',
72
+ rationale: 'Expectations are extracted through static analysis of source files. Without code, VERAX cannot work.',
73
+ implication: 'VERAX is designed for developers in their repositories, not for third-party auditing of closed-source applications.'
74
+ },
75
+
76
+ // Version: for tracking breaking changes
77
+ schemaVersion: 1
78
+ };
79
+
80
+ /**
81
+ * Format product definition for CLI display
82
+ */
83
+ export function formatProductDefinitionForCLI() {
84
+ const def = VERAX_PRODUCT_DEFINITION;
85
+ const lines = [];
86
+
87
+ lines.push('');
88
+ lines.push('═══════════════════════════════════════════════════════════════');
89
+ lines.push('VERAX PRODUCT DEFINITION');
90
+ lines.push('═══════════════════════════════════════════════════════════════');
91
+ lines.push('');
92
+ lines.push(`What: ${def.oneLiner}`);
93
+ lines.push('');
94
+ lines.push('Does:');
95
+ def.does.forEach(item => lines.push(` • ${item}`));
96
+ lines.push('');
97
+ lines.push('Does NOT:');
98
+ def.doesNot.forEach(item => lines.push(` • ${item}`));
99
+ lines.push('');
100
+ lines.push('EVIDENCE LAW (Mandatory):');
101
+ lines.push(` "${def.evidenceLaw.statement}"`);
102
+ lines.push(` Substantive evidence = DOM/URL/network/state changes or sensor data`);
103
+ lines.push(` Enforcement: CONFIRMED findings must have evidence, else downgraded to SUSPECTED`);
104
+ lines.push('');
105
+ lines.push('SOURCE CODE REQUIREMENT (Mandatory):');
106
+ lines.push(` "${def.sourceCodeRequirement.statement}"`);
107
+ lines.push('');
108
+ lines.push('═══════════════════════════════════════════════════════════════');
109
+ lines.push('');
110
+
111
+ return lines.join('\n');
112
+ }
113
+
114
+ // Consistent banner for all user-facing surfaces
115
+ export function getSourceCodeRequirementBanner() {
116
+ return 'VERAX requires local access to source code. It is not a public website scanner.';
117
+ }
118
+
119
+ /**
120
+ * Format just the Evidence Law for inline display
121
+ */
122
+ export function formatEvidenceLawForDisplay() {
123
+ const law = VERAX_PRODUCT_DEFINITION.evidenceLaw;
124
+ return `\n** EVIDENCE LAW: ${law.statement} **\n Substantive evidence = DOM/URL/network/state changes.\n CONFIRMED findings without evidence are downgraded to SUSPECTED.\n`;
125
+ }
126
+
127
+ export default VERAX_PRODUCT_DEFINITION;
@@ -0,0 +1,271 @@
1
+ /**
2
+ * PHASE 21.7 — Release Provenance Builder
3
+ *
4
+ * Generates release.provenance.json with complete build metadata.
5
+ * Dirty repo = BLOCKING.
6
+ */
7
+
8
+ import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
9
+ import { resolve, dirname } from 'path';
10
+ import { createHash } from 'crypto';
11
+ import { execSync } from 'child_process';
12
+ import { fileURLToPath } from 'url';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+
17
+ /**
18
+ * Get git commit hash
19
+ *
20
+ * @param {string} projectDir - Project directory
21
+ * @returns {string|null} Commit hash or null
22
+ */
23
+ function getGitCommit(projectDir) {
24
+ try {
25
+ const result = execSync('git rev-parse HEAD', {
26
+ cwd: projectDir,
27
+ encoding: 'utf-8',
28
+ stdio: ['ignore', 'pipe', 'ignore']
29
+ });
30
+ return result.trim();
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Check if git repo is dirty
38
+ *
39
+ * @param {string} projectDir - Project directory
40
+ * @returns {boolean} True if dirty
41
+ */
42
+ function isGitDirty(projectDir) {
43
+ try {
44
+ const result = execSync('git status --porcelain', {
45
+ cwd: projectDir,
46
+ encoding: 'utf-8',
47
+ stdio: ['ignore', 'pipe', 'ignore']
48
+ });
49
+ return result.trim().length > 0;
50
+ } catch {
51
+ // If not a git repo, consider it dirty
52
+ return true;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Get package version
58
+ *
59
+ * @param {string} projectDir - Project directory
60
+ * @returns {string|null} Version or null
61
+ */
62
+ function getPackageVersion(projectDir) {
63
+ try {
64
+ const pkgPath = resolve(projectDir, 'package.json');
65
+ if (!existsSync(pkgPath)) {
66
+ return null;
67
+ }
68
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
69
+ return pkg.version || null;
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Get environment info
77
+ *
78
+ * @returns {Object} Environment info
79
+ */
80
+ function getEnvironmentInfo() {
81
+ return {
82
+ node: process.version,
83
+ os: process.platform,
84
+ arch: process.arch
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Compute hash of a file
90
+ *
91
+ * @param {string} filePath - File path
92
+ * @returns {string|null} SHA256 hash or null
93
+ */
94
+ function hashFile(filePath) {
95
+ try {
96
+ if (!existsSync(filePath)) {
97
+ return null;
98
+ }
99
+ const content = readFileSync(filePath);
100
+ return createHash('sha256').update(content).digest('hex');
101
+ } catch {
102
+ return null;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Get policy hash
108
+ *
109
+ * @param {string} projectDir - Project directory
110
+ * @param {string} policyName - Policy name (guardrails or confidence)
111
+ * @returns {Promise<string|null>} Policy hash or null
112
+ */
113
+ async function getPolicyHash(projectDir, policyName) {
114
+ try {
115
+ // Try to load the policy
116
+ if (policyName === 'guardrails') {
117
+ const { loadGuardrailsPolicy } = await import('../guardrails/policy.loader.js');
118
+ const policy = loadGuardrailsPolicy(null, projectDir);
119
+ const policyStr = JSON.stringify(policy, null, 0);
120
+ return createHash('sha256').update(policyStr).digest('hex');
121
+ } else if (policyName === 'confidence') {
122
+ const { loadConfidencePolicy } = await import('../confidence/confidence.loader.js');
123
+ const policy = loadConfidencePolicy(null, projectDir);
124
+ const policyStr = JSON.stringify(policy, null, 0);
125
+ return createHash('sha256').update(policyStr).digest('hex');
126
+ }
127
+ return null;
128
+ } catch {
129
+ return null;
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Get artifact hashes
135
+ *
136
+ * @param {string} projectDir - Project directory
137
+ * @returns {Object} Artifact hashes
138
+ */
139
+ function getArtifactHashes(projectDir) {
140
+ const distPath = resolve(projectDir, 'dist');
141
+ const cliPath = resolve(projectDir, 'bin', 'verax.js');
142
+
143
+ return {
144
+ dist: existsSync(distPath) ? hashFile(resolve(distPath, 'index.js')) : null,
145
+ cli: hashFile(cliPath)
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Get schema version from artifacts
151
+ *
152
+ * @param {string} projectDir - Project directory
153
+ * @returns {string|null} Schema version or null
154
+ */
155
+ function getSchemaVersion(projectDir) {
156
+ try {
157
+ // Check for artifact registry
158
+ const registryPath = resolve(projectDir, 'src', 'verax', 'core', 'artifacts', 'registry.js');
159
+ if (existsSync(registryPath)) {
160
+ const content = readFileSync(registryPath, 'utf-8');
161
+ // Try to extract schema version from registry
162
+ const match = content.match(/SCHEMA_VERSION\s*[:=]\s*['"]([^'"]+)['"]/);
163
+ if (match) {
164
+ return match[1];
165
+ }
166
+ }
167
+ return '1.0.0'; // Default
168
+ } catch {
169
+ return '1.0.0';
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Check GA status
175
+ *
176
+ * @param {string} projectDir - Project directory
177
+ * @returns {Promise<string>} GA status (GA-READY, GA-BLOCKED, UNKNOWN)
178
+ */
179
+ async function getGAStatus(projectDir) {
180
+ try {
181
+ const { findLatestRunId } = await import('../../../cli/util/run-resolver.js');
182
+ const runId = findLatestRunId(projectDir);
183
+ if (!runId) {
184
+ return 'UNKNOWN';
185
+ }
186
+
187
+ const { checkGAStatus } = await import('../ga/ga.enforcer.js');
188
+ const check = checkGAStatus(projectDir, runId);
189
+
190
+ if (check.ready) {
191
+ return 'GA-READY';
192
+ } else {
193
+ return 'GA-BLOCKED';
194
+ }
195
+ } catch {
196
+ return 'UNKNOWN';
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Build release provenance
202
+ *
203
+ * @param {string} projectDir - Project directory
204
+ * @returns {Promise<Object>} Provenance object
205
+ * @throws {Error} If repo is dirty
206
+ */
207
+ export async function buildProvenance(projectDir) {
208
+ const gitCommit = getGitCommit(projectDir);
209
+ const gitDirty = isGitDirty(projectDir);
210
+
211
+ // HARD LOCK: Dirty repo = BLOCKING
212
+ if (gitDirty) {
213
+ throw new Error('Cannot build provenance: Git repository is dirty. Commit all changes first.');
214
+ }
215
+
216
+ const version = getPackageVersion(projectDir);
217
+ const env = getEnvironmentInfo();
218
+
219
+ // Get policy hashes
220
+ const guardrailsHash = await getPolicyHash(projectDir, 'guardrails');
221
+ const confidenceHash = await getPolicyHash(projectDir, 'confidence');
222
+
223
+ const gaStatus = await getGAStatus(projectDir);
224
+ const schemaVersion = getSchemaVersion(projectDir);
225
+ const hashes = getArtifactHashes(projectDir);
226
+
227
+ const provenance = {
228
+ version: version || 'unknown',
229
+ git: {
230
+ commit: gitCommit || 'unknown',
231
+ dirty: false
232
+ },
233
+ env: {
234
+ node: env.node,
235
+ os: env.os,
236
+ arch: env.arch
237
+ },
238
+ policies: {
239
+ guardrails: guardrailsHash || 'unknown',
240
+ confidence: confidenceHash || 'unknown'
241
+ },
242
+ gaStatus: gaStatus,
243
+ artifacts: {
244
+ schemaVersion: schemaVersion
245
+ },
246
+ hashes: hashes,
247
+ builtAt: new Date().toISOString()
248
+ };
249
+
250
+ return provenance;
251
+ }
252
+
253
+ /**
254
+ * Write provenance to file
255
+ *
256
+ * @param {string} projectDir - Project directory
257
+ * @param {Object} provenance - Provenance object
258
+ * @returns {string} Path to written file
259
+ */
260
+ export function writeProvenance(projectDir, provenance) {
261
+ const outputDir = resolve(projectDir, 'release');
262
+ if (!existsSync(outputDir)) {
263
+ mkdirSync(outputDir, { recursive: true });
264
+ }
265
+
266
+ const outputPath = resolve(outputDir, 'release.provenance.json');
267
+ writeFileSync(outputPath, JSON.stringify(provenance, null, 2), 'utf-8');
268
+
269
+ return outputPath;
270
+ }
271
+
@@ -0,0 +1,40 @@
1
+ /**
2
+ * ENTERPRISE READINESS — Release Report Writer
3
+ *
4
+ * Writes release.report.json artifact with release readiness check results.
5
+ */
6
+
7
+ import { writeFileSync, mkdirSync, existsSync } from 'fs';
8
+ import { resolve } from 'path';
9
+
10
+ /**
11
+ * Write release report
12
+ *
13
+ * @param {string} projectDir - Project directory
14
+ * @param {Object} releaseStatus - Release readiness status
15
+ * @returns {string} Path to written file
16
+ */
17
+ export function writeReleaseReport(projectDir, releaseStatus) {
18
+ const outputDir = resolve(projectDir, 'release');
19
+ if (!existsSync(outputDir)) {
20
+ mkdirSync(outputDir, { recursive: true });
21
+ }
22
+
23
+ const reportPath = resolve(outputDir, 'release.report.json');
24
+
25
+ const report = {
26
+ contractVersion: 1,
27
+ generatedAt: new Date().toISOString(),
28
+ releaseReady: releaseStatus.releaseReady || false,
29
+ status: releaseStatus.status || {},
30
+ summary: releaseStatus.summary || {},
31
+ failureCodes: Object.entries(releaseStatus.status || {})
32
+ .filter(([_, s]) => !s.ok)
33
+ .flatMap(([key, s]) => s.blockers?.map(b => `${key.toUpperCase()}_${b.code || 'BLOCKED'}`) || [])
34
+ };
35
+
36
+ writeFileSync(reportPath, JSON.stringify(report, null, 2), 'utf-8');
37
+
38
+ return reportPath;
39
+ }
40
+
@@ -0,0 +1,159 @@
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 { createInternalFailure } from '../failures/failure.factory.js';
12
+ import { FAILURE_CODE } from '../failures/failure.types.js';
13
+ import { isBaselineFrozen, enforceBaseline } from '../baseline/baseline.enforcer.js';
14
+ import { FailureLedger } from '../failures/failure.ledger.js';
15
+
16
+ /**
17
+ * Check if release is allowed
18
+ *
19
+ * @param {string} projectDir - Project directory
20
+ * @param {string} operation - Operation name (publish, release, tag)
21
+ * @throws {Error} If release is blocked
22
+ */
23
+ export async function enforceReleaseReadiness(projectDir, operation = 'release') {
24
+ const blockers = [];
25
+
26
+ // 1. Check GA status
27
+ try {
28
+ const runId = findLatestRunId(projectDir);
29
+ if (!runId) {
30
+ blockers.push('No runs found. Run a scan and verify GA readiness first.');
31
+ } else {
32
+ const gaCheck = checkGAStatus(projectDir, runId);
33
+ if (!gaCheck.ready) {
34
+ const blockerMessages = gaCheck.status?.blockers?.map(b => b.message).join('; ') || 'GA not ready';
35
+ blockers.push(`GA-BLOCKED: ${blockerMessages}`);
36
+ }
37
+ }
38
+ } catch (error) {
39
+ blockers.push(`GA check failed: ${error.message}`);
40
+ }
41
+
42
+ // 2. Check Provenance
43
+ const provenancePath = resolve(projectDir, 'release', 'release.provenance.json');
44
+ if (!existsSync(provenancePath)) {
45
+ blockers.push('Provenance not found. Run "verax release:check" first.');
46
+ } else {
47
+ try {
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
+ const sbom = JSON.parse(readFileSync(sbomPath, 'utf-8'));
67
+ if (!sbom.bomFormat || !sbom.components || !Array.isArray(sbom.components) || sbom.components.length === 0) {
68
+ blockers.push('Invalid or empty SBOM');
69
+ }
70
+ } catch (error) {
71
+ blockers.push(`Invalid SBOM: ${error.message}`);
72
+ }
73
+ }
74
+
75
+ // 4. Check Reproducibility
76
+ const reproducibilityPath = resolve(projectDir, 'release', 'reproducibility.report.json');
77
+ if (!existsSync(reproducibilityPath)) {
78
+ blockers.push('Reproducibility report not found. Run "verax release:check" first.');
79
+ } else {
80
+ try {
81
+ const report = JSON.parse(readFileSync(reproducibilityPath, 'utf-8'));
82
+ if (report.verdict !== 'REPRODUCIBLE') {
83
+ const differences = report.differences?.map(d => d.message).join('; ') || 'Build is not reproducible';
84
+ blockers.push(`NON_REPRODUCIBLE: ${differences}`);
85
+ }
86
+ } catch (error) {
87
+ blockers.push(`Invalid reproducibility report: ${error.message}`);
88
+ }
89
+ }
90
+
91
+ // 5. Check Security (PHASE 21.8)
92
+ const secretsPath = resolve(projectDir, 'release', 'security.secrets.report.json');
93
+ const vulnPath = resolve(projectDir, 'release', 'security.vuln.report.json');
94
+ const supplyChainPath = resolve(projectDir, 'release', 'security.supplychain.report.json');
95
+
96
+ if (!existsSync(secretsPath) || !existsSync(vulnPath) || !existsSync(supplyChainPath)) {
97
+ blockers.push('Security reports not found. Run "verax security:check" first.');
98
+ } else {
99
+ try {
100
+ // Check secrets
101
+ const secretsReport = JSON.parse(readFileSync(secretsPath, 'utf-8'));
102
+ if (secretsReport.hasSecrets) {
103
+ blockers.push(`SECURITY-BLOCKED: Secrets detected (${secretsReport.summary.total} finding(s))`);
104
+ }
105
+
106
+ // Check vulnerabilities
107
+ const vulnReport = JSON.parse(readFileSync(vulnPath, 'utf-8'));
108
+ if (vulnReport.blocking) {
109
+ blockers.push(`SECURITY-BLOCKED: Critical/High vulnerabilities detected (${vulnReport.summary.critical + vulnReport.summary.high} total)`);
110
+ }
111
+
112
+ // Check supply-chain
113
+ const supplyChainReport = JSON.parse(readFileSync(supplyChainPath, 'utf-8'));
114
+ if (!supplyChainReport.ok) {
115
+ blockers.push(`SECURITY-BLOCKED: Supply-chain violations (${supplyChainReport.summary.totalViolations} violation(s))`);
116
+ }
117
+ } catch (error) {
118
+ blockers.push(`Security check failed: ${error.message}`);
119
+ }
120
+ }
121
+
122
+ // 6. Check Performance (PHASE 21.9) - BLOCKING perf violations block release
123
+ const runId = findLatestRunId(projectDir);
124
+ if (runId) {
125
+ try {
126
+ const { checkPerformanceStatus } = await import('../perf/perf.enforcer.js');
127
+ const perfCheck = checkPerformanceStatus(projectDir, runId);
128
+
129
+ if (perfCheck.exists && !perfCheck.ok) {
130
+ blockers.push(`PERFORMANCE-BLOCKED: ${perfCheck.blockers.join('; ')}`);
131
+ }
132
+ } catch (error) {
133
+ // Performance check failure is not a blocker (may be from old runs)
134
+ }
135
+ }
136
+
137
+ // 7. Baseline Freeze Enforcement (PHASE 21.11)
138
+ // After GA, baseline must be frozen and unchanged
139
+ if (isBaselineFrozen(projectDir)) {
140
+ const failureLedger = new FailureLedger(projectDir, runId || 'unknown');
141
+ const baselineCheck = enforceBaseline(projectDir, failureLedger);
142
+ if (baselineCheck.blocked) {
143
+ blockers.push(`BASELINE-DRIFT: ${baselineCheck.message}. Changes to core contracts/policies after GA require MAJOR version bump and baseline regeneration.`);
144
+ }
145
+ }
146
+
147
+ // If any blockers, throw error
148
+ if (blockers.length > 0) {
149
+ const failure = createInternalFailure(
150
+ FAILURE_CODE.INTERNAL_UNEXPECTED_ERROR,
151
+ `Cannot ${operation}: RELEASE-BLOCKED. ${blockers.join('; ')}`,
152
+ 'release.enforcer',
153
+ { operation, blockers },
154
+ null
155
+ );
156
+ throw failure;
157
+ }
158
+ }
159
+