@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,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 12 — Route Intelligence Findings Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects route-related silent failures by correlating navigation promises
|
|
5
|
+
* with route definitions and evaluating outcomes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
buildRouteModels,
|
|
10
|
+
correlateNavigationWithRoute,
|
|
11
|
+
evaluateRouteNavigation,
|
|
12
|
+
buildRouteEvidence,
|
|
13
|
+
isRouteChangeFalsePositive,
|
|
14
|
+
} from '../core/route-intelligence.js';
|
|
15
|
+
import { computeConfidence } from './confidence-engine.js';
|
|
16
|
+
import { computeConfidenceForFinding } from '../core/confidence-engine.js';
|
|
17
|
+
import { buildAndEnforceEvidencePackage } from '../core/evidence-builder.js';
|
|
18
|
+
import { applyGuardrails } from '../core/guardrails-engine.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* PHASE 12: Detect route-related findings
|
|
22
|
+
*
|
|
23
|
+
* @param {Array} traces - Interaction traces
|
|
24
|
+
* @param {Object} manifest - Project manifest with routes and expectations
|
|
25
|
+
* @param {Array} findings - Findings array to append to
|
|
26
|
+
* @returns {Array} Route-related findings
|
|
27
|
+
*/
|
|
28
|
+
export function detectRouteFindings(traces, manifest, findings) {
|
|
29
|
+
const routeFindings = [];
|
|
30
|
+
|
|
31
|
+
// Build route models from manifest routes
|
|
32
|
+
const routeModels = buildRouteModels(manifest.routes || []);
|
|
33
|
+
|
|
34
|
+
// Process each trace
|
|
35
|
+
for (const trace of traces) {
|
|
36
|
+
const interaction = trace.interaction || {};
|
|
37
|
+
const beforeUrl = trace.before?.url || trace.sensors?.navigation?.beforeUrl || '';
|
|
38
|
+
const afterUrl = trace.after?.url || trace.sensors?.navigation?.afterUrl || '';
|
|
39
|
+
|
|
40
|
+
// Find navigation expectations for this interaction
|
|
41
|
+
const navigationExpectations = findNavigationExpectations(manifest, interaction, beforeUrl);
|
|
42
|
+
|
|
43
|
+
for (const expectation of navigationExpectations) {
|
|
44
|
+
const navigationTarget = expectation.targetPath || expectation.expectedTarget || '';
|
|
45
|
+
|
|
46
|
+
if (!navigationTarget) continue;
|
|
47
|
+
|
|
48
|
+
// Correlate navigation promise with route
|
|
49
|
+
const correlation = correlateNavigationWithRoute(navigationTarget, routeModels);
|
|
50
|
+
|
|
51
|
+
// Check for false positives
|
|
52
|
+
if (isRouteChangeFalsePositive(trace, correlation)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Evaluate route navigation outcome
|
|
57
|
+
const evaluation = evaluateRouteNavigation(correlation, trace, beforeUrl, afterUrl);
|
|
58
|
+
|
|
59
|
+
// Generate finding if needed
|
|
60
|
+
if (evaluation.outcome === 'SILENT_FAILURE' ||
|
|
61
|
+
evaluation.outcome === 'ROUTE_MISMATCH' ||
|
|
62
|
+
(evaluation.outcome === 'SUSPECTED' && evaluation.confidence >= 0.6)) {
|
|
63
|
+
|
|
64
|
+
// Build evidence
|
|
65
|
+
const evidence = buildRouteEvidence(correlation, expectation, evaluation, trace);
|
|
66
|
+
|
|
67
|
+
// Determine finding type
|
|
68
|
+
let findingType = 'route_silent_failure';
|
|
69
|
+
let reason = evaluation.reason || 'Route navigation promise not fulfilled';
|
|
70
|
+
|
|
71
|
+
if (evaluation.outcome === 'ROUTE_MISMATCH') {
|
|
72
|
+
findingType = 'route_mismatch';
|
|
73
|
+
reason = `Navigation occurred but target route does not match. Expected: ${correlation?.route?.path}, Actual: ${evidence.beforeAfter.afterUrl}`;
|
|
74
|
+
} else if (evaluation.outcome === 'SUSPECTED') {
|
|
75
|
+
findingType = 'route_ambiguous';
|
|
76
|
+
reason = 'Dynamic route cannot be deterministically validated';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// PHASE 15: Compute unified confidence
|
|
80
|
+
const unifiedConfidence = computeConfidenceForFinding({
|
|
81
|
+
findingType: findingType,
|
|
82
|
+
expectation,
|
|
83
|
+
sensors: trace.sensors || {},
|
|
84
|
+
comparisons: {
|
|
85
|
+
urlChanged: evidence.signals.urlChanged,
|
|
86
|
+
domChanged: evidence.signals.domChanged,
|
|
87
|
+
},
|
|
88
|
+
evidence,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// PHASE 12: Evidence Law - require sufficient evidence for CONFIRMED
|
|
92
|
+
const hasSufficientEvidence = evidence.beforeAfter.beforeUrl &&
|
|
93
|
+
evidence.beforeAfter.afterUrl &&
|
|
94
|
+
(evidence.signals.urlChanged ||
|
|
95
|
+
evidence.signals.routerStateChanged ||
|
|
96
|
+
evidence.signals.uiChanged ||
|
|
97
|
+
evidence.signals.domChanged);
|
|
98
|
+
|
|
99
|
+
const severity = hasSufficientEvidence && unifiedConfidence.score >= 0.8 ? 'CONFIRMED' : 'SUSPECTED';
|
|
100
|
+
|
|
101
|
+
const finding = {
|
|
102
|
+
type: findingType,
|
|
103
|
+
severity,
|
|
104
|
+
confidence: unifiedConfidence.score, // PHASE 15: Use unified confidence score (0..1)
|
|
105
|
+
confidenceLevel: unifiedConfidence.level, // PHASE 15: Add confidence level
|
|
106
|
+
confidenceReasons: unifiedConfidence.reasons, // PHASE 15: Add stable reason codes
|
|
107
|
+
interaction: {
|
|
108
|
+
type: interaction.type,
|
|
109
|
+
selector: interaction.selector,
|
|
110
|
+
label: interaction.label,
|
|
111
|
+
},
|
|
112
|
+
reason,
|
|
113
|
+
evidence,
|
|
114
|
+
source: {
|
|
115
|
+
file: expectation.source?.file || null,
|
|
116
|
+
line: expectation.source?.line || null,
|
|
117
|
+
column: expectation.source?.column || null,
|
|
118
|
+
context: expectation.source?.context || null,
|
|
119
|
+
astSource: expectation.source?.astSource || expectation.metadata?.astSource || null,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// PHASE 16: Build and enforce evidence package
|
|
124
|
+
const findingWithEvidence = buildAndEnforceEvidencePackage(finding, {
|
|
125
|
+
expectation,
|
|
126
|
+
trace,
|
|
127
|
+
evidence,
|
|
128
|
+
confidence: unifiedConfidence,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// PHASE 17: Apply guardrails (AFTER evidence builder)
|
|
132
|
+
const context = {
|
|
133
|
+
evidencePackage: findingWithEvidence.evidencePackage,
|
|
134
|
+
signals: findingWithEvidence.evidencePackage?.signals || evidence.signals || {},
|
|
135
|
+
confidenceReasons: unifiedConfidence.reasons || [],
|
|
136
|
+
promiseType: expectation?.type || null,
|
|
137
|
+
};
|
|
138
|
+
const { finding: findingWithGuardrails } = applyGuardrails(findingWithEvidence, context);
|
|
139
|
+
|
|
140
|
+
routeFindings.push(findingWithGuardrails);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return routeFindings;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Find navigation expectations matching the interaction
|
|
150
|
+
*/
|
|
151
|
+
function findNavigationExpectations(manifest, interaction, beforeUrl) {
|
|
152
|
+
const expectations = [];
|
|
153
|
+
|
|
154
|
+
// Check static expectations
|
|
155
|
+
if (manifest.staticExpectations) {
|
|
156
|
+
const beforePath = extractPathFromUrl(beforeUrl);
|
|
157
|
+
if (beforePath) {
|
|
158
|
+
const normalizedBefore = beforePath.replace(/\/$/, '') || '/';
|
|
159
|
+
|
|
160
|
+
for (const expectation of manifest.staticExpectations) {
|
|
161
|
+
if (expectation.type !== 'navigation' && expectation.type !== 'spa_navigation') {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const normalizedFrom = (expectation.fromPath || '').replace(/\/$/, '') || '/';
|
|
166
|
+
if (normalizedFrom === normalizedBefore) {
|
|
167
|
+
// Check selector match
|
|
168
|
+
const selectorHint = expectation.selectorHint || '';
|
|
169
|
+
const interactionSelector = interaction.selector || '';
|
|
170
|
+
|
|
171
|
+
if (!selectorHint || !interactionSelector ||
|
|
172
|
+
selectorHint === interactionSelector ||
|
|
173
|
+
selectorHint.includes(interactionSelector) ||
|
|
174
|
+
interactionSelector.includes(selectorHint)) {
|
|
175
|
+
expectations.push(expectation);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Check expectations from intel (AST-based)
|
|
183
|
+
if (manifest.expectations) {
|
|
184
|
+
for (const expectation of manifest.expectations) {
|
|
185
|
+
if (expectation.type === 'navigation' || expectation.type === 'spa_navigation') {
|
|
186
|
+
// Match by selector or label
|
|
187
|
+
const selectorHint = expectation.selectorHint || '';
|
|
188
|
+
const interactionSelector = interaction.selector || '';
|
|
189
|
+
const interactionLabel = (interaction.label || '').toLowerCase();
|
|
190
|
+
const expectationLabel = (expectation.promise?.value || '').toLowerCase();
|
|
191
|
+
|
|
192
|
+
if (selectorHint === interactionSelector ||
|
|
193
|
+
(expectationLabel && interactionLabel && expectationLabel.includes(interactionLabel))) {
|
|
194
|
+
expectations.push(expectation);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return expectations;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Extract path from URL
|
|
205
|
+
*/
|
|
206
|
+
function extractPathFromUrl(url) {
|
|
207
|
+
if (!url || typeof url !== 'string') return '';
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const urlObj = new URL(url);
|
|
211
|
+
return urlObj.pathname;
|
|
212
|
+
} catch {
|
|
213
|
+
// Relative URL
|
|
214
|
+
const pathMatch = url.match(/^([^?#]+)/);
|
|
215
|
+
return pathMatch ? pathMatch[1] : url;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
@@ -136,8 +136,8 @@ export function mapOwnership(finding, trace = {}) {
|
|
|
136
136
|
const findingType = finding.type || 'unknown';
|
|
137
137
|
const sensors = trace.sensors || {};
|
|
138
138
|
const hasNetwork = (sensors.network?.totalRequests || 0) > 0;
|
|
139
|
-
const
|
|
140
|
-
const
|
|
139
|
+
const _hasAria = sensors.aria !== undefined;
|
|
140
|
+
const _hasFocus = sensors.focus !== undefined;
|
|
141
141
|
const hasTiming = sensors.timing !== undefined;
|
|
142
142
|
|
|
143
143
|
// ACCESSIBILITY: Focus, ARIA, keyboard trap failures
|
|
@@ -54,8 +54,8 @@ export function classifySkipReason(manifest, interaction, beforeUrl, validation
|
|
|
54
54
|
const interactionSelector = interaction.selector || '';
|
|
55
55
|
|
|
56
56
|
if (selectorHint && interactionSelector) {
|
|
57
|
-
const normalizedSelectorHint = selectorHint.replace(/[
|
|
58
|
-
const normalizedInteractionSelector = interactionSelector.replace(/[
|
|
57
|
+
const normalizedSelectorHint = selectorHint.replace(/[[\]()]/g, '');
|
|
58
|
+
const normalizedInteractionSelector = interactionSelector.replace(/[[\]()]/g, '');
|
|
59
59
|
|
|
60
60
|
if (selectorHint === interactionSelector ||
|
|
61
61
|
selectorHint.includes(interactionSelector) ||
|
|
@@ -76,8 +76,8 @@ export function classifySkipReason(manifest, interaction, beforeUrl, validation
|
|
|
76
76
|
const interactionSelector = interaction.selector || '';
|
|
77
77
|
|
|
78
78
|
if (selectorHint && interactionSelector) {
|
|
79
|
-
const normalizedSelectorHint = selectorHint.replace(/[
|
|
80
|
-
const normalizedInteractionSelector = interactionSelector.replace(/[
|
|
79
|
+
const normalizedSelectorHint = selectorHint.replace(/[[\]()]/g, '');
|
|
80
|
+
const normalizedInteractionSelector = interactionSelector.replace(/[[\]()]/g, '');
|
|
81
81
|
|
|
82
82
|
if (selectorHint === interactionSelector ||
|
|
83
83
|
selectorHint.includes(interactionSelector) ||
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 13 — UI Feedback Findings Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects UI feedback-related silent failures by correlating promises
|
|
5
|
+
* with feedback signals and evaluating outcomes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
detectUIFeedbackSignals,
|
|
10
|
+
scoreUIFeedback,
|
|
11
|
+
correlatePromiseWithFeedback,
|
|
12
|
+
buildUIFeedbackEvidence,
|
|
13
|
+
FEEDBACK_SCORE,
|
|
14
|
+
} from '../core/ui-feedback-intelligence.js';
|
|
15
|
+
import { computeConfidence } from './confidence-engine.js';
|
|
16
|
+
import { computeConfidenceForFinding } from '../core/confidence-engine.js';
|
|
17
|
+
import { buildAndEnforceEvidencePackage } from '../core/evidence-builder.js';
|
|
18
|
+
import { applyGuardrails } from '../core/guardrails-engine.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* PHASE 13: Detect UI feedback-related findings
|
|
22
|
+
*
|
|
23
|
+
* @param {Array} traces - Interaction traces
|
|
24
|
+
* @param {Object} manifest - Project manifest with expectations
|
|
25
|
+
* @param {Array} findings - Findings array to append to
|
|
26
|
+
* @returns {Array} UI feedback-related findings
|
|
27
|
+
*/
|
|
28
|
+
export function detectUIFeedbackFindings(traces, manifest, findings) {
|
|
29
|
+
const feedbackFindings = [];
|
|
30
|
+
|
|
31
|
+
// Process each trace
|
|
32
|
+
for (const trace of traces) {
|
|
33
|
+
const interaction = trace.interaction || {};
|
|
34
|
+
|
|
35
|
+
// Find expectations for this interaction
|
|
36
|
+
const expectations = findExpectationsForInteraction(manifest, interaction, trace);
|
|
37
|
+
|
|
38
|
+
for (const expectation of expectations) {
|
|
39
|
+
// Detect UI feedback signals
|
|
40
|
+
const signals = detectUIFeedbackSignals(trace);
|
|
41
|
+
|
|
42
|
+
// Score feedback presence/absence
|
|
43
|
+
const feedbackScore = scoreUIFeedback(signals, expectation, trace);
|
|
44
|
+
|
|
45
|
+
// Correlate promise with feedback
|
|
46
|
+
const correlation = correlatePromiseWithFeedback(expectation, feedbackScore, trace);
|
|
47
|
+
|
|
48
|
+
// Generate finding if correlation indicates silent failure
|
|
49
|
+
if (correlation.outcome === 'CONFIRMED' || correlation.outcome === 'SUSPECTED') {
|
|
50
|
+
// Build evidence
|
|
51
|
+
const evidence = buildUIFeedbackEvidence(feedbackScore, correlation, trace, expectation);
|
|
52
|
+
|
|
53
|
+
// PHASE 13: Evidence Law - require sufficient evidence for CONFIRMED
|
|
54
|
+
const hasSufficientEvidence = evidence.beforeAfter.beforeScreenshot &&
|
|
55
|
+
evidence.beforeAfter.afterScreenshot &&
|
|
56
|
+
(evidence.feedback.signals.length > 0 ||
|
|
57
|
+
evidence.feedback.score === FEEDBACK_SCORE.MISSING);
|
|
58
|
+
|
|
59
|
+
// PHASE 15: Compute unified confidence
|
|
60
|
+
const unifiedConfidence = computeConfidenceForFinding({
|
|
61
|
+
findingType: findingType,
|
|
62
|
+
expectation,
|
|
63
|
+
sensors: trace.sensors || {},
|
|
64
|
+
comparisons: {},
|
|
65
|
+
evidence,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Legacy confidence for backward compatibility
|
|
69
|
+
const confidence = computeConfidence({
|
|
70
|
+
findingType: 'ui_feedback_silent_failure',
|
|
71
|
+
expectation,
|
|
72
|
+
sensors: trace.sensors || {},
|
|
73
|
+
comparisons: {},
|
|
74
|
+
attemptMeta: {},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Determine severity based on evidence
|
|
78
|
+
const severity = hasSufficientEvidence && correlation.outcome === 'CONFIRMED' && unifiedConfidence.score >= 0.8
|
|
79
|
+
? 'CONFIRMED'
|
|
80
|
+
: 'SUSPECTED';
|
|
81
|
+
|
|
82
|
+
// Determine finding type
|
|
83
|
+
let findingType = 'ui_feedback_silent_failure';
|
|
84
|
+
if (expectation.type === 'network_action' || expectation.type === 'network') {
|
|
85
|
+
findingType = 'network_feedback_missing';
|
|
86
|
+
} else if (expectation.type === 'navigation' || expectation.type === 'spa_navigation') {
|
|
87
|
+
findingType = 'navigation_feedback_missing';
|
|
88
|
+
} else if (expectation.type === 'validation' || expectation.type === 'form_submission') {
|
|
89
|
+
findingType = 'validation_feedback_missing';
|
|
90
|
+
} else if (expectation.type === 'state_action' || expectation.type === 'state') {
|
|
91
|
+
findingType = 'state_feedback_missing';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const finding = {
|
|
95
|
+
type: findingType,
|
|
96
|
+
severity,
|
|
97
|
+
confidence: unifiedConfidence.score, // PHASE 15: Use unified confidence score (0..1)
|
|
98
|
+
confidenceLevel: unifiedConfidence.level, // PHASE 15: Add confidence level
|
|
99
|
+
confidenceReasons: unifiedConfidence.reasons, // PHASE 15: Add stable reason codes
|
|
100
|
+
interaction: {
|
|
101
|
+
type: interaction.type,
|
|
102
|
+
selector: interaction.selector,
|
|
103
|
+
label: interaction.label,
|
|
104
|
+
},
|
|
105
|
+
reason: correlation.reason || feedbackScore.explanation,
|
|
106
|
+
evidence,
|
|
107
|
+
source: {
|
|
108
|
+
file: expectation.source?.file || null,
|
|
109
|
+
line: expectation.source?.line || null,
|
|
110
|
+
column: expectation.source?.column || null,
|
|
111
|
+
context: expectation.source?.context || null,
|
|
112
|
+
astSource: expectation.source?.astSource || null,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// PHASE 16: Build and enforce evidence package
|
|
117
|
+
const findingWithEvidence = buildAndEnforceEvidencePackage(finding, {
|
|
118
|
+
expectation,
|
|
119
|
+
trace,
|
|
120
|
+
evidence,
|
|
121
|
+
confidence: unifiedConfidence,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// PHASE 17: Apply guardrails (AFTER evidence builder)
|
|
125
|
+
const context = {
|
|
126
|
+
evidencePackage: findingWithEvidence.evidencePackage,
|
|
127
|
+
signals: findingWithEvidence.evidencePackage?.signals || evidence.signals || {},
|
|
128
|
+
confidenceReasons: unifiedConfidence.reasons || [],
|
|
129
|
+
promiseType: expectation?.type || null,
|
|
130
|
+
};
|
|
131
|
+
const { finding: findingWithGuardrails } = applyGuardrails(findingWithEvidence, context);
|
|
132
|
+
|
|
133
|
+
feedbackFindings.push(findingWithGuardrails);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return feedbackFindings;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Find expectations matching the interaction
|
|
143
|
+
*/
|
|
144
|
+
function findExpectationsForInteraction(manifest, interaction, trace) {
|
|
145
|
+
const expectations = [];
|
|
146
|
+
|
|
147
|
+
// Check static expectations
|
|
148
|
+
if (manifest.staticExpectations) {
|
|
149
|
+
const beforeUrl = trace.before?.url || trace.sensors?.navigation?.beforeUrl || '';
|
|
150
|
+
const beforePath = extractPathFromUrl(beforeUrl);
|
|
151
|
+
|
|
152
|
+
if (beforePath) {
|
|
153
|
+
const normalizedBefore = beforePath.replace(/\/$/, '') || '/';
|
|
154
|
+
|
|
155
|
+
for (const expectation of manifest.staticExpectations) {
|
|
156
|
+
const normalizedFrom = (expectation.fromPath || '').replace(/\/$/, '') || '/';
|
|
157
|
+
if (normalizedFrom === normalizedBefore) {
|
|
158
|
+
// Check selector match
|
|
159
|
+
const selectorHint = expectation.selectorHint || '';
|
|
160
|
+
const interactionSelector = interaction.selector || '';
|
|
161
|
+
|
|
162
|
+
if (!selectorHint || !interactionSelector ||
|
|
163
|
+
selectorHint === interactionSelector ||
|
|
164
|
+
selectorHint.includes(interactionSelector) ||
|
|
165
|
+
interactionSelector.includes(selectorHint)) {
|
|
166
|
+
expectations.push(expectation);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Check expectations from intel (AST-based)
|
|
174
|
+
if (manifest.expectations) {
|
|
175
|
+
for (const expectation of manifest.expectations) {
|
|
176
|
+
// Match by selector or label
|
|
177
|
+
const selectorHint = expectation.selectorHint || '';
|
|
178
|
+
const interactionSelector = interaction.selector || '';
|
|
179
|
+
const interactionLabel = (interaction.label || '').toLowerCase();
|
|
180
|
+
const expectationLabel = (expectation.promise?.value || '').toLowerCase();
|
|
181
|
+
|
|
182
|
+
if (selectorHint === interactionSelector ||
|
|
183
|
+
(expectationLabel && interactionLabel && expectationLabel.includes(interactionLabel))) {
|
|
184
|
+
expectations.push(expectation);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return expectations;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Extract path from URL
|
|
194
|
+
*/
|
|
195
|
+
function extractPathFromUrl(url) {
|
|
196
|
+
if (!url || typeof url !== 'string') return '';
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const urlObj = new URL(url);
|
|
200
|
+
return urlObj.pathname;
|
|
201
|
+
} catch {
|
|
202
|
+
// Relative URL
|
|
203
|
+
const pathMatch = url.match(/^([^?#]+)/);
|
|
204
|
+
return pathMatch ? pathMatch[1] : url;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
@@ -14,9 +14,7 @@
|
|
|
14
14
|
* PHASE 4: All observations include Silence lifecycle - type, trigger, evaluation status, confidence impact.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import
|
|
18
|
-
import path from 'path';
|
|
19
|
-
import { buildEvidenceIndex, writeEvidenceIndex } from './evidence-index.js';
|
|
17
|
+
import { buildEvidenceIndex } from './evidence-index.js';
|
|
20
18
|
import { CANONICAL_OUTCOMES } from '../core/canonical-outcomes.js';
|
|
21
19
|
import { SILENCE_TYPES, EVALUATION_STATUS } from '../core/silence-model.js';
|
|
22
20
|
import { inferPromiseFromInteraction } from '../core/promise-model.js';
|
|
@@ -57,29 +55,63 @@ export function computeObservationSummary(findings, observeTruth, learnTruth, co
|
|
|
57
55
|
);
|
|
58
56
|
const skippedCount = coverage.skippedInteractions || 0;
|
|
59
57
|
|
|
60
|
-
// Count findings by confidence
|
|
58
|
+
// PHASE 15: Count findings by unified confidence level
|
|
61
59
|
const findingsByConfidence = {
|
|
62
60
|
HIGH: 0,
|
|
63
61
|
MEDIUM: 0,
|
|
64
62
|
LOW: 0,
|
|
65
|
-
|
|
63
|
+
UNPROVEN: 0 // PHASE 15: Changed from UNKNOWN to UNPROVEN
|
|
66
64
|
};
|
|
67
65
|
const findingsByType = {};
|
|
68
66
|
const findingsByOutcome = {}; // PHASE 2: Added outcome tracking
|
|
69
67
|
const findingsByPromise = {}; // PHASE 3: Added promise tracking
|
|
70
68
|
|
|
71
69
|
for (const finding of (findings || [])) {
|
|
72
|
-
|
|
70
|
+
// PHASE 15: Use unified confidence level
|
|
71
|
+
const confidence = finding.confidenceLevel || finding.confidence?.level || 'UNPROVEN';
|
|
73
72
|
const type = finding.type || 'unknown';
|
|
74
73
|
const outcome = finding.outcome || CANONICAL_OUTCOMES.SILENT_FAILURE; // Default for legacy findings
|
|
75
74
|
const promiseType = finding.promise?.type || 'UNKNOWN_PROMISE'; // PHASE 3
|
|
76
75
|
|
|
77
|
-
if (
|
|
76
|
+
if (Object.prototype.hasOwnProperty.call(findingsByConfidence, confidence)) {
|
|
78
77
|
findingsByConfidence[confidence]++;
|
|
79
78
|
}
|
|
80
79
|
findingsByType[type] = (findingsByType[type] || 0) + 1;
|
|
81
80
|
findingsByOutcome[outcome] = (findingsByOutcome[outcome] || 0) + 1; // PHASE 2
|
|
82
81
|
findingsByPromise[promiseType] = (findingsByPromise[promiseType] || 0) + 1; // PHASE 3
|
|
82
|
+
|
|
83
|
+
// PHASE 16: Track evidence completeness
|
|
84
|
+
evidenceCompleteness.totalFindings++;
|
|
85
|
+
if (finding.evidencePackage) {
|
|
86
|
+
if (finding.evidencePackage.isComplete) {
|
|
87
|
+
evidenceCompleteness.completeEvidence++;
|
|
88
|
+
} else {
|
|
89
|
+
evidenceCompleteness.incompleteEvidence++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (finding.evidenceCompleteness?.downgraded) {
|
|
93
|
+
evidenceCompleteness.downgradedCount++;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// PHASE 17: Track guardrails
|
|
97
|
+
if (finding.guardrails) {
|
|
98
|
+
guardrailsSummary.totalFindingsProcessed++;
|
|
99
|
+
if (finding.guardrails.appliedRules) {
|
|
100
|
+
guardrailsSummary.appliedRulesCount += finding.guardrails.appliedRules.length;
|
|
101
|
+
}
|
|
102
|
+
if (finding.guardrails.contradictions) {
|
|
103
|
+
guardrailsSummary.contradictionsCount += finding.guardrails.contradictions.length;
|
|
104
|
+
}
|
|
105
|
+
if (finding.guardrails.finalDecision === 'SUSPECTED' && finding.severity === 'CONFIRMED') {
|
|
106
|
+
guardrailsSummary.downgradedCount++;
|
|
107
|
+
}
|
|
108
|
+
if (finding.guardrails.finalDecision === 'INFORMATIONAL') {
|
|
109
|
+
guardrailsSummary.informationalCount++;
|
|
110
|
+
}
|
|
111
|
+
if (finding.guardrails.recommendedStatus !== finding.severity && finding.severity === 'CONFIRMED') {
|
|
112
|
+
guardrailsSummary.preventedConfirmedCount++;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
83
115
|
}
|
|
84
116
|
|
|
85
117
|
// Calculate ratios (factual, not judgmental)
|
|
@@ -96,6 +128,24 @@ export function computeObservationSummary(findings, observeTruth, learnTruth, co
|
|
|
96
128
|
unprovenResults: unprovenTraces.length
|
|
97
129
|
};
|
|
98
130
|
|
|
131
|
+
// PHASE 16: Track evidence completeness summary
|
|
132
|
+
const evidenceCompleteness = {
|
|
133
|
+
totalFindings: 0,
|
|
134
|
+
completeEvidence: 0,
|
|
135
|
+
incompleteEvidence: 0,
|
|
136
|
+
downgradedCount: 0,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// PHASE 17: Track guardrails summary
|
|
140
|
+
const guardrailsSummary = {
|
|
141
|
+
totalFindingsProcessed: 0,
|
|
142
|
+
preventedConfirmedCount: 0,
|
|
143
|
+
downgradedCount: 0,
|
|
144
|
+
informationalCount: 0,
|
|
145
|
+
appliedRulesCount: 0,
|
|
146
|
+
contradictionsCount: 0,
|
|
147
|
+
};
|
|
148
|
+
|
|
99
149
|
// Build gap details
|
|
100
150
|
const gapDetails = [];
|
|
101
151
|
if (isBudgetExceeded) {
|
|
@@ -152,6 +202,8 @@ export function computeObservationSummary(findings, observeTruth, learnTruth, co
|
|
|
152
202
|
discrepanciesByPromise: findingsByPromise, // PHASE 3: Promise types
|
|
153
203
|
findings: findings || []
|
|
154
204
|
},
|
|
205
|
+
evidenceCompleteness: evidenceCompleteness, // PHASE 16: Evidence completeness summary
|
|
206
|
+
guardrailsSummary: guardrailsSummary, // PHASE 17: Guardrails summary
|
|
155
207
|
coverage: {
|
|
156
208
|
pagesEvaluated,
|
|
157
209
|
pagesDiscovered,
|
|
@@ -402,7 +454,7 @@ export function formatObservationSummary(observationSummary) {
|
|
|
402
454
|
for (const finding of obs.findings.slice(0, 3)) {
|
|
403
455
|
const outcome = finding.outcome ? ` [${finding.outcome}]` : '';
|
|
404
456
|
const promiseInfo = finding.promise ? ` (${finding.promise.type.replace(/_PROMISE$/, '')})` : '';
|
|
405
|
-
const
|
|
457
|
+
const _confStr = finding.confidence?.level ? ` (${finding.confidence.level} confidence)` : '';
|
|
406
458
|
const userStmt = finding.what_happened ? `User: ${finding.what_happened}` : '';
|
|
407
459
|
lines.push(` • ${finding.type}${outcome}${promiseInfo}`);
|
|
408
460
|
if (userStmt) lines.push(` ${userStmt}`);
|
|
@@ -454,7 +506,7 @@ export function formatObservationSummary(observationSummary) {
|
|
|
454
506
|
export function inferPromiseForSilence(silence) {
|
|
455
507
|
if (!silence) return null;
|
|
456
508
|
|
|
457
|
-
const { silence_type, scope, reason, context } = silence;
|
|
509
|
+
const { silence_type, scope: _scope, reason, context } = silence;
|
|
458
510
|
|
|
459
511
|
// Navigation-related silences
|
|
460
512
|
if (silence_type === SILENCE_TYPES.NAVIGATION_TIMEOUT ||
|