@veraxhq/verax 0.2.0 → 0.3.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 +14 -18
- package/bin/verax.js +7 -0
- package/package.json +15 -5
- package/src/cli/commands/baseline.js +104 -0
- package/src/cli/commands/default.js +323 -111
- package/src/cli/commands/doctor.js +36 -4
- package/src/cli/commands/ga.js +243 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +131 -2
- package/src/cli/commands/release-check.js +213 -0
- package/src/cli/commands/run.js +498 -103
- package/src/cli/commands/security-check.js +211 -0
- package/src/cli/commands/truth.js +114 -0
- package/src/cli/entry.js +305 -68
- 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 +546 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/detection-engine.js +4 -3
- package/src/cli/util/determinism-runner.js +123 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/env-url.js +4 -0
- package/src/cli/util/events.js +76 -0
- package/src/cli/util/expectation-extractor.js +380 -74
- package/src/cli/util/findings-writer.js +126 -15
- package/src/cli/util/learn-writer.js +3 -1
- package/src/cli/util/observation-engine.js +69 -23
- package/src/cli/util/observe-writer.js +3 -1
- package/src/cli/util/paths.js +6 -14
- package/src/cli/util/project-discovery.js +23 -0
- package/src/cli/util/project-writer.js +3 -1
- package/src/cli/util/redact.js +2 -2
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/runtime-budget.js +147 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +13 -1
- 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 +147 -0
- package/src/cli/util/svelte-state-detector.js +243 -0
- package/src/cli/util/vue-navigation-detector.js +177 -0
- package/src/cli/util/vue-sfc-extractor.js +162 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/types/global.d.ts +28 -0
- package/src/types/ts-ast.d.ts +24 -0
- package/src/verax/cli/doctor.js +2 -2
- package/src/verax/cli/finding-explainer.js +56 -3
- package/src/verax/cli/init.js +1 -1
- package/src/verax/cli/url-safety.js +12 -2
- package/src/verax/cli/wizard.js +13 -2
- package/src/verax/core/artifacts/registry.js +154 -0
- package/src/verax/core/artifacts/verifier.js +980 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +231 -0
- package/src/verax/core/budget-engine.js +1 -1
- package/src/verax/core/capabilities/gates.js +499 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +137 -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 +79 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +484 -0
- package/src/verax/core/confidence-engine.js +486 -0
- package/src/verax/core/confidence-engine.js.backup +471 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +185 -0
- package/src/verax/core/contracts/validators.js +381 -0
- package/src/verax/core/decision-snapshot.js +31 -4
- package/src/verax/core/decisions/decision.trace.js +276 -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 +364 -0
- package/src/verax/core/determinism/engine.js +221 -0
- package/src/verax/core/determinism/finding-identity.js +148 -0
- package/src/verax/core/determinism/normalize.js +438 -0
- package/src/verax/core/determinism/report-writer.js +92 -0
- package/src/verax/core/determinism/run-fingerprint.js +118 -0
- package/src/verax/core/determinism-model.js +35 -6
- package/src/verax/core/dynamic-route-intelligence.js +528 -0
- package/src/verax/core/evidence/evidence-capture-service.js +307 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +165 -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 +190 -0
- package/src/verax/core/failures/exit-codes.js +86 -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 +132 -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 +434 -0
- package/src/verax/core/ga/ga.enforcer.js +86 -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 +83 -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 +15 -7
- package/src/verax/core/observe/run-timeline.js +316 -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 +198 -0
- package/src/verax/core/pipeline-tracker.js +238 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +271 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +159 -0
- package/src/verax/core/release/reproducibility.check.js +221 -0
- package/src/verax/core/release/sbom.builder.js +283 -0
- package/src/verax/core/replay-validator.js +4 -4
- package/src/verax/core/replay.js +1 -1
- package/src/verax/core/report/cross-index.js +192 -0
- package/src/verax/core/report/human-summary.js +222 -0
- package/src/verax/core/route-intelligence.js +419 -0
- package/src/verax/core/security/secrets.scan.js +326 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +124 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +326 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/silence-impact.js +1 -1
- package/src/verax/core/silence-model.js +9 -7
- package/src/verax/core/truth/truth.certificate.js +250 -0
- package/src/verax/core/ui-feedback-intelligence.js +515 -0
- package/src/verax/detect/comparison.js +8 -3
- package/src/verax/detect/confidence-engine.js +645 -57
- package/src/verax/detect/confidence-helper.js +33 -0
- package/src/verax/detect/detection-engine.js +19 -2
- package/src/verax/detect/dynamic-route-findings.js +335 -0
- package/src/verax/detect/evidence-index.js +15 -65
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +56 -3
- package/src/verax/detect/explanation-helpers.js +1 -1
- package/src/verax/detect/finding-detector.js +2 -2
- package/src/verax/detect/findings-writer.js +149 -20
- package/src/verax/detect/flow-detector.js +4 -4
- package/src/verax/detect/index.js +265 -15
- package/src/verax/detect/interactive-findings.js +3 -4
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/route-findings.js +218 -0
- package/src/verax/detect/signal-mapper.js +2 -2
- package/src/verax/detect/skip-classifier.js +4 -4
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/verdict-engine.js +61 -9
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/flow/flow-engine.js +3 -2
- package/src/verax/flow/flow-spec.js +1 -2
- package/src/verax/index.js +413 -33
- package/src/verax/intel/effect-detector.js +1 -1
- package/src/verax/intel/index.js +2 -2
- package/src/verax/intel/route-extractor.js +3 -3
- package/src/verax/intel/vue-navigation-extractor.js +81 -18
- package/src/verax/intel/vue-router-extractor.js +4 -2
- package/src/verax/learn/action-contract-extractor.js +684 -66
- package/src/verax/learn/ast-contract-extractor.js +53 -1
- package/src/verax/learn/index.js +36 -2
- package/src/verax/learn/manifest-writer.js +28 -14
- package/src/verax/learn/route-extractor.js +1 -1
- package/src/verax/learn/route-validator.js +12 -8
- package/src/verax/learn/state-extractor.js +1 -1
- package/src/verax/learn/static-extractor-navigation.js +1 -1
- package/src/verax/learn/static-extractor-validation.js +2 -2
- package/src/verax/learn/static-extractor.js +8 -7
- package/src/verax/learn/ts-contract-resolver.js +14 -12
- package/src/verax/observe/browser.js +22 -3
- package/src/verax/observe/console-sensor.js +2 -2
- package/src/verax/observe/expectation-executor.js +2 -1
- package/src/verax/observe/focus-sensor.js +1 -1
- package/src/verax/observe/human-driver.js +29 -10
- package/src/verax/observe/index.js +92 -844
- package/src/verax/observe/interaction-discovery.js +27 -15
- package/src/verax/observe/interaction-runner.js +31 -14
- package/src/verax/observe/loading-sensor.js +6 -0
- package/src/verax/observe/navigation-sensor.js +1 -1
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +191 -0
- package/src/verax/observe/observe-runner.js +226 -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/settle.js +1 -0
- package/src/verax/observe/state-sensor.js +8 -4
- package/src/verax/observe/state-ui-sensor.js +7 -1
- package/src/verax/observe/traces-writer.js +27 -16
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/observe/ui-signal-sensor.js +155 -2
- package/src/verax/scan-summary-writer.js +46 -9
- package/src/verax/shared/artifact-manager.js +9 -6
- package/src/verax/shared/budget-profiles.js +2 -2
- package/src/verax/shared/caching.js +1 -1
- package/src/verax/shared/config-loader.js +1 -2
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/dynamic-route-utils.js +12 -6
- package/src/verax/shared/retry-policy.js +1 -6
- package/src/verax/shared/root-artifacts.js +1 -1
- package/src/verax/shared/view-switch-rules.js +208 -0
- package/src/verax/shared/zip-artifacts.js +1 -0
- package/src/verax/validate/context-validator.js +1 -1
- package/src/verax/observe/index.js.backup +0 -1
- package/src/verax/validate/context-validator.js.bak +0 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 13 — UI Feedback Deepening
|
|
3
|
+
*
|
|
4
|
+
* Unified UI feedback detection and intelligence layer that:
|
|
5
|
+
* - Defines canonical UI feedback taxonomy
|
|
6
|
+
* - Scores feedback presence/absence deterministically
|
|
7
|
+
* - Correlates feedback with promises
|
|
8
|
+
* - Provides evidence-backed findings
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* PHASE 13: UI Feedback Taxonomy
|
|
13
|
+
*/
|
|
14
|
+
export const FEEDBACK_TYPE = {
|
|
15
|
+
LOADING: 'loading',
|
|
16
|
+
DISABLED: 'disabled',
|
|
17
|
+
TOAST: 'toast',
|
|
18
|
+
MODAL: 'modal',
|
|
19
|
+
INLINE_MESSAGE: 'inline_message',
|
|
20
|
+
DOM_CHANGE: 'dom_change',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const FEEDBACK_SCORE = {
|
|
24
|
+
CONFIRMED: 'FEEDBACK_CONFIRMED',
|
|
25
|
+
MISSING: 'FEEDBACK_MISSING',
|
|
26
|
+
AMBIGUOUS: 'FEEDBACK_AMBIGUOUS',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* PHASE 13: Detect UI feedback signals from trace sensors
|
|
31
|
+
*
|
|
32
|
+
* @param {Object} trace - Interaction trace with sensors
|
|
33
|
+
* @returns {Array} Array of detected feedback signals
|
|
34
|
+
*/
|
|
35
|
+
export function detectUIFeedbackSignals(trace) {
|
|
36
|
+
const signals = [];
|
|
37
|
+
const sensors = trace.sensors || {};
|
|
38
|
+
const uiSignals = sensors.uiSignals || {};
|
|
39
|
+
const uiFeedback = sensors.uiFeedback || {};
|
|
40
|
+
const before = trace.before || {};
|
|
41
|
+
const after = trace.after || {};
|
|
42
|
+
|
|
43
|
+
const beforeSignals = uiSignals.before || {};
|
|
44
|
+
const afterSignals = uiSignals.after || {};
|
|
45
|
+
const diff = uiSignals.diff || {};
|
|
46
|
+
|
|
47
|
+
// 1. Loading indicators
|
|
48
|
+
if (afterSignals.hasLoadingIndicator ||
|
|
49
|
+
uiFeedback.signals?.loading?.appeared === true ||
|
|
50
|
+
uiFeedback.signals?.loading?.disappeared === true) {
|
|
51
|
+
signals.push({
|
|
52
|
+
type: FEEDBACK_TYPE.LOADING,
|
|
53
|
+
selector: findLoadingSelector(afterSignals),
|
|
54
|
+
confidence: 0.9,
|
|
55
|
+
evidence: {
|
|
56
|
+
before: beforeSignals.hasLoadingIndicator || false,
|
|
57
|
+
after: afterSignals.hasLoadingIndicator || false,
|
|
58
|
+
appeared: uiFeedback.signals?.loading?.appeared === true,
|
|
59
|
+
disappeared: uiFeedback.signals?.loading?.disappeared === true,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 2. Disabled/blocked states
|
|
65
|
+
const disabledChanged = diff.buttonStateChanged === true ||
|
|
66
|
+
(beforeSignals.disabledElements?.length || 0) !== (afterSignals.disabledElements?.length || 0) ||
|
|
67
|
+
uiFeedback.signals?.buttonStateTransition?.happened === true;
|
|
68
|
+
|
|
69
|
+
if (disabledChanged) {
|
|
70
|
+
signals.push({
|
|
71
|
+
type: FEEDBACK_TYPE.DISABLED,
|
|
72
|
+
selector: findDisabledSelector(afterSignals),
|
|
73
|
+
confidence: 0.85,
|
|
74
|
+
evidence: {
|
|
75
|
+
beforeCount: beforeSignals.disabledElements?.length || 0,
|
|
76
|
+
afterCount: afterSignals.disabledElements?.length || 0,
|
|
77
|
+
buttonStateChanged: diff.buttonStateChanged === true,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 3. Toast/snackbar notifications
|
|
83
|
+
if (afterSignals.hasStatusSignal ||
|
|
84
|
+
afterSignals.hasLiveRegion ||
|
|
85
|
+
uiFeedback.signals?.notification?.happened === true) {
|
|
86
|
+
signals.push({
|
|
87
|
+
type: FEEDBACK_TYPE.TOAST,
|
|
88
|
+
selector: findToastSelector(afterSignals),
|
|
89
|
+
confidence: 0.9,
|
|
90
|
+
evidence: {
|
|
91
|
+
hasStatusSignal: afterSignals.hasStatusSignal || false,
|
|
92
|
+
hasLiveRegion: afterSignals.hasLiveRegion || false,
|
|
93
|
+
notification: uiFeedback.signals?.notification?.happened === true,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 4. Modal/dialog confirmations
|
|
99
|
+
if (afterSignals.hasDialog ||
|
|
100
|
+
uiFeedback.signals?.domChange?.happened === true) {
|
|
101
|
+
// Check if dialog appeared
|
|
102
|
+
const dialogAppeared = !beforeSignals.hasDialog && afterSignals.hasDialog;
|
|
103
|
+
|
|
104
|
+
if (dialogAppeared) {
|
|
105
|
+
signals.push({
|
|
106
|
+
type: FEEDBACK_TYPE.MODAL,
|
|
107
|
+
selector: findDialogSelector(afterSignals),
|
|
108
|
+
confidence: 0.95,
|
|
109
|
+
evidence: {
|
|
110
|
+
before: beforeSignals.hasDialog || false,
|
|
111
|
+
after: afterSignals.hasDialog || false,
|
|
112
|
+
appeared: dialogAppeared,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 5. Inline success/error messages
|
|
119
|
+
if (afterSignals.hasErrorSignal ||
|
|
120
|
+
afterSignals.validationFeedbackDetected ||
|
|
121
|
+
uiFeedback.signals?.domChange?.happened === true) {
|
|
122
|
+
signals.push({
|
|
123
|
+
type: FEEDBACK_TYPE.INLINE_MESSAGE,
|
|
124
|
+
selector: findInlineMessageSelector(afterSignals),
|
|
125
|
+
confidence: 0.85,
|
|
126
|
+
evidence: {
|
|
127
|
+
hasErrorSignal: afterSignals.hasErrorSignal || false,
|
|
128
|
+
validationFeedbackDetected: afterSignals.validationFeedbackDetected || false,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 6. Meaningful DOM changes
|
|
134
|
+
const domChanged = trace.dom?.beforeHash !== trace.dom?.afterHash ||
|
|
135
|
+
uiFeedback.signals?.domChange?.happened === true ||
|
|
136
|
+
diff.changed === true;
|
|
137
|
+
|
|
138
|
+
if (domChanged) {
|
|
139
|
+
// Only count as feedback if it's a meaningful change (not just timestamps/random IDs)
|
|
140
|
+
const isMeaningful = isMeaningfulDOMChange(trace, uiFeedback);
|
|
141
|
+
|
|
142
|
+
if (isMeaningful) {
|
|
143
|
+
signals.push({
|
|
144
|
+
type: FEEDBACK_TYPE.DOM_CHANGE,
|
|
145
|
+
selector: null, // DOM change affects multiple elements
|
|
146
|
+
confidence: 0.7,
|
|
147
|
+
evidence: {
|
|
148
|
+
domHashChanged: trace.dom?.beforeHash !== trace.dom?.afterHash,
|
|
149
|
+
uiFeedbackDomChange: uiFeedback.signals?.domChange?.happened === true,
|
|
150
|
+
uiSignalsChanged: diff.changed === true,
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return signals;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* PHASE 13: Score feedback presence/absence
|
|
161
|
+
*
|
|
162
|
+
* @param {Array} signals - Detected feedback signals
|
|
163
|
+
* @param {Object} expectation - Promise/expectation that should have feedback
|
|
164
|
+
* @param {Object} trace - Interaction trace
|
|
165
|
+
* @returns {Object} Scoring result
|
|
166
|
+
*/
|
|
167
|
+
export function scoreUIFeedback(signals, expectation, trace) {
|
|
168
|
+
const sensors = trace.sensors || {};
|
|
169
|
+
const networkSensor = sensors.network || {};
|
|
170
|
+
const uiFeedback = sensors.uiFeedback || {};
|
|
171
|
+
|
|
172
|
+
// Determine expected feedback type based on expectation
|
|
173
|
+
const expectedFeedbackTypes = inferExpectedFeedbackTypes(expectation);
|
|
174
|
+
|
|
175
|
+
// Check if any expected feedback types are present
|
|
176
|
+
const matchingSignals = signals.filter(s =>
|
|
177
|
+
expectedFeedbackTypes.includes(s.type)
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// Overall UI feedback score from sensor
|
|
181
|
+
const overallScore = uiFeedback.overallUiFeedbackScore || 0;
|
|
182
|
+
|
|
183
|
+
// Network activity context
|
|
184
|
+
const hasNetworkActivity = networkSensor.hasNetworkActivity === true ||
|
|
185
|
+
(networkSensor.totalRequests || 0) > 0;
|
|
186
|
+
const hasNetworkFailure = networkSensor.failedRequests > 0 ||
|
|
187
|
+
networkSensor.topFailedUrls?.length > 0;
|
|
188
|
+
|
|
189
|
+
// Determine score
|
|
190
|
+
if (matchingSignals.length > 0 || overallScore > 0.5) {
|
|
191
|
+
return {
|
|
192
|
+
score: FEEDBACK_SCORE.CONFIRMED,
|
|
193
|
+
confidence: Math.max(...matchingSignals.map(s => s.confidence), overallScore),
|
|
194
|
+
explanation: buildFeedbackExplanation(matchingSignals, overallScore, 'confirmed'),
|
|
195
|
+
signals: matchingSignals,
|
|
196
|
+
topSignals: matchingSignals.slice(0, 3).map(s => ({
|
|
197
|
+
type: s.type,
|
|
198
|
+
confidence: s.confidence,
|
|
199
|
+
selector: s.selector,
|
|
200
|
+
})),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// If network activity but no feedback, likely missing
|
|
205
|
+
if (hasNetworkActivity && signals.length === 0 && overallScore < 0.3) {
|
|
206
|
+
return {
|
|
207
|
+
score: FEEDBACK_SCORE.MISSING,
|
|
208
|
+
confidence: hasNetworkFailure ? 0.9 : 0.7,
|
|
209
|
+
explanation: buildFeedbackExplanation([], overallScore, 'missing', {
|
|
210
|
+
hasNetworkActivity,
|
|
211
|
+
hasNetworkFailure,
|
|
212
|
+
}),
|
|
213
|
+
signals: [],
|
|
214
|
+
topSignals: [],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Ambiguous case
|
|
219
|
+
if (signals.length > 0 && overallScore > 0 && overallScore < 0.5) {
|
|
220
|
+
return {
|
|
221
|
+
score: FEEDBACK_SCORE.AMBIGUOUS,
|
|
222
|
+
confidence: 0.6,
|
|
223
|
+
explanation: buildFeedbackExplanation(signals, overallScore, 'ambiguous'),
|
|
224
|
+
signals: signals,
|
|
225
|
+
topSignals: signals.slice(0, 3).map(s => ({
|
|
226
|
+
type: s.type,
|
|
227
|
+
confidence: s.confidence,
|
|
228
|
+
selector: s.selector,
|
|
229
|
+
})),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Default: missing if no signals and low score
|
|
234
|
+
return {
|
|
235
|
+
score: FEEDBACK_SCORE.MISSING,
|
|
236
|
+
confidence: 0.5,
|
|
237
|
+
explanation: buildFeedbackExplanation([], overallScore, 'missing'),
|
|
238
|
+
signals: [],
|
|
239
|
+
topSignals: [],
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Infer expected feedback types from expectation
|
|
245
|
+
*/
|
|
246
|
+
function inferExpectedFeedbackTypes(expectation) {
|
|
247
|
+
const types = [];
|
|
248
|
+
|
|
249
|
+
if (!expectation) return types;
|
|
250
|
+
|
|
251
|
+
// Network expectations → loading + toast/error
|
|
252
|
+
if (expectation.type === 'network_action' || expectation.type === 'network') {
|
|
253
|
+
types.push(FEEDBACK_TYPE.LOADING);
|
|
254
|
+
types.push(FEEDBACK_TYPE.TOAST);
|
|
255
|
+
types.push(FEEDBACK_TYPE.INLINE_MESSAGE);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Navigation expectations → DOM change or modal
|
|
259
|
+
if (expectation.type === 'navigation' || expectation.type === 'spa_navigation') {
|
|
260
|
+
types.push(FEEDBACK_TYPE.DOM_CHANGE);
|
|
261
|
+
types.push(FEEDBACK_TYPE.MODAL);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// State expectations → DOM change or disabled state
|
|
265
|
+
if (expectation.type === 'state_action' || expectation.type === 'state') {
|
|
266
|
+
types.push(FEEDBACK_TYPE.DOM_CHANGE);
|
|
267
|
+
types.push(FEEDBACK_TYPE.DISABLED);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Validation expectations → inline message
|
|
271
|
+
if (expectation.type === 'validation' || expectation.type === 'form_submission') {
|
|
272
|
+
types.push(FEEDBACK_TYPE.INLINE_MESSAGE);
|
|
273
|
+
types.push(FEEDBACK_TYPE.TOAST);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return types;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Build explanation for feedback score
|
|
281
|
+
*/
|
|
282
|
+
function buildFeedbackExplanation(signals, overallScore, outcome, context = {}) {
|
|
283
|
+
const parts = [];
|
|
284
|
+
|
|
285
|
+
if (outcome === 'confirmed') {
|
|
286
|
+
if (signals.length > 0) {
|
|
287
|
+
parts.push(`Detected ${signals.length} feedback signal(s): ${signals.map(s => s.type).join(', ')}`);
|
|
288
|
+
}
|
|
289
|
+
if (overallScore > 0.5) {
|
|
290
|
+
parts.push(`Overall UI feedback score: ${overallScore.toFixed(2)}`);
|
|
291
|
+
}
|
|
292
|
+
} else if (outcome === 'missing') {
|
|
293
|
+
parts.push('No feedback signals detected');
|
|
294
|
+
if (context.hasNetworkActivity) {
|
|
295
|
+
parts.push('Network activity occurred but no feedback');
|
|
296
|
+
}
|
|
297
|
+
if (context.hasNetworkFailure) {
|
|
298
|
+
parts.push('Network failure occurred but no error feedback');
|
|
299
|
+
}
|
|
300
|
+
if (overallScore < 0.3) {
|
|
301
|
+
parts.push(`Low UI feedback score: ${overallScore.toFixed(2)}`);
|
|
302
|
+
}
|
|
303
|
+
} else if (outcome === 'ambiguous') {
|
|
304
|
+
parts.push('Feedback signals present but confidence is low');
|
|
305
|
+
if (signals.length > 0) {
|
|
306
|
+
parts.push(`Detected ${signals.length} signal(s) but overall score is ${overallScore.toFixed(2)}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return parts.join('. ');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Helper functions to find selectors
|
|
315
|
+
*/
|
|
316
|
+
function findLoadingSelector(signals) {
|
|
317
|
+
// Return a generic selector hint
|
|
318
|
+
return '[aria-busy="true"], [data-loading], [role="status"]';
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function findDisabledSelector(signals) {
|
|
322
|
+
return '[disabled], [aria-disabled="true"]';
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function findToastSelector(signals) {
|
|
326
|
+
return '[role="alert"], [role="status"], [aria-live], .toast, .snackbar';
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function findDialogSelector(signals) {
|
|
330
|
+
return '[role="dialog"], [aria-modal="true"]';
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function findInlineMessageSelector(signals) {
|
|
334
|
+
return '[role="alert"], .error, .success, [class*="message"]';
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Check if DOM change is meaningful (not just timestamps/random IDs)
|
|
339
|
+
*/
|
|
340
|
+
function isMeaningfulDOMChange(trace, uiFeedback) {
|
|
341
|
+
// If UI feedback sensor detected meaningful change, trust it
|
|
342
|
+
if (uiFeedback.signals?.domChange?.happened === true) {
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// If DOM hash changed, consider it meaningful
|
|
347
|
+
if (trace.dom?.beforeHash !== trace.dom?.afterHash) {
|
|
348
|
+
// Additional check: if UI signals changed, it's meaningful
|
|
349
|
+
const uiSignals = trace.sensors?.uiSignals || {};
|
|
350
|
+
if (uiSignals.diff?.changed === true) {
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* PHASE 13: Correlate promise with UI feedback
|
|
360
|
+
*
|
|
361
|
+
* @param {Object} expectation - Promise/expectation
|
|
362
|
+
* @param {Object} feedbackScore - Feedback scoring result
|
|
363
|
+
* @param {Object} trace - Interaction trace
|
|
364
|
+
* @returns {Object} Correlation result
|
|
365
|
+
*/
|
|
366
|
+
export function correlatePromiseWithFeedback(expectation, feedbackScore, trace) {
|
|
367
|
+
const sensors = trace.sensors || {};
|
|
368
|
+
const networkSensor = sensors.network || {};
|
|
369
|
+
const hasNetworkFailure = networkSensor.failedRequests > 0 ||
|
|
370
|
+
networkSensor.topFailedUrls?.length > 0;
|
|
371
|
+
|
|
372
|
+
// Rule 1: Network failed AND feedback missing → CONFIRMED silent failure
|
|
373
|
+
if (expectation.type === 'network_action' || expectation.type === 'network') {
|
|
374
|
+
if (hasNetworkFailure && feedbackScore.score === FEEDBACK_SCORE.MISSING) {
|
|
375
|
+
return {
|
|
376
|
+
outcome: 'CONFIRMED',
|
|
377
|
+
confidence: 0.9,
|
|
378
|
+
reason: 'Network request failed but no error feedback provided to user',
|
|
379
|
+
requiresEvidence: true,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Network succeeded but no feedback and no DOM change
|
|
384
|
+
const hasNetworkSuccess = networkSensor.successfulRequests > 0;
|
|
385
|
+
const hasDomChange = trace.dom?.beforeHash !== trace.dom?.afterHash;
|
|
386
|
+
const hasUrlChange = trace.sensors?.navigation?.urlChanged === true;
|
|
387
|
+
|
|
388
|
+
if (hasNetworkSuccess &&
|
|
389
|
+
feedbackScore.score === FEEDBACK_SCORE.MISSING &&
|
|
390
|
+
!hasDomChange &&
|
|
391
|
+
!hasUrlChange) {
|
|
392
|
+
return {
|
|
393
|
+
outcome: 'SUSPECTED',
|
|
394
|
+
confidence: 0.7,
|
|
395
|
+
reason: 'Network request succeeded but no feedback or visible change',
|
|
396
|
+
requiresEvidence: true,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Rule 2: Navigation promised but URL/UI unchanged
|
|
402
|
+
if (expectation.type === 'navigation' || expectation.type === 'spa_navigation') {
|
|
403
|
+
const urlChanged = trace.sensors?.navigation?.urlChanged === true;
|
|
404
|
+
const hasDomChange = trace.dom?.beforeHash !== trace.dom?.afterHash;
|
|
405
|
+
|
|
406
|
+
if (!urlChanged && !hasDomChange && feedbackScore.score === FEEDBACK_SCORE.MISSING) {
|
|
407
|
+
return {
|
|
408
|
+
outcome: 'CONFIRMED',
|
|
409
|
+
confidence: 0.85,
|
|
410
|
+
reason: 'Navigation promise not fulfilled - no URL change, DOM change, or feedback',
|
|
411
|
+
requiresEvidence: true,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Rule 3: Validation expected but no inline feedback
|
|
417
|
+
if (expectation.type === 'validation' || expectation.type === 'form_submission') {
|
|
418
|
+
if (feedbackScore.score === FEEDBACK_SCORE.MISSING) {
|
|
419
|
+
// Check if form was actually submitted
|
|
420
|
+
const formSubmitted = trace.interaction?.type === 'form';
|
|
421
|
+
|
|
422
|
+
if (formSubmitted) {
|
|
423
|
+
return {
|
|
424
|
+
outcome: 'SUSPECTED',
|
|
425
|
+
confidence: 0.7,
|
|
426
|
+
reason: 'Form submission expected validation feedback but none detected',
|
|
427
|
+
requiresEvidence: true,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Rule 4: State action but no UI feedback
|
|
434
|
+
if (expectation.type === 'state_action' || expectation.type === 'state') {
|
|
435
|
+
if (feedbackScore.score === FEEDBACK_SCORE.MISSING) {
|
|
436
|
+
// Check if state actually changed
|
|
437
|
+
const stateChanged = trace.sensors?.state?.changed?.length > 0;
|
|
438
|
+
|
|
439
|
+
if (stateChanged) {
|
|
440
|
+
return {
|
|
441
|
+
outcome: 'SUSPECTED',
|
|
442
|
+
confidence: 0.75,
|
|
443
|
+
reason: 'State changed but no UI feedback detected',
|
|
444
|
+
requiresEvidence: true,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Default: no correlation (feedback present or expectation doesn't require it)
|
|
451
|
+
return {
|
|
452
|
+
outcome: null,
|
|
453
|
+
confidence: 0,
|
|
454
|
+
reason: null,
|
|
455
|
+
requiresEvidence: false,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* PHASE 13: Build evidence for UI feedback finding
|
|
461
|
+
*
|
|
462
|
+
* @param {Object} feedbackScore - Feedback scoring result
|
|
463
|
+
* @param {Object} correlation - Promise-feedback correlation
|
|
464
|
+
* @param {Object} trace - Interaction trace
|
|
465
|
+
* @param {Object} expectation - Promise/expectation
|
|
466
|
+
* @returns {Object} Evidence object
|
|
467
|
+
*/
|
|
468
|
+
export function buildUIFeedbackEvidence(feedbackScore, correlation, trace, expectation) {
|
|
469
|
+
const evidence = {
|
|
470
|
+
feedback: {
|
|
471
|
+
score: feedbackScore.score,
|
|
472
|
+
confidence: feedbackScore.confidence,
|
|
473
|
+
explanation: feedbackScore.explanation,
|
|
474
|
+
signals: feedbackScore.signals.map(s => ({
|
|
475
|
+
type: s.type,
|
|
476
|
+
selector: s.selector,
|
|
477
|
+
confidence: s.confidence,
|
|
478
|
+
})),
|
|
479
|
+
topSignals: feedbackScore.topSignals,
|
|
480
|
+
},
|
|
481
|
+
beforeAfter: {
|
|
482
|
+
beforeScreenshot: trace.before?.screenshot || null,
|
|
483
|
+
afterScreenshot: trace.after?.screenshot || null,
|
|
484
|
+
beforeUrl: trace.before?.url || null,
|
|
485
|
+
afterUrl: trace.after?.url || null,
|
|
486
|
+
beforeDomHash: trace.dom?.beforeHash || null,
|
|
487
|
+
afterDomHash: trace.dom?.afterHash || null,
|
|
488
|
+
},
|
|
489
|
+
promise: {
|
|
490
|
+
type: expectation?.type || null,
|
|
491
|
+
value: expectation?.promise?.value || expectation?.targetPath || null,
|
|
492
|
+
source: expectation?.source || null,
|
|
493
|
+
context: expectation?.source?.context || null,
|
|
494
|
+
astSource: expectation?.source?.astSource || null,
|
|
495
|
+
},
|
|
496
|
+
sensors: {
|
|
497
|
+
uiSignals: trace.sensors?.uiSignals || null,
|
|
498
|
+
uiFeedback: trace.sensors?.uiFeedback || null,
|
|
499
|
+
network: trace.sensors?.network || null,
|
|
500
|
+
navigation: trace.sensors?.navigation || null,
|
|
501
|
+
},
|
|
502
|
+
correlation: {
|
|
503
|
+
outcome: correlation.outcome,
|
|
504
|
+
confidence: correlation.confidence,
|
|
505
|
+
reason: correlation.reason,
|
|
506
|
+
},
|
|
507
|
+
timing: {
|
|
508
|
+
// Timing window information if available
|
|
509
|
+
stabilizationWindow: trace.sensors?.uiFeedback?.timing || null,
|
|
510
|
+
},
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
return evidence;
|
|
514
|
+
}
|
|
515
|
+
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolve } from 'path';
|
|
2
2
|
import { getUrlPath, getScreenshotHash } from './evidence-validator.js';
|
|
3
|
+
import { getScreenshotDir } from '../core/run-id.js';
|
|
3
4
|
|
|
4
5
|
export function hasMeaningfulUrlChange(beforeUrl, afterUrl) {
|
|
5
6
|
const beforePath = getUrlPath(beforeUrl);
|
|
@@ -15,9 +16,13 @@ export function hasMeaningfulUrlChange(beforeUrl, afterUrl) {
|
|
|
15
16
|
return beforeNormalized !== afterNormalized;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
export function hasVisibleChange(beforeScreenshot, afterScreenshot, projectDir) {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
export function hasVisibleChange(beforeScreenshot, afterScreenshot, projectDir, runId) {
|
|
20
|
+
if (!runId) {
|
|
21
|
+
throw new Error('runId is required for hasVisibleChange');
|
|
22
|
+
}
|
|
23
|
+
const screenshotsDir = getScreenshotDir(projectDir, runId);
|
|
24
|
+
const beforePath = resolve(screenshotsDir, beforeScreenshot);
|
|
25
|
+
const afterPath = resolve(screenshotsDir, afterScreenshot);
|
|
21
26
|
|
|
22
27
|
const beforeHash = getScreenshotHash(beforePath);
|
|
23
28
|
const afterHash = getScreenshotHash(afterPath);
|