@veraxhq/verax 0.2.0 → 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 (217) hide show
  1. package/README.md +14 -18
  2. package/bin/verax.js +7 -0
  3. package/package.json +15 -5
  4. package/src/cli/commands/baseline.js +104 -0
  5. package/src/cli/commands/default.js +323 -111
  6. package/src/cli/commands/doctor.js +36 -4
  7. package/src/cli/commands/ga.js +243 -0
  8. package/src/cli/commands/gates.js +95 -0
  9. package/src/cli/commands/inspect.js +131 -2
  10. package/src/cli/commands/release-check.js +213 -0
  11. package/src/cli/commands/run.js +498 -103
  12. package/src/cli/commands/security-check.js +211 -0
  13. package/src/cli/commands/truth.js +114 -0
  14. package/src/cli/entry.js +305 -68
  15. package/src/cli/util/angular-component-extractor.js +179 -0
  16. package/src/cli/util/angular-navigation-detector.js +141 -0
  17. package/src/cli/util/angular-network-detector.js +161 -0
  18. package/src/cli/util/angular-state-detector.js +162 -0
  19. package/src/cli/util/ast-interactive-detector.js +546 -0
  20. package/src/cli/util/ast-network-detector.js +603 -0
  21. package/src/cli/util/ast-usestate-detector.js +602 -0
  22. package/src/cli/util/bootstrap-guard.js +86 -0
  23. package/src/cli/util/detection-engine.js +4 -3
  24. package/src/cli/util/determinism-runner.js +123 -0
  25. package/src/cli/util/determinism-writer.js +129 -0
  26. package/src/cli/util/env-url.js +4 -0
  27. package/src/cli/util/events.js +76 -0
  28. package/src/cli/util/expectation-extractor.js +380 -74
  29. package/src/cli/util/findings-writer.js +126 -15
  30. package/src/cli/util/learn-writer.js +3 -1
  31. package/src/cli/util/observation-engine.js +69 -23
  32. package/src/cli/util/observe-writer.js +3 -1
  33. package/src/cli/util/paths.js +6 -14
  34. package/src/cli/util/project-discovery.js +23 -0
  35. package/src/cli/util/project-writer.js +3 -1
  36. package/src/cli/util/redact.js +2 -2
  37. package/src/cli/util/run-resolver.js +64 -0
  38. package/src/cli/util/runtime-budget.js +147 -0
  39. package/src/cli/util/source-requirement.js +55 -0
  40. package/src/cli/util/summary-writer.js +13 -1
  41. package/src/cli/util/svelte-navigation-detector.js +163 -0
  42. package/src/cli/util/svelte-network-detector.js +80 -0
  43. package/src/cli/util/svelte-sfc-extractor.js +147 -0
  44. package/src/cli/util/svelte-state-detector.js +243 -0
  45. package/src/cli/util/vue-navigation-detector.js +177 -0
  46. package/src/cli/util/vue-sfc-extractor.js +162 -0
  47. package/src/cli/util/vue-state-detector.js +215 -0
  48. package/src/types/global.d.ts +28 -0
  49. package/src/types/ts-ast.d.ts +24 -0
  50. package/src/verax/cli/doctor.js +2 -2
  51. package/src/verax/cli/finding-explainer.js +56 -3
  52. package/src/verax/cli/init.js +1 -1
  53. package/src/verax/cli/url-safety.js +12 -2
  54. package/src/verax/cli/wizard.js +13 -2
  55. package/src/verax/core/artifacts/registry.js +154 -0
  56. package/src/verax/core/artifacts/verifier.js +980 -0
  57. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  58. package/src/verax/core/baseline/baseline.snapshot.js +231 -0
  59. package/src/verax/core/budget-engine.js +1 -1
  60. package/src/verax/core/capabilities/gates.js +499 -0
  61. package/src/verax/core/capabilities/registry.js +475 -0
  62. package/src/verax/core/confidence/confidence-compute.js +137 -0
  63. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  64. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  65. package/src/verax/core/confidence/confidence-weights.js +44 -0
  66. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  67. package/src/verax/core/confidence/confidence.loader.js +79 -0
  68. package/src/verax/core/confidence/confidence.schema.js +94 -0
  69. package/src/verax/core/confidence-engine-refactor.js +484 -0
  70. package/src/verax/core/confidence-engine.js +486 -0
  71. package/src/verax/core/confidence-engine.js.backup +471 -0
  72. package/src/verax/core/contracts/index.js +29 -0
  73. package/src/verax/core/contracts/types.js +185 -0
  74. package/src/verax/core/contracts/validators.js +381 -0
  75. package/src/verax/core/decision-snapshot.js +31 -4
  76. package/src/verax/core/decisions/decision.trace.js +276 -0
  77. package/src/verax/core/determinism/contract-writer.js +89 -0
  78. package/src/verax/core/determinism/contract.js +139 -0
  79. package/src/verax/core/determinism/diff.js +364 -0
  80. package/src/verax/core/determinism/engine.js +221 -0
  81. package/src/verax/core/determinism/finding-identity.js +148 -0
  82. package/src/verax/core/determinism/normalize.js +438 -0
  83. package/src/verax/core/determinism/report-writer.js +92 -0
  84. package/src/verax/core/determinism/run-fingerprint.js +118 -0
  85. package/src/verax/core/determinism-model.js +35 -6
  86. package/src/verax/core/dynamic-route-intelligence.js +528 -0
  87. package/src/verax/core/evidence/evidence-capture-service.js +307 -0
  88. package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
  89. package/src/verax/core/evidence-builder.js +487 -0
  90. package/src/verax/core/execution-mode-context.js +77 -0
  91. package/src/verax/core/execution-mode-detector.js +190 -0
  92. package/src/verax/core/failures/exit-codes.js +86 -0
  93. package/src/verax/core/failures/failure-summary.js +76 -0
  94. package/src/verax/core/failures/failure.factory.js +225 -0
  95. package/src/verax/core/failures/failure.ledger.js +132 -0
  96. package/src/verax/core/failures/failure.types.js +196 -0
  97. package/src/verax/core/failures/index.js +10 -0
  98. package/src/verax/core/ga/ga-report-writer.js +43 -0
  99. package/src/verax/core/ga/ga.artifact.js +49 -0
  100. package/src/verax/core/ga/ga.contract.js +434 -0
  101. package/src/verax/core/ga/ga.enforcer.js +86 -0
  102. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  103. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  104. package/src/verax/core/guardrails/policy.loader.js +83 -0
  105. package/src/verax/core/guardrails/policy.schema.js +110 -0
  106. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  107. package/src/verax/core/guardrails-engine.js +505 -0
  108. package/src/verax/core/incremental-store.js +15 -7
  109. package/src/verax/core/observe/run-timeline.js +316 -0
  110. package/src/verax/core/perf/perf.contract.js +186 -0
  111. package/src/verax/core/perf/perf.display.js +65 -0
  112. package/src/verax/core/perf/perf.enforcer.js +91 -0
  113. package/src/verax/core/perf/perf.monitor.js +209 -0
  114. package/src/verax/core/perf/perf.report.js +198 -0
  115. package/src/verax/core/pipeline-tracker.js +238 -0
  116. package/src/verax/core/product-definition.js +127 -0
  117. package/src/verax/core/release/provenance.builder.js +271 -0
  118. package/src/verax/core/release/release-report-writer.js +40 -0
  119. package/src/verax/core/release/release.enforcer.js +159 -0
  120. package/src/verax/core/release/reproducibility.check.js +221 -0
  121. package/src/verax/core/release/sbom.builder.js +283 -0
  122. package/src/verax/core/replay-validator.js +4 -4
  123. package/src/verax/core/replay.js +1 -1
  124. package/src/verax/core/report/cross-index.js +192 -0
  125. package/src/verax/core/report/human-summary.js +222 -0
  126. package/src/verax/core/route-intelligence.js +419 -0
  127. package/src/verax/core/security/secrets.scan.js +326 -0
  128. package/src/verax/core/security/security-report.js +50 -0
  129. package/src/verax/core/security/security.enforcer.js +124 -0
  130. package/src/verax/core/security/supplychain.defaults.json +38 -0
  131. package/src/verax/core/security/supplychain.policy.js +326 -0
  132. package/src/verax/core/security/vuln.scan.js +265 -0
  133. package/src/verax/core/silence-impact.js +1 -1
  134. package/src/verax/core/silence-model.js +9 -7
  135. package/src/verax/core/truth/truth.certificate.js +250 -0
  136. package/src/verax/core/ui-feedback-intelligence.js +515 -0
  137. package/src/verax/detect/comparison.js +8 -3
  138. package/src/verax/detect/confidence-engine.js +645 -57
  139. package/src/verax/detect/confidence-helper.js +33 -0
  140. package/src/verax/detect/detection-engine.js +19 -2
  141. package/src/verax/detect/dynamic-route-findings.js +335 -0
  142. package/src/verax/detect/evidence-index.js +15 -65
  143. package/src/verax/detect/expectation-chain-detector.js +417 -0
  144. package/src/verax/detect/expectation-model.js +56 -3
  145. package/src/verax/detect/explanation-helpers.js +1 -1
  146. package/src/verax/detect/finding-detector.js +2 -2
  147. package/src/verax/detect/findings-writer.js +149 -20
  148. package/src/verax/detect/flow-detector.js +4 -4
  149. package/src/verax/detect/index.js +265 -15
  150. package/src/verax/detect/interactive-findings.js +3 -4
  151. package/src/verax/detect/journey-stall-detector.js +558 -0
  152. package/src/verax/detect/route-findings.js +218 -0
  153. package/src/verax/detect/signal-mapper.js +2 -2
  154. package/src/verax/detect/skip-classifier.js +4 -4
  155. package/src/verax/detect/ui-feedback-findings.js +207 -0
  156. package/src/verax/detect/verdict-engine.js +61 -9
  157. package/src/verax/detect/view-switch-correlator.js +242 -0
  158. package/src/verax/flow/flow-engine.js +3 -2
  159. package/src/verax/flow/flow-spec.js +1 -2
  160. package/src/verax/index.js +413 -33
  161. package/src/verax/intel/effect-detector.js +1 -1
  162. package/src/verax/intel/index.js +2 -2
  163. package/src/verax/intel/route-extractor.js +3 -3
  164. package/src/verax/intel/vue-navigation-extractor.js +81 -18
  165. package/src/verax/intel/vue-router-extractor.js +4 -2
  166. package/src/verax/learn/action-contract-extractor.js +684 -66
  167. package/src/verax/learn/ast-contract-extractor.js +53 -1
  168. package/src/verax/learn/index.js +36 -2
  169. package/src/verax/learn/manifest-writer.js +28 -14
  170. package/src/verax/learn/route-extractor.js +1 -1
  171. package/src/verax/learn/route-validator.js +12 -8
  172. package/src/verax/learn/state-extractor.js +1 -1
  173. package/src/verax/learn/static-extractor-navigation.js +1 -1
  174. package/src/verax/learn/static-extractor-validation.js +2 -2
  175. package/src/verax/learn/static-extractor.js +8 -7
  176. package/src/verax/learn/ts-contract-resolver.js +14 -12
  177. package/src/verax/observe/browser.js +22 -3
  178. package/src/verax/observe/console-sensor.js +2 -2
  179. package/src/verax/observe/expectation-executor.js +2 -1
  180. package/src/verax/observe/focus-sensor.js +1 -1
  181. package/src/verax/observe/human-driver.js +29 -10
  182. package/src/verax/observe/index.js +92 -844
  183. package/src/verax/observe/interaction-discovery.js +27 -15
  184. package/src/verax/observe/interaction-runner.js +31 -14
  185. package/src/verax/observe/loading-sensor.js +6 -0
  186. package/src/verax/observe/navigation-sensor.js +1 -1
  187. package/src/verax/observe/observe-context.js +205 -0
  188. package/src/verax/observe/observe-helpers.js +191 -0
  189. package/src/verax/observe/observe-runner.js +226 -0
  190. package/src/verax/observe/observers/budget-observer.js +185 -0
  191. package/src/verax/observe/observers/console-observer.js +102 -0
  192. package/src/verax/observe/observers/coverage-observer.js +107 -0
  193. package/src/verax/observe/observers/interaction-observer.js +471 -0
  194. package/src/verax/observe/observers/navigation-observer.js +132 -0
  195. package/src/verax/observe/observers/network-observer.js +87 -0
  196. package/src/verax/observe/observers/safety-observer.js +82 -0
  197. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  198. package/src/verax/observe/settle.js +1 -0
  199. package/src/verax/observe/state-sensor.js +8 -4
  200. package/src/verax/observe/state-ui-sensor.js +7 -1
  201. package/src/verax/observe/traces-writer.js +27 -16
  202. package/src/verax/observe/ui-feedback-detector.js +742 -0
  203. package/src/verax/observe/ui-signal-sensor.js +155 -2
  204. package/src/verax/scan-summary-writer.js +46 -9
  205. package/src/verax/shared/artifact-manager.js +9 -6
  206. package/src/verax/shared/budget-profiles.js +2 -2
  207. package/src/verax/shared/caching.js +1 -1
  208. package/src/verax/shared/config-loader.js +1 -2
  209. package/src/verax/shared/css-spinner-rules.js +204 -0
  210. package/src/verax/shared/dynamic-route-utils.js +12 -6
  211. package/src/verax/shared/retry-policy.js +1 -6
  212. package/src/verax/shared/root-artifacts.js +1 -1
  213. package/src/verax/shared/view-switch-rules.js +208 -0
  214. package/src/verax/shared/zip-artifacts.js +1 -0
  215. package/src/verax/validate/context-validator.js +1 -1
  216. package/src/verax/observe/index.js.backup +0 -1
  217. package/src/verax/validate/context-validator.js.bak +0 -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,8 +110,8 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
53
110
  manifestPath: manifest.manifestPath
54
111
  };
55
112
 
113
+ // ARCHITECTURAL HARDENING: validateRoutes now always returns explicit validation object
56
114
  const validation = await validateRoutes(manifestForValidation, url);
57
-
58
115
  if (validation.warnings && validation.warnings.length > 0) {
59
116
  if (!manifest.learnTruth.warnings) {
60
117
  manifest.learnTruth.warnings = [];
@@ -62,9 +119,6 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
62
119
  manifest.learnTruth.warnings.push(...validation.warnings);
63
120
  }
64
121
 
65
- // Use budget profile if no override provided
66
- const scanBudget = scanBudgetOverride || createScanBudgetWithProfile();
67
-
68
122
  // PHASE 5: Generate deterministic runId and create run manifest
69
123
  const { getBaseOrigin } = await import('./observe/domain-boundary.js');
70
124
  const baseOrigin = getBaseOrigin(url);
@@ -76,6 +130,41 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
76
130
  manifestPath
77
131
  });
78
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
+
79
168
  // Create run manifest at start of execution
80
169
  const runManifest = createRunManifest(projectDir, runId, {
81
170
  url,
@@ -87,14 +176,62 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
87
176
  });
88
177
 
89
178
  const usedManifestPath = manifestPath || manifest.manifestPath;
90
- 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
+ }
91
218
 
92
219
  // Write a copy of the manifest into canonical run directory for replay integrity
93
220
  try {
94
221
  const { writeFileSync } = await import('fs');
95
222
  const manifestCopyPath = getArtifactPath(projectDir, runId, 'manifest.json');
96
223
  writeFileSync(manifestCopyPath, JSON.stringify(manifest, null, 2));
97
- } catch {}
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);
234
+ }
98
235
 
99
236
  // Create silence tracker from observation silences
100
237
  const silenceTracker = new SilenceTracker();
@@ -102,27 +239,69 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
102
239
  silenceTracker.recordBatch(observation.silences.entries);
103
240
  }
104
241
 
105
- 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
+ }
106
267
 
107
268
  const learnTruthWithValidation = {
108
269
  ...manifest.learnTruth,
109
270
  validation: validation
110
271
  };
111
272
 
273
+ // ARCHITECTURAL HARDENING: Track WRITE stage
274
+ pipelineTracker.startStage(PIPELINE_STAGES.WRITE);
112
275
  const runDir = getRunArtifactDir(projectDir, runId);
113
- const scanSummary = writeScanSummary(
114
- projectDir,
115
- url,
116
- manifest.projectType,
117
- learnTruthWithValidation,
118
- observation.observeTruth,
119
- findings.detectTruth,
120
- manifest.manifestPath,
121
- observation.tracesPath,
122
- findings.findingsPath,
123
- runDir,
124
- findings.findings // PHASE 7: Pass findings array for decision snapshot
125
- );
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
+ }
126
305
 
127
306
  // Compute observation summary from scan results (not a verdict)
128
307
  // Pass observation object (which includes traces) to observation engine
@@ -151,9 +330,204 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
151
330
  );
152
331
  observationSummary.evidenceIndexPath = evidenceIndexPath;
153
332
 
154
- // PHASE 5: Compute artifact hashes and update run manifest
155
- const artifactHashes = computeArtifactHashes(projectDir, runId);
156
- 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;
157
531
 
158
532
  return {
159
533
  manifest,
@@ -163,8 +537,14 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
163
537
  validation,
164
538
  coverageGaps: findings.coverageGaps || [],
165
539
  observationSummary,
166
- runId,
167
- runManifest
540
+ runId: failureLedger.runId,
541
+ runManifest,
542
+ failureLedger: failureLedger.export(),
543
+ ledgerPath,
544
+ determinismVerdict,
545
+ evidenceLawViolated,
546
+ verification,
547
+ pipelineStages: pipelineTracker.getAllStages()
168
548
  };
169
549
  }
170
550
 
@@ -65,7 +65,7 @@ function analyzeNode(node, sourceFile, projectRoot, eventType = null) {
65
65
  // VALIDATION INTELLIGENCE v1: Check for return false in submit context
66
66
  if (ts.isReturnStatement(node) && eventType === 'onSubmit') {
67
67
  const expression = node.expression;
68
- if (expression && ts.isFalseKeyword(expression)) {
68
+ if (expression && expression.kind === ts.SyntaxKind.FalseKeyword) {
69
69
  const location = getNodeLocation(sourceFile, node, projectRoot);
70
70
  return {
71
71
  type: 'validation_block',
@@ -122,7 +122,7 @@ export async function runCodeIntelligence(projectRoot) {
122
122
  matchAttribute: el.attrs.to ? 'to' : (el.attrs.href ? 'href' : null),
123
123
  proof: 'PROVEN_EXPECTATION',
124
124
  sourceRef: el.sourceRef,
125
- selectorHint: extractSelectorHint({ attributes: { properties: [] } }) || `${el.tag}`,
125
+ selectorHint: extractSelectorHint(/** @type {any} */ ({ attributes: { properties: [] } })) || `${el.tag}`,
126
126
  metadata: {
127
127
  elementFile: el.file,
128
128
  elementLine: el.line,
@@ -267,7 +267,7 @@ function generateExpectation(mapping, effect) {
267
267
  * @param {Object} effect - Effect object
268
268
  * @returns {string} - Expectation type
269
269
  */
270
- function determineExpectationType(effect) {
270
+ function _determineExpectationType(effect) {
271
271
  switch (effect.type) {
272
272
  case 'navigation':
273
273
  return 'navigation';
@@ -12,9 +12,9 @@
12
12
  */
13
13
 
14
14
  import ts from 'typescript';
15
- import { resolve, relative, sep, basename, dirname, extname } from 'path';
15
+ import { resolve, relative, basename, extname } from 'path';
16
16
  import { existsSync, readdirSync, statSync } from 'fs';
17
- import { parseFile, findNodes, getStringLiteral, getNodeLocation } from './ts-program.js';
17
+ import { parseFile, findNodes, getNodeLocation } from './ts-program.js';
18
18
  import { extractVueRoutes } from './vue-router-extractor.js';
19
19
  import { normalizeDynamicRoute } from '../shared/dynamic-route-utils.js';
20
20
 
@@ -27,7 +27,7 @@ const INTERNAL_PATH_PATTERNS = [
27
27
  /\/private/
28
28
  ];
29
29
 
30
- function isInternalRoute(path) {
30
+ function _isInternalRoute(path) {
31
31
  return INTERNAL_PATH_PATTERNS.some(pattern => pattern.test(path));
32
32
  }
33
33