@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
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # 🛡️ VERAX
2
2
 
3
+ > **SOURCE CODE REQUIRED** — VERAX requires local access to source code. It is not a public website scanner. See [docs/README.md](docs/README.md) for the canonical product contract.
4
+
3
5
  A forensic observation engine for real user outcomes
4
6
 
5
7
  VERAX observes and reports gaps between what your code explicitly promises and what users can actually observe.
@@ -229,27 +231,21 @@ MEDIUM (60–79) — likely discrepancy with some ambiguity
229
231
 
230
232
  LOW (<60) — weak or partial evidence; interpret cautiously
231
233
 
232
- 🧭 When VERAX is a good fit
233
-
234
- SaaS signup and pricing flows
235
-
236
- React and Next.js projects
237
-
238
- CI pipelines that need UX reality checks
239
-
240
- Teams that value evidence over assumptions
241
-
242
- 🚫 When VERAX is NOT a good fit
243
-
244
- Internal admin dashboards
245
-
246
- Authentication-heavy systems
234
+ 🧭 Supported use cases
247
235
 
248
- Apps built around highly dynamic routing
236
+ - React, Next.js, or static HTML projects where you can provide local source code
237
+ - CI pipelines that can mount the repository so VERAX can analyze it
238
+ - Developer workstations that need evidence-backed silent failure detection
239
+ - Teams that value deterministic evidence over heuristics or AI guesses
240
+ - Demo projects in this repo (see [demos/](demos/))
249
241
 
250
- Unsupported frameworks
242
+ 🚫 Not supported
251
243
 
252
- Teams expecting a full QA replacement
244
+ - URL-only scans without source code (fails fast with guidance)
245
+ - Production monitoring or hosted/public scanning
246
+ - Highly dynamic routing without static promises to analyze
247
+ - Closed-source third-party targets where code is unavailable
248
+ - Using VERAX as a drop-in QA replacement
253
249
 
254
250
  🧪 Project status
255
251
 
package/bin/verax.js CHANGED
@@ -2,10 +2,17 @@
2
2
 
3
3
  /**
4
4
  * VERAX CLI Shim
5
+ * PHASE 21.6.1: Verified entry point
5
6
  * Delegates to src/cli/entry.js
7
+ *
8
+ * This file MUST be the only entry point for the verax CLI.
9
+ * package.json "bin" field points to this file.
6
10
  */
7
11
 
8
12
  import('../src/cli/entry.js').catch((error) => {
9
13
  console.error(`Failed to load CLI: ${error.message}`);
14
+ if (error.stack) {
15
+ console.error(error.stack);
16
+ }
10
17
  process.exit(2);
11
18
  });
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@veraxhq/verax",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "VERAX - Silent failure detection for websites",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
8
- "verax": "./bin/verax.js"
8
+ "verax": "bin/verax.js"
9
9
  },
10
10
  "files": [
11
11
  "bin/",
@@ -34,7 +34,7 @@
34
34
  "@babel/parser": "^7.28.5",
35
35
  "@babel/traverse": "^7.28.5",
36
36
  "@reduxjs/toolkit": "^2.11.2",
37
- "@veraxhq/verax": "file:veraxhq-verax-0.2.1.tgz",
37
+ "@veraxhq/verax": "file:veraxhq-verax-0.3.0.tgz",
38
38
  "eslint": "^8.57.0",
39
39
  "next": "^16.1.1",
40
40
  "react": "^19.2.3",
@@ -0,0 +1,104 @@
1
+ /**
2
+ * PHASE 21.11 — Baseline Command
3
+ *
4
+ * `verax baseline` - Shows baseline hash and drift status
5
+ */
6
+
7
+ import { resolve } from 'path';
8
+ import { loadBaselineSnapshot, buildBaselineSnapshot } from '../../verax/core/baseline/baseline.snapshot.js';
9
+ import { compareBaselines, isBaselineFrozen } from '../../verax/core/baseline/baseline.enforcer.js';
10
+
11
+ /**
12
+ * Baseline command
13
+ *
14
+ * @param {string} projectDir - Project directory
15
+ * @param {Object} options - Command options
16
+ */
17
+ export async function baselineCommand(projectDir, options = {}) {
18
+ const { json = false } = options;
19
+
20
+ const frozen = loadBaselineSnapshot(projectDir);
21
+ const current = buildBaselineSnapshot(projectDir);
22
+
23
+ if (!frozen) {
24
+ if (json) {
25
+ console.log(JSON.stringify({
26
+ status: 'NO_BASELINE',
27
+ message: 'No baseline snapshot found (pre-GA)',
28
+ current: {
29
+ hash: current.baselineHash,
30
+ version: current.veraxVersion,
31
+ commit: current.gitCommit
32
+ }
33
+ }, null, 2));
34
+ } else {
35
+ console.log('\n=== Baseline Status ===\n');
36
+ console.log('Status: NO_BASELINE (pre-GA)');
37
+ console.log(`Current baseline hash: ${current.baselineHash}`);
38
+ console.log(`Version: ${current.veraxVersion}`);
39
+ console.log(`Commit: ${current.gitCommit}`);
40
+ console.log(`Dirty: ${current.gitDirty ? 'YES' : 'NO'}`);
41
+ console.log('\nNote: Baseline will be frozen when GA-READY is achieved.\n');
42
+ }
43
+ return;
44
+ }
45
+
46
+ const comparison = compareBaselines(current, frozen);
47
+ const frozenStatus = frozen.frozen ? 'FROZEN' : 'NOT_FROZEN';
48
+
49
+ if (json) {
50
+ console.log(JSON.stringify({
51
+ status: frozenStatus,
52
+ frozen: frozen.frozen,
53
+ drifted: comparison.drifted,
54
+ message: comparison.message,
55
+ frozenBaseline: {
56
+ hash: frozen.baselineHash,
57
+ version: frozen.veraxVersion,
58
+ commit: frozen.gitCommit,
59
+ timestamp: frozen.timestamp
60
+ },
61
+ currentBaseline: {
62
+ hash: current.baselineHash,
63
+ version: current.veraxVersion,
64
+ commit: current.gitCommit
65
+ },
66
+ differences: comparison.differences
67
+ }, null, 2));
68
+ } else {
69
+ console.log('\n=== Baseline Status ===\n');
70
+ console.log(`Status: ${frozenStatus}`);
71
+ console.log(`Frozen: ${frozen.frozen ? 'YES' : 'NO'}`);
72
+ console.log(`Drifted: ${comparison.drifted ? 'YES' : 'NO'}`);
73
+ console.log(`\nMessage: ${comparison.message}`);
74
+
75
+ console.log('\nFrozen Baseline:');
76
+ console.log(` Hash: ${frozen.baselineHash}`);
77
+ console.log(` Version: ${frozen.veraxVersion}`);
78
+ console.log(` Commit: ${frozen.gitCommit}`);
79
+ console.log(` Timestamp: ${frozen.timestamp}`);
80
+
81
+ console.log('\nCurrent Baseline:');
82
+ console.log(` Hash: ${current.baselineHash}`);
83
+ console.log(` Version: ${current.veraxVersion}`);
84
+ console.log(` Commit: ${current.gitCommit}`);
85
+ console.log(` Dirty: ${current.gitDirty ? 'YES' : 'NO'}`);
86
+
87
+ if (comparison.drifted) {
88
+ console.log('\n⚠️ BASELINE DRIFT DETECTED:');
89
+ for (const diff of comparison.differences) {
90
+ console.log(` - ${diff.message}`);
91
+ if (diff.component) {
92
+ console.log(` Component: ${diff.component}`);
93
+ }
94
+ }
95
+ console.log('\n⚠️ Changes to core contracts/policies after GA require:');
96
+ console.log(' 1. MAJOR version bump');
97
+ console.log(' 2. Baseline regeneration');
98
+ console.log(' 3. GA re-evaluation\n');
99
+ } else {
100
+ console.log('\n✓ Baseline integrity maintained\n');
101
+ }
102
+ }
103
+ }
104
+
@@ -3,6 +3,7 @@ import { existsSync, readFileSync } from 'fs';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { dirname } from 'path';
5
5
  import inquirer from 'inquirer';
6
+ import { assertExecutionBootstrapAllowed } from '../util/bootstrap-guard.js';
6
7
  import { DataError } from '../util/errors.js';
7
8
  import { generateRunId } from '../util/run-id.js';
8
9
  import { getRunPaths, ensureRunDirectories } from '../util/paths.js';
@@ -19,6 +20,7 @@ import { detectFindings } from '../util/detection-engine.js';
19
20
  import { writeFindingsJson } from '../util/findings-writer.js';
20
21
  import { writeSummaryJson } from '../util/summary-writer.js';
21
22
  import { computeRuntimeBudget, withTimeout } from '../util/runtime-budget.js';
23
+ import { assertHasLocalSource } from '../util/source-requirement.js';
22
24
 
23
25
  const __filename = fileURLToPath(import.meta.url);
24
26
  const __dirname = dirname(__filename);
@@ -29,7 +31,7 @@ function getVersion() {
29
31
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
30
32
  return pkg.version;
31
33
  } catch {
32
- return '0.2.0';
34
+ return '0.3.0';
33
35
  }
34
36
  }
35
37
 
@@ -44,6 +46,8 @@ export async function defaultCommand(options = {}) {
44
46
  url = null,
45
47
  json = false,
46
48
  verbose = false,
49
+ determinism = false,
50
+ determinismRuns = 2,
47
51
  } = options;
48
52
 
49
53
  const projectRoot = resolve(process.cwd());
@@ -53,6 +57,9 @@ export async function defaultCommand(options = {}) {
53
57
  if (!existsSync(srcPath)) {
54
58
  throw new DataError(`Source directory not found: ${srcPath}`);
55
59
  }
60
+
61
+ // Enforce local source availability (no URL-only scans)
62
+ assertHasLocalSource(srcPath);
56
63
 
57
64
  // Create event emitter
58
65
  const events = new RunEventEmitter();
@@ -93,6 +100,8 @@ export async function defaultCommand(options = {}) {
93
100
  try {
94
101
  const failedAt = new Date().toISOString();
95
102
  atomicWriteJson(paths.runStatusJson, {
103
+ contractVersion: 1,
104
+ artifactVersions: paths.artifactVersions,
96
105
  status: 'FAILED',
97
106
  runId,
98
107
  startedAt,
@@ -101,6 +110,8 @@ export async function defaultCommand(options = {}) {
101
110
  });
102
111
 
103
112
  atomicWriteJson(paths.runMetaJson, {
113
+ contractVersion: 1,
114
+ artifactVersions: paths.artifactVersions,
104
115
  veraxVersion: getVersion(),
105
116
  nodeVersion: process.version,
106
117
  platform: process.platform,
@@ -229,6 +240,9 @@ export async function defaultCommand(options = {}) {
229
240
  console.log(''); // blank line
230
241
  }
231
242
 
243
+ // PHASE 21.6.1: Runtime guard - crash if called during inspection
244
+ assertExecutionBootstrapAllowed('inquirer.prompt');
245
+
232
246
  const answer = await inquirer.prompt([
233
247
  {
234
248
  type: 'input',
@@ -275,12 +289,16 @@ export async function defaultCommand(options = {}) {
275
289
  let startedAt = now.toISOString();
276
290
 
277
291
  atomicWriteJson(paths.runStatusJson, {
292
+ contractVersion: 1,
293
+ artifactVersions: paths.artifactVersions,
278
294
  status: 'RUNNING',
279
295
  runId,
280
296
  startedAt,
281
297
  });
282
298
 
283
299
  atomicWriteJson(paths.runMetaJson, {
300
+ contractVersion: 1,
301
+ artifactVersions: paths.artifactVersions,
284
302
  veraxVersion: getVersion(),
285
303
  nodeVersion: process.version,
286
304
  platform: process.platform,
@@ -537,27 +555,9 @@ export async function defaultCommand(options = {}) {
537
555
 
538
556
  const completedAt = new Date().toISOString();
539
557
 
540
- atomicWriteJson(paths.runStatusJson, {
541
- status: 'COMPLETE',
542
- runId,
543
- startedAt,
544
- completedAt,
545
- });
546
-
547
- atomicWriteJson(paths.runMetaJson, {
548
- veraxVersion: getVersion(),
549
- nodeVersion: process.version,
550
- platform: process.platform,
551
- cwd: projectRoot,
552
- command: 'default',
553
- args: { url: resolvedUrl, src },
554
- url: resolvedUrl,
555
- src: srcPath,
556
- startedAt,
557
- completedAt,
558
- error: null,
559
- });
560
-
558
+ // Write detect results (or empty if detection failed)
559
+ const findingsResult = writeFindingsJson(paths.baseDir, detectData);
560
+
561
561
  // Write summary with stable digest
562
562
  writeSummaryJson(paths.summaryJson, {
563
563
  runId,
@@ -577,9 +577,6 @@ export async function defaultCommand(options = {}) {
577
577
  informational: detectData.stats?.informational || 0,
578
578
  });
579
579
 
580
- // Write detect results (or empty if detection failed)
581
- writeFindingsJson(paths.baseDir, detectData);
582
-
583
580
  // Write traces (include all events including heartbeats)
584
581
  const allEvents = events.getEvents();
585
582
  const tracesContent = allEvents
@@ -595,6 +592,53 @@ export async function defaultCommand(options = {}) {
595
592
 
596
593
  // Write observe results
597
594
  writeObserveJson(paths.baseDir, observeData);
595
+
596
+ // PHASE 6: Verify artifacts after all writers finish
597
+ const { verifyRun } = await import('../../verax/core/artifacts/verifier.js');
598
+ const verification = verifyRun(paths.baseDir, paths.artifactVersions);
599
+
600
+ // Determine final status based on verification
601
+ let finalStatus = 'COMPLETE';
602
+ if (!verification.ok) {
603
+ finalStatus = 'INVALID';
604
+ } else if (verification.warnings.length > 0) {
605
+ finalStatus = 'VALID_WITH_WARNINGS';
606
+ }
607
+
608
+ // Write completed status with contract + enforcement snapshot + verification
609
+ atomicWriteJson(paths.runStatusJson, {
610
+ contractVersion: 1,
611
+ artifactVersions: paths.artifactVersions,
612
+ status: finalStatus,
613
+ runId,
614
+ startedAt,
615
+ completedAt,
616
+ enforcement: findingsResult?.payload?.enforcement || null,
617
+ verification: {
618
+ ok: verification.ok,
619
+ status: finalStatus,
620
+ errorsCount: verification.errors.length,
621
+ warningsCount: verification.warnings.length,
622
+ verifiedAt: verification.verifiedAt
623
+ }
624
+ });
625
+
626
+ // Update metadata with completion time
627
+ atomicWriteJson(paths.runMetaJson, {
628
+ contractVersion: 1,
629
+ artifactVersions: paths.artifactVersions,
630
+ veraxVersion: getVersion(),
631
+ nodeVersion: process.version,
632
+ platform: process.platform,
633
+ cwd: projectRoot,
634
+ command: 'default',
635
+ args: { url: resolvedUrl, src },
636
+ url: resolvedUrl,
637
+ src: srcPath,
638
+ startedAt,
639
+ completedAt,
640
+ error: null,
641
+ });
598
642
 
599
643
  events.emit('phase:completed', {
600
644
  phase: 'Finalize Artifacts',
@@ -606,6 +650,12 @@ export async function defaultCommand(options = {}) {
606
650
  console.log('\nRun complete.');
607
651
  console.log(`Run ID: ${runId}`);
608
652
  console.log(`Artifacts: ${paths.baseDir}`);
653
+
654
+ // PHASE 6: Display verification results
655
+ const { formatVerificationOutput } = await import('../../verax/core/artifacts/verifier.js');
656
+ const verificationOutput = formatVerificationOutput(verification, verbose);
657
+ console.log('');
658
+ console.log(verificationOutput);
609
659
  }
610
660
 
611
661
  return { runId, paths, url: resolvedUrl, success: true };
@@ -623,6 +673,8 @@ export async function defaultCommand(options = {}) {
623
673
  try {
624
674
  const failedAt = new Date().toISOString();
625
675
  atomicWriteJson(paths.runStatusJson, {
676
+ contractVersion: 1,
677
+ artifactVersions: paths.artifactVersions,
626
678
  status: 'FAILED',
627
679
  runId,
628
680
  startedAt,
@@ -632,6 +684,8 @@ export async function defaultCommand(options = {}) {
632
684
 
633
685
  // Update metadata
634
686
  atomicWriteJson(paths.runMetaJson, {
687
+ contractVersion: 1,
688
+ artifactVersions: paths.artifactVersions,
635
689
  veraxVersion: getVersion(),
636
690
  nodeVersion: process.version,
637
691
  platform: process.platform,
@@ -0,0 +1,243 @@
1
+ /**
2
+ * PHASE 21.6 — GA Readiness CLI Command
3
+ *
4
+ * Pure inspection command. Evaluates GA readiness using existing artifacts only.
5
+ * No URL, no browser, no project execution.
6
+ */
7
+
8
+ import { evaluateGAReadiness } from '../../verax/core/ga/ga.contract.js';
9
+ import { writeGAStatus } from '../../verax/core/ga/ga.artifact.js';
10
+ import { writeGAReport } from '../../verax/core/ga/ga-report-writer.js';
11
+ import { GA_BLOCKER_CODE } from '../../verax/core/ga/ga.contract.js';
12
+ import { resolve } from 'path';
13
+ import { readFileSync, existsSync } from 'fs';
14
+ import { findLatestRunId, validateRunId } from '../util/run-resolver.js';
15
+ import { UsageError } from '../util/errors.js';
16
+
17
+ /**
18
+ * Load failure ledger summary
19
+ *
20
+ * @param {string} projectDir - Project directory
21
+ * @param {string} runId - Run ID
22
+ * @returns {Object|null} Failure ledger summary or null
23
+ */
24
+ function loadFailureLedger(projectDir, runId) {
25
+ const ledgerPath = resolve(projectDir, '.verax', 'runs', runId, 'failure.ledger.json');
26
+ if (!existsSync(ledgerPath)) {
27
+ return null;
28
+ }
29
+
30
+ try {
31
+ const content = readFileSync(ledgerPath, 'utf-8');
32
+ const ledger = JSON.parse(content);
33
+ return ledger.summary || null;
34
+ } catch (error) {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Load determinism verdict
41
+ *
42
+ * @param {string} projectDir - Project directory
43
+ * @param {string} runId - Run ID
44
+ * @returns {Promise<string|null>} Determinism verdict or null
45
+ */
46
+ async function loadDeterminismVerdict(projectDir, runId) {
47
+ const decisionsPath = resolve(projectDir, '.verax', 'runs', runId, 'decisions.json');
48
+ if (!existsSync(decisionsPath)) {
49
+ return null;
50
+ }
51
+
52
+ try {
53
+ const decisions = JSON.parse(readFileSync(decisionsPath, 'utf-8'));
54
+ const { DecisionRecorder } = await import('../../verax/core/determinism-model.js');
55
+ const recorder = DecisionRecorder.fromExport(decisions);
56
+ const { computeDeterminismVerdict } = await import('../../verax/core/determinism/contract.js');
57
+ const verdict = computeDeterminismVerdict(recorder);
58
+ return verdict.verdict;
59
+ } catch (error) {
60
+ return null;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Check for Evidence Law violations
66
+ *
67
+ * @param {string} projectDir - Project directory
68
+ * @param {string} runId - Run ID
69
+ * @returns {boolean} Whether Evidence Law was violated
70
+ */
71
+ function checkEvidenceLawViolations(projectDir, runId) {
72
+ const findingsPath = resolve(projectDir, '.verax', 'runs', runId, 'findings.json');
73
+ if (!existsSync(findingsPath)) {
74
+ return false;
75
+ }
76
+
77
+ try {
78
+ const content = readFileSync(findingsPath, 'utf-8');
79
+ const findings = JSON.parse(content);
80
+
81
+ if (!Array.isArray(findings.findings)) {
82
+ return false;
83
+ }
84
+
85
+ // Check for CONFIRMED findings with incomplete evidence
86
+ for (const finding of findings.findings) {
87
+ if ((finding.severity === 'CONFIRMED' || finding.status === 'CONFIRMED') &&
88
+ finding.evidencePackage && !finding.evidencePackage.isComplete) {
89
+ return true;
90
+ }
91
+ }
92
+
93
+ return false;
94
+ } catch (error) {
95
+ return false;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * PHASE 21.6.1: `verax ga` command
101
+ *
102
+ * Pure inspection command. No URL, no browser, no execution.
103
+ *
104
+ * @param {Object} options - Options
105
+ * @param {string} [options.runId] - Run ID (defaults to latest)
106
+ * @param {boolean} [options.json] - Output as JSON
107
+ */
108
+ export async function gaCommand(options = {}) {
109
+ const { runId: providedRunId = null, json = false } = options;
110
+
111
+ const projectDir = resolve(process.cwd());
112
+
113
+ // Resolve run ID: use provided or find latest
114
+ let runId = providedRunId;
115
+
116
+ if (!runId) {
117
+ // Find latest run
118
+ runId = findLatestRunId(projectDir);
119
+
120
+ if (!runId) {
121
+ // No runs found - GA is BLOCKED
122
+ const gaResult = {
123
+ pass: false,
124
+ blockers: [{
125
+ code: GA_BLOCKER_CODE.NO_RUNS_FOUND || 'GA_NO_RUNS_FOUND',
126
+ message: 'No runs found in .verax/runs/. Run a scan first.',
127
+ context: {}
128
+ }],
129
+ warnings: [],
130
+ summary: {
131
+ pass: false,
132
+ blockersCount: 1,
133
+ warningsCount: 0,
134
+ checkedAt: new Date().toISOString()
135
+ },
136
+ inputs: {
137
+ gates: null,
138
+ determinism: null,
139
+ evidenceLaw: null,
140
+ failureLedger: null
141
+ }
142
+ };
143
+
144
+ if (json) {
145
+ console.log(JSON.stringify({
146
+ gaReady: false,
147
+ blockers: gaResult.blockers,
148
+ warnings: [],
149
+ summary: gaResult.summary
150
+ }, null, 2));
151
+ } else {
152
+ console.log('\n' + '='.repeat(80));
153
+ console.log('GA READINESS EVALUATION');
154
+ console.log('='.repeat(80));
155
+ console.log('\nGA STATUS: ❌ BLOCKED');
156
+ console.log('\nBlockers:');
157
+ console.log('- No runs found in .verax/runs/. Run a scan first.');
158
+ console.log('='.repeat(80) + '\n');
159
+ }
160
+
161
+ process.exit(4);
162
+ return;
163
+ }
164
+ } else {
165
+ // Validate provided run ID
166
+ if (!validateRunId(projectDir, runId)) {
167
+ const error = new UsageError(`Run ID not found: ${runId}`);
168
+ // UsageError already has exit code 64
169
+ throw error;
170
+ }
171
+ }
172
+
173
+ // Load context from artifacts (pure filesystem reads)
174
+ const failureLedger = loadFailureLedger(projectDir, runId);
175
+ const determinismVerdict = await loadDeterminismVerdict(projectDir, runId);
176
+ const evidenceLawViolated = checkEvidenceLawViolations(projectDir, runId);
177
+
178
+ // Evaluate GA readiness
179
+ const gaResult = await evaluateGAReadiness({
180
+ projectDir,
181
+ runId,
182
+ determinismVerdict,
183
+ evidenceLawViolated,
184
+ failureLedger
185
+ });
186
+
187
+ // Write status artifact
188
+ const artifactPath = writeGAStatus(projectDir, runId, gaResult);
189
+
190
+ // Write GA report
191
+ const reportPath = writeGAReport(projectDir, runId, gaResult);
192
+
193
+ // Output
194
+ if (json) {
195
+ console.log(JSON.stringify({
196
+ gaReady: gaResult.pass,
197
+ blockers: gaResult.blockers,
198
+ warnings: gaResult.warnings,
199
+ summary: gaResult.summary,
200
+ artifactPath,
201
+ reportPath
202
+ }, null, 2));
203
+ } else {
204
+ console.log('\n' + '='.repeat(80));
205
+ console.log('GA READINESS EVALUATION');
206
+ console.log('='.repeat(80));
207
+ console.log(`\nGA STATUS: ${gaResult.pass ? '✅ READY' : '❌ BLOCKED'}`);
208
+
209
+ if (gaResult.blockers.length > 0) {
210
+ console.log('\nBlockers:');
211
+ for (const blocker of gaResult.blockers) {
212
+ console.log(`- ${blocker.message}`);
213
+ }
214
+ }
215
+
216
+ if (gaResult.warnings.length > 0) {
217
+ console.log('\nWarnings:');
218
+ for (const warning of gaResult.warnings) {
219
+ console.log(`- ${warning.message}`);
220
+ }
221
+ }
222
+
223
+ console.log(`\nSee: ${artifactPath}`);
224
+ console.log(`Report: ${reportPath}`);
225
+ console.log('='.repeat(80) + '\n');
226
+ }
227
+
228
+ // Exit codes: 0 = GA-READY, 2 = GA-BLOCKED, 70 = Internal corruption
229
+ if (!gaResult.pass) {
230
+ // Check if it's an internal corruption issue
231
+ const hasInternalBlocker = gaResult.blockers.some(b =>
232
+ b.code === 'GA_INTERNAL_FAILURES' ||
233
+ b.code === 'GA_CONTRACT_FAILURES'
234
+ );
235
+
236
+ if (hasInternalBlocker) {
237
+ process.exit(70);
238
+ } else {
239
+ process.exit(2);
240
+ }
241
+ }
242
+ }
243
+