@veraxhq/verax 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/README.md +28 -20
  2. package/bin/verax.js +11 -18
  3. package/package.json +28 -7
  4. package/src/cli/commands/baseline.js +1 -2
  5. package/src/cli/commands/default.js +72 -81
  6. package/src/cli/commands/doctor.js +29 -0
  7. package/src/cli/commands/ga.js +3 -0
  8. package/src/cli/commands/gates.js +1 -1
  9. package/src/cli/commands/inspect.js +6 -133
  10. package/src/cli/commands/release-check.js +2 -0
  11. package/src/cli/commands/run.js +74 -246
  12. package/src/cli/commands/security-check.js +2 -1
  13. package/src/cli/commands/truth.js +0 -1
  14. package/src/cli/entry.js +82 -309
  15. package/src/cli/util/angular-component-extractor.js +2 -2
  16. package/src/cli/util/angular-navigation-detector.js +2 -2
  17. package/src/cli/util/ast-interactive-detector.js +4 -6
  18. package/src/cli/util/ast-network-detector.js +3 -3
  19. package/src/cli/util/ast-promise-extractor.js +581 -0
  20. package/src/cli/util/ast-usestate-detector.js +3 -3
  21. package/src/cli/util/atomic-write.js +12 -1
  22. package/src/cli/util/console-reporter.js +72 -0
  23. package/src/cli/util/detection-engine.js +105 -41
  24. package/src/cli/util/determinism-runner.js +2 -1
  25. package/src/cli/util/determinism-writer.js +1 -1
  26. package/src/cli/util/digest-engine.js +359 -0
  27. package/src/cli/util/dom-diff.js +226 -0
  28. package/src/cli/util/env-url.js +0 -4
  29. package/src/cli/util/evidence-engine.js +287 -0
  30. package/src/cli/util/expectation-extractor.js +217 -367
  31. package/src/cli/util/findings-writer.js +19 -126
  32. package/src/cli/util/framework-detector.js +572 -0
  33. package/src/cli/util/idgen.js +1 -1
  34. package/src/cli/util/interaction-planner.js +529 -0
  35. package/src/cli/util/learn-writer.js +2 -2
  36. package/src/cli/util/ledger-writer.js +110 -0
  37. package/src/cli/util/monorepo-resolver.js +162 -0
  38. package/src/cli/util/observation-engine.js +127 -278
  39. package/src/cli/util/observe-writer.js +2 -2
  40. package/src/cli/util/paths.js +12 -3
  41. package/src/cli/util/project-discovery.js +284 -3
  42. package/src/cli/util/project-writer.js +2 -2
  43. package/src/cli/util/run-id.js +23 -27
  44. package/src/cli/util/run-result.js +778 -0
  45. package/src/cli/util/selector-resolver.js +235 -0
  46. package/src/cli/util/summary-writer.js +2 -1
  47. package/src/cli/util/svelte-navigation-detector.js +3 -3
  48. package/src/cli/util/svelte-sfc-extractor.js +0 -1
  49. package/src/cli/util/svelte-state-detector.js +1 -2
  50. package/src/cli/util/trust-activation-integration.js +496 -0
  51. package/src/cli/util/trust-activation-wrapper.js +85 -0
  52. package/src/cli/util/trust-integration-hooks.js +164 -0
  53. package/src/cli/util/types.js +153 -0
  54. package/src/cli/util/url-validation.js +40 -0
  55. package/src/cli/util/vue-navigation-detector.js +4 -3
  56. package/src/cli/util/vue-sfc-extractor.js +1 -2
  57. package/src/cli/util/vue-state-detector.js +1 -1
  58. package/src/types/fs-augment.d.ts +23 -0
  59. package/src/types/global.d.ts +137 -0
  60. package/src/types/internal-types.d.ts +35 -0
  61. package/src/verax/cli/finding-explainer.js +3 -56
  62. package/src/verax/cli/init.js +4 -18
  63. package/src/verax/core/action-classifier.js +4 -3
  64. package/src/verax/core/artifacts/registry.js +0 -15
  65. package/src/verax/core/artifacts/verifier.js +18 -8
  66. package/src/verax/core/baseline/baseline.snapshot.js +2 -0
  67. package/src/verax/core/capabilities/gates.js +7 -1
  68. package/src/verax/core/confidence/confidence-compute.js +14 -7
  69. package/src/verax/core/confidence/confidence.loader.js +1 -0
  70. package/src/verax/core/confidence-engine-refactor.js +8 -3
  71. package/src/verax/core/confidence-engine.js +162 -23
  72. package/src/verax/core/contracts/types.js +1 -0
  73. package/src/verax/core/contracts/validators.js +79 -4
  74. package/src/verax/core/decision-snapshot.js +3 -30
  75. package/src/verax/core/decisions/decision.trace.js +2 -0
  76. package/src/verax/core/determinism/contract-writer.js +2 -2
  77. package/src/verax/core/determinism/contract.js +1 -1
  78. package/src/verax/core/determinism/diff.js +42 -1
  79. package/src/verax/core/determinism/engine.js +7 -6
  80. package/src/verax/core/determinism/finding-identity.js +3 -2
  81. package/src/verax/core/determinism/normalize.js +32 -4
  82. package/src/verax/core/determinism/report-writer.js +1 -0
  83. package/src/verax/core/determinism/run-fingerprint.js +7 -2
  84. package/src/verax/core/dynamic-route-intelligence.js +8 -7
  85. package/src/verax/core/evidence/evidence-capture-service.js +1 -0
  86. package/src/verax/core/evidence/evidence-intent-ledger.js +2 -1
  87. package/src/verax/core/evidence-builder.js +2 -2
  88. package/src/verax/core/execution-mode-context.js +1 -1
  89. package/src/verax/core/execution-mode-detector.js +5 -3
  90. package/src/verax/core/failures/exit-codes.js +39 -37
  91. package/src/verax/core/failures/failure-summary.js +1 -1
  92. package/src/verax/core/failures/failure.factory.js +3 -3
  93. package/src/verax/core/failures/failure.ledger.js +3 -2
  94. package/src/verax/core/ga/ga.artifact.js +1 -1
  95. package/src/verax/core/ga/ga.contract.js +3 -2
  96. package/src/verax/core/ga/ga.enforcer.js +1 -0
  97. package/src/verax/core/guardrails/policy.loader.js +1 -0
  98. package/src/verax/core/guardrails/truth-reconciliation.js +1 -1
  99. package/src/verax/core/guardrails-engine.js +2 -2
  100. package/src/verax/core/incremental-store.js +1 -0
  101. package/src/verax/core/integrity/budget.js +138 -0
  102. package/src/verax/core/integrity/determinism.js +342 -0
  103. package/src/verax/core/integrity/integrity.js +208 -0
  104. package/src/verax/core/integrity/poisoning.js +108 -0
  105. package/src/verax/core/integrity/transaction.js +140 -0
  106. package/src/verax/core/observe/run-timeline.js +2 -0
  107. package/src/verax/core/perf/perf.report.js +2 -0
  108. package/src/verax/core/pipeline-tracker.js +5 -0
  109. package/src/verax/core/release/provenance.builder.js +73 -214
  110. package/src/verax/core/release/release.enforcer.js +14 -9
  111. package/src/verax/core/release/reproducibility.check.js +1 -0
  112. package/src/verax/core/release/sbom.builder.js +32 -23
  113. package/src/verax/core/replay-validator.js +2 -0
  114. package/src/verax/core/replay.js +4 -0
  115. package/src/verax/core/report/cross-index.js +6 -3
  116. package/src/verax/core/report/human-summary.js +141 -1
  117. package/src/verax/core/route-intelligence.js +4 -3
  118. package/src/verax/core/run-id.js +6 -3
  119. package/src/verax/core/run-manifest.js +4 -3
  120. package/src/verax/core/security/secrets.scan.js +10 -7
  121. package/src/verax/core/security/security.enforcer.js +4 -0
  122. package/src/verax/core/security/supplychain.policy.js +9 -1
  123. package/src/verax/core/security/vuln.scan.js +2 -2
  124. package/src/verax/core/truth/truth.certificate.js +3 -1
  125. package/src/verax/core/ui-feedback-intelligence.js +12 -46
  126. package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
  127. package/src/verax/detect/confidence-engine.js +100 -660
  128. package/src/verax/detect/confidence-helper.js +1 -0
  129. package/src/verax/detect/detection-engine.js +1 -18
  130. package/src/verax/detect/dynamic-route-findings.js +17 -14
  131. package/src/verax/detect/expectation-chain-detector.js +1 -1
  132. package/src/verax/detect/expectation-model.js +3 -5
  133. package/src/verax/detect/failure-cause-inference.js +293 -0
  134. package/src/verax/detect/findings-writer.js +126 -166
  135. package/src/verax/detect/flow-detector.js +2 -2
  136. package/src/verax/detect/form-silent-failure.js +98 -0
  137. package/src/verax/detect/index.js +51 -234
  138. package/src/verax/detect/invariants-enforcer.js +147 -0
  139. package/src/verax/detect/journey-stall-detector.js +4 -4
  140. package/src/verax/detect/navigation-silent-failure.js +82 -0
  141. package/src/verax/detect/problem-aggregator.js +361 -0
  142. package/src/verax/detect/route-findings.js +7 -6
  143. package/src/verax/detect/summary-writer.js +477 -0
  144. package/src/verax/detect/test-failure-cause-inference.js +314 -0
  145. package/src/verax/detect/ui-feedback-findings.js +18 -18
  146. package/src/verax/detect/verdict-engine.js +3 -57
  147. package/src/verax/detect/view-switch-correlator.js +2 -2
  148. package/src/verax/flow/flow-engine.js +2 -1
  149. package/src/verax/flow/flow-spec.js +0 -6
  150. package/src/verax/index.js +48 -412
  151. package/src/verax/intel/ts-program.js +1 -0
  152. package/src/verax/intel/vue-navigation-extractor.js +3 -0
  153. package/src/verax/learn/action-contract-extractor.js +67 -682
  154. package/src/verax/learn/ast-contract-extractor.js +1 -1
  155. package/src/verax/learn/flow-extractor.js +1 -0
  156. package/src/verax/learn/project-detector.js +5 -0
  157. package/src/verax/learn/react-router-extractor.js +2 -0
  158. package/src/verax/learn/route-validator.js +1 -4
  159. package/src/verax/learn/source-instrumenter.js +1 -0
  160. package/src/verax/learn/state-extractor.js +2 -1
  161. package/src/verax/learn/static-extractor.js +1 -0
  162. package/src/verax/observe/coverage-gaps.js +132 -0
  163. package/src/verax/observe/expectation-handler.js +126 -0
  164. package/src/verax/observe/incremental-skip.js +46 -0
  165. package/src/verax/observe/index.js +735 -84
  166. package/src/verax/observe/interaction-executor.js +192 -0
  167. package/src/verax/observe/interaction-runner.js +782 -530
  168. package/src/verax/observe/network-firewall.js +86 -0
  169. package/src/verax/observe/observation-builder.js +169 -0
  170. package/src/verax/observe/observe-context.js +1 -1
  171. package/src/verax/observe/observe-helpers.js +2 -1
  172. package/src/verax/observe/observe-runner.js +28 -24
  173. package/src/verax/observe/observers/budget-observer.js +3 -3
  174. package/src/verax/observe/observers/console-observer.js +4 -4
  175. package/src/verax/observe/observers/coverage-observer.js +4 -4
  176. package/src/verax/observe/observers/interaction-observer.js +3 -3
  177. package/src/verax/observe/observers/navigation-observer.js +4 -4
  178. package/src/verax/observe/observers/network-observer.js +4 -4
  179. package/src/verax/observe/observers/safety-observer.js +1 -1
  180. package/src/verax/observe/observers/ui-feedback-observer.js +4 -4
  181. package/src/verax/observe/page-traversal.js +138 -0
  182. package/src/verax/observe/snapshot-ops.js +94 -0
  183. package/src/verax/observe/ui-signal-sensor.js +2 -148
  184. package/src/verax/scan-summary-writer.js +10 -42
  185. package/src/verax/shared/artifact-manager.js +30 -13
  186. package/src/verax/shared/caching.js +1 -0
  187. package/src/verax/shared/expectation-tracker.js +1 -0
  188. package/src/verax/shared/zip-artifacts.js +6 -0
  189. package/src/verax/core/confidence-engine.js.backup +0 -471
  190. package/src/verax/shared/config-loader.js +0 -169
  191. /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
@@ -3,7 +3,7 @@ import traverse from '@babel/traverse';
3
3
  import { readFileSync } from 'fs';
4
4
  import { glob } from 'glob';
5
5
  import { resolve } from 'path';
6
- import { ExpectationProof } from '../shared/expectation-proof.js';
6
+ import { ExpectationProof } from '../shared/expectation-validation.js';
7
7
  import { normalizeTemplateLiteral } from '../shared/dynamic-route-utils.js';
8
8
 
9
9
  const MAX_FILES_TO_SCAN = 200;
@@ -14,6 +14,7 @@ import { isProvenExpectation } from '../shared/expectation-prover.js';
14
14
  function generateFlowId(steps) {
15
15
  const hashInput = steps.map(s => `${s.expectationType}:${s.source || s.handlerRef}`).join('|');
16
16
  const hash = createHash('sha256').update(hashInput).digest('hex');
17
+ // @ts-expect-error - digest returns string
17
18
  return `flow-${hash.substring(0, 8)}`;
18
19
  }
19
20
 
@@ -7,6 +7,7 @@ async function hasReactDependency(projectDir) {
7
7
  const packageJsonPath = resolve(projectDir, 'package.json');
8
8
  if (!existsSync(packageJsonPath)) return false;
9
9
 
10
+ // @ts-expect-error - readFileSync with encoding returns string
10
11
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
11
12
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
12
13
 
@@ -21,6 +22,7 @@ async function hasNextJs(projectDir) {
21
22
  const packageJsonPath = resolve(projectDir, 'package.json');
22
23
  if (!existsSync(packageJsonPath)) return false;
23
24
 
25
+ // @ts-expect-error - readFileSync with encoding returns string
24
26
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
25
27
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
26
28
 
@@ -35,6 +37,7 @@ async function hasReactRouter(projectDir) {
35
37
  const packageJsonPath = resolve(projectDir, 'package.json');
36
38
  if (!existsSync(packageJsonPath)) return false;
37
39
 
40
+ // @ts-expect-error - readFileSync with encoding returns string
38
41
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
39
42
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
40
43
 
@@ -49,6 +52,7 @@ async function hasVue(projectDir) {
49
52
  const packageJsonPath = resolve(projectDir, 'package.json');
50
53
  if (!existsSync(packageJsonPath)) return false;
51
54
 
55
+ // @ts-expect-error - readFileSync with encoding returns string
52
56
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
53
57
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
54
58
 
@@ -63,6 +67,7 @@ async function hasVueRouter(projectDir) {
63
67
  const packageJsonPath = resolve(projectDir, 'package.json');
64
68
  if (!existsSync(packageJsonPath)) return false;
65
69
 
70
+ // @ts-expect-error - readFileSync with encoding returns string
66
71
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
67
72
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
68
73
 
@@ -26,11 +26,13 @@ export async function extractReactRouterRoutes(projectDir) {
26
26
 
27
27
  for (const pattern of routePatterns) {
28
28
  let match;
29
+ // @ts-expect-error - readFileSync with encoding returns string
29
30
  while ((match = pattern.exec(content)) !== null) {
30
31
  let path = match[1];
31
32
 
32
33
  if (!path) {
33
34
  if (match[0].includes('createBrowserRouter')) {
35
+ // @ts-expect-error - readFileSync with encoding returns string
34
36
  const routesMatch = content.match(/createBrowserRouter\s*\(\s*\[([^\]]+)\]/s);
35
37
  if (routesMatch) {
36
38
  const routesContent = routesMatch[1];
@@ -15,10 +15,7 @@ export async function validateRoutes(manifest, baseUrl) {
15
15
  routesReachable: 0,
16
16
  routesUnreachable: 0,
17
17
  details: [],
18
- warnings: [{
19
- code: 'INVALID_BASE_URL',
20
- message: `Cannot parse base URL for validation: ${error.message}`
21
- }]
18
+ warnings: []
22
19
  };
23
20
  }
24
21
 
@@ -152,6 +152,7 @@ export async function instrumentFile(inputPath, outputPath, workspaceRoot) {
152
152
  const { mkdirSync } = await import('fs');
153
153
 
154
154
  const code = readFileSync(inputPath, 'utf-8');
155
+ // @ts-expect-error - readFileSync with encoding returns string
155
156
  const instrumented = instrumentJSX(code, inputPath, workspaceRoot);
156
157
 
157
158
  // Ensure output directory exists
@@ -3,7 +3,7 @@ import traverse from '@babel/traverse';
3
3
  import { readFileSync } from 'fs';
4
4
  import { glob } from 'glob';
5
5
  import { resolve } from 'path';
6
- import { ExpectationProof } from '../shared/expectation-proof.js';
6
+ import { ExpectationProof } from '../shared/expectation-validation.js';
7
7
 
8
8
  const MAX_FILES_TO_SCAN = 200;
9
9
 
@@ -141,6 +141,7 @@ function detectStateStores(projectDir) {
141
141
  try {
142
142
  const pkgPath = resolve(projectDir, 'package.json');
143
143
  const pkgContent = readFileSync(pkgPath, 'utf-8');
144
+ // @ts-expect-error - readFileSync with encoding returns string
144
145
  const pkg = JSON.parse(pkgContent);
145
146
 
146
147
  const allDeps = {
@@ -276,6 +276,7 @@ export async function extractStaticExpectations(projectDir, routes) {
276
276
 
277
277
  try {
278
278
  const content = readFileSync(filePath, 'utf-8');
279
+ // @ts-expect-error - readFileSync with encoding returns string
279
280
  const root = parse(content);
280
281
 
281
282
  const links = root.querySelectorAll('a[href]');
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Coverage Gap Accumulation & Warning Generation Module
3
+ * Extracted from observe/index.js (STAGE D2.2)
4
+ *
5
+ * Responsibility: Manage coverage gap collection from multiple sources
6
+ * and generate corresponding warning messages about incomplete coverage.
7
+ *
8
+ * This module encapsulates the logic for:
9
+ * 1. Accumulating coverage gaps from remaining unexecuted interactions
10
+ * 2. Detecting and recording frontier capping events
11
+ * 3. Building the coverage metrics object
12
+ * 4. Generating appropriate warning messages
13
+ */
14
+
15
+ /**
16
+ * Accumulate coverage gaps from remaining interactions and frontier capping.
17
+ *
18
+ * When the interaction execution loop exits early (due to budget constraints),
19
+ * any unexecuted interactions are collected as coverage gaps with metadata
20
+ * about why they couldn't be executed.
21
+ *
22
+ * @param {Array} remainingInteractionsGaps - Array of unexecuted interactions with reasons
23
+ * @param {Object} frontier - Frontier object with tracking metadata (frontierCapped, pagesVisited, pagesDiscovered)
24
+ * @param {string} pageUrl - Current page URL from page.url() for gap location context
25
+ * @param {Object} scanBudget - Scan budget configuration (maxUniqueUrls, maxTotalInteractions)
26
+ * @returns {Array} Array of coverage gap objects in expectationCoverageGaps format
27
+ */
28
+ export function accumulateCoverageGaps(remainingInteractionsGaps, frontier, pageUrl, scanBudget) {
29
+ const gaps = [];
30
+
31
+ // Add remaining interactions as coverage gaps
32
+ // These are interactions that existed but couldn't be executed due to budget constraints
33
+ if (remainingInteractionsGaps.length > 0) {
34
+ gaps.push(...remainingInteractionsGaps.map(gap => ({
35
+ expectationId: null,
36
+ type: gap.interaction.type,
37
+ reason: gap.reason,
38
+ fromPath: gap.url,
39
+ source: null,
40
+ evidence: {
41
+ interaction: gap.interaction
42
+ }
43
+ })));
44
+ }
45
+
46
+ // Record frontier capping as coverage gap if it occurred
47
+ // This indicates that the scan encountered the URL discovery limit
48
+ if (frontier.frontierCapped) {
49
+ gaps.push({
50
+ expectationId: null,
51
+ type: 'navigation',
52
+ reason: 'frontier_capped',
53
+ fromPath: pageUrl,
54
+ source: null,
55
+ evidence: {
56
+ message: `Frontier capped at ${scanBudget.maxUniqueUrls || 'unlimited'} unique URLs`
57
+ }
58
+ });
59
+ }
60
+
61
+ return gaps;
62
+ }
63
+
64
+ /**
65
+ * Build the coverage metrics object.
66
+ *
67
+ * The coverage object captures quantitative metrics about what was discovered
68
+ * versus what was actually executed, enabling downstream analysis of coverage
69
+ * completeness and bottlenecks.
70
+ *
71
+ * @param {number} totalInteractionsDiscovered - Total interactions found across all pages
72
+ * @param {number} totalInteractionsExecuted - Interactions actually executed
73
+ * @param {Object} scanBudget - Scan budget configuration
74
+ * @param {Object} frontier - Frontier object with pagesVisited and pagesDiscovered
75
+ * @param {Array} skippedInteractions - Array of interactions that were skipped for safety
76
+ * @param {Array} remainingInteractionsGaps - Array of unexecuted interactions
77
+ * @returns {Object} Coverage metrics object
78
+ */
79
+ export function buildCoverageObject(
80
+ totalInteractionsDiscovered,
81
+ totalInteractionsExecuted,
82
+ scanBudget,
83
+ frontier,
84
+ skippedInteractions,
85
+ remainingInteractionsGaps
86
+ ) {
87
+ return {
88
+ candidatesDiscovered: totalInteractionsDiscovered,
89
+ candidatesSelected: totalInteractionsExecuted,
90
+ cap: scanBudget.maxTotalInteractions,
91
+ capped: totalInteractionsExecuted >= scanBudget.maxTotalInteractions || remainingInteractionsGaps.length > 0,
92
+ pagesVisited: frontier.pagesVisited,
93
+ pagesDiscovered: frontier.pagesDiscovered,
94
+ skippedInteractions: skippedInteractions.length,
95
+ interactionsDiscovered: totalInteractionsDiscovered,
96
+ interactionsExecuted: totalInteractionsExecuted
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Generate warning messages based on coverage metrics.
102
+ *
103
+ * Warnings communicate to the user what limitations were encountered during
104
+ * observation, allowing them to understand coverage completeness and any
105
+ * safety-related interaction filtering.
106
+ *
107
+ * @param {Object} coverage - Coverage metrics object from buildCoverageObject()
108
+ * @param {Array} skippedInteractions - Array of interactions that were skipped for safety
109
+ * @returns {Array} Array of warning objects with code and message properties
110
+ */
111
+ export function generateCoverageWarnings(coverage, skippedInteractions) {
112
+ const warnings = [];
113
+
114
+ // Warn if coverage was incomplete due to capping
115
+ if (coverage.capped) {
116
+ warnings.push({
117
+ code: 'INTERACTIONS_CAPPED',
118
+ message: `Interaction execution capped. Visited ${coverage.pagesVisited} pages, discovered ${coverage.pagesDiscovered}, executed ${coverage.candidatesSelected} of ${coverage.candidatesDiscovered} interactions. Coverage incomplete.`
119
+ });
120
+ }
121
+
122
+ // Warn if interactions were skipped for safety
123
+ if (skippedInteractions.length > 0) {
124
+ warnings.push({
125
+ code: 'INTERACTIONS_SKIPPED',
126
+ message: `Skipped ${skippedInteractions.length} dangerous interactions`,
127
+ details: skippedInteractions
128
+ });
129
+ }
130
+
131
+ return warnings;
132
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * EXPECTATION HANDLER
3
+ *
4
+ * Manages manifest loading, snapshot comparison, and proven expectation execution.
5
+ */
6
+
7
+ import { readFileSync as readFileSyncWithEncoding, existsSync, writeFileSync, mkdirSync } from 'fs';
8
+ import { join } from 'path';
9
+
10
+ /**
11
+ * Load and execute proven expectations from manifest
12
+ *
13
+ * @param {Object} page - Playwright page
14
+ * @param {string} manifestPath - Path to manifest file
15
+ * @param {string} projectDir - Project directory
16
+ * @param {Object} silenceTracker - Silence tracker
17
+ * @returns {Promise<{success: boolean, results: Array|null}>}
18
+ */
19
+ export async function loadAndExecuteProvenExpectations(page, manifestPath, projectDir, silenceTracker) {
20
+ try {
21
+ if (!existsSync(manifestPath)) {
22
+ silenceTracker.record('expectation_manifest_not_found');
23
+ return { success: false, results: null };
24
+ }
25
+
26
+ const manifestContent = readFileSyncWithEncoding(manifestPath, 'utf8');
27
+ const manifest = JSON.parse(typeof manifestContent === 'string' ? manifestContent : manifestContent.toString());
28
+
29
+ if (!manifest.expectations || !Array.isArray(manifest.expectations)) {
30
+ silenceTracker.record('expectation_manifest_invalid_format');
31
+ return { success: false, results: null };
32
+ }
33
+
34
+ // Return empty results for now (expectation execution handled in main observe function)
35
+ return { success: true, results: [] };
36
+ } catch (error) {
37
+ silenceTracker.record('expectation_manifest_load_error');
38
+ return { success: false, results: null };
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Load and compare snapshot with previous observation
44
+ *
45
+ * @param {string} projectDir - Project directory
46
+ * @param {Object} silenceTracker - Silence tracker
47
+ * @returns {Promise<{currentSnapshot: Object|null, previousSnapshot: Object|null}>}
48
+ */
49
+ export async function loadAndCompareSnapshot(projectDir, silenceTracker) {
50
+ try {
51
+ const snapshotDir = join(projectDir, '.verax', 'snapshots');
52
+
53
+ if (!existsSync(snapshotDir)) {
54
+ mkdirSync(snapshotDir, { recursive: true });
55
+ }
56
+
57
+ // Load previous snapshot if exists
58
+ const previousSnapshotPath = join(snapshotDir, 'previous.json');
59
+ let previousSnapshot = null;
60
+ if (existsSync(previousSnapshotPath)) {
61
+ try {
62
+ const content = readFileSyncWithEncoding(previousSnapshotPath, 'utf8');
63
+ previousSnapshot = JSON.parse(typeof content === 'string' ? content : content.toString());
64
+ } catch {
65
+ silenceTracker.record('snapshot_previous_load_error');
66
+ }
67
+ }
68
+
69
+ // Load current snapshot if exists
70
+ const currentSnapshotPath = join(snapshotDir, 'current.json');
71
+ let currentSnapshot = null;
72
+ if (existsSync(currentSnapshotPath)) {
73
+ try {
74
+ const content = readFileSyncWithEncoding(currentSnapshotPath, 'utf8');
75
+ currentSnapshot = JSON.parse(typeof content === 'string' ? content : content.toString());
76
+ } catch {
77
+ silenceTracker.record('snapshot_current_load_error');
78
+ }
79
+ }
80
+
81
+ return { currentSnapshot, previousSnapshot };
82
+ } catch (error) {
83
+ silenceTracker.record('snapshot_comparison_error');
84
+ return { currentSnapshot: null, previousSnapshot: null };
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Build snapshot object from observation data
90
+ *
91
+ * @param {Array} traces - Array of interaction traces
92
+ * @param {string} baseOrigin - Base origin URL
93
+ * @returns {Object}
94
+ */
95
+ export function buildSnapshot(traces, baseOrigin) {
96
+ const snapshot = {
97
+ timestamp: new Date().toISOString(),
98
+ baseOrigin,
99
+ totalTraces: traces.length,
100
+ verifiedExpectations: traces.filter(t => t.expectationDriven).length,
101
+ observedExpectations: traces.filter(t => t.observedExpectation).length,
102
+ unprovable: traces.filter(t => t.unprovenResult).length
103
+ };
104
+
105
+ return snapshot;
106
+ }
107
+
108
+ /**
109
+ * Save snapshot for future comparison
110
+ *
111
+ * @param {Object} snapshot - Snapshot object
112
+ * @param {string} projectDir - Project directory
113
+ */
114
+ export function saveSnapshot(snapshot, projectDir) {
115
+ try {
116
+ const snapshotDir = join(projectDir, '.verax', 'snapshots');
117
+ if (!existsSync(snapshotDir)) {
118
+ mkdirSync(snapshotDir, { recursive: true });
119
+ }
120
+
121
+ const snapshotPath = join(snapshotDir, 'current.json');
122
+ writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2));
123
+ } catch (error) {
124
+ // Silently fail on snapshot save
125
+ }
126
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Incremental Skip Phantom Trace Builder
3
+ *
4
+ * Extracted from observe/index.js (STAGE D2.4)
5
+ *
6
+ * Builds the minimal phantom trace object that represents a skipped
7
+ * interaction in incremental mode. The phantom trace preserves interaction
8
+ * metadata but has no execution result (before/after URLs are identical).
9
+ *
10
+ * Preserves 100% of original behavior:
11
+ * - Same 5 top-level properties
12
+ * - Same nested object shapes
13
+ * - incremental flag always true
14
+ * - resultType always 'INCREMENTAL_SKIP'
15
+ */
16
+
17
+ /**
18
+ * Build a phantom trace for an incremental skip.
19
+ *
20
+ * When an interaction is skipped in incremental mode (because it was
21
+ * unchanged from the previous run), a phantom trace is created to:
22
+ * 1. Preserve the interaction metadata for the observation record
23
+ * 2. Mark the trace as incremental (via incremental: true) for filtering
24
+ * 3. Enable transparent output (trace appears in JSON but marked as skipped)
25
+ *
26
+ * @param {Object} params - Function parameters
27
+ * @param {Object} params.interaction - The interaction that was skipped
28
+ * @param {string} params.interaction.type - Interaction type (e.g., 'click')
29
+ * @param {string} params.interaction.selector - CSS selector
30
+ * @param {string} params.interaction.label - Human-readable label
31
+ * @param {string} params.currentUrl - Current page URL (used for before/after)
32
+ * @returns {Object} Phantom trace object with incremental flag set
33
+ */
34
+ export function buildIncrementalPhantomTrace({ interaction, currentUrl }) {
35
+ return {
36
+ interaction: {
37
+ type: interaction.type,
38
+ selector: interaction.selector,
39
+ label: interaction.label
40
+ },
41
+ before: { url: currentUrl },
42
+ after: { url: currentUrl },
43
+ incremental: true,
44
+ resultType: 'INCREMENTAL_SKIP'
45
+ };
46
+ }