@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,287 @@
1
+ import { writeFileSync, mkdirSync } from 'fs';
2
+ import { resolve } from 'path';
3
+ import { computeDOMDiff, hasFeedbackElements as _hasFeedbackElements, hasValidationErrors as _hasValidationErrors } from './dom-diff.js';
4
+
5
+ /**
6
+ * Evidence Engine
7
+ * Captures and bundles evidence for each interaction attempt
8
+ */
9
+
10
+ export class EvidenceBundle {
11
+ constructor(promiseId, expNum, evidencePath) {
12
+ this.promiseId = promiseId;
13
+ this.expNum = expNum;
14
+ this.evidencePath = evidencePath;
15
+ this.beforeScreenshot = null;
16
+ this.afterScreenshot = null;
17
+ this.beforeHTML = null;
18
+ this.afterHTML = null;
19
+ this.domDiff = null;
20
+ this.networkEvents = [];
21
+ this.networkEventsByTime = new Map(); // For correlation
22
+ this.consoleErrors = [];
23
+ this.actionStartTime = null; // When action was executed
24
+ this.timing = {
25
+ startedAt: null,
26
+ endedAt: null,
27
+ };
28
+ this.signals = {
29
+ navigationChanged: false,
30
+ domChanged: false,
31
+ feedbackSeen: false,
32
+ networkActivity: false,
33
+ consoleErrors: false,
34
+ meaningfulDomChange: false, // NEW: tracks only meaningful changes
35
+ correlatedNetworkActivity: false, // NEW: tracks network within action window
36
+ };
37
+ this.files = [];
38
+ this.correlatedRequests = []; // Network requests correlated to this action
39
+ }
40
+
41
+ /**
42
+ * Capture before state
43
+ */
44
+ async captureBeforeState(page, suffix = '') {
45
+ try {
46
+ this.beforeScreenshot = `exp_${this.expNum}_before${suffix}.png`;
47
+ const screenshotPath = resolve(this.evidencePath, this.beforeScreenshot);
48
+ await page.screenshot({ path: screenshotPath });
49
+ this.files.push(this.beforeScreenshot);
50
+ } catch (e) {
51
+ this.beforeScreenshot = null;
52
+ }
53
+
54
+ try {
55
+ this.beforeHTML = await page.content();
56
+ } catch (e) {
57
+ this.beforeHTML = null;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Capture after state
63
+ */
64
+ async captureAfterState(page, suffix = '') {
65
+ try {
66
+ this.afterScreenshot = `exp_${this.expNum}_after${suffix}.png`;
67
+ const screenshotPath = resolve(this.evidencePath, this.afterScreenshot);
68
+ await page.screenshot({ path: screenshotPath });
69
+ this.files.push(this.afterScreenshot);
70
+ } catch (e) {
71
+ this.afterScreenshot = null;
72
+ }
73
+
74
+ try {
75
+ this.afterHTML = await page.content();
76
+ } catch (e) {
77
+ this.afterHTML = null;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Analyze changes between before and after
83
+ */
84
+ analyzeChanges(urlBefore, urlAfter) {
85
+ // Navigation change
86
+ if (urlBefore && urlAfter && urlBefore !== urlAfter) {
87
+ this.signals.navigationChanged = true;
88
+ }
89
+
90
+ // DOM change
91
+ if (this.beforeHTML && this.afterHTML) {
92
+ this.domDiff = computeDOMDiff(this.beforeHTML, this.afterHTML);
93
+ if (this.domDiff.changed) {
94
+ this.signals.domChanged = true;
95
+ }
96
+
97
+ // Track meaningful changes separately
98
+ if (this.domDiff.isMeaningful) {
99
+ this.signals.meaningfulDomChange = true;
100
+ }
101
+
102
+ // Check for feedback elements visibility change
103
+ if (detectNewFeedback(this.beforeHTML, this.afterHTML)) {
104
+ this.signals.feedbackSeen = true;
105
+ }
106
+
107
+ // Check for validation error appearance
108
+ if (detectNewValidationError(this.beforeHTML, this.afterHTML)) {
109
+ this.signals.feedbackSeen = true;
110
+ }
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Record network events that occurred during this action
116
+ */
117
+ recordNetworkEvent(networkLog) {
118
+ this.networkEvents.push(networkLog);
119
+
120
+ // Track event time for correlation
121
+ if (networkLog.startTime) {
122
+ this.networkEventsByTime.set(networkLog.startTime, networkLog);
123
+ }
124
+
125
+ if (this.networkEvents.length > 0) {
126
+ this.signals.networkActivity = true;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Correlate network events to this action
132
+ * Captures requests that started within correlation window after action
133
+ * Window: 0-2500ms after action starts
134
+ */
135
+ correlateNetworkRequests() {
136
+ if (!this.actionStartTime || this.networkEvents.length === 0) {
137
+ return;
138
+ }
139
+
140
+ const actionTime = new Date(this.actionStartTime).getTime();
141
+ const correlationWindow = 2500; // milliseconds
142
+
143
+ for (const event of this.networkEvents) {
144
+ if (event.startTime) {
145
+ const eventTime = new Date(event.startTime).getTime();
146
+ if (eventTime >= actionTime && eventTime <= actionTime + correlationWindow) {
147
+ this.correlatedRequests.push(event);
148
+ this.signals.correlatedNetworkActivity = true;
149
+ }
150
+ }
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Record console errors
156
+ */
157
+ recordConsoleError(error) {
158
+ this.consoleErrors.push(error);
159
+ if (this.consoleErrors.length > 0) {
160
+ this.signals.consoleErrors = true;
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Finalize evidence bundle
166
+ */
167
+ finalize(_page) {
168
+ this.timing.endedAt = new Date().toISOString();
169
+
170
+ // Persist evidence files
171
+ try {
172
+ mkdirSync(this.evidencePath, { recursive: true });
173
+
174
+ // Save DOM diff if available
175
+ if (this.domDiff) {
176
+ const diffFile = `exp_${this.expNum}_dom_diff.json`;
177
+ writeFileSync(
178
+ resolve(this.evidencePath, diffFile),
179
+ JSON.stringify(this.domDiff, null, 2),
180
+ 'utf-8'
181
+ );
182
+ this.files.push(diffFile);
183
+ }
184
+
185
+ // Save network events if any
186
+ if (this.networkEvents.length > 0) {
187
+ const netFile = `exp_${this.expNum}_network.json`;
188
+ writeFileSync(
189
+ resolve(this.evidencePath, netFile),
190
+ JSON.stringify(this.networkEvents, null, 2),
191
+ 'utf-8'
192
+ );
193
+ this.files.push(netFile);
194
+ }
195
+
196
+ // Save console errors if any
197
+ if (this.consoleErrors.length > 0) {
198
+ const errFile = `exp_${this.expNum}_console_errors.json`;
199
+ writeFileSync(
200
+ resolve(this.evidencePath, errFile),
201
+ JSON.stringify(this.consoleErrors, null, 2),
202
+ 'utf-8'
203
+ );
204
+ this.files.push(errFile);
205
+ }
206
+ } catch (e) {
207
+ // Best effort
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Check if we have meaningful evidence
213
+ */
214
+ hasEvidence() {
215
+ return (
216
+ this.beforeScreenshot ||
217
+ this.afterScreenshot ||
218
+ this.domDiff?.changed ||
219
+ this.networkEvents.length > 0 ||
220
+ this.consoleErrors.length > 0 ||
221
+ Object.values(this.signals).some(v => v === true)
222
+ );
223
+ }
224
+
225
+ /**
226
+ * Summarize signals for observation
227
+ */
228
+ getSummary() {
229
+ return {
230
+ files: this.files,
231
+ timing: this.timing,
232
+ signals: this.signals,
233
+ hasEvidence: this.hasEvidence(),
234
+ };
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Create a bundle for a promise
240
+ */
241
+ export function createEvidenceBundle(promise, expNum, evidencePath) {
242
+ return new EvidenceBundle(promise.id, expNum, evidencePath);
243
+ }
244
+
245
+ /**
246
+ * Detect if new feedback elements appeared
247
+ */
248
+ function detectNewFeedback(htmlBefore, htmlAfter) {
249
+ const feedbackPatterns = [
250
+ 'role="alert"',
251
+ 'role="status"',
252
+ 'aria-live="polite"',
253
+ 'aria-live="assertive"',
254
+ 'class="toast"',
255
+ 'class="modal"',
256
+ 'class="dialog"',
257
+ ];
258
+
259
+ for (const pattern of feedbackPatterns) {
260
+ if (!htmlBefore.includes(pattern) && htmlAfter.includes(pattern)) {
261
+ return true;
262
+ }
263
+ }
264
+
265
+ return false;
266
+ }
267
+
268
+ /**
269
+ * Detect if new validation errors appeared
270
+ */
271
+ function detectNewValidationError(htmlBefore, htmlAfter) {
272
+ const errorPatterns = [
273
+ 'aria-invalid="true"',
274
+ 'aria-invalid=\'true\'',
275
+ 'class="error"',
276
+ 'class="invalid"',
277
+ '[data-error]',
278
+ ];
279
+
280
+ for (const pattern of errorPatterns) {
281
+ if (!htmlBefore.includes(pattern) && htmlAfter.includes(pattern)) {
282
+ return true;
283
+ }
284
+ }
285
+
286
+ return false;
287
+ }
@@ -1,10 +1,12 @@
1
1
  import { readdirSync, readFileSync, statSync } from 'fs';
2
2
  import { join, relative, resolve } from 'path';
3
3
  import { expIdFromHash, compareExpectations } from './idgen.js';
4
+ import { extractPromisesFromAST } from './ast-promise-extractor.js';
4
5
 
5
6
  /**
6
7
  * Static Expectation Extractor
7
- * Extracts explicit, static expectations from source files
8
+ * PHASE H2/M2: AST-based promise extraction
9
+ * Extracts explicit, static expectations from source files using AST parsing
8
10
  */
9
11
 
10
12
  export async function extractExpectations(projectProfile, _srcPath) {
@@ -61,7 +63,16 @@ function getScanPaths(projectProfile, sourceRoot) {
61
63
  }
62
64
 
63
65
  if (framework === 'react-vite' || framework === 'react-cra') {
64
- return [resolve(sourceRoot, 'src')];
66
+ const srcPath = resolve(sourceRoot, 'src');
67
+ try {
68
+ if (statSync(srcPath).isDirectory()) {
69
+ return [srcPath];
70
+ }
71
+ } catch (error) {
72
+ // src doesn't exist - scan root for React files
73
+ }
74
+ // Fallback: scan root directory for React files
75
+ return [sourceRoot];
65
76
  }
66
77
 
67
78
  if (framework === 'static-html') {
@@ -78,7 +89,7 @@ function getScanPaths(projectProfile, sourceRoot) {
78
89
  }
79
90
  }
80
91
 
81
- // Unknown framework - scan src if it exists
92
+ // Unknown framework - scan src if it exists, otherwise scan root
82
93
  const srcPath = resolve(sourceRoot, 'src');
83
94
  try {
84
95
  if (statSync(srcPath).isDirectory()) {
@@ -88,7 +99,8 @@ function getScanPaths(projectProfile, sourceRoot) {
88
99
  // src doesn't exist
89
100
  }
90
101
 
91
- return [];
102
+ // Fallback: scan root directory
103
+ return [sourceRoot];
92
104
  }
93
105
 
94
106
  /**
@@ -163,9 +175,15 @@ function scanFile(filePath, sourceRoot, skipped) {
163
175
  const relPath = relative(sourceRoot, filePath);
164
176
 
165
177
  if (filePath.endsWith('.html')) {
178
+ // HTML files: Use regex-based extraction (static HTML fallback)
166
179
  const htmlExpectations = extractHtmlExpectations(content, filePath, relPath);
167
180
  expectations.push(...htmlExpectations);
168
181
  } else {
182
+ // JS/JSX/TS/TSX files: Use AST-based extraction (PHASE H2)
183
+ const astPromises = extractPromisesFromAST(content, filePath, relPath);
184
+ expectations.push(...astPromises);
185
+
186
+ // Legacy regex-based extraction (preserved for navigation/network/state)
169
187
  const jsExpectations = extractJsExpectations(content, filePath, relPath, skipped);
170
188
  expectations.push(...jsExpectations);
171
189
  }
@@ -178,11 +196,12 @@ function scanFile(filePath, sourceRoot, skipped) {
178
196
 
179
197
  /**
180
198
  * Extract expectations from HTML files
199
+ * PHASE H2: Enhanced with button, form, and validation detection
181
200
  */
182
201
  function extractHtmlExpectations(content, filePath, relPath) {
183
202
  const expectations = [];
184
203
 
185
- // Extract <a href="/path"> links
204
+ // Extract <a href="/path"> links (navigation)
186
205
  const hrefRegex = /<a\s+[^>]*href=["']([^"']+)["']/gi;
187
206
  let match;
188
207
 
@@ -208,9 +227,136 @@ function extractHtmlExpectations(content, filePath, relPath) {
208
227
  }
209
228
  }
210
229
 
230
+ // Extract <button> elements (interaction promise)
231
+ const buttonRegex = /<button\s+[^>]*>/gi;
232
+ while ((match = buttonRegex.exec(content)) !== null) {
233
+ const lineNum = content.substring(0, match.index).split('\n').length;
234
+ const buttonText = extractTextFromTag(content, match.index, 'button');
235
+
236
+ expectations.push({
237
+ category: 'button',
238
+ type: 'interaction',
239
+ promise: {
240
+ kind: 'click',
241
+ value: buttonText || 'button click',
242
+ },
243
+ source: {
244
+ file: relPath,
245
+ line: lineNum,
246
+ column: match.index - content.lastIndexOf('\n', match.index),
247
+ },
248
+ selector: buttonText ? `button:contains("${buttonText}")` : 'button',
249
+ action: 'click',
250
+ expectedOutcome: 'ui-change',
251
+ confidenceHint: 'low',
252
+ });
253
+ }
254
+
255
+ // Extract <form> elements (submission promise)
256
+ const formRegex = /<form\s+[^>]*>/gi;
257
+ while ((match = formRegex.exec(content)) !== null) {
258
+ const lineNum = content.substring(0, match.index).split('\n').length;
259
+ const formTag = match[0];
260
+
261
+ // Check for action attribute
262
+ const actionMatch = /action=["']([^"']+)["']/.exec(formTag);
263
+ const hasAction = actionMatch && actionMatch[1];
264
+
265
+ expectations.push({
266
+ category: 'form',
267
+ type: 'interaction',
268
+ promise: {
269
+ kind: 'submit',
270
+ value: hasAction ? `form submit to ${actionMatch[1]}` : 'form submission',
271
+ },
272
+ source: {
273
+ file: relPath,
274
+ line: lineNum,
275
+ column: match.index - content.lastIndexOf('\n', match.index),
276
+ },
277
+ selector: hasAction ? `form[action="${actionMatch[1]}"]` : 'form',
278
+ action: 'submit',
279
+ expectedOutcome: hasAction ? 'navigation' : 'ui-change',
280
+ confidenceHint: hasAction ? 'medium' : 'low',
281
+ });
282
+ }
283
+
284
+ // Extract required input fields (validation promise)
285
+ const requiredRegex = /<input\s+[^>]*required[^>]*>/gi;
286
+ while ((match = requiredRegex.exec(content)) !== null) {
287
+ const lineNum = content.substring(0, match.index).split('\n').length;
288
+ const inputTag = match[0];
289
+
290
+ // Extract name or id for selector
291
+ const nameMatch = /name=["']([^"']+)["']/.exec(inputTag);
292
+ const idMatch = /id=["']([^"']+)["']/.exec(inputTag);
293
+ const selector = nameMatch ? `input[name="${nameMatch[1]}"]` :
294
+ idMatch ? `input#${idMatch[1]}` : 'input[required]';
295
+
296
+ expectations.push({
297
+ category: 'validation',
298
+ type: 'feedback',
299
+ promise: {
300
+ kind: 'validation',
301
+ value: 'required field validation',
302
+ },
303
+ source: {
304
+ file: relPath,
305
+ line: lineNum,
306
+ column: match.index - content.lastIndexOf('\n', match.index),
307
+ },
308
+ selector,
309
+ action: 'observe',
310
+ expectedOutcome: 'feedback',
311
+ confidenceHint: 'medium',
312
+ });
313
+ }
314
+
315
+ // Extract aria-live regions (feedback promise)
316
+ const ariaLiveRegex = /<[^>]+aria-live=["']([^"']+)["'][^>]*>/gi;
317
+ while ((match = ariaLiveRegex.exec(content)) !== null) {
318
+ const lineNum = content.substring(0, match.index).split('\n').length;
319
+
320
+ expectations.push({
321
+ category: 'feedback',
322
+ type: 'feedback',
323
+ promise: {
324
+ kind: 'ui-feedback',
325
+ value: 'live region update',
326
+ },
327
+ source: {
328
+ file: relPath,
329
+ line: lineNum,
330
+ column: match.index - content.lastIndexOf('\n', match.index),
331
+ },
332
+ selector: '[aria-live]',
333
+ action: 'observe',
334
+ expectedOutcome: 'feedback',
335
+ confidenceHint: 'medium',
336
+ });
337
+ }
338
+
211
339
  return expectations;
212
340
  }
213
341
 
342
+ /**
343
+ * Extract text content from HTML tag
344
+ */
345
+ function extractTextFromTag(content, startIndex, tagName) {
346
+ const closeTagRegex = new RegExp(`</${tagName}>`, 'i');
347
+ const closeMatch = closeTagRegex.exec(content.substring(startIndex));
348
+
349
+ if (!closeMatch) return '';
350
+
351
+ const endOfOpenTag = content.indexOf('>', startIndex);
352
+ if (endOfOpenTag === -1) return '';
353
+
354
+ const textContent = content.substring(endOfOpenTag + 1, startIndex + closeMatch.index);
355
+
356
+ // Strip HTML tags and trim
357
+ return textContent.replace(/<[^>]+>/g, '').trim();
358
+ }
359
+
214
360
  /**
215
361
  * Extract expectations from JavaScript/TypeScript files
216
362
  */
@@ -1,6 +1,7 @@
1
1
  import { atomicWriteJson } from './atomic-write.js';
2
2
  import { resolve } from 'path';
3
3
  import { findingIdFromExpectationId } from './idgen.js';
4
+ import { ARTIFACT_REGISTRY } from '../../verax/core/artifacts/registry.js';
4
5
 
5
6
  /**
6
7
  * Write findings.json artifact with deterministic IDs
@@ -15,6 +16,7 @@ export function writeFindingsJson(runDir, findingsData) {
15
16
  }));
16
17
 
17
18
  const payload = {
19
+ contractVersion: ARTIFACT_REGISTRY.findings.contractVersion,
18
20
  findings: findingsWithIds,
19
21
  total: findingsData.stats?.total || 0,
20
22
  stats: {
@@ -26,6 +28,7 @@ export function writeFindingsJson(runDir, findingsData) {
26
28
  informational: findingsData.stats?.informational || 0,
27
29
  },
28
30
  detectedAt: findingsData.detectedAt || new Date().toISOString(),
31
+ enforcement: findingsData.enforcement || null,
29
32
  };
30
33
 
31
34
  atomicWriteJson(findingsPath, payload);