@veraxhq/verax 0.2.1 → 0.4.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 (213) hide show
  1. package/README.md +10 -6
  2. package/bin/verax.js +11 -11
  3. package/package.json +29 -8
  4. package/src/cli/commands/baseline.js +103 -0
  5. package/src/cli/commands/default.js +51 -6
  6. package/src/cli/commands/doctor.js +29 -0
  7. package/src/cli/commands/ga.js +246 -0
  8. package/src/cli/commands/gates.js +95 -0
  9. package/src/cli/commands/inspect.js +4 -2
  10. package/src/cli/commands/release-check.js +215 -0
  11. package/src/cli/commands/run.js +45 -6
  12. package/src/cli/commands/security-check.js +212 -0
  13. package/src/cli/commands/truth.js +113 -0
  14. package/src/cli/entry.js +30 -20
  15. package/src/cli/util/angular-component-extractor.js +179 -0
  16. package/src/cli/util/angular-navigation-detector.js +141 -0
  17. package/src/cli/util/angular-network-detector.js +161 -0
  18. package/src/cli/util/angular-state-detector.js +162 -0
  19. package/src/cli/util/ast-interactive-detector.js +544 -0
  20. package/src/cli/util/ast-network-detector.js +603 -0
  21. package/src/cli/util/ast-promise-extractor.js +581 -0
  22. package/src/cli/util/ast-usestate-detector.js +602 -0
  23. package/src/cli/util/atomic-write.js +12 -1
  24. package/src/cli/util/bootstrap-guard.js +86 -0
  25. package/src/cli/util/console-reporter.js +72 -0
  26. package/src/cli/util/detection-engine.js +105 -41
  27. package/src/cli/util/determinism-runner.js +124 -0
  28. package/src/cli/util/determinism-writer.js +129 -0
  29. package/src/cli/util/digest-engine.js +359 -0
  30. package/src/cli/util/dom-diff.js +226 -0
  31. package/src/cli/util/evidence-engine.js +287 -0
  32. package/src/cli/util/expectation-extractor.js +151 -5
  33. package/src/cli/util/findings-writer.js +3 -0
  34. package/src/cli/util/framework-detector.js +572 -0
  35. package/src/cli/util/idgen.js +1 -1
  36. package/src/cli/util/interaction-planner.js +529 -0
  37. package/src/cli/util/learn-writer.js +2 -0
  38. package/src/cli/util/ledger-writer.js +110 -0
  39. package/src/cli/util/monorepo-resolver.js +162 -0
  40. package/src/cli/util/observation-engine.js +127 -278
  41. package/src/cli/util/observe-writer.js +2 -0
  42. package/src/cli/util/project-discovery.js +284 -0
  43. package/src/cli/util/project-writer.js +2 -0
  44. package/src/cli/util/run-id.js +23 -27
  45. package/src/cli/util/run-resolver.js +64 -0
  46. package/src/cli/util/run-result.js +778 -0
  47. package/src/cli/util/selector-resolver.js +235 -0
  48. package/src/cli/util/source-requirement.js +55 -0
  49. package/src/cli/util/summary-writer.js +2 -0
  50. package/src/cli/util/svelte-navigation-detector.js +163 -0
  51. package/src/cli/util/svelte-network-detector.js +80 -0
  52. package/src/cli/util/svelte-sfc-extractor.js +146 -0
  53. package/src/cli/util/svelte-state-detector.js +242 -0
  54. package/src/cli/util/trust-activation-integration.js +496 -0
  55. package/src/cli/util/trust-activation-wrapper.js +85 -0
  56. package/src/cli/util/trust-integration-hooks.js +164 -0
  57. package/src/cli/util/types.js +153 -0
  58. package/src/cli/util/url-validation.js +40 -0
  59. package/src/cli/util/vue-navigation-detector.js +178 -0
  60. package/src/cli/util/vue-sfc-extractor.js +161 -0
  61. package/src/cli/util/vue-state-detector.js +215 -0
  62. package/src/types/fs-augment.d.ts +23 -0
  63. package/src/types/global.d.ts +137 -0
  64. package/src/types/internal-types.d.ts +35 -0
  65. package/src/verax/cli/init.js +4 -18
  66. package/src/verax/core/action-classifier.js +4 -3
  67. package/src/verax/core/artifacts/registry.js +139 -0
  68. package/src/verax/core/artifacts/verifier.js +990 -0
  69. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  70. package/src/verax/core/baseline/baseline.snapshot.js +233 -0
  71. package/src/verax/core/capabilities/gates.js +505 -0
  72. package/src/verax/core/capabilities/registry.js +475 -0
  73. package/src/verax/core/confidence/confidence-compute.js +144 -0
  74. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  75. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  76. package/src/verax/core/confidence/confidence-weights.js +44 -0
  77. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  78. package/src/verax/core/confidence/confidence.loader.js +80 -0
  79. package/src/verax/core/confidence/confidence.schema.js +94 -0
  80. package/src/verax/core/confidence-engine-refactor.js +489 -0
  81. package/src/verax/core/confidence-engine.js +625 -0
  82. package/src/verax/core/contracts/index.js +29 -0
  83. package/src/verax/core/contracts/types.js +186 -0
  84. package/src/verax/core/contracts/validators.js +456 -0
  85. package/src/verax/core/decisions/decision.trace.js +278 -0
  86. package/src/verax/core/determinism/contract-writer.js +89 -0
  87. package/src/verax/core/determinism/contract.js +139 -0
  88. package/src/verax/core/determinism/diff.js +405 -0
  89. package/src/verax/core/determinism/engine.js +222 -0
  90. package/src/verax/core/determinism/finding-identity.js +149 -0
  91. package/src/verax/core/determinism/normalize.js +466 -0
  92. package/src/verax/core/determinism/report-writer.js +93 -0
  93. package/src/verax/core/determinism/run-fingerprint.js +123 -0
  94. package/src/verax/core/dynamic-route-intelligence.js +529 -0
  95. package/src/verax/core/evidence/evidence-capture-service.js +308 -0
  96. package/src/verax/core/evidence/evidence-intent-ledger.js +166 -0
  97. package/src/verax/core/evidence-builder.js +487 -0
  98. package/src/verax/core/execution-mode-context.js +77 -0
  99. package/src/verax/core/execution-mode-detector.js +192 -0
  100. package/src/verax/core/failures/exit-codes.js +88 -0
  101. package/src/verax/core/failures/failure-summary.js +76 -0
  102. package/src/verax/core/failures/failure.factory.js +225 -0
  103. package/src/verax/core/failures/failure.ledger.js +133 -0
  104. package/src/verax/core/failures/failure.types.js +196 -0
  105. package/src/verax/core/failures/index.js +10 -0
  106. package/src/verax/core/ga/ga-report-writer.js +43 -0
  107. package/src/verax/core/ga/ga.artifact.js +49 -0
  108. package/src/verax/core/ga/ga.contract.js +435 -0
  109. package/src/verax/core/ga/ga.enforcer.js +87 -0
  110. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  111. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  112. package/src/verax/core/guardrails/policy.loader.js +84 -0
  113. package/src/verax/core/guardrails/policy.schema.js +110 -0
  114. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  115. package/src/verax/core/guardrails-engine.js +505 -0
  116. package/src/verax/core/incremental-store.js +1 -0
  117. package/src/verax/core/integrity/budget.js +138 -0
  118. package/src/verax/core/integrity/determinism.js +342 -0
  119. package/src/verax/core/integrity/integrity.js +208 -0
  120. package/src/verax/core/integrity/poisoning.js +108 -0
  121. package/src/verax/core/integrity/transaction.js +140 -0
  122. package/src/verax/core/observe/run-timeline.js +318 -0
  123. package/src/verax/core/perf/perf.contract.js +186 -0
  124. package/src/verax/core/perf/perf.display.js +65 -0
  125. package/src/verax/core/perf/perf.enforcer.js +91 -0
  126. package/src/verax/core/perf/perf.monitor.js +209 -0
  127. package/src/verax/core/perf/perf.report.js +200 -0
  128. package/src/verax/core/pipeline-tracker.js +243 -0
  129. package/src/verax/core/product-definition.js +127 -0
  130. package/src/verax/core/release/provenance.builder.js +130 -0
  131. package/src/verax/core/release/release-report-writer.js +40 -0
  132. package/src/verax/core/release/release.enforcer.js +164 -0
  133. package/src/verax/core/release/reproducibility.check.js +222 -0
  134. package/src/verax/core/release/sbom.builder.js +292 -0
  135. package/src/verax/core/replay-validator.js +2 -0
  136. package/src/verax/core/replay.js +4 -0
  137. package/src/verax/core/report/cross-index.js +195 -0
  138. package/src/verax/core/report/human-summary.js +362 -0
  139. package/src/verax/core/route-intelligence.js +420 -0
  140. package/src/verax/core/run-id.js +6 -3
  141. package/src/verax/core/run-manifest.js +4 -3
  142. package/src/verax/core/security/secrets.scan.js +329 -0
  143. package/src/verax/core/security/security-report.js +50 -0
  144. package/src/verax/core/security/security.enforcer.js +128 -0
  145. package/src/verax/core/security/supplychain.defaults.json +38 -0
  146. package/src/verax/core/security/supplychain.policy.js +334 -0
  147. package/src/verax/core/security/vuln.scan.js +265 -0
  148. package/src/verax/core/truth/truth.certificate.js +252 -0
  149. package/src/verax/core/ui-feedback-intelligence.js +481 -0
  150. package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
  151. package/src/verax/detect/confidence-engine.js +62 -34
  152. package/src/verax/detect/confidence-helper.js +34 -0
  153. package/src/verax/detect/dynamic-route-findings.js +338 -0
  154. package/src/verax/detect/expectation-chain-detector.js +417 -0
  155. package/src/verax/detect/expectation-model.js +2 -2
  156. package/src/verax/detect/failure-cause-inference.js +293 -0
  157. package/src/verax/detect/findings-writer.js +131 -35
  158. package/src/verax/detect/flow-detector.js +2 -2
  159. package/src/verax/detect/form-silent-failure.js +98 -0
  160. package/src/verax/detect/index.js +46 -5
  161. package/src/verax/detect/invariants-enforcer.js +147 -0
  162. package/src/verax/detect/journey-stall-detector.js +558 -0
  163. package/src/verax/detect/navigation-silent-failure.js +82 -0
  164. package/src/verax/detect/problem-aggregator.js +361 -0
  165. package/src/verax/detect/route-findings.js +219 -0
  166. package/src/verax/detect/summary-writer.js +477 -0
  167. package/src/verax/detect/test-failure-cause-inference.js +314 -0
  168. package/src/verax/detect/ui-feedback-findings.js +207 -0
  169. package/src/verax/detect/view-switch-correlator.js +242 -0
  170. package/src/verax/flow/flow-engine.js +2 -1
  171. package/src/verax/flow/flow-spec.js +0 -6
  172. package/src/verax/index.js +4 -0
  173. package/src/verax/intel/ts-program.js +1 -0
  174. package/src/verax/intel/vue-navigation-extractor.js +3 -0
  175. package/src/verax/learn/action-contract-extractor.js +3 -0
  176. package/src/verax/learn/ast-contract-extractor.js +1 -1
  177. package/src/verax/learn/flow-extractor.js +1 -0
  178. package/src/verax/learn/project-detector.js +5 -0
  179. package/src/verax/learn/react-router-extractor.js +2 -0
  180. package/src/verax/learn/source-instrumenter.js +1 -0
  181. package/src/verax/learn/state-extractor.js +2 -1
  182. package/src/verax/learn/static-extractor.js +1 -0
  183. package/src/verax/observe/coverage-gaps.js +132 -0
  184. package/src/verax/observe/expectation-handler.js +126 -0
  185. package/src/verax/observe/incremental-skip.js +46 -0
  186. package/src/verax/observe/index.js +51 -155
  187. package/src/verax/observe/interaction-executor.js +192 -0
  188. package/src/verax/observe/interaction-runner.js +782 -513
  189. package/src/verax/observe/network-firewall.js +86 -0
  190. package/src/verax/observe/observation-builder.js +169 -0
  191. package/src/verax/observe/observe-context.js +205 -0
  192. package/src/verax/observe/observe-helpers.js +192 -0
  193. package/src/verax/observe/observe-runner.js +230 -0
  194. package/src/verax/observe/observers/budget-observer.js +185 -0
  195. package/src/verax/observe/observers/console-observer.js +102 -0
  196. package/src/verax/observe/observers/coverage-observer.js +107 -0
  197. package/src/verax/observe/observers/interaction-observer.js +471 -0
  198. package/src/verax/observe/observers/navigation-observer.js +132 -0
  199. package/src/verax/observe/observers/network-observer.js +87 -0
  200. package/src/verax/observe/observers/safety-observer.js +82 -0
  201. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  202. package/src/verax/observe/page-traversal.js +138 -0
  203. package/src/verax/observe/snapshot-ops.js +94 -0
  204. package/src/verax/observe/ui-feedback-detector.js +742 -0
  205. package/src/verax/scan-summary-writer.js +2 -0
  206. package/src/verax/shared/artifact-manager.js +25 -5
  207. package/src/verax/shared/caching.js +1 -0
  208. package/src/verax/shared/css-spinner-rules.js +204 -0
  209. package/src/verax/shared/expectation-tracker.js +1 -0
  210. package/src/verax/shared/view-switch-rules.js +208 -0
  211. package/src/verax/shared/zip-artifacts.js +6 -0
  212. package/src/verax/shared/config-loader.js +0 -169
  213. /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
@@ -0,0 +1,140 @@
1
+ /**
2
+ * PHASE 6A: Transactional Artifact System
3
+ *
4
+ * Provides ALL-OR-NOTHING artifact writes with staging directory.
5
+ * Ensures atomic finalization of complete artifact sets.
6
+ */
7
+
8
+ import { mkdirSync, renameSync, existsSync, rmSync, readdirSync } from 'fs';
9
+ import { join } from 'path';
10
+
11
+ /**
12
+ * Create staging directory for transactional writes
13
+ *
14
+ * @param {string} runDir - Run directory path
15
+ * @returns {{ ok: boolean, stagingDir?: string, error?: Error }} Result
16
+ */
17
+ export function createStagingDir(runDir) {
18
+ try {
19
+ const stagingDir = join(runDir, '.staging');
20
+
21
+ // Clean up any existing staging directory from previous crash
22
+ if (existsSync(stagingDir)) {
23
+ rmSync(stagingDir, { recursive: true, force: true });
24
+ }
25
+
26
+ mkdirSync(stagingDir, { recursive: true });
27
+
28
+ return { ok: true, stagingDir };
29
+ } catch (error) {
30
+ return { ok: false, error };
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Commit staging directory to final location (atomic rename)
36
+ *
37
+ * @param {string} runDir - Run directory path
38
+ * @returns {{ ok: boolean, error?: Error }} Result
39
+ */
40
+ export function commitStagingDir(runDir) {
41
+ try {
42
+ const stagingDir = join(runDir, '.staging');
43
+ const artifactsDir = join(runDir, 'artifacts');
44
+
45
+ if (!existsSync(stagingDir)) {
46
+ return {
47
+ ok: false,
48
+ error: new Error('Staging directory does not exist'),
49
+ };
50
+ }
51
+
52
+ // Ensure parent directory exists
53
+ mkdirSync(runDir, { recursive: true });
54
+
55
+ // Clean up existing artifacts directory if present
56
+ if (existsSync(artifactsDir)) {
57
+ rmSync(artifactsDir, { recursive: true, force: true });
58
+ }
59
+
60
+ // Atomic rename
61
+ renameSync(stagingDir, artifactsDir);
62
+
63
+ return { ok: true };
64
+ } catch (error) {
65
+ return { ok: false, error };
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Rollback staging directory (cleanup on failure)
71
+ *
72
+ * @param {string} runDir - Run directory path
73
+ * @returns {{ ok: boolean, error?: Error }} Result
74
+ */
75
+ export function rollbackStagingDir(runDir) {
76
+ try {
77
+ const stagingDir = join(runDir, '.staging');
78
+
79
+ if (existsSync(stagingDir)) {
80
+ rmSync(stagingDir, { recursive: true, force: true });
81
+ }
82
+
83
+ return { ok: true };
84
+ } catch (error) {
85
+ return { ok: false, error };
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Get staging artifact path
91
+ *
92
+ * @param {string} runDir - Run directory path
93
+ * @param {string} artifactName - Artifact filename
94
+ * @returns {string} Path to artifact in staging directory
95
+ */
96
+ export function getStagingPath(runDir, artifactName) {
97
+ return join(runDir, '.staging', artifactName);
98
+ }
99
+
100
+ /**
101
+ * Get final artifact path
102
+ *
103
+ * @param {string} runDir - Run directory path
104
+ * @param {string} artifactName - Artifact filename
105
+ * @returns {string} Path to artifact in final location
106
+ */
107
+ export function getFinalPath(runDir, artifactName) {
108
+ return join(runDir, 'artifacts', artifactName);
109
+ }
110
+
111
+ /**
112
+ * Check if staging directory exists
113
+ *
114
+ * @param {string} runDir - Run directory path
115
+ * @returns {boolean} True if staging exists
116
+ */
117
+ export function hasStagingDir(runDir) {
118
+ const stagingDir = join(runDir, '.staging');
119
+ return existsSync(stagingDir);
120
+ }
121
+
122
+ /**
123
+ * List files in staging directory
124
+ *
125
+ * @param {string} runDir - Run directory path
126
+ * @returns {string[]} List of filenames in staging
127
+ */
128
+ export function listStagingFiles(runDir) {
129
+ try {
130
+ const stagingDir = join(runDir, '.staging');
131
+
132
+ if (!existsSync(stagingDir)) {
133
+ return [];
134
+ }
135
+
136
+ return readdirSync(stagingDir);
137
+ } catch (error) {
138
+ return [];
139
+ }
140
+ }
@@ -0,0 +1,318 @@
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
+ // @ts-expect-error - readFileSync with encoding returns string
20
+ return JSON.parse(readFileSync(path, 'utf-8'));
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Build timeline from run artifacts
28
+ *
29
+ * @param {string} projectDir - Project directory
30
+ * @param {string} runId - Run ID
31
+ * @returns {Object} Timeline object
32
+ */
33
+ export function buildRunTimeline(projectDir, runId) {
34
+ const runDir = resolve(projectDir, '.verax', 'runs', runId);
35
+
36
+ if (!existsSync(runDir)) {
37
+ return null;
38
+ }
39
+
40
+ const timeline = [];
41
+
42
+ // Load summary for phase timestamps
43
+ const summary = loadArtifact(runDir, 'summary.json');
44
+ const runStatus = loadArtifact(runDir, 'run.status.json');
45
+ const decisions = loadArtifact(runDir, 'decisions.json');
46
+ const failureLedger = loadArtifact(runDir, 'failure.ledger.json');
47
+ const performanceReport = loadArtifact(runDir, 'performance.report.json');
48
+ const findings = loadArtifact(runDir, 'findings.json');
49
+
50
+ // Extract timestamps
51
+ const startedAt = summary?.startedAt || runStatus?.startedAt;
52
+ const completedAt = summary?.completedAt || runStatus?.completedAt;
53
+
54
+ // Phase events
55
+ if (startedAt) {
56
+ timeline.push({
57
+ timestamp: startedAt,
58
+ phase: 'RUN_START',
59
+ event: 'run_started',
60
+ data: {
61
+ runId,
62
+ url: summary?.url || null
63
+ }
64
+ });
65
+ }
66
+
67
+ // LEARN phase
68
+ if (summary?.metrics?.learnMs) {
69
+ timeline.push({
70
+ timestamp: startedAt || null,
71
+ phase: 'LEARN',
72
+ event: 'phase_started',
73
+ data: {
74
+ durationMs: summary.metrics.learnMs
75
+ }
76
+ });
77
+ timeline.push({
78
+ timestamp: startedAt ? new Date(Date.parse(startedAt) + summary.metrics.learnMs).toISOString() : null,
79
+ phase: 'LEARN',
80
+ event: 'phase_completed',
81
+ data: {
82
+ durationMs: summary.metrics.learnMs,
83
+ expectationsFound: summary.digest?.expectationsTotal || 0
84
+ }
85
+ });
86
+ }
87
+
88
+ // OBSERVE phase
89
+ if (summary?.metrics?.observeMs) {
90
+ const observeStart = startedAt ? new Date(Date.parse(startedAt) + (summary.metrics.learnMs || 0)).toISOString() : null;
91
+ timeline.push({
92
+ timestamp: observeStart,
93
+ phase: 'OBSERVE',
94
+ event: 'phase_started',
95
+ data: {
96
+ durationMs: summary.metrics.observeMs
97
+ }
98
+ });
99
+ timeline.push({
100
+ timestamp: observeStart ? new Date(Date.parse(observeStart) + summary.metrics.observeMs).toISOString() : null,
101
+ phase: 'OBSERVE',
102
+ event: 'phase_completed',
103
+ data: {
104
+ durationMs: summary.metrics.observeMs,
105
+ interactionsExecuted: summary.digest?.attempted || 0,
106
+ pagesVisited: summary.digest?.pagesVisited || 0
107
+ }
108
+ });
109
+ }
110
+
111
+ // DETECT phase
112
+ if (summary?.metrics?.detectMs) {
113
+ const detectStart = startedAt ? new Date(Date.parse(startedAt) + (summary.metrics.learnMs || 0) + (summary.metrics.observeMs || 0)).toISOString() : null;
114
+ timeline.push({
115
+ timestamp: detectStart,
116
+ phase: 'DETECT',
117
+ event: 'phase_started',
118
+ data: {
119
+ durationMs: summary.metrics.detectMs
120
+ }
121
+ });
122
+ timeline.push({
123
+ timestamp: detectStart ? new Date(Date.parse(detectStart) + summary.metrics.detectMs).toISOString() : null,
124
+ phase: 'DETECT',
125
+ event: 'phase_completed',
126
+ data: {
127
+ durationMs: summary.metrics.detectMs,
128
+ findingsDetected: Array.isArray(findings?.findings) ? findings.findings.length : 0
129
+ }
130
+ });
131
+ }
132
+
133
+ // Adaptive events from decisions
134
+ if (decisions?.adaptiveEvents) {
135
+ for (const event of decisions.adaptiveEvents) {
136
+ timeline.push({
137
+ timestamp: event.timestamp || null,
138
+ phase: event.phase || 'UNKNOWN',
139
+ event: 'adaptive_decision',
140
+ data: {
141
+ decisionId: event.decisionId,
142
+ reason: event.reason,
143
+ context: event.context || {}
144
+ }
145
+ });
146
+ }
147
+ }
148
+
149
+ // Budget violations from performance report
150
+ if (performanceReport?.violations) {
151
+ for (const violation of performanceReport.violations) {
152
+ timeline.push({
153
+ timestamp: performanceReport.generatedAt || null,
154
+ phase: 'PERFORMANCE',
155
+ event: 'budget_violation',
156
+ severity: 'BLOCKING',
157
+ data: {
158
+ type: violation.type,
159
+ actual: violation.actual,
160
+ budget: violation.budget,
161
+ message: violation.message
162
+ }
163
+ });
164
+ }
165
+ }
166
+
167
+ if (performanceReport?.warnings) {
168
+ for (const warning of performanceReport.warnings) {
169
+ timeline.push({
170
+ timestamp: performanceReport.generatedAt || null,
171
+ phase: 'PERFORMANCE',
172
+ event: 'budget_warning',
173
+ severity: 'DEGRADED',
174
+ data: {
175
+ type: warning.type,
176
+ actual: warning.actual,
177
+ budget: warning.budget,
178
+ message: warning.message
179
+ }
180
+ });
181
+ }
182
+ }
183
+
184
+ // Guardrails applied (from findings)
185
+ if (findings?.findings) {
186
+ for (const finding of findings.findings) {
187
+ if (finding.guardrails?.appliedRules && finding.guardrails.appliedRules.length > 0) {
188
+ timeline.push({
189
+ timestamp: finding.timestamp || null,
190
+ phase: 'DETECT',
191
+ event: 'guardrails_applied',
192
+ data: {
193
+ findingId: finding.findingId || finding.id,
194
+ rules: finding.guardrails.appliedRules.map(r => r.id || r),
195
+ finalDecision: finding.guardrails.finalDecision
196
+ }
197
+ });
198
+ }
199
+ }
200
+ }
201
+
202
+ // Evidence enforcement (from findings)
203
+ if (findings?.findings) {
204
+ for (const finding of findings.findings) {
205
+ if (finding.evidencePackage) {
206
+ timeline.push({
207
+ timestamp: finding.timestamp || null,
208
+ phase: 'DETECT',
209
+ event: 'evidence_enforced',
210
+ data: {
211
+ findingId: finding.findingId || finding.id,
212
+ isComplete: finding.evidencePackage.isComplete,
213
+ status: finding.severity || finding.status
214
+ }
215
+ });
216
+ }
217
+ }
218
+ }
219
+
220
+ // Failures from failure ledger
221
+ if (failureLedger?.failures) {
222
+ for (const failure of failureLedger.failures) {
223
+ timeline.push({
224
+ timestamp: failure.timestamp || (startedAt ? new Date(Date.parse(startedAt) + (failure.relativeTime || 0)).toISOString() : null),
225
+ phase: failure.phase || 'UNKNOWN',
226
+ event: 'failure_recorded',
227
+ severity: failure.severity || 'WARNING',
228
+ data: {
229
+ code: failure.code,
230
+ message: failure.message,
231
+ category: failure.category,
232
+ recoverable: failure.recoverable
233
+ }
234
+ });
235
+ }
236
+ }
237
+
238
+ // Run completion
239
+ if (completedAt) {
240
+ timeline.push({
241
+ timestamp: completedAt,
242
+ phase: 'RUN_COMPLETE',
243
+ event: 'run_completed',
244
+ data: {
245
+ status: summary?.status || runStatus?.status || 'UNKNOWN',
246
+ totalDurationMs: startedAt && completedAt ? Date.parse(completedAt) - Date.parse(startedAt) : null
247
+ }
248
+ });
249
+ }
250
+
251
+ // Sort by timestamp
252
+ timeline.sort((a, b) => {
253
+ if (!a.timestamp && !b.timestamp) return 0;
254
+ if (!a.timestamp) return 1;
255
+ if (!b.timestamp) return -1;
256
+ return Date.parse(a.timestamp) - Date.parse(b.timestamp);
257
+ });
258
+
259
+ return {
260
+ runId,
261
+ startedAt,
262
+ completedAt,
263
+ events: timeline,
264
+ summary: {
265
+ totalEvents: timeline.length,
266
+ byPhase: timeline.reduce((acc, e) => {
267
+ acc[e.phase] = (acc[e.phase] || 0) + 1;
268
+ return acc;
269
+ }, {}),
270
+ byEvent: timeline.reduce((acc, e) => {
271
+ acc[e.event] = (acc[e.event] || 0) + 1;
272
+ return acc;
273
+ }, {}),
274
+ blockingViolations: timeline.filter(e => e.severity === 'BLOCKING').length,
275
+ degradedWarnings: timeline.filter(e => e.severity === 'DEGRADED').length
276
+ },
277
+ generatedAt: new Date().toISOString()
278
+ };
279
+ }
280
+
281
+ /**
282
+ * Write timeline to file
283
+ *
284
+ * @param {string} projectDir - Project directory
285
+ * @param {string} runId - Run ID
286
+ * @param {Object} timeline - Timeline object
287
+ * @returns {string} Path to written file
288
+ */
289
+ export function writeRunTimeline(projectDir, runId, timeline) {
290
+ const runDir = resolve(projectDir, '.verax', 'runs', runId);
291
+ const outputPath = resolve(runDir, 'run.timeline.json');
292
+ writeFileSync(outputPath, JSON.stringify(timeline, null, 2), 'utf-8');
293
+ return outputPath;
294
+ }
295
+
296
+ /**
297
+ * Load timeline from file
298
+ *
299
+ * @param {string} projectDir - Project directory
300
+ * @param {string} runId - Run ID
301
+ * @returns {Object|null} Timeline or null
302
+ */
303
+ export function loadRunTimeline(projectDir, runId) {
304
+ const runDir = resolve(projectDir, '.verax', 'runs', runId);
305
+ const timelinePath = resolve(runDir, 'run.timeline.json');
306
+
307
+ if (!existsSync(timelinePath)) {
308
+ return null;
309
+ }
310
+
311
+ try {
312
+ // @ts-expect-error - readFileSync with encoding returns string
313
+ return JSON.parse(readFileSync(timelinePath, 'utf-8'));
314
+ } catch {
315
+ return null;
316
+ }
317
+ }
318
+
@@ -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
+