@veraxhq/verax 0.2.1 → 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 (213) hide show
  1. package/README.md +10 -6
  2. package/bin/verax.js +11 -11
  3. package/package.json +29 -8
  4. package/src/cli/commands/baseline.js +103 -0
  5. package/src/cli/commands/default.js +51 -6
  6. package/src/cli/commands/doctor.js +29 -0
  7. package/src/cli/commands/ga.js +246 -0
  8. package/src/cli/commands/gates.js +95 -0
  9. package/src/cli/commands/inspect.js +4 -2
  10. package/src/cli/commands/release-check.js +215 -0
  11. package/src/cli/commands/run.js +45 -6
  12. package/src/cli/commands/security-check.js +212 -0
  13. package/src/cli/commands/truth.js +113 -0
  14. package/src/cli/entry.js +30 -20
  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 +544 -0
  20. package/src/cli/util/ast-network-detector.js +603 -0
  21. package/src/cli/util/ast-promise-extractor.js +581 -0
  22. package/src/cli/util/ast-usestate-detector.js +602 -0
  23. package/src/cli/util/atomic-write.js +12 -1
  24. package/src/cli/util/bootstrap-guard.js +86 -0
  25. package/src/cli/util/console-reporter.js +72 -0
  26. package/src/cli/util/detection-engine.js +105 -41
  27. package/src/cli/util/determinism-runner.js +124 -0
  28. package/src/cli/util/determinism-writer.js +129 -0
  29. package/src/cli/util/digest-engine.js +359 -0
  30. package/src/cli/util/dom-diff.js +226 -0
  31. package/src/cli/util/evidence-engine.js +287 -0
  32. package/src/cli/util/expectation-extractor.js +151 -5
  33. package/src/cli/util/findings-writer.js +3 -0
  34. package/src/cli/util/framework-detector.js +572 -0
  35. package/src/cli/util/idgen.js +1 -1
  36. package/src/cli/util/interaction-planner.js +529 -0
  37. package/src/cli/util/learn-writer.js +2 -0
  38. package/src/cli/util/ledger-writer.js +110 -0
  39. package/src/cli/util/monorepo-resolver.js +162 -0
  40. package/src/cli/util/observation-engine.js +127 -278
  41. package/src/cli/util/observe-writer.js +2 -0
  42. package/src/cli/util/project-discovery.js +284 -0
  43. package/src/cli/util/project-writer.js +2 -0
  44. package/src/cli/util/run-id.js +23 -27
  45. package/src/cli/util/run-resolver.js +64 -0
  46. package/src/cli/util/run-result.js +778 -0
  47. package/src/cli/util/selector-resolver.js +235 -0
  48. package/src/cli/util/source-requirement.js +55 -0
  49. package/src/cli/util/summary-writer.js +2 -0
  50. package/src/cli/util/svelte-navigation-detector.js +163 -0
  51. package/src/cli/util/svelte-network-detector.js +80 -0
  52. package/src/cli/util/svelte-sfc-extractor.js +146 -0
  53. package/src/cli/util/svelte-state-detector.js +242 -0
  54. package/src/cli/util/trust-activation-integration.js +496 -0
  55. package/src/cli/util/trust-activation-wrapper.js +85 -0
  56. package/src/cli/util/trust-integration-hooks.js +164 -0
  57. package/src/cli/util/types.js +153 -0
  58. package/src/cli/util/url-validation.js +40 -0
  59. package/src/cli/util/vue-navigation-detector.js +178 -0
  60. package/src/cli/util/vue-sfc-extractor.js +161 -0
  61. package/src/cli/util/vue-state-detector.js +215 -0
  62. package/src/types/fs-augment.d.ts +23 -0
  63. package/src/types/global.d.ts +137 -0
  64. package/src/types/internal-types.d.ts +35 -0
  65. package/src/verax/cli/init.js +4 -18
  66. package/src/verax/core/action-classifier.js +4 -3
  67. package/src/verax/core/artifacts/registry.js +139 -0
  68. package/src/verax/core/artifacts/verifier.js +990 -0
  69. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  70. package/src/verax/core/baseline/baseline.snapshot.js +233 -0
  71. package/src/verax/core/capabilities/gates.js +505 -0
  72. package/src/verax/core/capabilities/registry.js +475 -0
  73. package/src/verax/core/confidence/confidence-compute.js +144 -0
  74. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  75. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  76. package/src/verax/core/confidence/confidence-weights.js +44 -0
  77. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  78. package/src/verax/core/confidence/confidence.loader.js +80 -0
  79. package/src/verax/core/confidence/confidence.schema.js +94 -0
  80. package/src/verax/core/confidence-engine-refactor.js +489 -0
  81. package/src/verax/core/confidence-engine.js +625 -0
  82. package/src/verax/core/contracts/index.js +29 -0
  83. package/src/verax/core/contracts/types.js +186 -0
  84. package/src/verax/core/contracts/validators.js +456 -0
  85. package/src/verax/core/decisions/decision.trace.js +278 -0
  86. package/src/verax/core/determinism/contract-writer.js +89 -0
  87. package/src/verax/core/determinism/contract.js +139 -0
  88. package/src/verax/core/determinism/diff.js +405 -0
  89. package/src/verax/core/determinism/engine.js +222 -0
  90. package/src/verax/core/determinism/finding-identity.js +149 -0
  91. package/src/verax/core/determinism/normalize.js +466 -0
  92. package/src/verax/core/determinism/report-writer.js +93 -0
  93. package/src/verax/core/determinism/run-fingerprint.js +123 -0
  94. package/src/verax/core/dynamic-route-intelligence.js +529 -0
  95. package/src/verax/core/evidence/evidence-capture-service.js +308 -0
  96. package/src/verax/core/evidence/evidence-intent-ledger.js +166 -0
  97. package/src/verax/core/evidence-builder.js +487 -0
  98. package/src/verax/core/execution-mode-context.js +77 -0
  99. package/src/verax/core/execution-mode-detector.js +192 -0
  100. package/src/verax/core/failures/exit-codes.js +88 -0
  101. package/src/verax/core/failures/failure-summary.js +76 -0
  102. package/src/verax/core/failures/failure.factory.js +225 -0
  103. package/src/verax/core/failures/failure.ledger.js +133 -0
  104. package/src/verax/core/failures/failure.types.js +196 -0
  105. package/src/verax/core/failures/index.js +10 -0
  106. package/src/verax/core/ga/ga-report-writer.js +43 -0
  107. package/src/verax/core/ga/ga.artifact.js +49 -0
  108. package/src/verax/core/ga/ga.contract.js +435 -0
  109. package/src/verax/core/ga/ga.enforcer.js +87 -0
  110. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  111. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  112. package/src/verax/core/guardrails/policy.loader.js +84 -0
  113. package/src/verax/core/guardrails/policy.schema.js +110 -0
  114. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  115. package/src/verax/core/guardrails-engine.js +505 -0
  116. package/src/verax/core/incremental-store.js +1 -0
  117. package/src/verax/core/integrity/budget.js +138 -0
  118. package/src/verax/core/integrity/determinism.js +342 -0
  119. package/src/verax/core/integrity/integrity.js +208 -0
  120. package/src/verax/core/integrity/poisoning.js +108 -0
  121. package/src/verax/core/integrity/transaction.js +140 -0
  122. package/src/verax/core/observe/run-timeline.js +318 -0
  123. package/src/verax/core/perf/perf.contract.js +186 -0
  124. package/src/verax/core/perf/perf.display.js +65 -0
  125. package/src/verax/core/perf/perf.enforcer.js +91 -0
  126. package/src/verax/core/perf/perf.monitor.js +209 -0
  127. package/src/verax/core/perf/perf.report.js +200 -0
  128. package/src/verax/core/pipeline-tracker.js +243 -0
  129. package/src/verax/core/product-definition.js +127 -0
  130. package/src/verax/core/release/provenance.builder.js +130 -0
  131. package/src/verax/core/release/release-report-writer.js +40 -0
  132. package/src/verax/core/release/release.enforcer.js +164 -0
  133. package/src/verax/core/release/reproducibility.check.js +222 -0
  134. package/src/verax/core/release/sbom.builder.js +292 -0
  135. package/src/verax/core/replay-validator.js +2 -0
  136. package/src/verax/core/replay.js +4 -0
  137. package/src/verax/core/report/cross-index.js +195 -0
  138. package/src/verax/core/report/human-summary.js +362 -0
  139. package/src/verax/core/route-intelligence.js +420 -0
  140. package/src/verax/core/run-id.js +6 -3
  141. package/src/verax/core/run-manifest.js +4 -3
  142. package/src/verax/core/security/secrets.scan.js +329 -0
  143. package/src/verax/core/security/security-report.js +50 -0
  144. package/src/verax/core/security/security.enforcer.js +128 -0
  145. package/src/verax/core/security/supplychain.defaults.json +38 -0
  146. package/src/verax/core/security/supplychain.policy.js +334 -0
  147. package/src/verax/core/security/vuln.scan.js +265 -0
  148. package/src/verax/core/truth/truth.certificate.js +252 -0
  149. package/src/verax/core/ui-feedback-intelligence.js +481 -0
  150. package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
  151. package/src/verax/detect/confidence-engine.js +62 -34
  152. package/src/verax/detect/confidence-helper.js +34 -0
  153. package/src/verax/detect/dynamic-route-findings.js +338 -0
  154. package/src/verax/detect/expectation-chain-detector.js +417 -0
  155. package/src/verax/detect/expectation-model.js +2 -2
  156. package/src/verax/detect/failure-cause-inference.js +293 -0
  157. package/src/verax/detect/findings-writer.js +131 -35
  158. package/src/verax/detect/flow-detector.js +2 -2
  159. package/src/verax/detect/form-silent-failure.js +98 -0
  160. package/src/verax/detect/index.js +46 -5
  161. package/src/verax/detect/invariants-enforcer.js +147 -0
  162. package/src/verax/detect/journey-stall-detector.js +558 -0
  163. package/src/verax/detect/navigation-silent-failure.js +82 -0
  164. package/src/verax/detect/problem-aggregator.js +361 -0
  165. package/src/verax/detect/route-findings.js +219 -0
  166. package/src/verax/detect/summary-writer.js +477 -0
  167. package/src/verax/detect/test-failure-cause-inference.js +314 -0
  168. package/src/verax/detect/ui-feedback-findings.js +207 -0
  169. package/src/verax/detect/view-switch-correlator.js +242 -0
  170. package/src/verax/flow/flow-engine.js +2 -1
  171. package/src/verax/flow/flow-spec.js +0 -6
  172. package/src/verax/index.js +4 -0
  173. package/src/verax/intel/ts-program.js +1 -0
  174. package/src/verax/intel/vue-navigation-extractor.js +3 -0
  175. package/src/verax/learn/action-contract-extractor.js +3 -0
  176. package/src/verax/learn/ast-contract-extractor.js +1 -1
  177. package/src/verax/learn/flow-extractor.js +1 -0
  178. package/src/verax/learn/project-detector.js +5 -0
  179. package/src/verax/learn/react-router-extractor.js +2 -0
  180. package/src/verax/learn/source-instrumenter.js +1 -0
  181. package/src/verax/learn/state-extractor.js +2 -1
  182. package/src/verax/learn/static-extractor.js +1 -0
  183. package/src/verax/observe/coverage-gaps.js +132 -0
  184. package/src/verax/observe/expectation-handler.js +126 -0
  185. package/src/verax/observe/incremental-skip.js +46 -0
  186. package/src/verax/observe/index.js +51 -155
  187. package/src/verax/observe/interaction-executor.js +192 -0
  188. package/src/verax/observe/interaction-runner.js +782 -513
  189. package/src/verax/observe/network-firewall.js +86 -0
  190. package/src/verax/observe/observation-builder.js +169 -0
  191. package/src/verax/observe/observe-context.js +205 -0
  192. package/src/verax/observe/observe-helpers.js +192 -0
  193. package/src/verax/observe/observe-runner.js +230 -0
  194. package/src/verax/observe/observers/budget-observer.js +185 -0
  195. package/src/verax/observe/observers/console-observer.js +102 -0
  196. package/src/verax/observe/observers/coverage-observer.js +107 -0
  197. package/src/verax/observe/observers/interaction-observer.js +471 -0
  198. package/src/verax/observe/observers/navigation-observer.js +132 -0
  199. package/src/verax/observe/observers/network-observer.js +87 -0
  200. package/src/verax/observe/observers/safety-observer.js +82 -0
  201. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  202. package/src/verax/observe/page-traversal.js +138 -0
  203. package/src/verax/observe/snapshot-ops.js +94 -0
  204. package/src/verax/observe/ui-feedback-detector.js +742 -0
  205. package/src/verax/scan-summary-writer.js +2 -0
  206. package/src/verax/shared/artifact-manager.js +25 -5
  207. package/src/verax/shared/caching.js +1 -0
  208. package/src/verax/shared/css-spinner-rules.js +204 -0
  209. package/src/verax/shared/expectation-tracker.js +1 -0
  210. package/src/verax/shared/view-switch-rules.js +208 -0
  211. package/src/verax/shared/zip-artifacts.js +6 -0
  212. package/src/verax/shared/config-loader.js +0 -169
  213. /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
@@ -0,0 +1,164 @@
1
+ /**
2
+ * PHASE 6B: ACTIVATION
3
+ *
4
+ * Activates Phase 6A by enforcing it as MANDATORY for all scan paths.
5
+ *
6
+ * This module wraps Phase 6A and integrates it with run.js to ensure:
7
+ * 1. All scans use Phase 6A (no bypass possible)
8
+ * 2. Artifacts can only be written to staging
9
+ * 3. Integrity is verified before commit
10
+ * 4. Poison markers prevent reading corrupted runs
11
+ */
12
+
13
+ import { initPhase6A as phase6aInit, completePhase6A as phase6aComplete, rollbackPhase6A as phase6aRollback, checkPoisonMarker } from './trust-activation-integration.js';
14
+ import { join } from 'path';
15
+ import { existsSync } from 'fs';
16
+
17
+ /**
18
+ * Initialize Phase 6A (poison marker + staging directory)
19
+ * MANDATORY at run start - called before any artifact writing
20
+ *
21
+ * @param {string} runDir - Run directory (e.g., .verax/runs/<runId>)
22
+ * @returns {Promise<{ success: boolean, error?: Error }>}
23
+ */
24
+ export async function initializePhase6A(runDir) {
25
+ try {
26
+ const result = await phase6aInit(runDir);
27
+ return result;
28
+ } catch (error) {
29
+ return { success: false, error };
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Get paths with staging directory redirection
35
+ * MANDATORY - replaces all artifact paths to point to staging directory
36
+ *
37
+ * This ensures ALL artifact writes go to staging instead of final location.
38
+ *
39
+ * @param {Object} paths - Original paths from getRunPaths()
40
+ * @returns {Object} Modified paths with staging redirection
41
+ */
42
+ export function getStagingRedirectedPaths(paths) {
43
+ const stagingDir = join(paths.baseDir, '.staging');
44
+
45
+ const redirected = { ...paths };
46
+
47
+ // List of artifact path keys that should be redirected to staging
48
+ const artifactKeys = [
49
+ 'summary', 'findings', 'ledger', 'learn', 'observe',
50
+ 'summaryJson', 'findingsJson', 'learnJson', 'observeJson', 'ledgerJson'
51
+ ];
52
+
53
+ for (const key of artifactKeys) {
54
+ if (redirected[key] && typeof redirected[key] === 'string') {
55
+ // Extract just the filename from the path (works on Windows and Unix)
56
+ // Split by both / and \ to handle any path separator
57
+ const parts = redirected[key].replace(/\\/g, '/').split('/');
58
+ const filename = parts[parts.length - 1];
59
+
60
+ if (filename) {
61
+ // Place in staging directory
62
+ redirected[key] = join(stagingDir, filename);
63
+ }
64
+ }
65
+ }
66
+
67
+ return redirected;
68
+ }
69
+
70
+ /**
71
+ * Complete Phase 6A - verify integrity and commit artifacts
72
+ * MANDATORY on success - generates manifest, verifies, commits staging to final
73
+ *
74
+ * @param {string} runDir - Run directory (base, not staging)
75
+ * @returns {Promise<{ success: boolean, verification?: any, error?: Error }>}
76
+ */
77
+ export async function completePhase6A(runDir) {
78
+ try {
79
+ const result = await phase6aComplete(runDir);
80
+ return result;
81
+ } catch (error) {
82
+ return { success: false, error };
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Rollback Phase 6A on error
88
+ * MANDATORY in catch block - cleans staging but KEEPS poison marker
89
+ *
90
+ * @param {string} runDir - Run directory (base, not staging)
91
+ * @returns {Promise<{ success: boolean, error?: Error }>}
92
+ */
93
+ export async function rollbackPhase6A(runDir) {
94
+ try {
95
+ // Pass generic error - the phase6a module will record it
96
+ const error = new Error('Scan execution failed or was interrupted');
97
+ const result = await phase6aRollback(runDir, error);
98
+ return result;
99
+ } catch (error) {
100
+ return { success: false, error };
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Enforce poison marker check before reading a run
106
+ * MANDATORY before inspect, before loading artifacts, etc
107
+ *
108
+ * Prevents reading from incomplete/corrupted previous runs.
109
+ *
110
+ * @param {string} runDir - Run directory
111
+ * @throws {Error} If poison marker exists
112
+ */
113
+ export function enforcePoisonCheckBeforeRead(runDir) {
114
+ const poisonCheck = checkPoisonMarker(runDir);
115
+
116
+ if (poisonCheck.hasPoisonMarker) {
117
+ throw new Error(
118
+ `Cannot read from this run: poison marker present (incomplete/corrupted run). ` +
119
+ `The previous scan did not complete successfully. ` +
120
+ `Artifacts may be incomplete or corrupted. ` +
121
+ `Run 'verax doctor' to diagnose.`
122
+ );
123
+ }
124
+ return { safe: true };
125
+ }
126
+
127
+ /**
128
+ * Verify artifacts before reading (check integrity manifest)
129
+ * RECOMMENDED before loading summary.json, findings.json, etc
130
+ *
131
+ * @param {string} runDir - Run directory
132
+ * @returns {{ ok: boolean, error?: string }}
133
+ */
134
+ export function verifyArtifactsBeforeRead(runDir) {
135
+ try {
136
+ // Check that integrity manifest exists (in staging directory)
137
+ const stagingDir = join(runDir, '.staging');
138
+ const manifestPath = join(stagingDir, 'integrity.manifest.json');
139
+
140
+ if (!existsSync(manifestPath)) {
141
+ return {
142
+ ok: false,
143
+ error: 'Integrity manifest missing - run may be incomplete or from older version'
144
+ };
145
+ }
146
+
147
+ // In future: verify checksums match manifest
148
+
149
+ return { ok: true };
150
+ } catch (error) {
151
+ return { ok: false, error: error.message };
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Check budget compliance during execution
157
+ * Placeholder for Phase 6C
158
+ *
159
+ * @returns {{ ok: boolean, violations?: string[] }}
160
+ */
161
+ export function checkBudgetCompliance() {
162
+ // Placeholder for Phase 6C
163
+ return { ok: true };
164
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * PHASE 2: Canonical Type System for VERAX
3
+ *
4
+ * Single source of truth for all valid types in the analysis pipeline:
5
+ * - EXPECTATION_TYPE: Types of expectations extracted from source code
6
+ * - FINDING_TYPE: Types of findings detected during analysis
7
+ * - SKIP_REASON: Structured reasons why expectations were skipped
8
+ *
9
+ * Validation happens at CREATION TIME, not at enforcement time.
10
+ * No object may be created with an invalid type.
11
+ */
12
+
13
+ /**
14
+ * Valid expectation types extracted from source code.
15
+ * These represent what the code is expected to do.
16
+ */
17
+ export const EXPECTATION_TYPE = Object.freeze({
18
+ // Navigation: Expects a URL change or page navigation
19
+ NAVIGATION: 'navigation',
20
+
21
+ // Network: Expects a network call to be made
22
+ NETWORK: 'network',
23
+
24
+ // State: Expects a state change or promise fulfillment
25
+ STATE: 'state',
26
+ });
27
+
28
+ /**
29
+ * Valid finding types detected during analysis.
30
+ * These represent what the code is NOT doing that it should be.
31
+ */
32
+ export const FINDING_TYPE = Object.freeze({
33
+ // Silent Failures: Expectation not met with no observable effect
34
+ SILENT_FAILURE: 'silent_failure',
35
+ NETWORK_SILENT_FAILURE: 'network_silent_failure',
36
+ VALIDATION_SILENT_FAILURE: 'validation_silent_failure',
37
+ FLOW_SILENT_FAILURE: 'flow_silent_failure',
38
+ DYNAMIC_ROUTE_SILENT_FAILURE: 'dynamic_route_silent_failure',
39
+
40
+ // Observable Breaks: Expectation not met with visible effects
41
+ OBSERVED_BREAK: 'observed_break',
42
+
43
+ // UI Feedback: Missing user feedback after action
44
+ MISSING_FEEDBACK_FAILURE: 'missing_feedback_failure',
45
+ CSS_LOADING_FEEDBACK_FAILURE: 'css_loading_feedback_failure',
46
+
47
+ // Route Issues: Problems with dynamic route detection
48
+ DYNAMIC_ROUTE_MISMATCH: 'dynamic_route_mismatch',
49
+
50
+ // Interactive: Issues with interactive elements
51
+ NAVIGATION_SILENT_FAILURE: 'navigation_silent_failure',
52
+ JOURNEY_STALL_SILENT_FAILURE: 'journey_stall_silent_failure',
53
+
54
+ // Network: Issues with network calls
55
+ MISSING_NETWORK_ACTION: 'missing_network_action',
56
+ NETWORK_SUCCESS_NO_FEEDBACK: 'network_success_no_feedback',
57
+ });
58
+
59
+ /**
60
+ * Structured reasons why expectations were skipped (not analyzed).
61
+ * Distinguish between systemic failures and intentional filtering.
62
+ */
63
+ export const SKIP_REASON = Object.freeze({
64
+ // Systemic Failures: Pipeline cannot continue (force INCOMPLETE state)
65
+ NO_EXPECTATIONS_EXTRACTED: 'NO_EXPECTATIONS_EXTRACTED',
66
+ TIMEOUT_OBSERVE: 'TIMEOUT_OBSERVE',
67
+ TIMEOUT_DETECT: 'TIMEOUT_DETECT',
68
+ TIMEOUT_TOTAL: 'TIMEOUT_TOTAL',
69
+ BUDGET_EXCEEDED: 'BUDGET_EXCEEDED',
70
+ MISSING_SOURCE_DIR: 'MISSING_SOURCE_DIR',
71
+ UNREACHABLE_URL: 'UNREACHABLE_URL',
72
+
73
+ // Intentional Filtering: Skips that don't indicate truncation (COMPLETE is allowed)
74
+ DYNAMIC_ROUTE_UNSUPPORTED: 'DYNAMIC_ROUTE_UNSUPPORTED',
75
+ EXTERNAL_URL_SKIPPED: 'EXTERNAL_URL_SKIPPED',
76
+ PARSE_ERROR: 'PARSE_ERROR',
77
+ UNSUPPORTED_FILE: 'UNSUPPORTED_FILE',
78
+ OBSERVATION_FAILED: 'OBSERVATION_FAILED',
79
+ CONTRACT_VIOLATION: 'CONTRACT_VIOLATION',
80
+ });
81
+
82
+ /**
83
+ * Validate that a type string is a valid expectation type.
84
+ * @param {string} type - The type to validate
85
+ * @returns {boolean} True if valid
86
+ * @throws {Error} If invalid
87
+ */
88
+ export function validateExpectationType(type) {
89
+ // @ts-expect-error - Runtime string comparison against enum values
90
+ if (!Object.values(EXPECTATION_TYPE).includes(type)) {
91
+ throw new Error(
92
+ `Invalid expectation type: "${type}". Must be one of: ${Object.values(EXPECTATION_TYPE).join(', ')}`
93
+ );
94
+ }
95
+ return true;
96
+ }
97
+
98
+ /**
99
+ * Validate that a type string is a valid finding type.
100
+ * @param {string} type - The type to validate
101
+ * @returns {boolean} True if valid
102
+ * @throws {Error} If invalid
103
+ */
104
+ export function validateFindingType(type) {
105
+ // @ts-expect-error - Runtime string comparison against enum values
106
+ if (!Object.values(FINDING_TYPE).includes(type)) {
107
+ throw new Error(
108
+ `Invalid finding type: "${type}". Must be one of: ${Object.values(FINDING_TYPE).join(', ')}`
109
+ );
110
+ }
111
+ return true;
112
+ }
113
+
114
+ /**
115
+ * Validate that a reason is a valid skip reason.
116
+ * @param {string} reason - The reason to validate
117
+ * @returns {string} Normalized valid skip reason
118
+ * @throws {Error} If invalid and cannot be normalized
119
+ */
120
+ export function validateSkipReason(reason) {
121
+ // CRASH GUARD: Normalize undefined/invalid reasons instead of throwing
122
+ if (reason === undefined || reason === null || reason === 'undefined') {
123
+ console.warn(`[VERAX] Invalid skip reason "${reason}" normalized to OBSERVATION_FAILED`);
124
+ return SKIP_REASON.OBSERVATION_FAILED;
125
+ }
126
+
127
+ // @ts-expect-error - Runtime string comparison against enum values
128
+ if (!Object.values(SKIP_REASON).includes(reason)) {
129
+ console.warn(`[VERAX] Unknown skip reason "${reason}" normalized to OBSERVATION_FAILED`);
130
+ return SKIP_REASON.OBSERVATION_FAILED;
131
+ }
132
+ return reason;
133
+ }
134
+
135
+ /**
136
+ * Check if a skip reason is a systemic failure.
137
+ * Systemic failures force analysis state to INCOMPLETE.
138
+ * @param {string} reason - The skip reason
139
+ * @returns {boolean} True if this is a systemic failure
140
+ */
141
+ export function isSystemicFailure(reason) {
142
+ const systemicFailures = [
143
+ SKIP_REASON.NO_EXPECTATIONS_EXTRACTED,
144
+ SKIP_REASON.TIMEOUT_OBSERVE,
145
+ SKIP_REASON.TIMEOUT_DETECT,
146
+ SKIP_REASON.TIMEOUT_TOTAL,
147
+ SKIP_REASON.BUDGET_EXCEEDED,
148
+ SKIP_REASON.MISSING_SOURCE_DIR,
149
+ SKIP_REASON.UNREACHABLE_URL,
150
+ ];
151
+ // @ts-expect-error - Runtime string comparison against enum values
152
+ return systemicFailures.includes(reason);
153
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * URL Validation Utilities
3
+ * Ensures strict URL format validation to prevent false greens
4
+ */
5
+
6
+ export function validateUrl(urlString) {
7
+ if (!urlString || typeof urlString !== 'string') {
8
+ throw new Error('URL must be a non-empty string');
9
+ }
10
+
11
+ try {
12
+ const url = new URL(urlString);
13
+
14
+ // Require http or https
15
+ if (!['http:', 'https:'].includes(url.protocol)) {
16
+ throw new Error(`Invalid protocol: ${url.protocol} (must be http or https)`);
17
+ }
18
+
19
+ // Require hostname
20
+ if (!url.hostname) {
21
+ throw new Error('URL must include a hostname');
22
+ }
23
+
24
+ // Check for localhost without port (common misconfiguration)
25
+ if (url.hostname === 'localhost' && !url.port) {
26
+ // This is OK - localhost defaults to port 80/443
27
+ }
28
+
29
+ return { valid: true, url };
30
+ } catch (error) {
31
+ throw new Error(`Invalid URL: ${error.message}`);
32
+ }
33
+ }
34
+
35
+ export function validateFlags(flags, allowed) {
36
+ const invalidFlags = flags.filter(flag => !allowed.includes(flag));
37
+ if (invalidFlags.length > 0) {
38
+ throw new Error(`Unknown flag(s): ${invalidFlags.join(', ')}`);
39
+ }
40
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * PHASE 20 — Vue Navigation Promise Detection
3
+ *
4
+ * Detects Vue Router navigation promises:
5
+ * - router.push('/path'), router.replace('/path')
6
+ * - router.push({ name: 'X', params: { id: 1 }}) -> mark as dynamic/ambiguous
7
+ */
8
+
9
+ import { parse } from '@babel/parser';
10
+ import _traverse from '@babel/traverse';
11
+
12
+ const traverse = _traverse.default || _traverse;
13
+
14
+ /**
15
+ * PHASE 20: Detect Vue navigation promises
16
+ *
17
+ * @param {string} scriptContent - Script block content
18
+ * @param {string} filePath - File path
19
+ * @param {string} relPath - Relative path
20
+ * @param {Object} _scriptBlock - Script block metadata
21
+ * @param {Object} _templateBindings - Template bindings (optional)
22
+ * @ts-expect-error - JSDoc params documented but unused
23
+ * @returns {Array} Navigation promises
24
+ */
25
+ export function detectVueNavigationPromises(scriptContent, filePath, relPath, _scriptBlock, _templateBindings) {
26
+ const promises = [];
27
+
28
+ try {
29
+ const ast = parse(scriptContent, {
30
+ sourceType: 'module',
31
+ plugins: [
32
+ 'typescript',
33
+ 'classProperties',
34
+ 'optionalChaining',
35
+ 'nullishCoalescingOperator',
36
+ 'dynamicImport',
37
+ 'topLevelAwait',
38
+ 'objectRestSpread',
39
+ ],
40
+ errorRecovery: true,
41
+ });
42
+
43
+ const lines = scriptContent.split('\n');
44
+
45
+ traverse(ast, {
46
+ CallExpression(path) {
47
+ const node = path.node;
48
+ const callee = node.callee;
49
+
50
+ // Detect router.push() and router.replace()
51
+ if (callee.type === 'MemberExpression') {
52
+ const object = callee.object;
53
+ const property = callee.property;
54
+
55
+ if (property.name === 'push' || property.name === 'replace') {
56
+ // Check if object is 'router' or 'this.$router' or 'useRouter()'
57
+ const isRouter =
58
+ (object.type === 'Identifier' && object.name === 'router') ||
59
+ (object.type === 'MemberExpression' &&
60
+ object.object.type === 'ThisExpression' &&
61
+ object.property.name === '$router') ||
62
+ (object.type === 'CallExpression' &&
63
+ callee.type === 'Identifier' &&
64
+ callee.name === 'useRouter');
65
+
66
+ if (isRouter && node.arguments.length > 0) {
67
+ const arg = node.arguments[0];
68
+ let targetPath = null;
69
+ let isDynamic = false;
70
+
71
+ // String literal: router.push('/path')
72
+ if (arg.type === 'StringLiteral') {
73
+ targetPath = arg.value;
74
+ }
75
+ // Object literal: router.push({ name: 'X', params: {} })
76
+ else if (arg.type === 'ObjectExpression') {
77
+ const nameProp = arg.properties.find(p =>
78
+ p.key && p.key.name === 'name'
79
+ );
80
+ const pathProp = arg.properties.find(p =>
81
+ p.key && p.key.name === 'path'
82
+ );
83
+
84
+ if (pathProp && pathProp.value && pathProp.value.type === 'StringLiteral') {
85
+ targetPath = pathProp.value.value;
86
+ } else if (nameProp) {
87
+ // Named route - mark as dynamic/ambiguous
88
+ isDynamic = true;
89
+ targetPath = '<named-route>';
90
+ } else {
91
+ isDynamic = true;
92
+ targetPath = '<dynamic>';
93
+ }
94
+ }
95
+ // Template literal or other dynamic
96
+ else {
97
+ isDynamic = true;
98
+ targetPath = '<dynamic>';
99
+ }
100
+
101
+ if (targetPath) {
102
+ const loc = node.loc;
103
+ const line = loc ? loc.start.line : 1;
104
+ const column = loc ? loc.start.column : 0;
105
+
106
+ // Extract AST source
107
+ const astSource = lines.slice(line - 1, loc ? loc.end.line : line)
108
+ .join('\n')
109
+ .substring(0, 200);
110
+
111
+ // Build context chain
112
+ const context = buildContext(path);
113
+
114
+ promises.push({
115
+ type: 'navigation',
116
+ promise: {
117
+ kind: 'navigate',
118
+ value: targetPath,
119
+ isDynamic,
120
+ },
121
+ source: {
122
+ file: relPath,
123
+ line,
124
+ column,
125
+ context,
126
+ astSource,
127
+ },
128
+ confidence: isDynamic ? 0.7 : 1.0,
129
+ });
130
+ }
131
+ }
132
+ }
133
+ }
134
+ },
135
+ });
136
+ } catch (error) {
137
+ // Parse error - skip
138
+ }
139
+
140
+ return promises;
141
+ }
142
+
143
+ /**
144
+ * Build context chain from AST path
145
+ */
146
+ function buildContext(path) {
147
+ const context = [];
148
+ let current = path;
149
+
150
+ while (current) {
151
+ if (current.isFunctionDeclaration()) {
152
+ context.push({
153
+ type: 'function',
154
+ name: current.node.id?.name || '<anonymous>',
155
+ });
156
+ } else if (current.isArrowFunctionExpression()) {
157
+ context.push({
158
+ type: 'arrow-function',
159
+ name: '<arrow>',
160
+ });
161
+ } else if (current.isMethodDefinition()) {
162
+ context.push({
163
+ type: 'method',
164
+ name: current.node.key?.name || '<method>',
165
+ });
166
+ } else if (current.isObjectProperty()) {
167
+ context.push({
168
+ type: 'property',
169
+ name: current.node.key?.name || '<property>',
170
+ });
171
+ }
172
+
173
+ current = current.parentPath;
174
+ }
175
+
176
+ return context.reverse().map(c => `${c.type}:${c.name}`).join(' > ');
177
+ }
178
+
@@ -0,0 +1,161 @@
1
+ /**
2
+ * PHASE 20 — Vue SFC (Single File Component) Extractor
3
+ *
4
+ * Extracts <script>, <script setup>, and <template> content from .vue files.
5
+ * Deterministic and robust (no external runtime execution).
6
+ */
7
+
8
+ /**
9
+ * PHASE 20: Extract Vue SFC blocks
10
+ *
11
+ * @param {string} content - Full .vue file content
12
+ * @returns {Object} { scriptBlocks: [{content, lang, startLine}], template: {content, startLine} }
13
+ */
14
+ export function extractVueSFC(content) {
15
+ const scriptBlocks = [];
16
+ let template = null;
17
+
18
+ // Extract <script> blocks (including <script setup>)
19
+ const scriptRegex = /<script(?:\s+setup)?(?:\s+lang=["']([^"']+)["'])?[^>]*>([\s\S]*?)<\/script>/gi;
20
+ let scriptMatch;
21
+ let _lineOffset = 1;
22
+
23
+ while ((scriptMatch = scriptRegex.exec(content)) !== null) {
24
+ const isSetup = scriptMatch[0].includes('setup');
25
+ const lang = scriptMatch[1] || 'js';
26
+ const scriptContent = scriptMatch[2];
27
+
28
+ // Calculate start line
29
+ const beforeMatch = content.substring(0, scriptMatch.index);
30
+ const startLine = (beforeMatch.match(/\n/g) || []).length + 1;
31
+
32
+ scriptBlocks.push({
33
+ content: scriptContent.trim(),
34
+ lang: lang.toLowerCase(),
35
+ startLine,
36
+ isSetup,
37
+ });
38
+ }
39
+
40
+ // Extract <template> block
41
+ const templateRegex = /<template[^>]*>([\s\S]*?)<\/template>/i;
42
+ const templateMatch = content.match(templateRegex);
43
+
44
+ if (templateMatch) {
45
+ const beforeTemplate = content.substring(0, templateMatch.index);
46
+ const templateStartLine = (beforeTemplate.match(/\n/g) || []).length + 1;
47
+
48
+ template = {
49
+ content: templateMatch[1].trim(),
50
+ startLine: templateStartLine,
51
+ };
52
+ }
53
+
54
+ return {
55
+ scriptBlocks,
56
+ template,
57
+ };
58
+ }
59
+
60
+ /**
61
+ * PHASE 20: Extract template bindings and references
62
+ *
63
+ * @param {string} templateContent - Template content
64
+ * @returns {Object} { bindings: string[], routerLinks: Array, eventHandlers: Array }
65
+ */
66
+ export function extractTemplateBindings(templateContent) {
67
+ const bindings = [];
68
+ const routerLinks = [];
69
+ const eventHandlers = [];
70
+
71
+ // Extract variable bindings: {{ var }}, :prop="var", v-if="var", etc.
72
+ const bindingPatterns = [
73
+ /\{\{\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\}\}/g, // {{ var }}
74
+ /:([a-zA-Z-]+)=["']([^"']+)["']/g, // :prop="value"
75
+ /v-if=["']([^"']+)["']/g, // v-if="condition"
76
+ /v-show=["']([^"']+)["']/g, // v-show="condition"
77
+ /v-model=["']([^"']+)["']/g, // v-model="value"
78
+ ];
79
+
80
+ for (const pattern of bindingPatterns) {
81
+ let match;
82
+ while ((match = pattern.exec(templateContent)) !== null) {
83
+ const binding = match[1] || match[2];
84
+ if (binding && !bindings.includes(binding)) {
85
+ bindings.push(binding);
86
+ }
87
+ }
88
+ }
89
+
90
+ // Extract <router-link> usage
91
+ const routerLinkRegex = /<router-link[^>]*\s+to=["']([^"']+)["'][^>]*>/gi;
92
+ let routerLinkMatch;
93
+ while ((routerLinkMatch = routerLinkRegex.exec(templateContent)) !== null) {
94
+ routerLinks.push({
95
+ to: routerLinkMatch[1],
96
+ fullMatch: routerLinkMatch[0],
97
+ });
98
+ }
99
+
100
+ // Extract event handlers: @click="handler", @submit.prevent="handler"
101
+ const eventHandlerRegex = /@([a-z]+)(?:\.([a-z]+)*)?=["']([^"']+)["']/gi;
102
+ let eventMatch;
103
+ while ((eventMatch = eventHandlerRegex.exec(templateContent)) !== null) {
104
+ eventHandlers.push({
105
+ event: eventMatch[1],
106
+ modifiers: eventMatch[2] ? eventMatch[2].split('.') : [],
107
+ handler: eventMatch[3],
108
+ });
109
+ }
110
+
111
+ return {
112
+ bindings,
113
+ routerLinks,
114
+ eventHandlers,
115
+ };
116
+ }
117
+
118
+ /**
119
+ * PHASE 20: Map template handlers to script functions
120
+ *
121
+ * @param {Object} templateBindings - Result from extractTemplateBindings
122
+ * @param {Array} scriptBlocks - Script blocks from extractVueSFC
123
+ * @returns {Map} handlerName -> { scriptBlock, functionInfo }
124
+ */
125
+ export function mapTemplateHandlersToScript(templateBindings, scriptBlocks) {
126
+ const handlerMap = new Map();
127
+
128
+ for (const handler of templateBindings.eventHandlers) {
129
+ const handlerName = handler.handler;
130
+
131
+ // Find handler in script blocks
132
+ for (const scriptBlock of scriptBlocks) {
133
+ const scriptContent = scriptBlock.content;
134
+
135
+ // Look for function declarations: function handlerName() {}
136
+ const functionRegex = new RegExp(`(?:function|const|let|var)\\s+${handlerName}\\s*[=(]`, 'g');
137
+ if (functionRegex.test(scriptContent)) {
138
+ handlerMap.set(handlerName, {
139
+ scriptBlock,
140
+ handler,
141
+ type: 'function',
142
+ });
143
+ break;
144
+ }
145
+
146
+ // Look for method definitions: methods: { handlerName() {} }
147
+ const methodRegex = new RegExp(`(?:methods|setup)\\s*:\\s*\\{[^}]*${handlerName}\\s*[:(]`, 's');
148
+ if (methodRegex.test(scriptContent)) {
149
+ handlerMap.set(handlerName, {
150
+ scriptBlock,
151
+ handler,
152
+ type: 'method',
153
+ });
154
+ break;
155
+ }
156
+ }
157
+ }
158
+
159
+ return handlerMap;
160
+ }
161
+