@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
@@ -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
+ }
@@ -17,11 +17,12 @@ const traverse = _traverse.default || _traverse;
17
17
  * @param {string} scriptContent - Script block content
18
18
  * @param {string} filePath - File path
19
19
  * @param {string} relPath - Relative path
20
- * @param {Object} scriptBlock - Script block metadata
21
- * @param {Object} templateBindings - Template bindings (optional)
20
+ * @param {Object} _scriptBlock - Script block metadata
21
+ * @param {Object} _templateBindings - Template bindings (optional)
22
+ * @ts-expect-error - JSDoc params documented but unused
22
23
  * @returns {Array} Navigation promises
23
24
  */
24
- export function detectVueNavigationPromises(scriptContent, filePath, relPath, scriptBlock, templateBindings) {
25
+ export function detectVueNavigationPromises(scriptContent, filePath, relPath, _scriptBlock, _templateBindings) {
25
26
  const promises = [];
26
27
 
27
28
  try {
@@ -9,7 +9,6 @@
9
9
  * PHASE 20: Extract Vue SFC blocks
10
10
  *
11
11
  * @param {string} content - Full .vue file content
12
- * @param {string} filePath - Path to the .vue file (for context)
13
12
  * @returns {Object} { scriptBlocks: [{content, lang, startLine}], template: {content, startLine} }
14
13
  */
15
14
  export function extractVueSFC(content) {
@@ -19,7 +18,7 @@ export function extractVueSFC(content) {
19
18
  // Extract <script> blocks (including <script setup>)
20
19
  const scriptRegex = /<script(?:\s+setup)?(?:\s+lang=["']([^"']+)["'])?[^>]*>([\s\S]*?)<\/script>/gi;
21
20
  let scriptMatch;
22
- let lineOffset = 1;
21
+ let _lineOffset = 1;
23
22
 
24
23
  while ((scriptMatch = scriptRegex.exec(content)) !== null) {
25
24
  const isSetup = scriptMatch[0].includes('setup');
@@ -108,7 +108,7 @@ export function detectVueStatePromises(scriptContent, filePath, relPath, scriptB
108
108
  const varName = left.object.name;
109
109
 
110
110
  if (refDeclarations.has(varName) && templateVars.has(varName)) {
111
- const decl = refDeclarations.get(varName);
111
+ const _decl = refDeclarations.get(varName);
112
112
  const loc = node.loc;
113
113
  const line = loc ? loc.start.line : 1;
114
114
  const column = loc ? loc.start.column : 0;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Type augmentation for Node.js fs module
3
+ * Provides more precise return types for readFileSync with encoding
4
+ */
5
+
6
+ declare module 'fs' {
7
+ import { PathLike } from 'fs';
8
+
9
+ /**
10
+ * Augment readFileSync to return string when encoding is specified
11
+ */
12
+ export function readFileSync(
13
+ path: PathLike | number,
14
+ options?: { encoding?: null; flag?: string } | null
15
+ ): Buffer;
16
+
17
+ export function readFileSync(
18
+ path: PathLike | number,
19
+ options: { encoding: BufferEncoding; flag?: string } | BufferEncoding
20
+ ): string;
21
+ }
22
+
23
+ export {};
@@ -24,5 +24,142 @@ import type { Page as PlaywrightPage } from 'playwright';
24
24
  // Re-export for use in JS files
25
25
  export type Page = PlaywrightPage;
26
26
 
27
+ // Node.js built-in module declarations
28
+ declare module 'fs' {
29
+ export interface Stats {
30
+ isDirectory(): boolean;
31
+ isFile(): boolean;
32
+ size: number;
33
+ mtime: Date;
34
+ }
35
+
36
+ export interface WriteFileOptions {
37
+ encoding?: BufferEncoding;
38
+ mode?: number;
39
+ flag?: string;
40
+ }
41
+
42
+ export interface ReadFileOptions {
43
+ encoding?: BufferEncoding | string;
44
+ flag?: string;
45
+ }
46
+
47
+ export interface RmOptions {
48
+ force?: boolean;
49
+ recursive?: boolean;
50
+ maxRetries?: number;
51
+ }
52
+
53
+ export interface MkdirOptions {
54
+ recursive?: boolean;
55
+ mode?: number;
56
+ }
57
+
58
+ export interface ReaddirOptions {
59
+ encoding?: BufferEncoding | string;
60
+ withFileTypes?: boolean;
61
+ }
62
+
63
+ export function readFileSync(path: string | Buffer, encoding?: string | ReadFileOptions): string | Buffer;
64
+ export function writeFileSync(path: string, data: string | Buffer, options?: WriteFileOptions | string): void;
65
+ export function existsSync(path: string | Buffer): boolean;
66
+ export function mkdirSync(path: string, options?: MkdirOptions): string | undefined;
67
+ export function rmSync(path: string, options?: RmOptions): void;
68
+ export function readdirSync(path: string, options?: ReaddirOptions | string): string[] | any[];
69
+ export function statSync(path: string): Stats;
70
+ export function renameSync(oldPath: string, newPath: string): void;
71
+ export function unlinkSync(path: string): void;
72
+ export function mkdtempSync(prefix: string): string;
73
+ export function appendFileSync(path: string, data: string | Buffer, options?: WriteFileOptions | string): void;
74
+ export function createWriteStream(path: string, options?: any): any;
75
+ }
76
+
77
+ declare module 'path' {
78
+ export function resolve(...paths: string[]): string;
79
+ export function join(...paths: string[]): string;
80
+ export function dirname(p: string): string;
81
+ export function basename(p: string, ext?: string): string;
82
+ export function extname(p: string): string;
83
+ export function relative(from: string, to: string): string;
84
+ export function normalize(p: string): string;
85
+ export const sep: string;
86
+ export const delimiter: string;
87
+ export const posix: {
88
+ resolve: typeof resolve;
89
+ join: typeof join;
90
+ dirname: typeof dirname;
91
+ basename: typeof basename;
92
+ };
93
+ export const win32: {
94
+ resolve: typeof resolve;
95
+ join: typeof join;
96
+ dirname: typeof dirname;
97
+ basename: typeof basename;
98
+ };
99
+ }
100
+
101
+ declare module 'crypto' {
102
+ export interface Hash {
103
+ update(data: string | Buffer, encoding?: string): Hash;
104
+ digest(encoding?: string): string | Buffer;
105
+ }
106
+
107
+ export function createHash(algorithm: string): Hash;
108
+ export function randomBytes(size: number): Buffer;
109
+ export function createHmac(algorithm: string, key: string | Buffer): {
110
+ update(data: string | Buffer, encoding?: string): any;
111
+ digest(encoding?: string): string;
112
+ };
113
+ }
114
+
115
+ declare module 'os' {
116
+ export function tmpdir(): string;
117
+ export function cwd(): string;
118
+ }
119
+
120
+ declare module 'url' {
121
+ export function fileURLToPath(url: string | URL): string;
122
+ export class URL {
123
+ constructor(input: string, base?: string | URL);
124
+ href: string;
125
+ protocol: string;
126
+ host: string;
127
+ pathname: string;
128
+ }
129
+ }
130
+
131
+ declare module 'http' {
132
+ export interface Server {
133
+ close(callback?: () => void): void;
134
+ listen(port?: number, host?: string, callback?: () => void): void;
135
+ }
136
+
137
+ export function createServer(requestListener?: (req: any, res: any) => void): Server;
138
+ }
139
+
140
+ declare global {
141
+ class Buffer {
142
+ static from(data: string, encoding?: string): Buffer;
143
+ static isBuffer(obj: any): obj is Buffer;
144
+ toString(encoding?: string): string;
145
+ }
146
+
147
+ namespace NodeJS {
148
+ interface ProcessEnv {
149
+ [key: string]: string | undefined;
150
+ }
151
+
152
+ interface Process {
153
+ env: ProcessEnv;
154
+ argv: string[];
155
+ cwd(): string;
156
+ exit(code?: number): never;
157
+ on(event: string, listener: (...args: any[]) => void): void;
158
+ }
159
+ }
160
+
161
+ const process: NodeJS.Process;
162
+ }
163
+
27
164
  export {};
28
165
 
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Internal type definitions for VERAX
3
+ * Minimal types to satisfy TypeScript checking
4
+ *
5
+ * @typedef {Object} ObserveContext
6
+ * @property {string} url
7
+ * @property {string} projectDir
8
+ * @property {string} runId
9
+ * @property {SilenceTracker} silenceTracker
10
+ * @property {*} decisionRecorder
11
+ * @property {*} scanBudget
12
+ *
13
+ * @typedef {Object} RunState
14
+ * @property {number} totalInteractions
15
+ * @property {number} totalPages
16
+ * @property {number} startTime
17
+ *
18
+ * @typedef {Object} SilenceTracker
19
+ * @property {function(*): void} record
20
+ * @property {function(): *} getSilenceReport
21
+ *
22
+ * @typedef {Object} PageFrontier
23
+ * @property {function(string, *=): void} addPage
24
+ * @property {function(): *} getNextPage
25
+ * @property {function(): boolean} hasPages
26
+ *
27
+ * @typedef {Object} Observation
28
+ * @property {string} type
29
+ * @property {*} data
30
+ * @property {number=} timestamp
31
+ */
32
+
33
+ // Export as module
34
+ export {};
35
+
@@ -14,22 +14,11 @@
14
14
  export function formatFinding(finding, expectation = null) {
15
15
  const lines = [];
16
16
 
17
- // PHASE 15: Finding type and unified confidence
18
- const confidenceLevel = finding.confidenceLevel || finding.confidence?.level || 'UNPROVEN';
19
- const confidenceScore = finding.confidence !== undefined
20
- ? (finding.confidence <= 1 ? Math.round(finding.confidence * 100) : finding.confidence)
21
- : (finding.confidence?.score || 0);
22
- const confidenceReasons = finding.confidenceReasons || [];
23
-
17
+ // Finding type and confidence
18
+ const confidenceLevel = finding.confidence?.level || 'UNKNOWN';
19
+ const confidenceScore = finding.confidence?.score || 0;
24
20
  lines.push(` [${confidenceLevel} (${confidenceScore}%)] ${finding.type || 'unknown'}`);
25
21
 
26
- // PHASE 15: Show top confidence reasons
27
- if (confidenceReasons.length > 0) {
28
- const topReasons = confidenceReasons.slice(0, 3);
29
- const reasonText = topReasons.join(', ');
30
- lines.push(` └─ Confidence: ${reasonText}`);
31
- }
32
-
33
22
  // Expectation (what was promised)
34
23
  if (expectation) {
35
24
  let expectationDesc = '';
@@ -95,48 +84,6 @@ export function formatFinding(finding, expectation = null) {
95
84
  lines.push(` └─ Source: ${expectation.source.file}${sourceLine}`);
96
85
  }
97
86
 
98
- // PHASE 16: Show evidence completeness
99
- if (finding.evidencePackage) {
100
- if (finding.evidencePackage.isComplete) {
101
- lines.push(` └─ Evidence: Complete`);
102
- } else {
103
- lines.push(` └─ Evidence: Incomplete (missing: ${finding.evidencePackage.missingEvidence.join(', ')})`);
104
- }
105
- }
106
-
107
- // PHASE 16: Show downgrade reason if evidence was incomplete
108
- if (finding.evidenceCompleteness?.downgraded) {
109
- lines.push(` └─ Downgraded: ${finding.evidenceCompleteness.downgradeReason}`);
110
- }
111
-
112
- // PHASE 17/23: Show guardrails applied and reconciliation
113
- if (finding.guardrails) {
114
- const guardrails = finding.guardrails;
115
- if (guardrails.appliedRules && guardrails.appliedRules.length > 0) {
116
- const ruleCodes = guardrails.appliedRules.map(r => r.code || r.ruleId).filter(Boolean);
117
- if (ruleCodes.length > 0) {
118
- lines.push(` └─ Final status due to guardrails: ${ruleCodes.join(', ')}`);
119
- }
120
- }
121
- if (guardrails.contradictions && guardrails.contradictions.length > 0) {
122
- lines.push(` └─ Contradiction: ${guardrails.contradictions[0].message}`);
123
- }
124
-
125
- // PHASE 23: Show confidence reconciliation if present
126
- if (guardrails.reconciliation) {
127
- const recon = guardrails.reconciliation;
128
- if (recon.confidenceBefore !== recon.confidenceAfter) {
129
- const beforePercent = Math.round(recon.confidenceBefore * 100);
130
- const afterPercent = Math.round(recon.confidenceAfter * 100);
131
- lines.push(` └─ Confidence: ${beforePercent}% → ${afterPercent}% (${recon.confidenceLevelBefore} → ${recon.confidenceLevelAfter})`);
132
- }
133
- if (recon.reconciliationReasons && recon.reconciliationReasons.length > 0) {
134
- const topReasons = recon.reconciliationReasons.slice(0, 2);
135
- lines.push(` └─ Reconciliation: ${topReasons.join(', ')}`);
136
- }
137
- }
138
- }
139
-
140
87
  return lines.join('\n');
141
88
  }
142
89