@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,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.9 — Performance Monitor
|
|
3
|
+
*
|
|
4
|
+
* Monitors runtime, memory, event loop delay, and execution counters.
|
|
5
|
+
* Records timestamped samples and peak values.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { performance } from 'perf_hooks';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Performance Monitor
|
|
12
|
+
*/
|
|
13
|
+
export class PerformanceMonitor {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.startTime = performance.now();
|
|
16
|
+
this.startMemory = process.memoryUsage();
|
|
17
|
+
this.samples = [];
|
|
18
|
+
this.peakMemoryRSS = this.startMemory.rss;
|
|
19
|
+
this.peakMemoryHeapUsed = this.startMemory.heapUsed;
|
|
20
|
+
this.peakMemoryHeapTotal = this.startMemory.heapTotal;
|
|
21
|
+
this.peakEventLoopDelay = 0;
|
|
22
|
+
|
|
23
|
+
// Counters
|
|
24
|
+
this.pagesVisited = 0;
|
|
25
|
+
this.interactionsExecuted = 0;
|
|
26
|
+
|
|
27
|
+
// Phase tracking
|
|
28
|
+
this.phaseStartTimes = {};
|
|
29
|
+
this.phaseDurations = {};
|
|
30
|
+
|
|
31
|
+
// Event loop monitoring
|
|
32
|
+
this.eventLoopMonitor = null;
|
|
33
|
+
this.lastEventLoopCheck = performance.now();
|
|
34
|
+
this.eventLoopDelays = [];
|
|
35
|
+
|
|
36
|
+
this.startEventLoopMonitoring();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Start event loop delay monitoring
|
|
41
|
+
*/
|
|
42
|
+
startEventLoopMonitoring() {
|
|
43
|
+
const checkInterval = 100; // Check every 100ms
|
|
44
|
+
|
|
45
|
+
this.eventLoopMonitor = setInterval(() => {
|
|
46
|
+
const now = performance.now();
|
|
47
|
+
const delay = now - this.lastEventLoopCheck - checkInterval;
|
|
48
|
+
|
|
49
|
+
if (delay > 0) {
|
|
50
|
+
this.eventLoopDelays.push(delay);
|
|
51
|
+
if (delay > this.peakEventLoopDelay) {
|
|
52
|
+
this.peakEventLoopDelay = delay;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.lastEventLoopCheck = now;
|
|
57
|
+
}, checkInterval);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Record a sample
|
|
62
|
+
*/
|
|
63
|
+
sample(phase = null) {
|
|
64
|
+
const now = performance.now();
|
|
65
|
+
const memory = process.memoryUsage();
|
|
66
|
+
|
|
67
|
+
// Update peaks
|
|
68
|
+
if (memory.rss > this.peakMemoryRSS) {
|
|
69
|
+
this.peakMemoryRSS = memory.rss;
|
|
70
|
+
}
|
|
71
|
+
if (memory.heapUsed > this.peakMemoryHeapUsed) {
|
|
72
|
+
this.peakMemoryHeapUsed = memory.heapUsed;
|
|
73
|
+
}
|
|
74
|
+
if (memory.heapTotal > this.peakMemoryHeapTotal) {
|
|
75
|
+
this.peakMemoryHeapTotal = memory.heapTotal;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Calculate average event loop delay
|
|
79
|
+
const avgEventLoopDelay = this.eventLoopDelays.length > 0
|
|
80
|
+
? this.eventLoopDelays.reduce((a, b) => a + b, 0) / this.eventLoopDelays.length
|
|
81
|
+
: 0;
|
|
82
|
+
|
|
83
|
+
this.samples.push({
|
|
84
|
+
timestamp: now,
|
|
85
|
+
elapsedMs: now - this.startTime,
|
|
86
|
+
phase,
|
|
87
|
+
memory: {
|
|
88
|
+
rss: memory.rss,
|
|
89
|
+
heapUsed: memory.heapUsed,
|
|
90
|
+
heapTotal: memory.heapTotal,
|
|
91
|
+
external: memory.external
|
|
92
|
+
},
|
|
93
|
+
eventLoopDelay: avgEventLoopDelay,
|
|
94
|
+
pagesVisited: this.pagesVisited,
|
|
95
|
+
interactionsExecuted: this.interactionsExecuted
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Keep only last 1000 samples to avoid memory bloat
|
|
99
|
+
if (this.samples.length > 1000) {
|
|
100
|
+
this.samples.shift();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Reset event loop delays array periodically
|
|
104
|
+
if (this.eventLoopDelays.length > 100) {
|
|
105
|
+
this.eventLoopDelays = this.eventLoopDelays.slice(-50);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Start a phase
|
|
111
|
+
*/
|
|
112
|
+
startPhase(phaseName) {
|
|
113
|
+
this.phaseStartTimes[phaseName] = performance.now();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* End a phase
|
|
118
|
+
*/
|
|
119
|
+
endPhase(phaseName) {
|
|
120
|
+
if (this.phaseStartTimes[phaseName]) {
|
|
121
|
+
const duration = performance.now() - this.phaseStartTimes[phaseName];
|
|
122
|
+
this.phaseDurations[phaseName] = (this.phaseDurations[phaseName] || 0) + duration;
|
|
123
|
+
delete this.phaseStartTimes[phaseName];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Increment pages visited
|
|
129
|
+
*/
|
|
130
|
+
incrementPages() {
|
|
131
|
+
this.pagesVisited++;
|
|
132
|
+
this.sample('PAGE_VISIT');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Increment interactions executed
|
|
137
|
+
*/
|
|
138
|
+
incrementInteractions() {
|
|
139
|
+
this.interactionsExecuted++;
|
|
140
|
+
this.sample('INTERACTION');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get current metrics
|
|
145
|
+
*/
|
|
146
|
+
getCurrentMetrics() {
|
|
147
|
+
const now = performance.now();
|
|
148
|
+
const memory = process.memoryUsage();
|
|
149
|
+
const avgEventLoopDelay = this.eventLoopDelays.length > 0
|
|
150
|
+
? this.eventLoopDelays.reduce((a, b) => a + b, 0) / this.eventLoopDelays.length
|
|
151
|
+
: 0;
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
runtimeMs: now - this.startTime,
|
|
155
|
+
memoryRSS: memory.rss,
|
|
156
|
+
memoryHeapUsed: memory.heapUsed,
|
|
157
|
+
memoryHeapTotal: memory.heapTotal,
|
|
158
|
+
peakMemoryRSS: this.peakMemoryRSS,
|
|
159
|
+
peakMemoryHeapUsed: this.peakMemoryHeapUsed,
|
|
160
|
+
peakEventLoopDelay: this.peakEventLoopDelay,
|
|
161
|
+
avgEventLoopDelay: avgEventLoopDelay,
|
|
162
|
+
pagesVisited: this.pagesVisited,
|
|
163
|
+
interactionsExecuted: this.interactionsExecuted,
|
|
164
|
+
phaseDurations: { ...this.phaseDurations }
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get final report
|
|
170
|
+
*/
|
|
171
|
+
getFinalReport() {
|
|
172
|
+
const metrics = this.getCurrentMetrics();
|
|
173
|
+
|
|
174
|
+
// Find slow phases
|
|
175
|
+
const slowPhases = Object.entries(this.phaseDurations)
|
|
176
|
+
.filter(([_, duration]) => duration > 5000) // Phases > 5s
|
|
177
|
+
.map(([phase, duration]) => ({
|
|
178
|
+
phase,
|
|
179
|
+
durationMs: duration,
|
|
180
|
+
percentage: (duration / metrics.runtimeMs) * 100
|
|
181
|
+
}))
|
|
182
|
+
.sort((a, b) => b.durationMs - a.durationMs);
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
...metrics,
|
|
186
|
+
slowPhases,
|
|
187
|
+
sampleCount: this.samples.length,
|
|
188
|
+
samples: this.samples.slice(-100) // Last 100 samples only
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Stop monitoring
|
|
194
|
+
*/
|
|
195
|
+
stop() {
|
|
196
|
+
if (this.eventLoopMonitor) {
|
|
197
|
+
clearInterval(this.eventLoopMonitor);
|
|
198
|
+
this.eventLoopMonitor = null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Create a performance monitor instance
|
|
205
|
+
*/
|
|
206
|
+
export function createPerformanceMonitor() {
|
|
207
|
+
return new PerformanceMonitor();
|
|
208
|
+
}
|
|
209
|
+
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.9 — Performance Report
|
|
3
|
+
*
|
|
4
|
+
* Generates performance.report.json with budgets, actual usage, peaks, and violations.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync, statSync, readdirSync } from 'fs';
|
|
8
|
+
import { resolve } from 'path';
|
|
9
|
+
import { getPerfBudget, evaluatePerfBudget } from './perf.contract.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Calculate artifacts size
|
|
13
|
+
*
|
|
14
|
+
* @param {string} runDir - Run directory
|
|
15
|
+
* @returns {number} Size in MB
|
|
16
|
+
*/
|
|
17
|
+
function calculateArtifactsSize(runDir) {
|
|
18
|
+
let totalSize = 0;
|
|
19
|
+
|
|
20
|
+
function scanDirectory(dir) {
|
|
21
|
+
try {
|
|
22
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
23
|
+
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
const fullPath = resolve(dir, entry.name);
|
|
26
|
+
|
|
27
|
+
if (entry.isDirectory()) {
|
|
28
|
+
scanDirectory(fullPath);
|
|
29
|
+
} else if (entry.isFile()) {
|
|
30
|
+
try {
|
|
31
|
+
const stats = statSync(fullPath);
|
|
32
|
+
totalSize += stats.size;
|
|
33
|
+
} catch {
|
|
34
|
+
// Skip unreadable files
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
// Skip inaccessible directories
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (existsSync(runDir)) {
|
|
44
|
+
scanDirectory(runDir);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return totalSize / (1024 * 1024); // Convert to MB
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Load pipeline stage timings from run.meta.json
|
|
52
|
+
*
|
|
53
|
+
* @param {string} projectDir - Project directory
|
|
54
|
+
* @param {string} runId - Run ID
|
|
55
|
+
* @returns {Object|null} Stage timings or null
|
|
56
|
+
*/
|
|
57
|
+
function loadPipelineStageTimings(projectDir, runId) {
|
|
58
|
+
const runDir = resolve(projectDir, '.verax', 'runs', runId);
|
|
59
|
+
const metaPath = resolve(runDir, 'run.meta.json');
|
|
60
|
+
|
|
61
|
+
if (!existsSync(metaPath)) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
|
|
67
|
+
return meta.pipelineStages || null;
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Generate performance report
|
|
75
|
+
*
|
|
76
|
+
* @param {string} projectDir - Project directory
|
|
77
|
+
* @param {string} runId - Run ID
|
|
78
|
+
* @param {Object} monitorReport - Monitor report from PerformanceMonitor
|
|
79
|
+
* @param {string} profile - Budget profile name
|
|
80
|
+
* @returns {Object} Performance report
|
|
81
|
+
*/
|
|
82
|
+
export function generatePerformanceReport(projectDir, runId, monitorReport, profile = 'STANDARD') {
|
|
83
|
+
const budget = getPerfBudget(profile);
|
|
84
|
+
const runDir = resolve(projectDir, '.verax', 'runs', runId);
|
|
85
|
+
const artifactsSizeMB = calculateArtifactsSize(runDir);
|
|
86
|
+
|
|
87
|
+
// Load pipeline stage timings
|
|
88
|
+
const pipelineStages = loadPipelineStageTimings(projectDir, runId);
|
|
89
|
+
|
|
90
|
+
const actual = {
|
|
91
|
+
runtimeMs: monitorReport.runtimeMs,
|
|
92
|
+
memoryRSS: monitorReport.memoryRSS,
|
|
93
|
+
pagesVisited: monitorReport.pagesVisited,
|
|
94
|
+
interactionsExecuted: monitorReport.interactionsExecuted,
|
|
95
|
+
artifactsSizeMB: artifactsSizeMB,
|
|
96
|
+
eventLoopDelayMs: monitorReport.avgEventLoopDelay || 0
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const evaluation = evaluatePerfBudget(actual, budget);
|
|
100
|
+
|
|
101
|
+
// Extract stage timings if available
|
|
102
|
+
const stageTimings = {};
|
|
103
|
+
if (pipelineStages) {
|
|
104
|
+
for (const [stageName, stageData] of Object.entries(pipelineStages)) {
|
|
105
|
+
if (stageData.durationMs !== null && stageData.durationMs !== undefined) {
|
|
106
|
+
stageTimings[stageName] = {
|
|
107
|
+
durationMs: stageData.durationMs,
|
|
108
|
+
startedAt: stageData.startedAt,
|
|
109
|
+
completedAt: stageData.completedAt,
|
|
110
|
+
status: stageData.status
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const report = {
|
|
117
|
+
contractVersion: 1,
|
|
118
|
+
runId,
|
|
119
|
+
profile,
|
|
120
|
+
budget: {
|
|
121
|
+
maxRuntimeMs: budget.maxRuntimeMs,
|
|
122
|
+
maxMemoryRSS: budget.maxMemoryRSS,
|
|
123
|
+
maxPagesVisited: budget.maxPagesVisited,
|
|
124
|
+
maxInteractionsExecuted: budget.maxInteractionsExecuted,
|
|
125
|
+
maxArtifactsSizeMB: budget.maxArtifactsSizeMB,
|
|
126
|
+
maxEventLoopDelayMs: budget.maxEventLoopDelayMs
|
|
127
|
+
},
|
|
128
|
+
actual,
|
|
129
|
+
peaks: {
|
|
130
|
+
memoryRSS: monitorReport.peakMemoryRSS,
|
|
131
|
+
memoryHeapUsed: monitorReport.peakMemoryHeapUsed,
|
|
132
|
+
memoryHeapTotal: monitorReport.peakMemoryHeapTotal,
|
|
133
|
+
eventLoopDelay: monitorReport.peakEventLoopDelay
|
|
134
|
+
},
|
|
135
|
+
stageTimings: Object.keys(stageTimings).length > 0 ? stageTimings : null,
|
|
136
|
+
networkRequests: monitorReport.networkRequests || 0,
|
|
137
|
+
violations: evaluation.violations,
|
|
138
|
+
warnings: evaluation.warnings,
|
|
139
|
+
verdict: evaluation.verdict,
|
|
140
|
+
ok: evaluation.ok,
|
|
141
|
+
summary: {
|
|
142
|
+
...evaluation.summary,
|
|
143
|
+
runtimeMs: actual.runtimeMs,
|
|
144
|
+
memoryRSSMB: (actual.memoryRSS / (1024 * 1024)).toFixed(2),
|
|
145
|
+
pagesVisited: actual.pagesVisited,
|
|
146
|
+
networkRequests: monitorReport.networkRequests || 0,
|
|
147
|
+
interactionsExecuted: actual.interactionsExecuted,
|
|
148
|
+
artifactsSizeMB: actual.artifactsSizeMB.toFixed(2),
|
|
149
|
+
slowPhases: monitorReport.slowPhases || []
|
|
150
|
+
},
|
|
151
|
+
generatedAt: new Date().toISOString()
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return report;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Write performance report
|
|
159
|
+
*
|
|
160
|
+
* @param {string} projectDir - Project directory
|
|
161
|
+
* @param {string} runId - Run ID
|
|
162
|
+
* @param {Object} report - Performance report
|
|
163
|
+
* @returns {string} Path to written file
|
|
164
|
+
*/
|
|
165
|
+
export function writePerformanceReport(projectDir, runId, report) {
|
|
166
|
+
const runDir = resolve(projectDir, '.verax', 'runs', runId);
|
|
167
|
+
if (!existsSync(runDir)) {
|
|
168
|
+
mkdirSync(runDir, { recursive: true });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const outputPath = resolve(runDir, 'performance.report.json');
|
|
172
|
+
writeFileSync(outputPath, JSON.stringify(report, null, 2), 'utf-8');
|
|
173
|
+
|
|
174
|
+
return outputPath;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Load performance report
|
|
179
|
+
*
|
|
180
|
+
* @param {string} projectDir - Project directory
|
|
181
|
+
* @param {string} runId - Run ID
|
|
182
|
+
* @returns {Object|null} Performance report or null
|
|
183
|
+
*/
|
|
184
|
+
export function loadPerformanceReport(projectDir, runId) {
|
|
185
|
+
const reportPath = resolve(projectDir, '.verax', 'runs', runId, 'performance.report.json');
|
|
186
|
+
|
|
187
|
+
if (!existsSync(reportPath)) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const content = readFileSync(reportPath, 'utf-8');
|
|
193
|
+
return JSON.parse(content);
|
|
194
|
+
} catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE: Pipeline Stage Tracker
|
|
3
|
+
*
|
|
4
|
+
* Enforces explicit, single execution path with stage-by-stage tracking.
|
|
5
|
+
* Every stage must be recorded in run.meta.json with timestamp.
|
|
6
|
+
* No stage may execute without being recorded.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
10
|
+
import { dirname } from 'path';
|
|
11
|
+
import { getArtifactPath } from './run-id.js';
|
|
12
|
+
import { computeRunFingerprint } from './determinism/run-fingerprint.js';
|
|
13
|
+
|
|
14
|
+
const PIPELINE_STAGES = {
|
|
15
|
+
LEARN: 'LEARN',
|
|
16
|
+
OBSERVE: 'OBSERVE',
|
|
17
|
+
DETECT: 'DETECT',
|
|
18
|
+
WRITE: 'WRITE',
|
|
19
|
+
VERIFY: 'VERIFY',
|
|
20
|
+
VERDICT: 'VERDICT'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const STAGE_ORDER = [
|
|
24
|
+
PIPELINE_STAGES.LEARN,
|
|
25
|
+
PIPELINE_STAGES.OBSERVE,
|
|
26
|
+
PIPELINE_STAGES.DETECT,
|
|
27
|
+
PIPELINE_STAGES.WRITE,
|
|
28
|
+
PIPELINE_STAGES.VERIFY,
|
|
29
|
+
PIPELINE_STAGES.VERDICT
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Pipeline Stage Tracker
|
|
34
|
+
*
|
|
35
|
+
* Enforces single, explicit execution path with mandatory stage recording.
|
|
36
|
+
*/
|
|
37
|
+
export class PipelineTracker {
|
|
38
|
+
constructor(projectDir, runId, runFingerprintParams = null) {
|
|
39
|
+
this.projectDir = projectDir;
|
|
40
|
+
this.runId = runId;
|
|
41
|
+
this.stages = {};
|
|
42
|
+
this.currentStage = null;
|
|
43
|
+
this.completedStages = [];
|
|
44
|
+
this.metaPath = getArtifactPath(projectDir, runId, 'run.meta.json');
|
|
45
|
+
this.runFingerprintParams = runFingerprintParams;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Start a pipeline stage
|
|
50
|
+
*
|
|
51
|
+
* @param {string} stageName - Stage name (must be from PIPELINE_STAGES)
|
|
52
|
+
* @throws {Error} If stage is invalid or out of order
|
|
53
|
+
*/
|
|
54
|
+
startStage(stageName) {
|
|
55
|
+
if (!Object.values(PIPELINE_STAGES).includes(stageName)) {
|
|
56
|
+
throw new Error(`Invalid pipeline stage: ${stageName}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const stageIndex = STAGE_ORDER.indexOf(stageName);
|
|
60
|
+
if (stageIndex === -1) {
|
|
61
|
+
throw new Error(`Unknown pipeline stage: ${stageName}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (this.currentStage !== null) {
|
|
65
|
+
throw new Error(`Cannot start ${stageName}: ${this.currentStage} is still active`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const expectedIndex = this.completedStages.length;
|
|
69
|
+
if (stageIndex !== expectedIndex) {
|
|
70
|
+
throw new Error(`Pipeline stage out of order: expected ${STAGE_ORDER[expectedIndex]}, got ${stageName}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.currentStage = stageName;
|
|
74
|
+
this.stages[stageName] = {
|
|
75
|
+
name: stageName,
|
|
76
|
+
startedAt: new Date().toISOString(),
|
|
77
|
+
completedAt: null,
|
|
78
|
+
durationMs: null,
|
|
79
|
+
status: 'RUNNING'
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
this._writeMeta();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Complete a pipeline stage
|
|
87
|
+
*
|
|
88
|
+
* @param {string} stageName - Stage name (must match current stage)
|
|
89
|
+
* @param {Object} metadata - Optional stage metadata
|
|
90
|
+
* @throws {Error} If stage name doesn't match current stage
|
|
91
|
+
*/
|
|
92
|
+
completeStage(stageName, metadata = {}) {
|
|
93
|
+
if (this.currentStage !== stageName) {
|
|
94
|
+
throw new Error(`Cannot complete ${stageName}: current stage is ${this.currentStage}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const completedAt = new Date().toISOString();
|
|
98
|
+
const startedAt = new Date(this.stages[stageName].startedAt);
|
|
99
|
+
const durationMs = new Date(completedAt) - startedAt;
|
|
100
|
+
|
|
101
|
+
this.stages[stageName] = {
|
|
102
|
+
...this.stages[stageName],
|
|
103
|
+
completedAt,
|
|
104
|
+
durationMs,
|
|
105
|
+
status: 'COMPLETE',
|
|
106
|
+
...metadata
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
this.completedStages.push(stageName);
|
|
110
|
+
this.currentStage = null;
|
|
111
|
+
|
|
112
|
+
this._writeMeta();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Fail a pipeline stage
|
|
117
|
+
*
|
|
118
|
+
* @param {string} stageName - Stage name (must match current stage)
|
|
119
|
+
* @param {Error|string} error - Error object or message
|
|
120
|
+
* @throws {Error} If stage name doesn't match current stage
|
|
121
|
+
*/
|
|
122
|
+
failStage(stageName, error) {
|
|
123
|
+
if (this.currentStage !== stageName) {
|
|
124
|
+
throw new Error(`Cannot fail ${stageName}: current stage is ${this.currentStage}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const completedAt = new Date().toISOString();
|
|
128
|
+
const startedAt = new Date(this.stages[stageName].startedAt);
|
|
129
|
+
const durationMs = new Date(completedAt) - startedAt;
|
|
130
|
+
|
|
131
|
+
this.stages[stageName] = {
|
|
132
|
+
...this.stages[stageName],
|
|
133
|
+
completedAt,
|
|
134
|
+
durationMs,
|
|
135
|
+
status: 'FAILED',
|
|
136
|
+
error: error instanceof Error ? error.message : String(error),
|
|
137
|
+
errorStack: error instanceof Error ? error.stack : null
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
this.completedStages.push(stageName);
|
|
141
|
+
this.currentStage = null;
|
|
142
|
+
|
|
143
|
+
this._writeMeta();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get current stage
|
|
148
|
+
*
|
|
149
|
+
* @returns {string|null} Current stage name or null
|
|
150
|
+
*/
|
|
151
|
+
getCurrentStage() {
|
|
152
|
+
return this.currentStage;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get stage execution data
|
|
157
|
+
*
|
|
158
|
+
* @param {string} stageName - Stage name
|
|
159
|
+
* @returns {Object|null} Stage data or null if not executed
|
|
160
|
+
*/
|
|
161
|
+
getStage(stageName) {
|
|
162
|
+
return this.stages[stageName] || null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get all stages
|
|
167
|
+
*
|
|
168
|
+
* @returns {Object} Map of stage names to stage data
|
|
169
|
+
*/
|
|
170
|
+
getAllStages() {
|
|
171
|
+
return { ...this.stages };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Check if a stage has completed
|
|
176
|
+
*
|
|
177
|
+
* @param {string} stageName - Stage name
|
|
178
|
+
* @returns {boolean} True if stage completed
|
|
179
|
+
*/
|
|
180
|
+
hasCompleted(stageName) {
|
|
181
|
+
return this.completedStages.includes(stageName);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Check if verification has completed (required before verdict)
|
|
186
|
+
*
|
|
187
|
+
* @returns {boolean} True if verification completed
|
|
188
|
+
*/
|
|
189
|
+
hasVerificationCompleted() {
|
|
190
|
+
return this.hasCompleted(PIPELINE_STAGES.VERIFY);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Write run.meta.json with stage execution data
|
|
195
|
+
*/
|
|
196
|
+
_writeMeta() {
|
|
197
|
+
// Ensure directory exists
|
|
198
|
+
try {
|
|
199
|
+
mkdirSync(dirname(this.metaPath), { recursive: true });
|
|
200
|
+
} catch {
|
|
201
|
+
// Directory might already exist, ignore
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let existingMeta = {};
|
|
205
|
+
try {
|
|
206
|
+
const content = readFileSync(this.metaPath, 'utf-8');
|
|
207
|
+
existingMeta = JSON.parse(content);
|
|
208
|
+
} catch {
|
|
209
|
+
existingMeta = {};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// PHASE 25: Compute run fingerprint if params provided
|
|
213
|
+
let runFingerprint = existingMeta.runFingerprint || null;
|
|
214
|
+
if (this.runFingerprintParams && !runFingerprint) {
|
|
215
|
+
runFingerprint = computeRunFingerprint({
|
|
216
|
+
...this.runFingerprintParams,
|
|
217
|
+
projectDir: this.projectDir
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const updatedMeta = {
|
|
222
|
+
...existingMeta,
|
|
223
|
+
runId: this.runId,
|
|
224
|
+
runFingerprint,
|
|
225
|
+
pipeline: {
|
|
226
|
+
stages: this.stages,
|
|
227
|
+
completedStages: this.completedStages,
|
|
228
|
+
currentStage: this.currentStage,
|
|
229
|
+
lastUpdated: new Date().toISOString()
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
writeFileSync(this.metaPath, JSON.stringify(updatedMeta, null, 2) + '\n', 'utf-8');
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export { PIPELINE_STAGES, STAGE_ORDER };
|
|
238
|
+
|