@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
@@ -17,6 +17,9 @@ import { detectFindings } from '../util/detection-engine.js';
17
17
  import { writeFindingsJson } from '../util/findings-writer.js';
18
18
  import { writeSummaryJson } from '../util/summary-writer.js';
19
19
  import { computeRuntimeBudget, withTimeout } from '../util/runtime-budget.js';
20
+ import { assertHasLocalSource } from '../util/source-requirement.js';
21
+ import { runWithDeterminism } from '../util/determinism-runner.js';
22
+ import { runDeterminismCheck } from '../../verax/core/determinism/engine.js';
20
23
 
21
24
  const __filename = fileURLToPath(import.meta.url);
22
25
  const __dirname = dirname(__filename);
@@ -27,7 +30,7 @@ function getVersion() {
27
30
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
28
31
  return pkg.version;
29
32
  } catch {
30
- return '0.2.0';
33
+ return '0.3.0';
31
34
  }
32
35
  }
33
36
 
@@ -36,17 +39,68 @@ function getVersion() {
36
39
  * Strict, non-interactive CLI mode with explicit flags
37
40
  */
38
41
  export async function runCommand(options) {
42
+ return await runCommandInternal(options);
43
+ }
44
+
45
+ /**
46
+ * Internal run command implementation
47
+ */
48
+ async function runCommandInternal(options) {
39
49
  const {
40
50
  url,
51
+ fixture,
41
52
  src = '.',
42
53
  out = '.verax',
43
54
  json = false,
44
55
  verbose = false,
56
+ determinism = false,
57
+ determinismRuns = 2,
45
58
  } = options;
46
59
 
60
+ // PHASE 25: Support fixture mode for determinism
61
+ let resolvedUrl = url;
62
+ let fixtureId = null;
63
+
64
+ if (fixture && !url) {
65
+ // Extract URL from fixture
66
+ const { resolve } = await import('path');
67
+ const { existsSync, readFileSync } = await import('fs');
68
+ const { fileURLToPath } = await import('url');
69
+ const { dirname } = await import('path');
70
+ const __filename = fileURLToPath(import.meta.url);
71
+ const __dirname = dirname(__filename);
72
+
73
+ const fixturePath = resolve(__dirname, '..', '..', '..', 'test', 'fixtures', 'realistic', fixture);
74
+ if (existsSync(fixturePath)) {
75
+ // Try to read package.json or index.html to extract URL
76
+ const packagePath = resolve(fixturePath, 'package.json');
77
+ const indexPath = resolve(fixturePath, 'index.html');
78
+
79
+ if (existsSync(packagePath)) {
80
+ try {
81
+ const pkg = JSON.parse(readFileSync(packagePath, 'utf-8'));
82
+ if (pkg.verax && pkg.verax.url) {
83
+ resolvedUrl = pkg.verax.url;
84
+ fixtureId = fixture;
85
+ }
86
+ } catch {
87
+ // Ignore parse errors
88
+ }
89
+ }
90
+
91
+ // If no URL found, use default localhost URL for fixture
92
+ if (!resolvedUrl) {
93
+ resolvedUrl = `http://localhost:5173`; // Default Vite dev server
94
+ fixtureId = fixture;
95
+ }
96
+ } else {
97
+ throw new DataError(`Fixture not found: ${fixture}`);
98
+ }
99
+ }
100
+
47
101
  // Validate required arguments
48
- if (!url) {
49
- throw new UsageError('Missing required argument: --url <url>');
102
+ if (!resolvedUrl) {
103
+ throw new UsageError('Missing required argument: --url <url> or --fixture <fixture>');
50
104
  }
51
105
 
52
106
  const projectRoot = resolve(process.cwd());
@@ -56,6 +110,9 @@ export async function runCommand(options) {
56
110
  if (!existsSync(srcPath)) {
57
111
  throw new DataError(`Source directory not found: ${srcPath}`);
58
112
  }
113
+
114
+ // Enforce local source availability (no URL-only scans)
115
+ assertHasLocalSource(srcPath);
59
116
 
60
117
  // Create event emitter
61
118
  const events = new RunEventEmitter();
@@ -92,6 +149,8 @@ export async function runCommand(options) {
92
149
  try {
93
150
  const failedAt = new Date().toISOString();
94
151
  atomicWriteJson(paths.runStatusJson, {
152
+ contractVersion: 1,
153
+ artifactVersions: paths.artifactVersions,
95
154
  status: 'FAILED',
96
155
  runId,
97
156
  startedAt,
@@ -100,6 +159,8 @@ export async function runCommand(options) {
100
159
  });
101
160
 
102
161
  atomicWriteJson(paths.runMetaJson, {
162
+ contractVersion: 1,
163
+ artifactVersions: paths.artifactVersions,
103
164
  veraxVersion: getVersion(),
104
165
  nodeVersion: process.version,
105
166
  platform: process.platform,
@@ -120,7 +181,7 @@ export async function runCommand(options) {
120
181
  startedAt,
121
182
  completedAt: failedAt,
122
183
  command: 'run',
123
- url,
184
+ url: resolvedUrl,
124
185
  notes: `Run timed out: ${reason}`,
125
186
  }, {
126
187
  expectationsTotal: 0,
@@ -145,6 +206,70 @@ export async function runCommand(options) {
145
206
  });
146
207
  };
147
208
 
209
+ // PHASE 25: If determinism mode, wrap execution
210
+ if (determinism) {
211
+ const scanFn = async (runConfig) => {
212
+ // Execute a single scan run
213
+ const singleRunId = generateRunId();
214
+ const singlePaths = getRunPaths(projectRoot, out, singleRunId);
215
+ ensureRunDirectories(singlePaths);
216
+
217
+ // Execute scan (reuse existing logic but with single run)
218
+ // This is a simplified version - in production, you'd extract the scan logic
219
+ const { scan } = await import('../../verax/index.js');
220
+ const scanResult = await scan(
221
+ projectRoot,
222
+ resolvedUrl,
223
+ null, // manifestPath
224
+ null, // scanBudgetOverride
225
+ {}, // safetyFlags
226
+ singleRunId
227
+ );
228
+
229
+ return {
230
+ runId: singleRunId,
231
+ artifactPaths: {
232
+ findings: singlePaths.findingsJson,
233
+ runStatus: singlePaths.runStatusJson,
234
+ summary: singlePaths.summaryJson,
235
+ learn: singlePaths.learnJson,
236
+ observe: singlePaths.observeJson,
237
+ runDir: singlePaths.baseDir
238
+ }
239
+ };
240
+ };
241
+
242
+ const determinismResult = await runWithDeterminism(scanFn, {
243
+ runs: determinismRuns,
244
+ out,
245
+ url: resolvedUrl,
246
+ fixture: fixtureId,
247
+ src,
248
+ verbose,
249
+ json
250
+ });
251
+
252
+ // PHASE 25: Output determinism report path
253
+ if (!json) {
254
+ console.log(`\nDeterminism check complete.`);
255
+ console.log(`Verdict: ${determinismResult.verdict}`);
256
+ console.log(`Report: ${determinismResult.reportPath}`);
257
+ } else {
258
+ console.log(JSON.stringify({
259
+ type: 'determinism:complete',
260
+ verdict: determinismResult.verdict,
261
+ reportPath: determinismResult.reportPath,
262
+ summary: determinismResult.summary
263
+ }));
264
+ }
265
+
266
+ return {
267
+ success: determinismResult.verdict === 'DETERMINISTIC' || determinismResult.verdict === 'NON_DETERMINISTIC_EXPECTED',
268
+ verdict: determinismResult.verdict,
269
+ reportPath: determinismResult.reportPath
270
+ };
271
+ }
272
+
148
273
  try {
149
274
  // Generate run ID
150
275
  runId = generateRunId();
@@ -197,6 +322,8 @@ export async function runCommand(options) {
197
322
  startedAt = now.toISOString();
198
323
 
199
324
  atomicWriteJson(paths.runStatusJson, {
325
+ contractVersion: 1,
326
+ artifactVersions: paths.artifactVersions,
200
327
  status: 'RUNNING',
201
328
  runId,
202
329
  startedAt,
@@ -204,13 +331,16 @@ export async function runCommand(options) {
204
331
 
205
332
  // Write metadata
206
333
  atomicWriteJson(paths.runMetaJson, {
334
+ contractVersion: 1,
335
+ artifactVersions: paths.artifactVersions,
207
336
  veraxVersion: getVersion(),
208
337
  nodeVersion: process.version,
209
338
  platform: process.platform,
210
339
  cwd: projectRoot,
211
340
  command: 'run',
212
- args: { url, src, out },
213
- url,
341
+ args: { url: resolvedUrl, fixture: fixtureId, src, out },
342
+ url: resolvedUrl,
343
+ fixtureId: fixtureId,
214
344
  src: srcPath,
215
345
  startedAt,
216
346
  completedAt: null,
@@ -402,29 +532,6 @@ export async function runCommand(options) {
402
532
 
403
533
  const completedAt = new Date().toISOString();
404
534
 
405
- // Write completed status
406
- atomicWriteJson(paths.runStatusJson, {
407
- status: 'COMPLETE',
408
- runId,
409
- startedAt,
410
- completedAt,
411
- });
412
-
413
- // Update metadata with completion time
414
- atomicWriteJson(paths.runMetaJson, {
415
- veraxVersion: getVersion(),
416
- nodeVersion: process.version,
417
- platform: process.platform,
418
- cwd: projectRoot,
419
- command: 'run',
420
- args: { url, src, out },
421
- url,
422
- src: srcPath,
423
- startedAt,
424
- completedAt,
425
- error: null,
426
- });
427
-
428
535
  const runDurationMs = completedAt && startedAt ? (Date.parse(completedAt) - Date.parse(startedAt)) : 0;
429
536
  const metrics = {
430
537
  learnMs: observeData?.timings?.learnMs || 0,
@@ -439,6 +546,9 @@ export async function runCommand(options) {
439
546
  UNKNOWN: 0,
440
547
  };
441
548
 
549
+ // Write detect results (or empty if detection failed)
550
+ const findingsResult = writeFindingsJson(paths.baseDir, detectData);
551
+
442
552
  // Write summary with stable digest
443
553
  writeSummaryJson(paths.summaryJson, {
444
554
  runId,
@@ -462,9 +572,6 @@ export async function runCommand(options) {
462
572
  ...findingsCounts,
463
573
  });
464
574
 
465
- // Write detect results (or empty if detection failed)
466
- writeFindingsJson(paths.baseDir, detectData);
467
-
468
575
  // Write traces (include all events including heartbeats)
469
576
  const allEvents = events.getEvents();
470
577
  const tracesContent = allEvents
@@ -480,6 +587,77 @@ export async function runCommand(options) {
480
587
 
481
588
  // Write observe results
482
589
  writeObserveJson(paths.baseDir, observeData);
590
+
591
+ // PHASE 6: Verify artifacts after all writers finish
592
+ const { verifyRun } = await import('../../verax/core/artifacts/verifier.js');
593
+ const verification = verifyRun(paths.baseDir, paths.artifactVersions);
594
+
595
+ // Determine final status based on verification
596
+ let finalStatus = 'COMPLETE';
597
+ if (!verification.ok) {
598
+ finalStatus = 'INVALID';
599
+ } else if (verification.warnings.length > 0) {
600
+ finalStatus = 'VALID_WITH_WARNINGS';
601
+ }
602
+
603
+ // PHASE 21.2: Compute determinism summary for run.status.json
604
+ let determinismSummary = null;
605
+ try {
606
+ const decisionsPath = resolve(paths.baseDir, 'decisions.json');
607
+ if (existsSync(decisionsPath)) {
608
+ const decisions = JSON.parse(readFileSync(decisionsPath, 'utf-8'));
609
+ const { DecisionRecorder } = await import('../../verax/core/determinism-model.js');
610
+ const recorder = DecisionRecorder.fromExport(decisions);
611
+ const { computeDeterminismVerdict } = await import('../../verax/core/determinism/contract.js');
612
+ const verdict = computeDeterminismVerdict(recorder);
613
+
614
+ determinismSummary = {
615
+ verdict: verdict.verdict, // DETERMINISTIC or NON_DETERMINISTIC
616
+ message: verdict.message,
617
+ adaptiveEventsCount: verdict.adaptiveEvents.length
618
+ };
619
+ }
620
+ } catch (error) {
621
+ // Ignore errors - determinism summary is optional
622
+ }
623
+
624
+ // Write completed status with contract + enforcement snapshot + verification + determinism
625
+ atomicWriteJson(paths.runStatusJson, {
626
+ contractVersion: 1,
627
+ artifactVersions: paths.artifactVersions,
628
+ status: finalStatus,
629
+ runId,
630
+ startedAt,
631
+ completedAt,
632
+ enforcement: findingsResult?.payload?.enforcement || null,
633
+ verification: {
634
+ ok: verification.ok,
635
+ status: finalStatus,
636
+ errorsCount: verification.errors.length,
637
+ warningsCount: verification.warnings.length,
638
+ verifiedAt: verification.verifiedAt
639
+ },
640
+ // PHASE 21.2: Determinism summary
641
+ determinismSummary: determinismSummary
642
+ });
643
+
644
+ // Update metadata with completion time
645
+ atomicWriteJson(paths.runMetaJson, {
646
+ contractVersion: 1,
647
+ artifactVersions: paths.artifactVersions,
648
+ veraxVersion: getVersion(),
649
+ nodeVersion: process.version,
650
+ platform: process.platform,
651
+ cwd: projectRoot,
652
+ command: 'run',
653
+ args: { url: resolvedUrl, fixture: fixtureId, src, out },
654
+ url: resolvedUrl,
655
+ fixtureId: fixtureId,
656
+ src: srcPath,
657
+ startedAt,
658
+ completedAt,
659
+ error: null,
660
+ });
483
661
 
484
662
  events.emit('phase:completed', {
485
663
  phase: 'Finalize Artifacts',
@@ -511,6 +689,34 @@ export async function runCommand(options) {
511
689
  console.log('\nRun complete.');
512
690
  console.log(`Run ID: ${runId}`);
513
691
  console.log(`Artifacts: ${paths.baseDir}`);
692
+
693
+ // PHASE 21.2: Display determinism truth
694
+ if (determinismSummary) {
695
+ console.log('');
696
+ if (determinismSummary.verdict === 'DETERMINISTIC') {
697
+ console.log('Deterministic: YES');
698
+ } else {
699
+ console.log(`Deterministic: NO (${determinismSummary.message})`);
700
+ if (determinismSummary.adaptiveEventsCount > 0) {
701
+ console.log(` Adaptive events detected: ${determinismSummary.adaptiveEventsCount}`);
702
+ }
703
+ }
704
+ }
705
+
706
+ // PHASE 6: Display verification results
707
+ const { formatVerificationOutput } = await import('../../verax/core/artifacts/verifier.js');
708
+ const verificationOutput = formatVerificationOutput(verification, verbose);
709
+ console.log('');
710
+ console.log(verificationOutput);
711
+
712
+ // PHASE 21.9: Display performance metrics
713
+ const { loadPerformanceReport } = await import('../../verax/core/perf/perf.report.js');
714
+ const { formatPerformanceMetrics } = await import('../../verax/core/perf/perf.display.js');
715
+ const perfReport = loadPerformanceReport(projectRoot, runId);
716
+ if (perfReport) {
717
+ console.log('');
718
+ console.log(formatPerformanceMetrics(perfReport));
719
+ }
514
720
  }
515
721
 
516
722
  return { runId, paths, success: true };
@@ -528,6 +734,8 @@ export async function runCommand(options) {
528
734
  try {
529
735
  const failedAt = new Date().toISOString();
530
736
  atomicWriteJson(paths.runStatusJson, {
737
+ contractVersion: 1,
738
+ artifactVersions: paths.artifactVersions,
531
739
  status: 'FAILED',
532
740
  runId,
533
741
  startedAt,
@@ -537,13 +745,16 @@ export async function runCommand(options) {
537
745
 
538
746
  // Update metadata
539
747
  atomicWriteJson(paths.runMetaJson, {
748
+ contractVersion: 1,
749
+ artifactVersions: paths.artifactVersions,
540
750
  veraxVersion: getVersion(),
541
751
  nodeVersion: process.version,
542
752
  platform: process.platform,
543
753
  cwd: projectRoot,
544
754
  command: 'run',
545
- args: { url, src, out },
546
- url,
755
+ args: { url: resolvedUrl || url, fixture: fixtureId, src, out },
756
+ url: resolvedUrl || url,
757
+ fixtureId: fixtureId,
547
758
  src: srcPath,
548
759
  startedAt,
549
760
  completedAt: failedAt,
@@ -558,7 +769,7 @@ export async function runCommand(options) {
558
769
  startedAt,
559
770
  completedAt: failedAt,
560
771
  command: 'run',
561
- url,
772
+ url: resolvedUrl || url,
562
773
  notes: `Run failed: ${error.message}`,
563
774
  }, {
564
775
  expectationsTotal: 0,
@@ -0,0 +1,211 @@
1
+ /**
2
+ * PHASE 21.8 — Security Check CLI Command
3
+ *
4
+ * Checks security baseline: secrets, vulnerabilities, supply-chain.
5
+ * Exit codes: 0 = SECURITY-OK, 6 = SECURITY-BLOCKED, 70 = Internal corruption
6
+ */
7
+
8
+ import { scanSecrets, writeSecretsReport } from '../../verax/core/security/secrets.scan.js';
9
+ import { scanVulnerabilities, writeVulnReport } from '../../verax/core/security/vuln.scan.js';
10
+ import { evaluateSupplyChainPolicy, writeSupplyChainReport } from '../../verax/core/security/supplychain.policy.js';
11
+ import { writeSecurityReport } from '../../verax/core/security/security-report.js';
12
+ import { resolve } from 'path';
13
+ import { mkdirSync, existsSync } from 'fs';
14
+
15
+ /**
16
+ * Security check command
17
+ *
18
+ * @param {Object} options - Options
19
+ * @param {boolean} [options.json] - Output as JSON
20
+ */
21
+ export async function securityCheckCommand(options = {}) {
22
+ const { json = false } = options;
23
+ const projectDir = resolve(process.cwd());
24
+
25
+ const status = {
26
+ secrets: { ok: false, hasSecrets: false, blockers: [], tool: 'VERAX_SECRETS_SCANNER' },
27
+ vulnerabilities: { ok: false, blocking: false, blockers: [], warnings: [], tool: null, availability: 'UNKNOWN' },
28
+ supplychain: { ok: false, violations: [], blockers: [], tool: 'VERAX_SUPPLYCHAIN_POLICY' }
29
+ };
30
+
31
+ // 1. Scan for secrets
32
+ try {
33
+ const secretsResult = await scanSecrets(projectDir);
34
+ writeSecretsReport(projectDir, secretsResult);
35
+
36
+ status.secrets.ok = secretsResult.ok;
37
+ status.secrets.hasSecrets = secretsResult.hasSecrets;
38
+
39
+ if (secretsResult.hasSecrets) {
40
+ const critical = secretsResult.findings.filter(f => f.severity === 'CRITICAL');
41
+ const high = secretsResult.findings.filter(f => f.severity === 'HIGH');
42
+
43
+ if (critical.length > 0) {
44
+ status.secrets.blockers.push(`${critical.length} CRITICAL secret(s) detected`);
45
+ }
46
+ if (high.length > 0) {
47
+ status.secrets.blockers.push(`${high.length} HIGH severity secret(s) detected`);
48
+ }
49
+
50
+ // Add sample findings (first 3)
51
+ const sampleFindings = secretsResult.findings.slice(0, 3).map(f =>
52
+ `${f.type} in ${f.file}:${f.line}`
53
+ );
54
+ status.secrets.blockers.push(`Sample findings: ${sampleFindings.join(', ')}`);
55
+ }
56
+ } catch (error) {
57
+ status.secrets.blockers.push(`Secrets scan failed: ${error.message}`);
58
+ }
59
+
60
+ // 2. Scan vulnerabilities
61
+ let vulnResult = null;
62
+ try {
63
+ vulnResult = await scanVulnerabilities(projectDir);
64
+ writeVulnReport(projectDir, vulnResult);
65
+
66
+ status.vulnerabilities.ok = !vulnResult.blocking;
67
+ status.vulnerabilities.blocking = vulnResult.blocking;
68
+ status.vulnerabilities.tool = vulnResult.tool || null;
69
+ status.vulnerabilities.availability = vulnResult.availability || 'UNKNOWN';
70
+ status.vulnerabilities.osvAvailable = vulnResult.osvAvailable || false;
71
+
72
+ if (vulnResult.availability === 'NOT_AVAILABLE') {
73
+ status.vulnerabilities.warnings.push('OSV scanner not available, using npm audit fallback');
74
+ }
75
+
76
+ if (vulnResult.blocking) {
77
+ if (vulnResult.summary.critical > 0) {
78
+ status.vulnerabilities.blockers.push(`${vulnResult.summary.critical} CRITICAL vulnerability/vulnerabilities`);
79
+ }
80
+ if (vulnResult.summary.high > 0) {
81
+ status.vulnerabilities.blockers.push(`${vulnResult.summary.high} HIGH severity vulnerability/vulnerabilities`);
82
+ }
83
+ }
84
+
85
+ if (vulnResult.summary.medium > 0 && !vulnResult.blocking) {
86
+ status.vulnerabilities.warnings.push(`${vulnResult.summary.medium} MEDIUM severity vulnerability/vulnerabilities (non-blocking)`);
87
+ }
88
+ } catch (error) {
89
+ status.vulnerabilities.blockers.push(`Vulnerability scan failed: ${error.message}`);
90
+ }
91
+
92
+ // 3. Check supply-chain policy
93
+ let supplyChainResult = null;
94
+ try {
95
+ supplyChainResult = await evaluateSupplyChainPolicy(projectDir);
96
+ writeSupplyChainReport(projectDir, supplyChainResult);
97
+
98
+ status.supplychain.ok = supplyChainResult.ok;
99
+ status.supplychain.violations = supplyChainResult.violations;
100
+
101
+ if (!supplyChainResult.ok) {
102
+ for (const violation of supplyChainResult.violations) {
103
+ status.supplychain.blockers.push(violation.message);
104
+ }
105
+ }
106
+ } catch (error) {
107
+ status.supplychain.blockers.push(`Supply-chain check failed: ${error.message}`);
108
+ }
109
+
110
+ // Determine overall status
111
+ const allOk = status.secrets.ok && status.vulnerabilities.ok && status.supplychain.ok;
112
+
113
+ // Write unified security report
114
+ let secretsReport = null;
115
+ try {
116
+ const secretsPath = resolve(projectDir, 'release', 'security.secrets.report.json');
117
+ if (existsSync(secretsPath)) {
118
+ const { readFileSync } = await import('fs');
119
+ secretsReport = JSON.parse(readFileSync(secretsPath, 'utf-8'));
120
+ }
121
+ } catch {
122
+ // Ignore
123
+ }
124
+
125
+ let vulnReportData = vulnResult;
126
+ let supplyChainReportData = supplyChainResult;
127
+
128
+ const unifiedReport = {
129
+ securityOk: allOk,
130
+ status,
131
+ summary: {
132
+ secrets: status.secrets.ok ? 'OK' : 'BLOCKED',
133
+ vulnerabilities: status.vulnerabilities.ok ? 'OK' : (status.vulnerabilities.blocking ? 'BLOCKED' : (status.vulnerabilities.availability === 'NOT_AVAILABLE' ? 'NOT_AVAILABLE' : 'WARN')),
134
+ supplychain: status.supplychain.ok ? 'OK' : 'BLOCKED'
135
+ },
136
+ secretsReport,
137
+ vulnReport: vulnReportData,
138
+ supplyChainReport: supplyChainReportData
139
+ };
140
+
141
+ const unifiedReportPath = writeSecurityReport(projectDir, unifiedReport);
142
+ const hasInternalCorruption =
143
+ status.secrets.blockers.some(b => b.includes('corruption') || b.includes('Internal')) ||
144
+ status.vulnerabilities.blockers.some(b => b.includes('corruption')) ||
145
+ status.supplychain.blockers.some(b => b.includes('corruption'));
146
+
147
+ // Output
148
+ if (json) {
149
+ console.log(JSON.stringify({
150
+ securityOk: allOk,
151
+ status,
152
+ summary: {
153
+ secrets: status.secrets.ok ? 'OK' : 'BLOCKED',
154
+ vulnerabilities: status.vulnerabilities.ok ? 'OK' : (status.vulnerabilities.blocking ? 'BLOCKED' : (status.vulnerabilities.availability === 'NOT_AVAILABLE' ? 'NOT_AVAILABLE' : 'WARN')),
155
+ supplychain: status.supplychain.ok ? 'OK' : 'BLOCKED'
156
+ },
157
+ unifiedReportPath: unifiedReportPath
158
+ }, null, 2));
159
+ } else {
160
+ console.log('\n' + '='.repeat(80));
161
+ console.log('SECURITY BASELINE CHECK');
162
+ console.log('='.repeat(80));
163
+
164
+ console.log(`\nSecrets: ${status.secrets.ok ? '✅ OK' : '❌ BLOCKED'}`);
165
+ if (status.secrets.blockers.length > 0) {
166
+ for (const blocker of status.secrets.blockers) {
167
+ console.log(` - ${blocker}`);
168
+ }
169
+ }
170
+
171
+ console.log(`\nVulnerabilities: ${status.vulnerabilities.ok ? '✅ OK' : (status.vulnerabilities.blocking ? '❌ BLOCKED' : '⚠️ WARN')}`);
172
+ if (status.vulnerabilities.blockers.length > 0) {
173
+ for (const blocker of status.vulnerabilities.blockers) {
174
+ console.log(` - ${blocker}`);
175
+ }
176
+ }
177
+ if (status.vulnerabilities.warnings.length > 0) {
178
+ for (const warning of status.vulnerabilities.warnings) {
179
+ console.log(` ⚠️ ${warning}`);
180
+ }
181
+ }
182
+
183
+ console.log(`\nSupply-chain: ${status.supplychain.ok ? '✅ OK' : '❌ BLOCKED'}`);
184
+ if (status.supplychain.blockers.length > 0) {
185
+ for (const blocker of status.supplychain.blockers) {
186
+ console.log(` - ${blocker}`);
187
+ }
188
+ }
189
+
190
+ console.log(`\nOverall: ${allOk ? '✅ SECURITY-OK' : '❌ SECURITY-BLOCKED'}`);
191
+ console.log(`\nSee unified report: ${unifiedReportPath}`);
192
+ console.log('='.repeat(80) + '\n');
193
+ }
194
+
195
+ // Exit codes: 0 = SECURITY-OK, 6 = SECURITY-BLOCKED, 70 = Internal corruption
196
+ // NOT_AVAILABLE tools exit 0 only if policy allows (strict mode would exit 2)
197
+ const hasNotAvailable = status.vulnerabilities.availability === 'NOT_AVAILABLE';
198
+ const strictMode = process.env.VERAX_SECURITY_STRICT === '1';
199
+
200
+ if (allOk) {
201
+ process.exit(0);
202
+ } else if (hasNotAvailable && !strictMode) {
203
+ // NOT_AVAILABLE is not a blocker unless strict mode
204
+ process.exit(0);
205
+ } else if (hasInternalCorruption) {
206
+ process.exit(70);
207
+ } else {
208
+ process.exit(6);
209
+ }
210
+ }
211
+