@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
@@ -9,8 +9,29 @@ import SilenceTracker from './core/silence-model.js';
9
9
  import { generateRunId, getRunArtifactDir, getArtifactPath } from './core/run-id.js';
10
10
  import { createRunManifest, updateRunManifestHashes } from './core/run-manifest.js';
11
11
  import { computeArtifactHashes } from './core/run-id.js';
12
+ import { FailureLedger } from './core/failures/failure.ledger.js';
13
+ import { errorToFailure, createIOFailure, createInternalFailure } from './core/failures/failure.factory.js';
14
+ import { FAILURE_CODE, EXECUTION_PHASE } from './core/failures/failure.types.js';
15
+ import { createPerformanceMonitor } from './core/perf/perf.monitor.js';
16
+ import { generatePerformanceReport, writePerformanceReport } from './core/perf/perf.report.js';
17
+ import { recordPerformanceViolations } from './core/perf/perf.enforcer.js';
18
+ import { PipelineTracker, PIPELINE_STAGES } from './core/pipeline-tracker.js';
19
+ import { verifyRun } from './core/artifacts/verifier.js';
20
+ import { getArtifactVersions } from './core/artifacts/registry.js';
12
21
 
13
22
  export async function scan(projectDir, url, manifestPath = null, scanBudgetOverride = null, safetyFlags = {}) {
23
+ // PHASE 21.5: Initialize failure ledger (runId will be set after learn)
24
+ const scanBudget = scanBudgetOverride || createScanBudgetWithProfile();
25
+ const failureLedger = new FailureLedger(null, projectDir);
26
+
27
+ // PHASE 21.9: Initialize performance monitor
28
+ const perfMonitor = createPerformanceMonitor();
29
+ perfMonitor.startPhase('LEARN');
30
+
31
+ // ARCHITECTURAL HARDENING: Initialize pipeline tracker early (before runId is known)
32
+ // We'll set runId on the tracker once it's generated
33
+ let pipelineTracker = null;
34
+
14
35
  // If manifestPath is provided, read it first before learn() overwrites it
15
36
  let loadedManifest = null;
16
37
  if (manifestPath) {
@@ -19,26 +40,62 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
19
40
  const manifestContent = JSON.parse(readFileSync(manifestPath, 'utf-8'));
20
41
  loadedManifest = manifestContent;
21
42
  } catch (e) {
22
- // Fall through to learn if we can't read the manifest
43
+ // PHASE 21.5: Record I/O failure
44
+ const failure = createIOFailure(
45
+ FAILURE_CODE.IO_READ_FAILED,
46
+ `Failed to read manifest: ${e.message}`,
47
+ 'scan',
48
+ { manifestPath, error: e.message },
49
+ true // Recoverable - will fall through to learn
50
+ );
51
+ failureLedger.record(failure);
23
52
  }
24
53
  }
25
54
 
55
+ // ARCHITECTURAL HARDENING: Track LEARN stage
56
+ // Note: We can't track LEARN yet because we don't have runId, but we'll track it retroactively
57
+ const learnStartTime = Date.now();
26
58
  const learnedManifest = await learn(projectDir);
59
+ const learnEndTime = Date.now();
60
+ perfMonitor.endPhase('LEARN');
27
61
 
28
- // Merge: prefer loaded manifest for routes, but use learned for project type and truth
62
+ // ARCHITECTURAL HARDENING: Explicit manifest merging with deterministic precedence
63
+ // Rule: Loaded manifest routes take precedence, but learned manifest provides truth and project type
29
64
  let manifest;
30
65
  if (loadedManifest) {
66
+ // Explicit merge: loaded manifest routes override learned, but learned provides truth
31
67
  manifest = {
32
68
  ...learnedManifest,
69
+ // Routes from loaded manifest (if present) override learned routes
70
+ publicRoutes: loadedManifest.publicRoutes || learnedManifest.publicRoutes || [],
71
+ routes: loadedManifest.routes || learnedManifest.routes || [],
72
+ internalRoutes: loadedManifest.internalRoutes || learnedManifest.internalRoutes || [],
73
+ staticExpectations: loadedManifest.staticExpectations || learnedManifest.staticExpectations || [],
74
+ // Project type: prefer loaded, fallback to learned
33
75
  projectType: loadedManifest.projectType || learnedManifest.projectType,
34
- publicRoutes: loadedManifest.publicRoutes || learnedManifest.publicRoutes,
35
- routes: loadedManifest.routes || learnedManifest.routes,
36
- internalRoutes: loadedManifest.internalRoutes || learnedManifest.internalRoutes,
37
- staticExpectations: loadedManifest.staticExpectations || learnedManifest.staticExpectations,
38
- manifestPath: manifestPath
76
+ // Always preserve learned truth (this is the source of truth)
77
+ learnTruth: learnedManifest.learnTruth || {},
78
+ // Track manifest source for auditability
79
+ manifestSource: 'merged',
80
+ manifestPath: manifestPath,
81
+ mergeMetadata: {
82
+ loadedManifestProvided: true,
83
+ learnedManifestProvided: true,
84
+ routesSource: loadedManifest.routes ? 'loaded' : 'learned',
85
+ projectTypeSource: loadedManifest.projectType ? 'loaded' : 'learned'
86
+ }
39
87
  };
40
88
  } else {
41
- manifest = learnedManifest;
89
+ manifest = {
90
+ ...learnedManifest,
91
+ manifestSource: 'learned',
92
+ mergeMetadata: {
93
+ loadedManifestProvided: false,
94
+ learnedManifestProvided: true,
95
+ routesSource: 'learned',
96
+ projectTypeSource: 'learned'
97
+ }
98
+ };
42
99
  }
43
100
 
44
101
  if (!url) {
@@ -53,18 +110,8 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
53
110
  manifestPath: manifest.manifestPath
54
111
  };
55
112
 
56
- let validation = await validateRoutes(manifestForValidation, url);
57
-
58
- if (!validation) {
59
- // validateRoutes might return null if routes cannot be validated
60
- validation = {
61
- routesValidated: 0,
62
- routesReachable: 0,
63
- routesUnreachable: 0,
64
- details: [],
65
- warnings: []
66
- };
67
- }
113
+ // ARCHITECTURAL HARDENING: validateRoutes now always returns explicit validation object
114
+ const validation = await validateRoutes(manifestForValidation, url);
68
115
  if (validation.warnings && validation.warnings.length > 0) {
69
116
  if (!manifest.learnTruth.warnings) {
70
117
  manifest.learnTruth.warnings = [];
@@ -72,9 +119,6 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
72
119
  manifest.learnTruth.warnings.push(...validation.warnings);
73
120
  }
74
121
 
75
- // Use budget profile if no override provided
76
- const scanBudget = scanBudgetOverride || createScanBudgetWithProfile();
77
-
78
122
  // PHASE 5: Generate deterministic runId and create run manifest
79
123
  const { getBaseOrigin } = await import('./observe/domain-boundary.js');
80
124
  const baseOrigin = getBaseOrigin(url);
@@ -86,6 +130,41 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
86
130
  manifestPath
87
131
  });
88
132
 
133
+ // PHASE 21.5: Set runId in failure ledger
134
+ failureLedger.runId = runId;
135
+
136
+ // ARCHITECTURAL HARDENING: Initialize pipeline tracker now that we have runId
137
+ // PHASE 25: Pass runFingerprint params to PipelineTracker
138
+ const runFingerprintParams = url ? {
139
+ url,
140
+ projectDir,
141
+ manifestPath: null,
142
+ fixtureId: null
143
+ } : null;
144
+ pipelineTracker = new PipelineTracker(projectDir, runId, runFingerprintParams);
145
+
146
+ // Record LEARN stage retroactively (it completed before we had runId)
147
+ try {
148
+ pipelineTracker.stages[PIPELINE_STAGES.LEARN] = {
149
+ name: PIPELINE_STAGES.LEARN,
150
+ startedAt: new Date(learnStartTime).toISOString(),
151
+ completedAt: new Date(learnEndTime).toISOString(),
152
+ durationMs: learnEndTime - learnStartTime,
153
+ status: 'COMPLETE'
154
+ };
155
+ pipelineTracker.completedStages.push(PIPELINE_STAGES.LEARN);
156
+ pipelineTracker._writeMeta();
157
+ } catch (error) {
158
+ const failure = createInternalFailure(
159
+ FAILURE_CODE.INTERNAL_UNEXPECTED_ERROR,
160
+ `Failed to record LEARN stage: ${error.message}`,
161
+ 'scan',
162
+ { error: error.message },
163
+ error.stack
164
+ );
165
+ failureLedger.record(failure);
166
+ }
167
+
89
168
  // Create run manifest at start of execution
90
169
  const runManifest = createRunManifest(projectDir, runId, {
91
170
  url,
@@ -97,15 +176,61 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
97
176
  });
98
177
 
99
178
  const usedManifestPath = manifestPath || manifest.manifestPath;
100
- const observation = await observe(url, usedManifestPath, scanBudget, safetyFlags, projectDir, runId);
179
+
180
+ // ARCHITECTURAL HARDENING: Track OBSERVE stage
181
+ pipelineTracker.startStage(PIPELINE_STAGES.OBSERVE);
182
+ perfMonitor.startPhase('OBSERVE');
183
+ let observation;
184
+ try {
185
+ observation = await observe(url, usedManifestPath, scanBudget, safetyFlags, projectDir, runId);
186
+ perfMonitor.endPhase('OBSERVE');
187
+ pipelineTracker.completeStage(PIPELINE_STAGES.OBSERVE, {
188
+ pagesVisited: observation.observeTruth?.pagesVisited || 0,
189
+ interactionsExecuted: observation.observeTruth?.interactionsExecuted || 0
190
+ });
191
+ } catch (error) {
192
+ perfMonitor.endPhase('OBSERVE');
193
+ pipelineTracker.failStage(PIPELINE_STAGES.OBSERVE, error);
194
+ const failure = errorToFailure(
195
+ error,
196
+ FAILURE_CODE.OBSERVE_EXECUTION_FAILED,
197
+ 'OBSERVE',
198
+ EXECUTION_PHASE.OBSERVE,
199
+ 'observe',
200
+ { manifestPath: usedManifestPath }
201
+ );
202
+ failureLedger.record(failure);
203
+ throw error;
204
+ }
205
+
206
+ // Update performance monitor with observed metrics
207
+ if (observation.observeTruth) {
208
+ const pagesVisited = observation.observeTruth.pagesVisited || 0;
209
+ const interactionsExecuted = observation.observeTruth.interactionsExecuted || observation.observeTruth.candidatesSelected || 0;
210
+
211
+ for (let i = 0; i < pagesVisited; i++) {
212
+ perfMonitor.incrementPages();
213
+ }
214
+ for (let i = 0; i < interactionsExecuted; i++) {
215
+ perfMonitor.incrementInteractions();
216
+ }
217
+ }
101
218
 
102
219
  // Write a copy of the manifest into canonical run directory for replay integrity
103
220
  try {
104
221
  const { writeFileSync } = await import('fs');
105
222
  const manifestCopyPath = getArtifactPath(projectDir, runId, 'manifest.json');
106
223
  writeFileSync(manifestCopyPath, JSON.stringify(manifest, null, 2));
107
- } catch {
108
- // Ignore write errors
224
+ } catch (error) {
225
+ // PHASE 21.5: Record I/O failure (WARNING - recoverable)
226
+ const failure = createIOFailure(
227
+ FAILURE_CODE.IO_WRITE_FAILED,
228
+ `Failed to write manifest copy: ${error.message}`,
229
+ 'scan',
230
+ { manifestPath, error: error.message },
231
+ true
232
+ );
233
+ failureLedger.record(failure);
109
234
  }
110
235
 
111
236
  // Create silence tracker from observation silences
@@ -114,27 +239,69 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
114
239
  silenceTracker.recordBatch(observation.silences.entries);
115
240
  }
116
241
 
117
- const findings = await detect(usedManifestPath, observation.tracesPath, validation, observation.expectationCoverageGaps || [], silenceTracker);
242
+ // ARCHITECTURAL HARDENING: Track DETECT stage
243
+ pipelineTracker.startStage(PIPELINE_STAGES.DETECT);
244
+ perfMonitor.startPhase('DETECT');
245
+ let findings;
246
+ try {
247
+ findings = await detect(usedManifestPath, observation.tracesPath, validation, observation.expectationCoverageGaps || [], silenceTracker);
248
+ perfMonitor.endPhase('DETECT');
249
+ pipelineTracker.completeStage(PIPELINE_STAGES.DETECT, {
250
+ findingsCount: findings.findings?.length || 0,
251
+ coverageGapsCount: findings.coverageGaps?.length || 0
252
+ });
253
+ } catch (error) {
254
+ perfMonitor.endPhase('DETECT');
255
+ pipelineTracker.failStage(PIPELINE_STAGES.DETECT, error);
256
+ const failure = errorToFailure(
257
+ error,
258
+ FAILURE_CODE.DETECT_FINDING_PROCESSING_FAILED,
259
+ 'DETECT',
260
+ EXECUTION_PHASE.DETECT,
261
+ 'detect',
262
+ { manifestPath: usedManifestPath }
263
+ );
264
+ failureLedger.record(failure);
265
+ throw error; // Re-throw BLOCKING failures
266
+ }
118
267
 
119
268
  const learnTruthWithValidation = {
120
269
  ...manifest.learnTruth,
121
270
  validation: validation
122
271
  };
123
272
 
273
+ // ARCHITECTURAL HARDENING: Track WRITE stage
274
+ pipelineTracker.startStage(PIPELINE_STAGES.WRITE);
124
275
  const runDir = getRunArtifactDir(projectDir, runId);
125
- const scanSummary = writeScanSummary(
126
- projectDir,
127
- url,
128
- manifest.projectType,
129
- learnTruthWithValidation,
130
- observation.observeTruth,
131
- findings.detectTruth,
132
- manifest.manifestPath,
133
- observation.tracesPath,
134
- findings.findingsPath,
135
- runDir,
136
- findings.findings // PHASE 7: Pass findings array for decision snapshot
137
- );
276
+ let scanSummary;
277
+ try {
278
+ scanSummary = await writeScanSummary(
279
+ projectDir,
280
+ url,
281
+ manifest.projectType,
282
+ learnTruthWithValidation,
283
+ observation.observeTruth,
284
+ findings.detectTruth,
285
+ manifest.manifestPath,
286
+ observation.tracesPath,
287
+ findings.findingsPath,
288
+ runDir,
289
+ findings.findings // PHASE 7: Pass findings array for decision snapshot
290
+ );
291
+ pipelineTracker.completeStage(PIPELINE_STAGES.WRITE);
292
+ } catch (error) {
293
+ pipelineTracker.failStage(PIPELINE_STAGES.WRITE, error);
294
+ const failure = errorToFailure(
295
+ error,
296
+ FAILURE_CODE.IO_WRITE_FAILED,
297
+ 'WRITE',
298
+ EXECUTION_PHASE.WRITE,
299
+ 'writeScanSummary',
300
+ { runDir }
301
+ );
302
+ failureLedger.record(failure);
303
+ throw error;
304
+ }
138
305
 
139
306
  // Compute observation summary from scan results (not a verdict)
140
307
  // Pass observation object (which includes traces) to observation engine
@@ -163,9 +330,204 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
163
330
  );
164
331
  observationSummary.evidenceIndexPath = evidenceIndexPath;
165
332
 
166
- // PHASE 5: Compute artifact hashes and update run manifest
167
- const artifactHashes = computeArtifactHashes(projectDir, runId);
168
- updateRunManifestHashes(projectDir, runId, artifactHashes);
333
+ // PHASE 21.4: Write policy artifacts
334
+ try {
335
+ const { writeFileSync } = await import('fs');
336
+ const { loadGuardrailsPolicy, getPolicyReport: getGuardrailsPolicyReport } = await import('./core/guardrails/policy.loader.js');
337
+ const { loadConfidencePolicy, getPolicyReport: getConfidencePolicyReport } = await import('./core/confidence/confidence.loader.js');
338
+
339
+ const guardrailsPolicy = loadGuardrailsPolicy();
340
+ const confidencePolicy = loadConfidencePolicy();
341
+
342
+ const guardrailsPolicyPath = getArtifactPath(projectDir, failureLedger.runId, 'guardrails.policy.json');
343
+ const confidencePolicyPath = getArtifactPath(projectDir, failureLedger.runId, 'confidence.policy.json');
344
+
345
+ writeFileSync(guardrailsPolicyPath, JSON.stringify({
346
+ ...getGuardrailsPolicyReport(guardrailsPolicy),
347
+ rules: guardrailsPolicy.rules.map(r => ({
348
+ id: r.id,
349
+ category: r.category,
350
+ action: r.action,
351
+ mandatory: r.mandatory
352
+ }))
353
+ }, null, 2));
354
+
355
+ writeFileSync(confidencePolicyPath, JSON.stringify(getConfidencePolicyReport(confidencePolicy), null, 2));
356
+ } catch (error) {
357
+ // PHASE 21.5: Record I/O failure (WARNING - recoverable)
358
+ const failure = createIOFailure(
359
+ FAILURE_CODE.IO_WRITE_FAILED,
360
+ `Failed to write policy artifacts: ${error.message}`,
361
+ 'scan',
362
+ { error: error.message },
363
+ true
364
+ );
365
+ failureLedger.record(failure);
366
+ }
367
+
368
+
369
+ // ARCHITECTURAL HARDENING: Write failure ledger with fallback to run.status.json
370
+ let ledgerPath;
371
+ try {
372
+ ledgerPath = failureLedger.write();
373
+ } catch (error) {
374
+ // If ledger write fails, write fallback entry to run.status.json
375
+ const failure = createInternalFailure(
376
+ FAILURE_CODE.INTERNAL_UNEXPECTED_ERROR,
377
+ `Failed to write failure ledger: ${error.message}`,
378
+ 'scan',
379
+ { error: error.message },
380
+ error.stack
381
+ );
382
+ failureLedger.record(failure);
383
+
384
+ // Write fallback failure entry to run.status.json
385
+ try {
386
+ const { writeFileSync, readFileSync } = await import('fs');
387
+ const runStatusPath = getArtifactPath(projectDir, runId, 'run.status.json');
388
+ let runStatus = { status: 'FAILED', failures: [] };
389
+ try {
390
+ const existing = readFileSync(runStatusPath, 'utf-8');
391
+ runStatus = JSON.parse(existing);
392
+ } catch {
393
+ // File doesn't exist or is invalid, use defaults
394
+ }
395
+ runStatus.failures = runStatus.failures || [];
396
+ runStatus.failures.push({
397
+ code: FAILURE_CODE.INTERNAL_UNEXPECTED_ERROR,
398
+ message: `Failed to write failure ledger: ${error.message}`,
399
+ timestamp: new Date().toISOString(),
400
+ recoverable: false
401
+ });
402
+ runStatus.ledgerWriteFailed = true;
403
+ runStatus.ledgerWriteError = error.message;
404
+ writeFileSync(runStatusPath, JSON.stringify(runStatus, null, 2) + '\n', 'utf-8');
405
+ } catch (fallbackError) {
406
+ // Even fallback write failed - log only
407
+ console.error('CRITICAL: Failed to write failure ledger and fallback:', error.message, fallbackError.message);
408
+ }
409
+ }
410
+
411
+ // PHASE 21.9: Generate and write performance report
412
+ try {
413
+ perfMonitor.stop();
414
+ const monitorReport = perfMonitor.getFinalReport();
415
+ const profileName = process.env.VERAX_BUDGET_PROFILE || 'STANDARD';
416
+ const perfReport = generatePerformanceReport(projectDir, runId, monitorReport, profileName);
417
+ writePerformanceReport(projectDir, runId, perfReport);
418
+
419
+ // Record performance violations in failure ledger
420
+ recordPerformanceViolations(projectDir, runId, failureLedger);
421
+ } catch (error) {
422
+ // Record performance report failure (WARNING - recoverable)
423
+ const failure = createIOFailure(
424
+ FAILURE_CODE.IO_WRITE_FAILED,
425
+ `Failed to write performance report: ${error.message}`,
426
+ 'scan',
427
+ { error: error.message },
428
+ true
429
+ );
430
+ failureLedger.record(failure);
431
+ }
432
+
433
+ // ARCHITECTURAL HARDENING: Track VERIFY stage
434
+ pipelineTracker.startStage(PIPELINE_STAGES.VERIFY);
435
+ let verification = null;
436
+ try {
437
+ const artifactVersions = getArtifactVersions();
438
+ verification = verifyRun(runDir, artifactVersions);
439
+ pipelineTracker.completeStage(PIPELINE_STAGES.VERIFY, {
440
+ ok: verification.ok,
441
+ errorsCount: verification.errors.length,
442
+ warningsCount: verification.warnings.length
443
+ });
444
+ } catch (error) {
445
+ pipelineTracker.failStage(PIPELINE_STAGES.VERIFY, error);
446
+ const failure = errorToFailure(
447
+ error,
448
+ FAILURE_CODE.VERIFICATION_FAILED,
449
+ 'VERIFY',
450
+ EXECUTION_PHASE.VERIFY,
451
+ 'verifyRun',
452
+ { runDir }
453
+ );
454
+ failureLedger.record(failure);
455
+ // Verification failure is not blocking, but we record it
456
+ verification = {
457
+ ok: false,
458
+ errors: [error.message],
459
+ warnings: [],
460
+ verifiedAt: new Date().toISOString()
461
+ };
462
+ }
463
+
464
+ // ARCHITECTURAL HARDENING: Track VERDICT stage (only after verification completes)
465
+ pipelineTracker.startStage(PIPELINE_STAGES.VERDICT);
466
+ try {
467
+ // PHASE 5: Compute artifact hashes and update run manifest
468
+ const artifactHashes = computeArtifactHashes(projectDir, runId);
469
+ updateRunManifestHashes(projectDir, runId, artifactHashes);
470
+
471
+ // Verdict computation (determinism and evidence law checks)
472
+ let determinismVerdict = null;
473
+ let evidenceLawViolated = false;
474
+ try {
475
+ const decisionsPath = getArtifactPath(projectDir, runId, 'decisions.json');
476
+ const { readFileSync, existsSync } = await import('fs');
477
+ if (existsSync(decisionsPath)) {
478
+ const decisions = JSON.parse(readFileSync(decisionsPath, 'utf-8'));
479
+ const { DecisionRecorder } = await import('./core/determinism-model.js');
480
+ const recorder = DecisionRecorder.fromExport(decisions);
481
+ const { computeDeterminismVerdict } = await import('./core/determinism/contract.js');
482
+ const verdict = computeDeterminismVerdict(recorder);
483
+ determinismVerdict = verdict.verdict;
484
+ }
485
+
486
+ // Check for Evidence Law violations (incomplete evidence with CONFIRMED findings)
487
+ if (findings.findings) {
488
+ for (const finding of findings.findings) {
489
+ if ((finding.severity === 'CONFIRMED' || finding.status === 'CONFIRMED') &&
490
+ finding.evidencePackage && !finding.evidencePackage.isComplete) {
491
+ evidenceLawViolated = true;
492
+ break;
493
+ }
494
+ }
495
+ }
496
+ } catch (error) {
497
+ // Record failure but don't block
498
+ const failure = createInternalFailure(
499
+ FAILURE_CODE.INTERNAL_UNEXPECTED_ERROR,
500
+ `Failed to compute determinism verdict: ${error.message}`,
501
+ 'scan',
502
+ { error: error.message },
503
+ error.stack
504
+ );
505
+ failureLedger.record(failure);
506
+ }
507
+
508
+ pipelineTracker.completeStage(PIPELINE_STAGES.VERDICT, {
509
+ determinismVerdict,
510
+ evidenceLawViolated,
511
+ verificationOk: verification?.ok || false
512
+ });
513
+ } catch (error) {
514
+ pipelineTracker.failStage(PIPELINE_STAGES.VERDICT, error);
515
+ const failure = errorToFailure(
516
+ error,
517
+ FAILURE_CODE.VERDICT_COMPUTATION_FAILED,
518
+ 'VERDICT',
519
+ EXECUTION_PHASE.VERDICT,
520
+ 'computeVerdict',
521
+ {}
522
+ );
523
+ failureLedger.record(failure);
524
+ throw error;
525
+ }
526
+
527
+ // Extract verdict data from pipeline tracker
528
+ const verdictStage = pipelineTracker.getStage(PIPELINE_STAGES.VERDICT);
529
+ const determinismVerdict = verdictStage?.determinismVerdict || null;
530
+ const evidenceLawViolated = verdictStage?.evidenceLawViolated || false;
169
531
 
170
532
  return {
171
533
  manifest,
@@ -175,8 +537,14 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
175
537
  validation,
176
538
  coverageGaps: findings.coverageGaps || [],
177
539
  observationSummary,
178
- runId,
179
- runManifest
540
+ runId: failureLedger.runId,
541
+ runManifest,
542
+ failureLedger: failureLedger.export(),
543
+ ledgerPath,
544
+ determinismVerdict,
545
+ evidenceLawViolated,
546
+ verification,
547
+ pipelineStages: pipelineTracker.getAllStages()
180
548
  };
181
549
  }
182
550