@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,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.10 — Unified Run Timeline
|
|
3
|
+
*
|
|
4
|
+
* Builds chronological timeline of all events in a run.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, existsSync, writeFileSync } from 'fs';
|
|
8
|
+
import { resolve } from 'path';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Load artifact JSON
|
|
12
|
+
*/
|
|
13
|
+
function loadArtifact(runDir, filename) {
|
|
14
|
+
const path = resolve(runDir, filename);
|
|
15
|
+
if (!existsSync(path)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Build timeline from run artifacts
|
|
27
|
+
*
|
|
28
|
+
* @param {string} projectDir - Project directory
|
|
29
|
+
* @param {string} runId - Run ID
|
|
30
|
+
* @returns {Object} Timeline object
|
|
31
|
+
*/
|
|
32
|
+
export function buildRunTimeline(projectDir, runId) {
|
|
33
|
+
const runDir = resolve(projectDir, '.verax', 'runs', runId);
|
|
34
|
+
|
|
35
|
+
if (!existsSync(runDir)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const timeline = [];
|
|
40
|
+
|
|
41
|
+
// Load summary for phase timestamps
|
|
42
|
+
const summary = loadArtifact(runDir, 'summary.json');
|
|
43
|
+
const runStatus = loadArtifact(runDir, 'run.status.json');
|
|
44
|
+
const decisions = loadArtifact(runDir, 'decisions.json');
|
|
45
|
+
const failureLedger = loadArtifact(runDir, 'failure.ledger.json');
|
|
46
|
+
const performanceReport = loadArtifact(runDir, 'performance.report.json');
|
|
47
|
+
const findings = loadArtifact(runDir, 'findings.json');
|
|
48
|
+
|
|
49
|
+
// Extract timestamps
|
|
50
|
+
const startedAt = summary?.startedAt || runStatus?.startedAt;
|
|
51
|
+
const completedAt = summary?.completedAt || runStatus?.completedAt;
|
|
52
|
+
|
|
53
|
+
// Phase events
|
|
54
|
+
if (startedAt) {
|
|
55
|
+
timeline.push({
|
|
56
|
+
timestamp: startedAt,
|
|
57
|
+
phase: 'RUN_START',
|
|
58
|
+
event: 'run_started',
|
|
59
|
+
data: {
|
|
60
|
+
runId,
|
|
61
|
+
url: summary?.url || null
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// LEARN phase
|
|
67
|
+
if (summary?.metrics?.learnMs) {
|
|
68
|
+
timeline.push({
|
|
69
|
+
timestamp: startedAt || null,
|
|
70
|
+
phase: 'LEARN',
|
|
71
|
+
event: 'phase_started',
|
|
72
|
+
data: {
|
|
73
|
+
durationMs: summary.metrics.learnMs
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
timeline.push({
|
|
77
|
+
timestamp: startedAt ? new Date(Date.parse(startedAt) + summary.metrics.learnMs).toISOString() : null,
|
|
78
|
+
phase: 'LEARN',
|
|
79
|
+
event: 'phase_completed',
|
|
80
|
+
data: {
|
|
81
|
+
durationMs: summary.metrics.learnMs,
|
|
82
|
+
expectationsFound: summary.digest?.expectationsTotal || 0
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// OBSERVE phase
|
|
88
|
+
if (summary?.metrics?.observeMs) {
|
|
89
|
+
const observeStart = startedAt ? new Date(Date.parse(startedAt) + (summary.metrics.learnMs || 0)).toISOString() : null;
|
|
90
|
+
timeline.push({
|
|
91
|
+
timestamp: observeStart,
|
|
92
|
+
phase: 'OBSERVE',
|
|
93
|
+
event: 'phase_started',
|
|
94
|
+
data: {
|
|
95
|
+
durationMs: summary.metrics.observeMs
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
timeline.push({
|
|
99
|
+
timestamp: observeStart ? new Date(Date.parse(observeStart) + summary.metrics.observeMs).toISOString() : null,
|
|
100
|
+
phase: 'OBSERVE',
|
|
101
|
+
event: 'phase_completed',
|
|
102
|
+
data: {
|
|
103
|
+
durationMs: summary.metrics.observeMs,
|
|
104
|
+
interactionsExecuted: summary.digest?.attempted || 0,
|
|
105
|
+
pagesVisited: summary.digest?.pagesVisited || 0
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// DETECT phase
|
|
111
|
+
if (summary?.metrics?.detectMs) {
|
|
112
|
+
const detectStart = startedAt ? new Date(Date.parse(startedAt) + (summary.metrics.learnMs || 0) + (summary.metrics.observeMs || 0)).toISOString() : null;
|
|
113
|
+
timeline.push({
|
|
114
|
+
timestamp: detectStart,
|
|
115
|
+
phase: 'DETECT',
|
|
116
|
+
event: 'phase_started',
|
|
117
|
+
data: {
|
|
118
|
+
durationMs: summary.metrics.detectMs
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
timeline.push({
|
|
122
|
+
timestamp: detectStart ? new Date(Date.parse(detectStart) + summary.metrics.detectMs).toISOString() : null,
|
|
123
|
+
phase: 'DETECT',
|
|
124
|
+
event: 'phase_completed',
|
|
125
|
+
data: {
|
|
126
|
+
durationMs: summary.metrics.detectMs,
|
|
127
|
+
findingsDetected: Array.isArray(findings?.findings) ? findings.findings.length : 0
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Adaptive events from decisions
|
|
133
|
+
if (decisions?.adaptiveEvents) {
|
|
134
|
+
for (const event of decisions.adaptiveEvents) {
|
|
135
|
+
timeline.push({
|
|
136
|
+
timestamp: event.timestamp || null,
|
|
137
|
+
phase: event.phase || 'UNKNOWN',
|
|
138
|
+
event: 'adaptive_decision',
|
|
139
|
+
data: {
|
|
140
|
+
decisionId: event.decisionId,
|
|
141
|
+
reason: event.reason,
|
|
142
|
+
context: event.context || {}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Budget violations from performance report
|
|
149
|
+
if (performanceReport?.violations) {
|
|
150
|
+
for (const violation of performanceReport.violations) {
|
|
151
|
+
timeline.push({
|
|
152
|
+
timestamp: performanceReport.generatedAt || null,
|
|
153
|
+
phase: 'PERFORMANCE',
|
|
154
|
+
event: 'budget_violation',
|
|
155
|
+
severity: 'BLOCKING',
|
|
156
|
+
data: {
|
|
157
|
+
type: violation.type,
|
|
158
|
+
actual: violation.actual,
|
|
159
|
+
budget: violation.budget,
|
|
160
|
+
message: violation.message
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (performanceReport?.warnings) {
|
|
167
|
+
for (const warning of performanceReport.warnings) {
|
|
168
|
+
timeline.push({
|
|
169
|
+
timestamp: performanceReport.generatedAt || null,
|
|
170
|
+
phase: 'PERFORMANCE',
|
|
171
|
+
event: 'budget_warning',
|
|
172
|
+
severity: 'DEGRADED',
|
|
173
|
+
data: {
|
|
174
|
+
type: warning.type,
|
|
175
|
+
actual: warning.actual,
|
|
176
|
+
budget: warning.budget,
|
|
177
|
+
message: warning.message
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Guardrails applied (from findings)
|
|
184
|
+
if (findings?.findings) {
|
|
185
|
+
for (const finding of findings.findings) {
|
|
186
|
+
if (finding.guardrails?.appliedRules && finding.guardrails.appliedRules.length > 0) {
|
|
187
|
+
timeline.push({
|
|
188
|
+
timestamp: finding.timestamp || null,
|
|
189
|
+
phase: 'DETECT',
|
|
190
|
+
event: 'guardrails_applied',
|
|
191
|
+
data: {
|
|
192
|
+
findingId: finding.findingId || finding.id,
|
|
193
|
+
rules: finding.guardrails.appliedRules.map(r => r.id || r),
|
|
194
|
+
finalDecision: finding.guardrails.finalDecision
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Evidence enforcement (from findings)
|
|
202
|
+
if (findings?.findings) {
|
|
203
|
+
for (const finding of findings.findings) {
|
|
204
|
+
if (finding.evidencePackage) {
|
|
205
|
+
timeline.push({
|
|
206
|
+
timestamp: finding.timestamp || null,
|
|
207
|
+
phase: 'DETECT',
|
|
208
|
+
event: 'evidence_enforced',
|
|
209
|
+
data: {
|
|
210
|
+
findingId: finding.findingId || finding.id,
|
|
211
|
+
isComplete: finding.evidencePackage.isComplete,
|
|
212
|
+
status: finding.severity || finding.status
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Failures from failure ledger
|
|
220
|
+
if (failureLedger?.failures) {
|
|
221
|
+
for (const failure of failureLedger.failures) {
|
|
222
|
+
timeline.push({
|
|
223
|
+
timestamp: failure.timestamp || (startedAt ? new Date(Date.parse(startedAt) + (failure.relativeTime || 0)).toISOString() : null),
|
|
224
|
+
phase: failure.phase || 'UNKNOWN',
|
|
225
|
+
event: 'failure_recorded',
|
|
226
|
+
severity: failure.severity || 'WARNING',
|
|
227
|
+
data: {
|
|
228
|
+
code: failure.code,
|
|
229
|
+
message: failure.message,
|
|
230
|
+
category: failure.category,
|
|
231
|
+
recoverable: failure.recoverable
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Run completion
|
|
238
|
+
if (completedAt) {
|
|
239
|
+
timeline.push({
|
|
240
|
+
timestamp: completedAt,
|
|
241
|
+
phase: 'RUN_COMPLETE',
|
|
242
|
+
event: 'run_completed',
|
|
243
|
+
data: {
|
|
244
|
+
status: summary?.status || runStatus?.status || 'UNKNOWN',
|
|
245
|
+
totalDurationMs: startedAt && completedAt ? Date.parse(completedAt) - Date.parse(startedAt) : null
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Sort by timestamp
|
|
251
|
+
timeline.sort((a, b) => {
|
|
252
|
+
if (!a.timestamp && !b.timestamp) return 0;
|
|
253
|
+
if (!a.timestamp) return 1;
|
|
254
|
+
if (!b.timestamp) return -1;
|
|
255
|
+
return Date.parse(a.timestamp) - Date.parse(b.timestamp);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
runId,
|
|
260
|
+
startedAt,
|
|
261
|
+
completedAt,
|
|
262
|
+
events: timeline,
|
|
263
|
+
summary: {
|
|
264
|
+
totalEvents: timeline.length,
|
|
265
|
+
byPhase: timeline.reduce((acc, e) => {
|
|
266
|
+
acc[e.phase] = (acc[e.phase] || 0) + 1;
|
|
267
|
+
return acc;
|
|
268
|
+
}, {}),
|
|
269
|
+
byEvent: timeline.reduce((acc, e) => {
|
|
270
|
+
acc[e.event] = (acc[e.event] || 0) + 1;
|
|
271
|
+
return acc;
|
|
272
|
+
}, {}),
|
|
273
|
+
blockingViolations: timeline.filter(e => e.severity === 'BLOCKING').length,
|
|
274
|
+
degradedWarnings: timeline.filter(e => e.severity === 'DEGRADED').length
|
|
275
|
+
},
|
|
276
|
+
generatedAt: new Date().toISOString()
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Write timeline to file
|
|
282
|
+
*
|
|
283
|
+
* @param {string} projectDir - Project directory
|
|
284
|
+
* @param {string} runId - Run ID
|
|
285
|
+
* @param {Object} timeline - Timeline object
|
|
286
|
+
* @returns {string} Path to written file
|
|
287
|
+
*/
|
|
288
|
+
export function writeRunTimeline(projectDir, runId, timeline) {
|
|
289
|
+
const runDir = resolve(projectDir, '.verax', 'runs', runId);
|
|
290
|
+
const outputPath = resolve(runDir, 'run.timeline.json');
|
|
291
|
+
writeFileSync(outputPath, JSON.stringify(timeline, null, 2), 'utf-8');
|
|
292
|
+
return outputPath;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Load timeline from file
|
|
297
|
+
*
|
|
298
|
+
* @param {string} projectDir - Project directory
|
|
299
|
+
* @param {string} runId - Run ID
|
|
300
|
+
* @returns {Object|null} Timeline or null
|
|
301
|
+
*/
|
|
302
|
+
export function loadRunTimeline(projectDir, runId) {
|
|
303
|
+
const runDir = resolve(projectDir, '.verax', 'runs', runId);
|
|
304
|
+
const timelinePath = resolve(runDir, 'run.timeline.json');
|
|
305
|
+
|
|
306
|
+
if (!existsSync(timelinePath)) {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
return JSON.parse(readFileSync(timelinePath, 'utf-8'));
|
|
312
|
+
} catch {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.9 — Performance Budget Contract
|
|
3
|
+
*
|
|
4
|
+
* Defines hard limits for runtime, memory, pages, interactions, and artifacts.
|
|
5
|
+
* Exceeding runtime or memory = BLOCKING.
|
|
6
|
+
* Exceeding interactions/pages = DEGRADED.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Default performance budgets
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_PERF_BUDGET = {
|
|
13
|
+
// Runtime limits (ms)
|
|
14
|
+
maxRuntimeMs: 300000, // 5 minutes
|
|
15
|
+
|
|
16
|
+
// Memory limits (bytes)
|
|
17
|
+
maxMemoryRSS: 1024 * 1024 * 1024, // 1GB
|
|
18
|
+
|
|
19
|
+
// Page/interaction limits
|
|
20
|
+
maxPagesVisited: 100,
|
|
21
|
+
maxInteractionsExecuted: 500,
|
|
22
|
+
|
|
23
|
+
// Artifact size limits (bytes)
|
|
24
|
+
maxArtifactsSizeMB: 500, // 500MB
|
|
25
|
+
|
|
26
|
+
// Event loop delay threshold (ms)
|
|
27
|
+
maxEventLoopDelayMs: 100
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Performance budget profiles
|
|
32
|
+
*/
|
|
33
|
+
export const PERF_BUDGET_PROFILES = {
|
|
34
|
+
QUICK: {
|
|
35
|
+
maxRuntimeMs: 20000, // 20s
|
|
36
|
+
maxMemoryRSS: 512 * 1024 * 1024, // 512MB
|
|
37
|
+
maxPagesVisited: 5,
|
|
38
|
+
maxInteractionsExecuted: 50,
|
|
39
|
+
maxArtifactsSizeMB: 50,
|
|
40
|
+
maxEventLoopDelayMs: 50
|
|
41
|
+
},
|
|
42
|
+
STANDARD: {
|
|
43
|
+
...DEFAULT_PERF_BUDGET
|
|
44
|
+
},
|
|
45
|
+
THOROUGH: {
|
|
46
|
+
maxRuntimeMs: 600000, // 10 minutes
|
|
47
|
+
maxMemoryRSS: 2 * 1024 * 1024 * 1024, // 2GB
|
|
48
|
+
maxPagesVisited: 200,
|
|
49
|
+
maxInteractionsExecuted: 1000,
|
|
50
|
+
maxArtifactsSizeMB: 1000, // 1GB
|
|
51
|
+
maxEventLoopDelayMs: 200
|
|
52
|
+
},
|
|
53
|
+
EXHAUSTIVE: {
|
|
54
|
+
maxRuntimeMs: 1800000, // 30 minutes
|
|
55
|
+
maxMemoryRSS: 4 * 1024 * 1024 * 1024, // 4GB
|
|
56
|
+
maxPagesVisited: 500,
|
|
57
|
+
maxInteractionsExecuted: 5000,
|
|
58
|
+
maxArtifactsSizeMB: 2000, // 2GB
|
|
59
|
+
maxEventLoopDelayMs: 500
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get performance budget for a profile
|
|
65
|
+
*
|
|
66
|
+
* @param {string} profileName - Profile name (QUICK, STANDARD, THOROUGH, EXHAUSTIVE)
|
|
67
|
+
* @returns {Object} Performance budget
|
|
68
|
+
*/
|
|
69
|
+
export function getPerfBudget(profileName = 'STANDARD') {
|
|
70
|
+
const profile = PERF_BUDGET_PROFILES[profileName.toUpperCase()];
|
|
71
|
+
return profile || PERF_BUDGET_PROFILES.STANDARD;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Evaluate performance against budget
|
|
76
|
+
*
|
|
77
|
+
* @param {Object} actual - Actual performance metrics
|
|
78
|
+
* @param {Object} budget - Performance budget
|
|
79
|
+
* @returns {Object} Evaluation result
|
|
80
|
+
*/
|
|
81
|
+
export function evaluatePerfBudget(actual, budget) {
|
|
82
|
+
const violations = [];
|
|
83
|
+
const warnings = [];
|
|
84
|
+
|
|
85
|
+
// BLOCKING violations
|
|
86
|
+
if (actual.runtimeMs > budget.maxRuntimeMs) {
|
|
87
|
+
violations.push({
|
|
88
|
+
type: 'RUNTIME_EXCEEDED',
|
|
89
|
+
severity: 'BLOCKING',
|
|
90
|
+
actual: actual.runtimeMs,
|
|
91
|
+
budget: budget.maxRuntimeMs,
|
|
92
|
+
excess: actual.runtimeMs - budget.maxRuntimeMs,
|
|
93
|
+
message: `Runtime exceeded: ${actual.runtimeMs}ms > ${budget.maxRuntimeMs}ms`
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (actual.memoryRSS > budget.maxMemoryRSS) {
|
|
98
|
+
violations.push({
|
|
99
|
+
type: 'MEMORY_EXCEEDED',
|
|
100
|
+
severity: 'BLOCKING',
|
|
101
|
+
actual: actual.memoryRSS,
|
|
102
|
+
budget: budget.maxMemoryRSS,
|
|
103
|
+
excess: actual.memoryRSS - budget.maxMemoryRSS,
|
|
104
|
+
message: `Memory exceeded: ${formatBytes(actual.memoryRSS)} > ${formatBytes(budget.maxMemoryRSS)}`
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// DEGRADED violations
|
|
109
|
+
if (actual.pagesVisited > budget.maxPagesVisited) {
|
|
110
|
+
warnings.push({
|
|
111
|
+
type: 'PAGES_EXCEEDED',
|
|
112
|
+
severity: 'DEGRADED',
|
|
113
|
+
actual: actual.pagesVisited,
|
|
114
|
+
budget: budget.maxPagesVisited,
|
|
115
|
+
excess: actual.pagesVisited - budget.maxPagesVisited,
|
|
116
|
+
message: `Pages visited exceeded: ${actual.pagesVisited} > ${budget.maxPagesVisited}`
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (actual.interactionsExecuted > budget.maxInteractionsExecuted) {
|
|
121
|
+
warnings.push({
|
|
122
|
+
type: 'INTERACTIONS_EXCEEDED',
|
|
123
|
+
severity: 'DEGRADED',
|
|
124
|
+
actual: actual.interactionsExecuted,
|
|
125
|
+
budget: budget.maxInteractionsExecuted,
|
|
126
|
+
excess: actual.interactionsExecuted - budget.maxInteractionsExecuted,
|
|
127
|
+
message: `Interactions executed exceeded: ${actual.interactionsExecuted} > ${budget.maxInteractionsExecuted}`
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (actual.artifactsSizeMB > budget.maxArtifactsSizeMB) {
|
|
132
|
+
warnings.push({
|
|
133
|
+
type: 'ARTIFACTS_SIZE_EXCEEDED',
|
|
134
|
+
severity: 'DEGRADED',
|
|
135
|
+
actual: actual.artifactsSizeMB,
|
|
136
|
+
budget: budget.maxArtifactsSizeMB,
|
|
137
|
+
excess: actual.artifactsSizeMB - budget.maxArtifactsSizeMB,
|
|
138
|
+
message: `Artifacts size exceeded: ${actual.artifactsSizeMB}MB > ${budget.maxArtifactsSizeMB}MB`
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (actual.eventLoopDelayMs > budget.maxEventLoopDelayMs) {
|
|
143
|
+
warnings.push({
|
|
144
|
+
type: 'EVENT_LOOP_DELAY_EXCEEDED',
|
|
145
|
+
severity: 'DEGRADED',
|
|
146
|
+
actual: actual.eventLoopDelayMs,
|
|
147
|
+
budget: budget.maxEventLoopDelayMs,
|
|
148
|
+
excess: actual.eventLoopDelayMs - budget.maxEventLoopDelayMs,
|
|
149
|
+
message: `Event loop delay exceeded: ${actual.eventLoopDelayMs}ms > ${budget.maxEventLoopDelayMs}ms`
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const hasBlocking = violations.length > 0;
|
|
154
|
+
const hasDegraded = warnings.length > 0;
|
|
155
|
+
|
|
156
|
+
let verdict = 'OK';
|
|
157
|
+
if (hasBlocking) {
|
|
158
|
+
verdict = 'BLOCKED';
|
|
159
|
+
} else if (hasDegraded) {
|
|
160
|
+
verdict = 'DEGRADED';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
verdict,
|
|
165
|
+
ok: !hasBlocking,
|
|
166
|
+
violations,
|
|
167
|
+
warnings,
|
|
168
|
+
summary: {
|
|
169
|
+
blocking: violations.length,
|
|
170
|
+
degraded: warnings.length,
|
|
171
|
+
total: violations.length + warnings.length
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Format bytes to human-readable string
|
|
178
|
+
*/
|
|
179
|
+
function formatBytes(bytes) {
|
|
180
|
+
if (bytes === 0) return '0 B';
|
|
181
|
+
const k = 1024;
|
|
182
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
183
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
184
|
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
185
|
+
}
|
|
186
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.9 — Performance Display
|
|
3
|
+
*
|
|
4
|
+
* Formats performance metrics for CLI display.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { loadPerformanceReport } from './perf.report.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Format performance metrics for display
|
|
11
|
+
*
|
|
12
|
+
* @param {Object} report - Performance report
|
|
13
|
+
* @returns {string} Formatted string
|
|
14
|
+
*/
|
|
15
|
+
export function formatPerformanceMetrics(report) {
|
|
16
|
+
if (!report) {
|
|
17
|
+
return 'Performance: No report available';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const lines = [];
|
|
21
|
+
lines.push('Performance:');
|
|
22
|
+
|
|
23
|
+
// Runtime
|
|
24
|
+
const runtimeSec = (report.actual.runtimeMs / 1000).toFixed(1);
|
|
25
|
+
const runtimeBudgetSec = (report.budget.maxRuntimeMs / 1000).toFixed(0);
|
|
26
|
+
const runtimeStatus = report.actual.runtimeMs <= report.budget.maxRuntimeMs ? 'OK' : 'EXCEEDED';
|
|
27
|
+
lines.push(` Runtime: ${runtimeSec}s / ${runtimeBudgetSec}s ${runtimeStatus}`);
|
|
28
|
+
|
|
29
|
+
// Memory
|
|
30
|
+
const memoryMB = (report.actual.memoryRSS / (1024 * 1024)).toFixed(0);
|
|
31
|
+
const memoryBudgetMB = (report.budget.maxMemoryRSS / (1024 * 1024)).toFixed(0);
|
|
32
|
+
const memoryStatus = report.actual.memoryRSS <= report.budget.maxMemoryRSS ? 'OK' : 'EXCEEDED';
|
|
33
|
+
lines.push(` Memory: ${memoryMB}MB / ${memoryBudgetMB}MB ${memoryStatus}`);
|
|
34
|
+
|
|
35
|
+
// Pages
|
|
36
|
+
const pagesStatus = report.actual.pagesVisited <= report.budget.maxPagesVisited ? 'OK' : 'EXCEEDED';
|
|
37
|
+
lines.push(` Pages: ${report.actual.pagesVisited} / ${report.budget.maxPagesVisited} ${pagesStatus}`);
|
|
38
|
+
|
|
39
|
+
// Interactions
|
|
40
|
+
const interactionsStatus = report.actual.interactionsExecuted <= report.budget.maxInteractionsExecuted ? 'OK' : 'EXCEEDED';
|
|
41
|
+
lines.push(` Interactions: ${report.actual.interactionsExecuted} / ${report.budget.maxInteractionsExecuted} ${interactionsStatus}`);
|
|
42
|
+
|
|
43
|
+
// Verdict
|
|
44
|
+
const verdictSymbol = report.verdict === 'OK' ? '✅' : report.verdict === 'DEGRADED' ? '⚠️' : '❌';
|
|
45
|
+
lines.push(`Verdict: ${verdictSymbol} ${report.verdict}`);
|
|
46
|
+
|
|
47
|
+
return lines.join('\n');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Display performance metrics in inspect command
|
|
52
|
+
*
|
|
53
|
+
* @param {string} projectDir - Project directory
|
|
54
|
+
* @param {string} runId - Run ID
|
|
55
|
+
*/
|
|
56
|
+
export function displayPerformanceInInspect(projectDir, runId) {
|
|
57
|
+
const report = loadPerformanceReport(projectDir, runId);
|
|
58
|
+
|
|
59
|
+
if (!report) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log('\n' + formatPerformanceMetrics(report));
|
|
64
|
+
}
|
|
65
|
+
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.9 — Performance Enforcer
|
|
3
|
+
*
|
|
4
|
+
* Records performance violations in failure ledger and blocks GA/Release.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { loadPerformanceReport } from './perf.report.js';
|
|
8
|
+
import { createInternalFailure } from '../failures/failure.factory.js';
|
|
9
|
+
import { FAILURE_CODE, EXECUTION_PHASE } from '../failures/failure.types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Record performance violations in failure ledger
|
|
13
|
+
*
|
|
14
|
+
* @param {string} projectDir - Project directory
|
|
15
|
+
* @param {string} runId - Run ID
|
|
16
|
+
* @param {Object} failureLedger - Failure ledger instance
|
|
17
|
+
*/
|
|
18
|
+
export function recordPerformanceViolations(projectDir, runId, failureLedger) {
|
|
19
|
+
const report = loadPerformanceReport(projectDir, runId);
|
|
20
|
+
|
|
21
|
+
if (!report) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Record BLOCKING violations
|
|
26
|
+
for (const violation of report.violations) {
|
|
27
|
+
const failure = createInternalFailure(
|
|
28
|
+
FAILURE_CODE.INTERNAL_UNEXPECTED_ERROR,
|
|
29
|
+
violation.message,
|
|
30
|
+
'perf.enforcer',
|
|
31
|
+
{
|
|
32
|
+
type: violation.type,
|
|
33
|
+
actual: violation.actual,
|
|
34
|
+
budget: violation.budget,
|
|
35
|
+
excess: violation.excess
|
|
36
|
+
},
|
|
37
|
+
EXECUTION_PHASE.RUNTIME
|
|
38
|
+
);
|
|
39
|
+
failureLedger.record(failure);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Record DEGRADED warnings
|
|
43
|
+
for (const warning of report.warnings) {
|
|
44
|
+
const failure = createInternalFailure(
|
|
45
|
+
FAILURE_CODE.INTERNAL_UNEXPECTED_ERROR,
|
|
46
|
+
warning.message,
|
|
47
|
+
'perf.enforcer',
|
|
48
|
+
{
|
|
49
|
+
type: warning.type,
|
|
50
|
+
actual: warning.actual,
|
|
51
|
+
budget: warning.budget,
|
|
52
|
+
excess: warning.excess
|
|
53
|
+
},
|
|
54
|
+
EXECUTION_PHASE.RUNTIME
|
|
55
|
+
);
|
|
56
|
+
failure.severity = 'DEGRADED';
|
|
57
|
+
failureLedger.record(failure);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check performance status for GA/Release
|
|
63
|
+
*
|
|
64
|
+
* @param {string} projectDir - Project directory
|
|
65
|
+
* @param {string} runId - Run ID
|
|
66
|
+
* @returns {Object} Performance status
|
|
67
|
+
*/
|
|
68
|
+
export function checkPerformanceStatus(projectDir, runId) {
|
|
69
|
+
const report = loadPerformanceReport(projectDir, runId);
|
|
70
|
+
|
|
71
|
+
if (!report) {
|
|
72
|
+
return {
|
|
73
|
+
exists: false,
|
|
74
|
+
ok: false,
|
|
75
|
+
verdict: 'UNKNOWN',
|
|
76
|
+
blockers: ['Performance report not found']
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const hasBlocking = report.violations.length > 0;
|
|
81
|
+
const hasDegraded = report.warnings.length > 0;
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
exists: true,
|
|
85
|
+
ok: !hasBlocking,
|
|
86
|
+
verdict: report.verdict,
|
|
87
|
+
blockers: hasBlocking ? report.violations.map(v => v.message) : [],
|
|
88
|
+
warnings: hasDegraded ? report.warnings.map(w => w.message) : []
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|