@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,481 @@
|
|
|
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
|
+
// Evidence-only: no inferred feedback expectations
|
|
173
|
+
const expectedFeedbackTypes = [];
|
|
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: matchingSignals.length > 0
|
|
194
|
+
? Math.max(...matchingSignals.map(s => s.confidence))
|
|
195
|
+
: overallScore,
|
|
196
|
+
explanation: buildFeedbackExplanation(matchingSignals, overallScore, 'confirmed'),
|
|
197
|
+
signals: matchingSignals,
|
|
198
|
+
topSignals: matchingSignals.slice(0, 3).map(s => ({
|
|
199
|
+
type: s.type,
|
|
200
|
+
confidence: s.confidence,
|
|
201
|
+
selector: s.selector,
|
|
202
|
+
})),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// If network activity but no feedback, likely missing
|
|
207
|
+
if (hasNetworkActivity && signals.length === 0 && overallScore < 0.3) {
|
|
208
|
+
return {
|
|
209
|
+
score: FEEDBACK_SCORE.MISSING,
|
|
210
|
+
confidence: hasNetworkFailure ? 0.9 : 0.7,
|
|
211
|
+
explanation: buildFeedbackExplanation([], overallScore, 'missing', {
|
|
212
|
+
hasNetworkActivity,
|
|
213
|
+
hasNetworkFailure,
|
|
214
|
+
}),
|
|
215
|
+
signals: [],
|
|
216
|
+
topSignals: [],
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Ambiguous case
|
|
221
|
+
if (signals.length > 0 && overallScore > 0 && overallScore < 0.5) {
|
|
222
|
+
return {
|
|
223
|
+
score: FEEDBACK_SCORE.AMBIGUOUS,
|
|
224
|
+
confidence: 0.6,
|
|
225
|
+
explanation: buildFeedbackExplanation(signals, overallScore, 'ambiguous'),
|
|
226
|
+
signals: signals,
|
|
227
|
+
topSignals: signals.slice(0, 3).map(s => ({
|
|
228
|
+
type: s.type,
|
|
229
|
+
confidence: s.confidence,
|
|
230
|
+
selector: s.selector,
|
|
231
|
+
})),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Default: missing if no signals and low score
|
|
236
|
+
return {
|
|
237
|
+
score: FEEDBACK_SCORE.MISSING,
|
|
238
|
+
confidence: 0.5,
|
|
239
|
+
explanation: buildFeedbackExplanation([], overallScore, 'missing'),
|
|
240
|
+
signals: [],
|
|
241
|
+
topSignals: [],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Build explanation for feedback score
|
|
247
|
+
*/
|
|
248
|
+
function buildFeedbackExplanation(signals, overallScore, outcome, context = {}) {
|
|
249
|
+
const parts = [];
|
|
250
|
+
|
|
251
|
+
if (outcome === 'confirmed') {
|
|
252
|
+
if (signals.length > 0) {
|
|
253
|
+
parts.push(`Detected ${signals.length} feedback signal(s): ${signals.map(s => s.type).join(', ')}`);
|
|
254
|
+
}
|
|
255
|
+
if (overallScore > 0.5) {
|
|
256
|
+
parts.push(`Overall UI feedback score: ${overallScore.toFixed(2)}`);
|
|
257
|
+
}
|
|
258
|
+
} else if (outcome === 'missing') {
|
|
259
|
+
parts.push('No feedback signals detected');
|
|
260
|
+
if (context.hasNetworkActivity) {
|
|
261
|
+
parts.push('Network activity occurred but no feedback');
|
|
262
|
+
}
|
|
263
|
+
if (context.hasNetworkFailure) {
|
|
264
|
+
parts.push('Network failure occurred but no error feedback');
|
|
265
|
+
}
|
|
266
|
+
if (overallScore < 0.3) {
|
|
267
|
+
parts.push(`Low UI feedback score: ${overallScore.toFixed(2)}`);
|
|
268
|
+
}
|
|
269
|
+
} else if (outcome === 'ambiguous') {
|
|
270
|
+
parts.push('Feedback signals present but confidence is low');
|
|
271
|
+
if (signals.length > 0) {
|
|
272
|
+
parts.push(`Detected ${signals.length} signal(s) but overall score is ${overallScore.toFixed(2)}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return parts.join('. ');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Helper functions to find selectors
|
|
281
|
+
*/
|
|
282
|
+
function findLoadingSelector(_signals) {
|
|
283
|
+
// Return a generic selector hint
|
|
284
|
+
return '[aria-busy="true"], [data-loading], [role="status"]';
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function findDisabledSelector(_signals) {
|
|
288
|
+
return '[disabled], [aria-disabled="true"]';
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function findToastSelector(_signals) {
|
|
292
|
+
return '[role="alert"], [role="status"], [aria-live], .toast, .snackbar';
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function findDialogSelector(_signals) {
|
|
296
|
+
return '[role="dialog"], [aria-modal="true"]';
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function findInlineMessageSelector(_signals) {
|
|
300
|
+
return '[role="alert"], .error, .success, [class*="message"]';
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Check if DOM change is meaningful (not just timestamps/random IDs)
|
|
305
|
+
*/
|
|
306
|
+
function isMeaningfulDOMChange(trace, uiFeedback) {
|
|
307
|
+
// If UI feedback sensor detected meaningful change, trust it
|
|
308
|
+
if (uiFeedback.signals?.domChange?.happened === true) {
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// If DOM hash changed, consider it meaningful
|
|
313
|
+
if (trace.dom?.beforeHash !== trace.dom?.afterHash) {
|
|
314
|
+
// Additional check: if UI signals changed, it's meaningful
|
|
315
|
+
const uiSignals = trace.sensors?.uiSignals || {};
|
|
316
|
+
if (uiSignals.diff?.changed === true) {
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* PHASE 13: Correlate promise with UI feedback
|
|
326
|
+
*
|
|
327
|
+
* @param {Object} expectation - Promise/expectation
|
|
328
|
+
* @param {Object} feedbackScore - Feedback scoring result
|
|
329
|
+
* @param {Object} trace - Interaction trace
|
|
330
|
+
* @returns {Object} Correlation result
|
|
331
|
+
*/
|
|
332
|
+
export function correlatePromiseWithFeedback(expectation, feedbackScore, trace) {
|
|
333
|
+
const sensors = trace.sensors || {};
|
|
334
|
+
const networkSensor = sensors.network || {};
|
|
335
|
+
const hasNetworkFailure = networkSensor.failedRequests > 0 ||
|
|
336
|
+
networkSensor.topFailedUrls?.length > 0;
|
|
337
|
+
|
|
338
|
+
// Rule 1: Network failed AND feedback missing → CONFIRMED silent failure
|
|
339
|
+
if (expectation.type === 'network_action' || expectation.type === 'network') {
|
|
340
|
+
if (hasNetworkFailure && feedbackScore.score === FEEDBACK_SCORE.MISSING) {
|
|
341
|
+
return {
|
|
342
|
+
outcome: 'CONFIRMED',
|
|
343
|
+
confidence: 0.9,
|
|
344
|
+
reason: 'Network request failed but no error feedback provided to user',
|
|
345
|
+
requiresEvidence: true,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Network succeeded but no feedback and no DOM change
|
|
350
|
+
const hasNetworkSuccess = networkSensor.successfulRequests > 0;
|
|
351
|
+
const hasDomChange = trace.dom?.beforeHash !== trace.dom?.afterHash;
|
|
352
|
+
const hasUrlChange = trace.sensors?.navigation?.urlChanged === true;
|
|
353
|
+
|
|
354
|
+
if (hasNetworkSuccess &&
|
|
355
|
+
feedbackScore.score === FEEDBACK_SCORE.MISSING &&
|
|
356
|
+
!hasDomChange &&
|
|
357
|
+
!hasUrlChange) {
|
|
358
|
+
return {
|
|
359
|
+
outcome: 'SUSPECTED',
|
|
360
|
+
confidence: 0.7,
|
|
361
|
+
reason: 'Network request succeeded but no feedback or visible change',
|
|
362
|
+
requiresEvidence: true,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Rule 2: Navigation promised but URL/UI unchanged
|
|
368
|
+
if (expectation.type === 'navigation' || expectation.type === 'spa_navigation') {
|
|
369
|
+
const urlChanged = trace.sensors?.navigation?.urlChanged === true;
|
|
370
|
+
const hasDomChange = trace.dom?.beforeHash !== trace.dom?.afterHash;
|
|
371
|
+
|
|
372
|
+
if (!urlChanged && !hasDomChange && feedbackScore.score === FEEDBACK_SCORE.MISSING) {
|
|
373
|
+
return {
|
|
374
|
+
outcome: 'CONFIRMED',
|
|
375
|
+
confidence: 0.85,
|
|
376
|
+
reason: 'Navigation promise not fulfilled - no URL change, DOM change, or feedback',
|
|
377
|
+
requiresEvidence: true,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Rule 3: Validation expected but no inline feedback
|
|
383
|
+
if (expectation.type === 'validation' || expectation.type === 'form_submission') {
|
|
384
|
+
if (feedbackScore.score === FEEDBACK_SCORE.MISSING) {
|
|
385
|
+
// Check if form was actually submitted
|
|
386
|
+
const formSubmitted = trace.interaction?.type === 'form';
|
|
387
|
+
|
|
388
|
+
if (formSubmitted) {
|
|
389
|
+
return {
|
|
390
|
+
outcome: 'SUSPECTED',
|
|
391
|
+
confidence: 0.7,
|
|
392
|
+
reason: 'Form submission expected validation feedback but none detected',
|
|
393
|
+
requiresEvidence: true,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Rule 4: State action but no UI feedback
|
|
400
|
+
if (expectation.type === 'state_action' || expectation.type === 'state') {
|
|
401
|
+
if (feedbackScore.score === FEEDBACK_SCORE.MISSING) {
|
|
402
|
+
// Check if state actually changed
|
|
403
|
+
const stateChanged = trace.sensors?.state?.changed?.length > 0;
|
|
404
|
+
|
|
405
|
+
if (stateChanged) {
|
|
406
|
+
return {
|
|
407
|
+
outcome: 'SUSPECTED',
|
|
408
|
+
confidence: 0.75,
|
|
409
|
+
reason: 'State changed but no UI feedback detected',
|
|
410
|
+
requiresEvidence: true,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Default: no correlation (feedback present or expectation doesn't require it)
|
|
417
|
+
return {
|
|
418
|
+
outcome: null,
|
|
419
|
+
confidence: 0,
|
|
420
|
+
reason: null,
|
|
421
|
+
requiresEvidence: false,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* PHASE 13: Build evidence for UI feedback finding
|
|
427
|
+
*
|
|
428
|
+
* @param {Object} feedbackScore - Feedback scoring result
|
|
429
|
+
* @param {Object} correlation - Promise-feedback correlation
|
|
430
|
+
* @param {Object} trace - Interaction trace
|
|
431
|
+
* @param {Object} expectation - Promise/expectation
|
|
432
|
+
* @returns {Object} Evidence object
|
|
433
|
+
*/
|
|
434
|
+
export function buildUIFeedbackEvidence(feedbackScore, correlation, trace, expectation) {
|
|
435
|
+
const evidence = {
|
|
436
|
+
feedback: {
|
|
437
|
+
score: feedbackScore.score,
|
|
438
|
+
confidence: feedbackScore.confidence,
|
|
439
|
+
explanation: feedbackScore.explanation,
|
|
440
|
+
signals: feedbackScore.signals.map(s => ({
|
|
441
|
+
type: s.type,
|
|
442
|
+
selector: s.selector,
|
|
443
|
+
confidence: s.confidence,
|
|
444
|
+
})),
|
|
445
|
+
topSignals: feedbackScore.topSignals,
|
|
446
|
+
},
|
|
447
|
+
beforeAfter: {
|
|
448
|
+
beforeScreenshot: trace.before?.screenshot || null,
|
|
449
|
+
afterScreenshot: trace.after?.screenshot || null,
|
|
450
|
+
beforeUrl: trace.before?.url || null,
|
|
451
|
+
afterUrl: trace.after?.url || null,
|
|
452
|
+
beforeDomHash: trace.dom?.beforeHash || null,
|
|
453
|
+
afterDomHash: trace.dom?.afterHash || null,
|
|
454
|
+
},
|
|
455
|
+
promise: {
|
|
456
|
+
type: expectation?.type || null,
|
|
457
|
+
value: expectation?.promise?.value || expectation?.targetPath || null,
|
|
458
|
+
source: expectation?.source || null,
|
|
459
|
+
context: expectation?.source?.context || null,
|
|
460
|
+
astSource: expectation?.source?.astSource || null,
|
|
461
|
+
},
|
|
462
|
+
sensors: {
|
|
463
|
+
uiSignals: trace.sensors?.uiSignals || null,
|
|
464
|
+
uiFeedback: trace.sensors?.uiFeedback || null,
|
|
465
|
+
network: trace.sensors?.network || null,
|
|
466
|
+
navigation: trace.sensors?.navigation || null,
|
|
467
|
+
},
|
|
468
|
+
correlation: {
|
|
469
|
+
outcome: correlation.outcome,
|
|
470
|
+
confidence: correlation.confidence,
|
|
471
|
+
reason: correlation.reason,
|
|
472
|
+
},
|
|
473
|
+
timing: {
|
|
474
|
+
// Timing window information if available
|
|
475
|
+
stabilizationWindow: trace.sensors?.uiFeedback?.timing || null,
|
|
476
|
+
},
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
return evidence;
|
|
480
|
+
}
|
|
481
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conditional UI Stale State Detection
|
|
3
|
+
*
|
|
4
|
+
* Detects UI elements that should update/disappear when state changes but don't:
|
|
5
|
+
* - State mutation observed (via state sensor or console logs)
|
|
6
|
+
* - Dependent UI element remains unchanged
|
|
7
|
+
* - Expected element missing or still visible
|
|
8
|
+
*
|
|
9
|
+
* CONFIDENCE: MEDIUM (heuristic-based)
|
|
10
|
+
* PARTIAL SUPPORT: Heuristic detection, not semantic analysis
|
|
11
|
+
*
|
|
12
|
+
* Note: Does NOT detect async race conditions (UNSUPPORTED - too many false positives)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { hasDomChange } from './comparison.js';
|
|
16
|
+
import { enrichFindingWithExplanations } from './finding-detector.js';
|
|
17
|
+
|
|
18
|
+
export function detectConditionalUiSilentFailures(traces, manifest, findings) {
|
|
19
|
+
// Parameters:
|
|
20
|
+
// traces - array of interaction traces from observation
|
|
21
|
+
// manifest - project manifest (contains expectations)
|
|
22
|
+
// findings - array to append new findings to (mutated in-place)
|
|
23
|
+
|
|
24
|
+
for (const trace of traces) {
|
|
25
|
+
const sensors = trace.sensors || {};
|
|
26
|
+
const stateSignals = sensors.state || {};
|
|
27
|
+
const uiSignals = sensors.uiSignals || {};
|
|
28
|
+
const uiDiff = uiSignals.diff || {};
|
|
29
|
+
const domChanged = hasDomChange(trace);
|
|
30
|
+
|
|
31
|
+
// Heuristic: State changed but UI didn't
|
|
32
|
+
// This suggests a stale UI or conditional rendering bug
|
|
33
|
+
const stateChanged = stateSignals.changed === true ||
|
|
34
|
+
(stateSignals.changed && stateSignals.changed.length > 0);
|
|
35
|
+
const uiChanged = uiDiff.changed === true;
|
|
36
|
+
|
|
37
|
+
// Detection logic:
|
|
38
|
+
// State changed but UI did not
|
|
39
|
+
// This is a common pattern for stale UI bugs in React/Vue
|
|
40
|
+
if (stateChanged && !uiChanged && !domChanged) {
|
|
41
|
+
const interaction = trace.interaction || {};
|
|
42
|
+
|
|
43
|
+
// Skip if it's a form input (expected state-only change)
|
|
44
|
+
if (interaction.type === 'interaction' && interaction.category === 'button') {
|
|
45
|
+
const evidence = {
|
|
46
|
+
before: trace.before?.screenshot || trace.beforeScreenshot || '',
|
|
47
|
+
after: trace.after?.screenshot || trace.afterScreenshot || '',
|
|
48
|
+
beforeUrl: trace.before?.url || trace.beforeUrl || '',
|
|
49
|
+
afterUrl: trace.after?.url || trace.afterUrl || '',
|
|
50
|
+
stateChanged,
|
|
51
|
+
uiChanged,
|
|
52
|
+
domChanged,
|
|
53
|
+
reason: 'State updated but UI did not reflect the change (stale UI)'
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const finding = {
|
|
57
|
+
type: 'conditional_ui_silent_failure',
|
|
58
|
+
description: `Conditional UI element did not update when state changed`,
|
|
59
|
+
summary: `Button/interaction caused state change but UI did not update (stale UI pattern)`,
|
|
60
|
+
explanation: `State mutation was detected but the UI elements dependent on that state did not update. This is a common pattern in React/Vue applications where conditional rendering or dynamic classes don't update properly.`,
|
|
61
|
+
evidence,
|
|
62
|
+
confidence: {
|
|
63
|
+
level: 0.60, // MEDIUM - heuristic-based
|
|
64
|
+
reasons: [
|
|
65
|
+
'State mutation observed',
|
|
66
|
+
'UI elements did not update to reflect new state',
|
|
67
|
+
'Likely stale UI or conditional rendering bug'
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
promise: {
|
|
71
|
+
type: 'conditional_ui_update',
|
|
72
|
+
expected: 'State change triggers UI update (conditional render, class change, etc.)',
|
|
73
|
+
actual: 'State changed but UI remained unchanged'
|
|
74
|
+
},
|
|
75
|
+
capabilityNote: 'PARTIAL SUPPORT: Detection based on state sensor data and heuristics. Does not perform semantic analysis of UI logic. Async race conditions are UNSUPPORTED due to high false positive rate.'
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Enrich with explanations
|
|
79
|
+
enrichFindingWithExplanations(finding, trace);
|
|
80
|
+
findings.push(finding);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|