@veraxhq/verax 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/README.md +14 -18
  2. package/bin/verax.js +7 -0
  3. package/package.json +3 -3
  4. package/src/cli/commands/baseline.js +104 -0
  5. package/src/cli/commands/default.js +79 -25
  6. package/src/cli/commands/ga.js +243 -0
  7. package/src/cli/commands/gates.js +95 -0
  8. package/src/cli/commands/inspect.js +131 -2
  9. package/src/cli/commands/release-check.js +213 -0
  10. package/src/cli/commands/run.js +246 -35
  11. package/src/cli/commands/security-check.js +211 -0
  12. package/src/cli/commands/truth.js +114 -0
  13. package/src/cli/entry.js +304 -67
  14. package/src/cli/util/angular-component-extractor.js +179 -0
  15. package/src/cli/util/angular-navigation-detector.js +141 -0
  16. package/src/cli/util/angular-network-detector.js +161 -0
  17. package/src/cli/util/angular-state-detector.js +162 -0
  18. package/src/cli/util/ast-interactive-detector.js +546 -0
  19. package/src/cli/util/ast-network-detector.js +603 -0
  20. package/src/cli/util/ast-usestate-detector.js +602 -0
  21. package/src/cli/util/bootstrap-guard.js +86 -0
  22. package/src/cli/util/determinism-runner.js +123 -0
  23. package/src/cli/util/determinism-writer.js +129 -0
  24. package/src/cli/util/env-url.js +4 -0
  25. package/src/cli/util/expectation-extractor.js +369 -73
  26. package/src/cli/util/findings-writer.js +126 -16
  27. package/src/cli/util/learn-writer.js +3 -1
  28. package/src/cli/util/observe-writer.js +3 -1
  29. package/src/cli/util/paths.js +3 -12
  30. package/src/cli/util/project-discovery.js +3 -0
  31. package/src/cli/util/project-writer.js +3 -1
  32. package/src/cli/util/run-resolver.js +64 -0
  33. package/src/cli/util/source-requirement.js +55 -0
  34. package/src/cli/util/summary-writer.js +1 -0
  35. package/src/cli/util/svelte-navigation-detector.js +163 -0
  36. package/src/cli/util/svelte-network-detector.js +80 -0
  37. package/src/cli/util/svelte-sfc-extractor.js +147 -0
  38. package/src/cli/util/svelte-state-detector.js +243 -0
  39. package/src/cli/util/vue-navigation-detector.js +177 -0
  40. package/src/cli/util/vue-sfc-extractor.js +162 -0
  41. package/src/cli/util/vue-state-detector.js +215 -0
  42. package/src/verax/cli/finding-explainer.js +56 -3
  43. package/src/verax/core/artifacts/registry.js +154 -0
  44. package/src/verax/core/artifacts/verifier.js +980 -0
  45. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  46. package/src/verax/core/baseline/baseline.snapshot.js +231 -0
  47. package/src/verax/core/capabilities/gates.js +499 -0
  48. package/src/verax/core/capabilities/registry.js +475 -0
  49. package/src/verax/core/confidence/confidence-compute.js +137 -0
  50. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  51. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  52. package/src/verax/core/confidence/confidence-weights.js +44 -0
  53. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  54. package/src/verax/core/confidence/confidence.loader.js +79 -0
  55. package/src/verax/core/confidence/confidence.schema.js +94 -0
  56. package/src/verax/core/confidence-engine-refactor.js +484 -0
  57. package/src/verax/core/confidence-engine.js +486 -0
  58. package/src/verax/core/confidence-engine.js.backup +471 -0
  59. package/src/verax/core/contracts/index.js +29 -0
  60. package/src/verax/core/contracts/types.js +185 -0
  61. package/src/verax/core/contracts/validators.js +381 -0
  62. package/src/verax/core/decision-snapshot.js +30 -3
  63. package/src/verax/core/decisions/decision.trace.js +276 -0
  64. package/src/verax/core/determinism/contract-writer.js +89 -0
  65. package/src/verax/core/determinism/contract.js +139 -0
  66. package/src/verax/core/determinism/diff.js +364 -0
  67. package/src/verax/core/determinism/engine.js +221 -0
  68. package/src/verax/core/determinism/finding-identity.js +148 -0
  69. package/src/verax/core/determinism/normalize.js +438 -0
  70. package/src/verax/core/determinism/report-writer.js +92 -0
  71. package/src/verax/core/determinism/run-fingerprint.js +118 -0
  72. package/src/verax/core/dynamic-route-intelligence.js +528 -0
  73. package/src/verax/core/evidence/evidence-capture-service.js +307 -0
  74. package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
  75. package/src/verax/core/evidence-builder.js +487 -0
  76. package/src/verax/core/execution-mode-context.js +77 -0
  77. package/src/verax/core/execution-mode-detector.js +190 -0
  78. package/src/verax/core/failures/exit-codes.js +86 -0
  79. package/src/verax/core/failures/failure-summary.js +76 -0
  80. package/src/verax/core/failures/failure.factory.js +225 -0
  81. package/src/verax/core/failures/failure.ledger.js +132 -0
  82. package/src/verax/core/failures/failure.types.js +196 -0
  83. package/src/verax/core/failures/index.js +10 -0
  84. package/src/verax/core/ga/ga-report-writer.js +43 -0
  85. package/src/verax/core/ga/ga.artifact.js +49 -0
  86. package/src/verax/core/ga/ga.contract.js +434 -0
  87. package/src/verax/core/ga/ga.enforcer.js +86 -0
  88. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  89. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  90. package/src/verax/core/guardrails/policy.loader.js +83 -0
  91. package/src/verax/core/guardrails/policy.schema.js +110 -0
  92. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  93. package/src/verax/core/guardrails-engine.js +505 -0
  94. package/src/verax/core/observe/run-timeline.js +316 -0
  95. package/src/verax/core/perf/perf.contract.js +186 -0
  96. package/src/verax/core/perf/perf.display.js +65 -0
  97. package/src/verax/core/perf/perf.enforcer.js +91 -0
  98. package/src/verax/core/perf/perf.monitor.js +209 -0
  99. package/src/verax/core/perf/perf.report.js +198 -0
  100. package/src/verax/core/pipeline-tracker.js +238 -0
  101. package/src/verax/core/product-definition.js +127 -0
  102. package/src/verax/core/release/provenance.builder.js +271 -0
  103. package/src/verax/core/release/release-report-writer.js +40 -0
  104. package/src/verax/core/release/release.enforcer.js +159 -0
  105. package/src/verax/core/release/reproducibility.check.js +221 -0
  106. package/src/verax/core/release/sbom.builder.js +283 -0
  107. package/src/verax/core/report/cross-index.js +192 -0
  108. package/src/verax/core/report/human-summary.js +222 -0
  109. package/src/verax/core/route-intelligence.js +419 -0
  110. package/src/verax/core/security/secrets.scan.js +326 -0
  111. package/src/verax/core/security/security-report.js +50 -0
  112. package/src/verax/core/security/security.enforcer.js +124 -0
  113. package/src/verax/core/security/supplychain.defaults.json +38 -0
  114. package/src/verax/core/security/supplychain.policy.js +326 -0
  115. package/src/verax/core/security/vuln.scan.js +265 -0
  116. package/src/verax/core/truth/truth.certificate.js +250 -0
  117. package/src/verax/core/ui-feedback-intelligence.js +515 -0
  118. package/src/verax/detect/confidence-engine.js +628 -40
  119. package/src/verax/detect/confidence-helper.js +33 -0
  120. package/src/verax/detect/detection-engine.js +18 -1
  121. package/src/verax/detect/dynamic-route-findings.js +335 -0
  122. package/src/verax/detect/expectation-chain-detector.js +417 -0
  123. package/src/verax/detect/expectation-model.js +3 -1
  124. package/src/verax/detect/findings-writer.js +141 -5
  125. package/src/verax/detect/index.js +229 -5
  126. package/src/verax/detect/journey-stall-detector.js +558 -0
  127. package/src/verax/detect/route-findings.js +218 -0
  128. package/src/verax/detect/ui-feedback-findings.js +207 -0
  129. package/src/verax/detect/verdict-engine.js +57 -3
  130. package/src/verax/detect/view-switch-correlator.js +242 -0
  131. package/src/verax/index.js +413 -45
  132. package/src/verax/learn/action-contract-extractor.js +682 -64
  133. package/src/verax/learn/route-validator.js +4 -1
  134. package/src/verax/observe/index.js +88 -843
  135. package/src/verax/observe/interaction-runner.js +25 -8
  136. package/src/verax/observe/observe-context.js +205 -0
  137. package/src/verax/observe/observe-helpers.js +191 -0
  138. package/src/verax/observe/observe-runner.js +226 -0
  139. package/src/verax/observe/observers/budget-observer.js +185 -0
  140. package/src/verax/observe/observers/console-observer.js +102 -0
  141. package/src/verax/observe/observers/coverage-observer.js +107 -0
  142. package/src/verax/observe/observers/interaction-observer.js +471 -0
  143. package/src/verax/observe/observers/navigation-observer.js +132 -0
  144. package/src/verax/observe/observers/network-observer.js +87 -0
  145. package/src/verax/observe/observers/safety-observer.js +82 -0
  146. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  147. package/src/verax/observe/ui-feedback-detector.js +742 -0
  148. package/src/verax/observe/ui-signal-sensor.js +148 -2
  149. package/src/verax/scan-summary-writer.js +42 -8
  150. package/src/verax/shared/artifact-manager.js +8 -5
  151. package/src/verax/shared/css-spinner-rules.js +204 -0
  152. package/src/verax/shared/view-switch-rules.js +208 -0
@@ -0,0 +1,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
+