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