@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.
- package/README.md +10 -6
- package/bin/verax.js +11 -11
- package/package.json +29 -8
- package/src/cli/commands/baseline.js +103 -0
- package/src/cli/commands/default.js +51 -6
- package/src/cli/commands/doctor.js +29 -0
- package/src/cli/commands/ga.js +246 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +4 -2
- package/src/cli/commands/release-check.js +215 -0
- package/src/cli/commands/run.js +45 -6
- package/src/cli/commands/security-check.js +212 -0
- package/src/cli/commands/truth.js +113 -0
- package/src/cli/entry.js +30 -20
- package/src/cli/util/angular-component-extractor.js +179 -0
- package/src/cli/util/angular-navigation-detector.js +141 -0
- package/src/cli/util/angular-network-detector.js +161 -0
- package/src/cli/util/angular-state-detector.js +162 -0
- package/src/cli/util/ast-interactive-detector.js +544 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-promise-extractor.js +581 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/atomic-write.js +12 -1
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/console-reporter.js +72 -0
- package/src/cli/util/detection-engine.js +105 -41
- package/src/cli/util/determinism-runner.js +124 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/digest-engine.js +359 -0
- package/src/cli/util/dom-diff.js +226 -0
- package/src/cli/util/evidence-engine.js +287 -0
- package/src/cli/util/expectation-extractor.js +151 -5
- package/src/cli/util/findings-writer.js +3 -0
- package/src/cli/util/framework-detector.js +572 -0
- package/src/cli/util/idgen.js +1 -1
- package/src/cli/util/interaction-planner.js +529 -0
- package/src/cli/util/learn-writer.js +2 -0
- package/src/cli/util/ledger-writer.js +110 -0
- package/src/cli/util/monorepo-resolver.js +162 -0
- package/src/cli/util/observation-engine.js +127 -278
- package/src/cli/util/observe-writer.js +2 -0
- package/src/cli/util/project-discovery.js +284 -0
- package/src/cli/util/project-writer.js +2 -0
- package/src/cli/util/run-id.js +23 -27
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/run-result.js +778 -0
- package/src/cli/util/selector-resolver.js +235 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +2 -0
- package/src/cli/util/svelte-navigation-detector.js +163 -0
- package/src/cli/util/svelte-network-detector.js +80 -0
- package/src/cli/util/svelte-sfc-extractor.js +146 -0
- package/src/cli/util/svelte-state-detector.js +242 -0
- package/src/cli/util/trust-activation-integration.js +496 -0
- package/src/cli/util/trust-activation-wrapper.js +85 -0
- package/src/cli/util/trust-integration-hooks.js +164 -0
- package/src/cli/util/types.js +153 -0
- package/src/cli/util/url-validation.js +40 -0
- package/src/cli/util/vue-navigation-detector.js +178 -0
- package/src/cli/util/vue-sfc-extractor.js +161 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/types/fs-augment.d.ts +23 -0
- package/src/types/global.d.ts +137 -0
- package/src/types/internal-types.d.ts +35 -0
- package/src/verax/cli/init.js +4 -18
- package/src/verax/core/action-classifier.js +4 -3
- package/src/verax/core/artifacts/registry.js +139 -0
- package/src/verax/core/artifacts/verifier.js +990 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +233 -0
- package/src/verax/core/capabilities/gates.js +505 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +144 -0
- package/src/verax/core/confidence/confidence-invariants.js +234 -0
- package/src/verax/core/confidence/confidence-report-writer.js +112 -0
- package/src/verax/core/confidence/confidence-weights.js +44 -0
- package/src/verax/core/confidence/confidence.defaults.js +65 -0
- package/src/verax/core/confidence/confidence.loader.js +80 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +489 -0
- package/src/verax/core/confidence-engine.js +625 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +186 -0
- package/src/verax/core/contracts/validators.js +456 -0
- package/src/verax/core/decisions/decision.trace.js +278 -0
- package/src/verax/core/determinism/contract-writer.js +89 -0
- package/src/verax/core/determinism/contract.js +139 -0
- package/src/verax/core/determinism/diff.js +405 -0
- package/src/verax/core/determinism/engine.js +222 -0
- package/src/verax/core/determinism/finding-identity.js +149 -0
- package/src/verax/core/determinism/normalize.js +466 -0
- package/src/verax/core/determinism/report-writer.js +93 -0
- package/src/verax/core/determinism/run-fingerprint.js +123 -0
- package/src/verax/core/dynamic-route-intelligence.js +529 -0
- package/src/verax/core/evidence/evidence-capture-service.js +308 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +166 -0
- package/src/verax/core/evidence-builder.js +487 -0
- package/src/verax/core/execution-mode-context.js +77 -0
- package/src/verax/core/execution-mode-detector.js +192 -0
- package/src/verax/core/failures/exit-codes.js +88 -0
- package/src/verax/core/failures/failure-summary.js +76 -0
- package/src/verax/core/failures/failure.factory.js +225 -0
- package/src/verax/core/failures/failure.ledger.js +133 -0
- package/src/verax/core/failures/failure.types.js +196 -0
- package/src/verax/core/failures/index.js +10 -0
- package/src/verax/core/ga/ga-report-writer.js +43 -0
- package/src/verax/core/ga/ga.artifact.js +49 -0
- package/src/verax/core/ga/ga.contract.js +435 -0
- package/src/verax/core/ga/ga.enforcer.js +87 -0
- package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
- package/src/verax/core/guardrails/policy.defaults.js +210 -0
- package/src/verax/core/guardrails/policy.loader.js +84 -0
- package/src/verax/core/guardrails/policy.schema.js +110 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
- package/src/verax/core/guardrails-engine.js +505 -0
- package/src/verax/core/incremental-store.js +1 -0
- package/src/verax/core/integrity/budget.js +138 -0
- package/src/verax/core/integrity/determinism.js +342 -0
- package/src/verax/core/integrity/integrity.js +208 -0
- package/src/verax/core/integrity/poisoning.js +108 -0
- package/src/verax/core/integrity/transaction.js +140 -0
- package/src/verax/core/observe/run-timeline.js +318 -0
- package/src/verax/core/perf/perf.contract.js +186 -0
- package/src/verax/core/perf/perf.display.js +65 -0
- package/src/verax/core/perf/perf.enforcer.js +91 -0
- package/src/verax/core/perf/perf.monitor.js +209 -0
- package/src/verax/core/perf/perf.report.js +200 -0
- package/src/verax/core/pipeline-tracker.js +243 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +130 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +164 -0
- package/src/verax/core/release/reproducibility.check.js +222 -0
- package/src/verax/core/release/sbom.builder.js +292 -0
- package/src/verax/core/replay-validator.js +2 -0
- package/src/verax/core/replay.js +4 -0
- package/src/verax/core/report/cross-index.js +195 -0
- package/src/verax/core/report/human-summary.js +362 -0
- package/src/verax/core/route-intelligence.js +420 -0
- package/src/verax/core/run-id.js +6 -3
- package/src/verax/core/run-manifest.js +4 -3
- package/src/verax/core/security/secrets.scan.js +329 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +128 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +334 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/truth/truth.certificate.js +252 -0
- package/src/verax/core/ui-feedback-intelligence.js +481 -0
- package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
- package/src/verax/detect/confidence-engine.js +62 -34
- package/src/verax/detect/confidence-helper.js +34 -0
- package/src/verax/detect/dynamic-route-findings.js +338 -0
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +2 -2
- package/src/verax/detect/failure-cause-inference.js +293 -0
- package/src/verax/detect/findings-writer.js +131 -35
- package/src/verax/detect/flow-detector.js +2 -2
- package/src/verax/detect/form-silent-failure.js +98 -0
- package/src/verax/detect/index.js +46 -5
- package/src/verax/detect/invariants-enforcer.js +147 -0
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/navigation-silent-failure.js +82 -0
- package/src/verax/detect/problem-aggregator.js +361 -0
- package/src/verax/detect/route-findings.js +219 -0
- package/src/verax/detect/summary-writer.js +477 -0
- package/src/verax/detect/test-failure-cause-inference.js +314 -0
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/flow/flow-engine.js +2 -1
- package/src/verax/flow/flow-spec.js +0 -6
- package/src/verax/index.js +4 -0
- package/src/verax/intel/ts-program.js +1 -0
- package/src/verax/intel/vue-navigation-extractor.js +3 -0
- package/src/verax/learn/action-contract-extractor.js +3 -0
- package/src/verax/learn/ast-contract-extractor.js +1 -1
- package/src/verax/learn/flow-extractor.js +1 -0
- package/src/verax/learn/project-detector.js +5 -0
- package/src/verax/learn/react-router-extractor.js +2 -0
- package/src/verax/learn/source-instrumenter.js +1 -0
- package/src/verax/learn/state-extractor.js +2 -1
- package/src/verax/learn/static-extractor.js +1 -0
- package/src/verax/observe/coverage-gaps.js +132 -0
- package/src/verax/observe/expectation-handler.js +126 -0
- package/src/verax/observe/incremental-skip.js +46 -0
- package/src/verax/observe/index.js +51 -155
- package/src/verax/observe/interaction-executor.js +192 -0
- package/src/verax/observe/interaction-runner.js +782 -513
- package/src/verax/observe/network-firewall.js +86 -0
- package/src/verax/observe/observation-builder.js +169 -0
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +192 -0
- package/src/verax/observe/observe-runner.js +230 -0
- package/src/verax/observe/observers/budget-observer.js +185 -0
- package/src/verax/observe/observers/console-observer.js +102 -0
- package/src/verax/observe/observers/coverage-observer.js +107 -0
- package/src/verax/observe/observers/interaction-observer.js +471 -0
- package/src/verax/observe/observers/navigation-observer.js +132 -0
- package/src/verax/observe/observers/network-observer.js +87 -0
- package/src/verax/observe/observers/safety-observer.js +82 -0
- package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
- package/src/verax/observe/page-traversal.js +138 -0
- package/src/verax/observe/snapshot-ops.js +94 -0
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/scan-summary-writer.js +2 -0
- package/src/verax/shared/artifact-manager.js +25 -5
- package/src/verax/shared/caching.js +1 -0
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/expectation-tracker.js +1 -0
- package/src/verax/shared/view-switch-rules.js +208 -0
- package/src/verax/shared/zip-artifacts.js +6 -0
- package/src/verax/shared/config-loader.js +0 -169
- /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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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);
|