@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,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
|
+
|