@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,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Problem Aggregator
|
|
3
|
+
*
|
|
4
|
+
* Groups raw findings into decision-level problems for executive review.
|
|
5
|
+
*
|
|
6
|
+
* RULES:
|
|
7
|
+
* - Deterministic grouping (no ML, no heuristics)
|
|
8
|
+
* - Every problem references underlying findings
|
|
9
|
+
* - Raw findings are preserved (never hidden)
|
|
10
|
+
* - Evidence is deduplicated across group
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { basename } from 'path';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Aggregate findings into decision-level problems
|
|
17
|
+
*
|
|
18
|
+
* @param {Array} findings - Raw findings from detection
|
|
19
|
+
* @param {Object} manifest - Project manifest
|
|
20
|
+
* @returns {Array} Array of ProblemGroup objects
|
|
21
|
+
*/
|
|
22
|
+
export function aggregateProblems(findings, manifest) {
|
|
23
|
+
if (!findings || findings.length === 0) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Group findings by page/route
|
|
28
|
+
const byPage = groupByPage(findings);
|
|
29
|
+
|
|
30
|
+
// Within each page, group by user intent
|
|
31
|
+
const problems = [];
|
|
32
|
+
|
|
33
|
+
for (const [page, pageFindings] of Object.entries(byPage)) {
|
|
34
|
+
const intentGroups = groupByIntent(pageFindings);
|
|
35
|
+
|
|
36
|
+
for (const [intent, intentFindings] of Object.entries(intentGroups)) {
|
|
37
|
+
// Create a problem group
|
|
38
|
+
const problem = createProblemGroup(page, intent, intentFindings, manifest);
|
|
39
|
+
if (problem) {
|
|
40
|
+
problems.push(problem);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Sort by impact (HIGH > MEDIUM > LOW) then confidence (HIGH > LOW)
|
|
46
|
+
problems.sort((a, b) => {
|
|
47
|
+
const impactOrder = { HIGH: 3, MEDIUM: 2, LOW: 1, UNKNOWN: 0 };
|
|
48
|
+
const impactDiff = impactOrder[b.impact] - impactOrder[a.impact];
|
|
49
|
+
if (impactDiff !== 0) return impactDiff;
|
|
50
|
+
return b.confidence - a.confidence;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return problems;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Group findings by page/route
|
|
58
|
+
*/
|
|
59
|
+
function groupByPage(findings) {
|
|
60
|
+
const groups = {};
|
|
61
|
+
|
|
62
|
+
for (const finding of findings) {
|
|
63
|
+
const page = extractPage(finding);
|
|
64
|
+
if (!groups[page]) {
|
|
65
|
+
groups[page] = [];
|
|
66
|
+
}
|
|
67
|
+
groups[page].push(finding);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return groups;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Extract page identifier from finding
|
|
75
|
+
*/
|
|
76
|
+
function extractPage(finding) {
|
|
77
|
+
if (!finding.source || !finding.source.file) {
|
|
78
|
+
return 'unknown';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Extract filename without extension
|
|
82
|
+
const file = finding.source.file;
|
|
83
|
+
const filename = basename(file, '.jsx').replace(/\.js$/, '');
|
|
84
|
+
|
|
85
|
+
// Map common patterns to routes
|
|
86
|
+
if (filename.match(/dashboard/i)) return 'dashboard';
|
|
87
|
+
if (filename.match(/profile/i)) return 'profile';
|
|
88
|
+
if (filename.match(/settings/i)) return 'settings';
|
|
89
|
+
if (filename.match(/home|index/i)) return 'home';
|
|
90
|
+
if (filename.match(/login|auth/i)) return 'auth';
|
|
91
|
+
|
|
92
|
+
return filename.toLowerCase();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Group findings by user intent
|
|
97
|
+
*/
|
|
98
|
+
function groupByIntent(findings) {
|
|
99
|
+
const groups = {};
|
|
100
|
+
|
|
101
|
+
for (const finding of findings) {
|
|
102
|
+
const intent = inferIntent(finding);
|
|
103
|
+
if (!groups[intent]) {
|
|
104
|
+
groups[intent] = [];
|
|
105
|
+
}
|
|
106
|
+
groups[intent].push(finding);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return groups;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Infer user intent from finding
|
|
114
|
+
*/
|
|
115
|
+
function inferIntent(finding) {
|
|
116
|
+
const promise = finding.promise || {};
|
|
117
|
+
const type = finding.type;
|
|
118
|
+
const source = finding.source || {};
|
|
119
|
+
const file = source.file || '';
|
|
120
|
+
|
|
121
|
+
// Form submission
|
|
122
|
+
if (type === 'form' || promise.kind === 'submit') {
|
|
123
|
+
return 'save';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Navigation
|
|
127
|
+
if (type === 'navigation' || promise.kind === 'navigate') {
|
|
128
|
+
return 'navigate';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Auth actions - explicit button clicks
|
|
132
|
+
if (promise.kind === 'click' && promise.value && promise.value.match(/login|logout|signin|signout/i)) {
|
|
133
|
+
return 'auth';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// State changes - be more specific about what kind
|
|
137
|
+
if (type === 'state' || promise.kind === 'state_mutation') {
|
|
138
|
+
const value = promise.value || '';
|
|
139
|
+
|
|
140
|
+
// Loading/saving state - usually indicates stuck loading indicator
|
|
141
|
+
if (value.match(/loading|saving|submitting/i) || file.match(/dashboard/i)) {
|
|
142
|
+
return 'loading_feedback';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Auth-related state
|
|
146
|
+
if (value.match(/auth|user|login/i) || file.match(/auth|profile/i)) {
|
|
147
|
+
// If in Profile.jsx specifically, it's conditional UI bug
|
|
148
|
+
if (file.match(/profile/i)) {
|
|
149
|
+
return 'conditional_ui';
|
|
150
|
+
}
|
|
151
|
+
return 'auth_state';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Generic state update
|
|
155
|
+
return 'state_update';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Feedback/validation
|
|
159
|
+
if (type === 'feedback' || promise.kind === 'validation') {
|
|
160
|
+
return 'validation';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Button clicks
|
|
164
|
+
if (promise.kind === 'click') {
|
|
165
|
+
return 'interact';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return 'other';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Create a problem group from findings
|
|
173
|
+
*/
|
|
174
|
+
function createProblemGroup(page, intent, findings, _manifest) {
|
|
175
|
+
if (findings.length === 0) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Generate problem ID
|
|
180
|
+
const id = `problem_${page}_${intent}`;
|
|
181
|
+
|
|
182
|
+
// Determine title based on intent
|
|
183
|
+
const title = generateTitle(page, intent, findings);
|
|
184
|
+
|
|
185
|
+
// Aggregate impact (highest wins)
|
|
186
|
+
const impact = aggregateImpact(findings);
|
|
187
|
+
|
|
188
|
+
// Aggregate confidence (average)
|
|
189
|
+
const confidence = aggregateConfidence(findings);
|
|
190
|
+
|
|
191
|
+
// Deduplicate evidence
|
|
192
|
+
const evidence = deduplicateEvidence(findings);
|
|
193
|
+
|
|
194
|
+
// Extract finding IDs
|
|
195
|
+
const findingIds = findings.map(f => f.id);
|
|
196
|
+
|
|
197
|
+
// Generate explanation
|
|
198
|
+
const explanation = generateExplanation(page, intent, findings);
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
id,
|
|
202
|
+
title,
|
|
203
|
+
page,
|
|
204
|
+
userIntent: intent,
|
|
205
|
+
impact,
|
|
206
|
+
confidence,
|
|
207
|
+
findings: findingIds,
|
|
208
|
+
findingCount: findings.length,
|
|
209
|
+
evidence,
|
|
210
|
+
whatUserTried: explanation.whatUserTried,
|
|
211
|
+
whatWasExpected: explanation.whatWasExpected,
|
|
212
|
+
whatActuallyHappened: explanation.whatActuallyHappened,
|
|
213
|
+
whyItMatters: explanation.whyItMatters
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Generate human-readable title for problem
|
|
219
|
+
*/
|
|
220
|
+
function generateTitle(page, intent, findings) {
|
|
221
|
+
const pageName = page.charAt(0).toUpperCase() + page.slice(1);
|
|
222
|
+
|
|
223
|
+
const intentMap = {
|
|
224
|
+
navigate: `${pageName} page doesn't load`,
|
|
225
|
+
save: `${pageName} form submission fails silently`,
|
|
226
|
+
auth: `${pageName} authentication fails silently`,
|
|
227
|
+
auth_state: `${pageName} authentication state doesn't update UI`,
|
|
228
|
+
conditional_ui: `${pageName} conditional UI doesn't update after state change`,
|
|
229
|
+
loading_feedback: `${pageName} loading state never resolves`,
|
|
230
|
+
state_update: `${pageName} state changes don't update UI`,
|
|
231
|
+
validation: `${pageName} validation feedback missing`,
|
|
232
|
+
interact: `${pageName} interactive elements don't work`,
|
|
233
|
+
other: `${pageName} has broken functionality`
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
return intentMap[intent] || `${pageName} has ${findings.length} silent failures`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Aggregate impact across findings (highest wins)
|
|
241
|
+
*/
|
|
242
|
+
function aggregateImpact(findings) {
|
|
243
|
+
const impactOrder = { HIGH: 3, MEDIUM: 2, LOW: 1, UNKNOWN: 0 };
|
|
244
|
+
let maxImpact = 'UNKNOWN';
|
|
245
|
+
let maxValue = 0;
|
|
246
|
+
|
|
247
|
+
for (const finding of findings) {
|
|
248
|
+
const impact = finding.impact || 'UNKNOWN';
|
|
249
|
+
const value = impactOrder[impact] || 0;
|
|
250
|
+
if (value > maxValue) {
|
|
251
|
+
maxValue = value;
|
|
252
|
+
maxImpact = impact;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return maxImpact;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Aggregate confidence across findings (average)
|
|
261
|
+
*/
|
|
262
|
+
function aggregateConfidence(findings) {
|
|
263
|
+
if (findings.length === 0) return 0;
|
|
264
|
+
|
|
265
|
+
const sum = findings.reduce((acc, f) => acc + (f.confidence || 0.5), 0);
|
|
266
|
+
return Math.round((sum / findings.length) * 100) / 100;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Deduplicate evidence across findings
|
|
271
|
+
*/
|
|
272
|
+
function deduplicateEvidence(findings) {
|
|
273
|
+
const evidenceMap = new Map();
|
|
274
|
+
|
|
275
|
+
for (const finding of findings) {
|
|
276
|
+
if (!finding.evidence) continue;
|
|
277
|
+
|
|
278
|
+
for (const ev of finding.evidence) {
|
|
279
|
+
const key = `${ev.type}:${ev.path || 'none'}`;
|
|
280
|
+
if (!evidenceMap.has(key)) {
|
|
281
|
+
evidenceMap.set(key, ev);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return Array.from(evidenceMap.values());
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Generate explanation for problem
|
|
291
|
+
*/
|
|
292
|
+
function generateExplanation(page, intent, findings) {
|
|
293
|
+
const pageName = page.charAt(0).toUpperCase() + page.slice(1);
|
|
294
|
+
const count = findings.length;
|
|
295
|
+
|
|
296
|
+
// Build explanation based on intent
|
|
297
|
+
const explanations = {
|
|
298
|
+
navigate: {
|
|
299
|
+
whatUserTried: `Navigate to ${pageName} page`,
|
|
300
|
+
whatWasExpected: 'Page content loads and displays',
|
|
301
|
+
whatActuallyHappened: `URL changed but page content did not render (${count} state mutations stuck)`,
|
|
302
|
+
whyItMatters: 'Users cannot access the intended page or see its content'
|
|
303
|
+
},
|
|
304
|
+
save: {
|
|
305
|
+
whatUserTried: `Submit form on ${pageName} page`,
|
|
306
|
+
whatWasExpected: 'Form submits and shows success feedback (message, redirect, or toast)',
|
|
307
|
+
whatActuallyHappened: `Form submitted but no feedback was provided to user (${count} related issues)`,
|
|
308
|
+
whyItMatters: 'Users don\'t know if their data was saved or if they should retry'
|
|
309
|
+
},
|
|
310
|
+
auth: {
|
|
311
|
+
whatUserTried: `Log in or log out using ${pageName} page buttons`,
|
|
312
|
+
whatWasExpected: 'Button click triggers authentication action with visual feedback',
|
|
313
|
+
whatActuallyHappened: `Button clicks produced no observable effect (${count} buttons don't work)`,
|
|
314
|
+
whyItMatters: 'Users cannot complete authentication workflows'
|
|
315
|
+
},
|
|
316
|
+
auth_state: {
|
|
317
|
+
whatUserTried: `Authenticate using ${pageName} page`,
|
|
318
|
+
whatWasExpected: 'Authentication state changes update UI accordingly',
|
|
319
|
+
whatActuallyHappened: `Auth state changed but UI did not reflect changes (${count} state mutations)`,
|
|
320
|
+
whyItMatters: 'Users cannot tell their authentication status'
|
|
321
|
+
},
|
|
322
|
+
conditional_ui: {
|
|
323
|
+
whatUserTried: `Interact with conditional UI on ${pageName} page`,
|
|
324
|
+
whatWasExpected: 'UI elements appear/disappear based on state (e.g., Login button hides after login)',
|
|
325
|
+
whatActuallyHappened: `State changed but conditional UI did not update (${count} stale elements)`,
|
|
326
|
+
whyItMatters: 'Users see UI elements that should be hidden or vice versa, causing confusion'
|
|
327
|
+
},
|
|
328
|
+
loading_feedback: {
|
|
329
|
+
whatUserTried: `Interact with ${pageName} page`,
|
|
330
|
+
whatWasExpected: 'Loading indicator appears then resolves when content is ready',
|
|
331
|
+
whatActuallyHappened: `Loading states were set but never cleared (${count} stuck states)`,
|
|
332
|
+
whyItMatters: 'Users see perpetual loading spinners or incomplete UI'
|
|
333
|
+
},
|
|
334
|
+
state_update: {
|
|
335
|
+
whatUserTried: `Interact with ${pageName} page elements`,
|
|
336
|
+
whatWasExpected: 'UI updates to reflect new state',
|
|
337
|
+
whatActuallyHappened: `State changed but UI did not update (${count} stale UI elements)`,
|
|
338
|
+
whyItMatters: 'Users see outdated information that doesn\'t match actual state'
|
|
339
|
+
},
|
|
340
|
+
validation: {
|
|
341
|
+
whatUserTried: `Submit invalid data on ${pageName} form`,
|
|
342
|
+
whatWasExpected: 'Validation errors display clearly',
|
|
343
|
+
whatActuallyHappened: `Validation feedback elements missing (${count} issues)`,
|
|
344
|
+
whyItMatters: 'Users cannot correct their mistakes or understand what went wrong'
|
|
345
|
+
},
|
|
346
|
+
interact: {
|
|
347
|
+
whatUserTried: `Click buttons or interact with elements on ${pageName}`,
|
|
348
|
+
whatWasExpected: 'Interactive elements respond with visible feedback',
|
|
349
|
+
whatActuallyHappened: `${count} interactive elements produced no observable effect`,
|
|
350
|
+
whyItMatters: 'Users cannot complete intended actions or workflows'
|
|
351
|
+
},
|
|
352
|
+
other: {
|
|
353
|
+
whatUserTried: `Use ${pageName} page functionality`,
|
|
354
|
+
whatWasExpected: 'Actions produce visible results',
|
|
355
|
+
whatActuallyHappened: `${count} actions failed silently with no user feedback`,
|
|
356
|
+
whyItMatters: 'Core functionality is broken but fails without error messages'
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
return explanations[intent] || explanations.other;
|
|
361
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
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 { computeConfidenceForFinding } from '../core/confidence-engine.js';
|
|
16
|
+
import { buildAndEnforceEvidencePackage } from '../core/evidence-builder.js';
|
|
17
|
+
import { applyGuardrails } from '../core/guardrails-engine.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* PHASE 12: Detect route-related findings
|
|
21
|
+
*
|
|
22
|
+
* @param {Array} traces - Interaction traces
|
|
23
|
+
* @param {Object} manifest - Project manifest with routes and expectations
|
|
24
|
+
* @param {Array} _findings - Findings array to append to
|
|
25
|
+
* @ts-expect-error - JSDoc param documented but unused
|
|
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
|
+
options: {}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// PHASE 12: Evidence Law - require sufficient evidence for CONFIRMED
|
|
93
|
+
const hasSufficientEvidence = evidence.beforeAfter.beforeUrl &&
|
|
94
|
+
evidence.beforeAfter.afterUrl &&
|
|
95
|
+
(evidence.signals.urlChanged ||
|
|
96
|
+
evidence.signals.routerStateChanged ||
|
|
97
|
+
evidence.signals.uiChanged ||
|
|
98
|
+
evidence.signals.domChanged);
|
|
99
|
+
|
|
100
|
+
const severity = hasSufficientEvidence && (unifiedConfidence.score01 || unifiedConfidence.score || 0) >= 0.8 ? 'CONFIRMED' : 'SUSPECTED';
|
|
101
|
+
|
|
102
|
+
const finding = {
|
|
103
|
+
type: findingType,
|
|
104
|
+
severity,
|
|
105
|
+
confidence: unifiedConfidence.score01 || unifiedConfidence.score || 0, // Contract v1: score01 canonical
|
|
106
|
+
confidenceLevel: unifiedConfidence.level, // PHASE 15: Add confidence level
|
|
107
|
+
confidenceReasons: unifiedConfidence.topReasons || unifiedConfidence.reasons || [], // Contract v1: topReasons
|
|
108
|
+
interaction: {
|
|
109
|
+
type: interaction.type,
|
|
110
|
+
selector: interaction.selector,
|
|
111
|
+
label: interaction.label,
|
|
112
|
+
},
|
|
113
|
+
reason,
|
|
114
|
+
evidence,
|
|
115
|
+
source: {
|
|
116
|
+
file: expectation.source?.file || null,
|
|
117
|
+
line: expectation.source?.line || null,
|
|
118
|
+
column: expectation.source?.column || null,
|
|
119
|
+
context: expectation.source?.context || null,
|
|
120
|
+
astSource: expectation.source?.astSource || expectation.metadata?.astSource || null,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// PHASE 16: Build and enforce evidence package
|
|
125
|
+
const findingWithEvidence = buildAndEnforceEvidencePackage(finding, {
|
|
126
|
+
expectation,
|
|
127
|
+
trace,
|
|
128
|
+
evidence,
|
|
129
|
+
confidence: unifiedConfidence,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// PHASE 17: Apply guardrails (AFTER evidence builder)
|
|
133
|
+
const context = {
|
|
134
|
+
evidencePackage: findingWithEvidence.evidencePackage,
|
|
135
|
+
signals: findingWithEvidence.evidencePackage?.signals || evidence.signals || {},
|
|
136
|
+
confidenceReasons: unifiedConfidence.reasons || [],
|
|
137
|
+
promiseType: expectation?.type || null,
|
|
138
|
+
};
|
|
139
|
+
const { finding: findingWithGuardrails } = applyGuardrails(findingWithEvidence, context);
|
|
140
|
+
|
|
141
|
+
routeFindings.push(findingWithGuardrails);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return routeFindings;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Find navigation expectations matching the interaction
|
|
151
|
+
*/
|
|
152
|
+
function findNavigationExpectations(manifest, interaction, beforeUrl) {
|
|
153
|
+
const expectations = [];
|
|
154
|
+
|
|
155
|
+
// Check static expectations
|
|
156
|
+
if (manifest.staticExpectations) {
|
|
157
|
+
const beforePath = extractPathFromUrl(beforeUrl);
|
|
158
|
+
if (beforePath) {
|
|
159
|
+
const normalizedBefore = beforePath.replace(/\/$/, '') || '/';
|
|
160
|
+
|
|
161
|
+
for (const expectation of manifest.staticExpectations) {
|
|
162
|
+
if (expectation.type !== 'navigation' && expectation.type !== 'spa_navigation') {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const normalizedFrom = (expectation.fromPath || '').replace(/\/$/, '') || '/';
|
|
167
|
+
if (normalizedFrom === normalizedBefore) {
|
|
168
|
+
// Check selector match
|
|
169
|
+
const selectorHint = expectation.selectorHint || '';
|
|
170
|
+
const interactionSelector = interaction.selector || '';
|
|
171
|
+
|
|
172
|
+
if (!selectorHint || !interactionSelector ||
|
|
173
|
+
selectorHint === interactionSelector ||
|
|
174
|
+
selectorHint.includes(interactionSelector) ||
|
|
175
|
+
interactionSelector.includes(selectorHint)) {
|
|
176
|
+
expectations.push(expectation);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Check expectations from intel (AST-based)
|
|
184
|
+
if (manifest.expectations) {
|
|
185
|
+
for (const expectation of manifest.expectations) {
|
|
186
|
+
if (expectation.type === 'navigation' || expectation.type === 'spa_navigation') {
|
|
187
|
+
// Match by selector or label
|
|
188
|
+
const selectorHint = expectation.selectorHint || '';
|
|
189
|
+
const interactionSelector = interaction.selector || '';
|
|
190
|
+
const interactionLabel = (interaction.label || '').toLowerCase();
|
|
191
|
+
const expectationLabel = (expectation.promise?.value || '').toLowerCase();
|
|
192
|
+
|
|
193
|
+
if (selectorHint === interactionSelector ||
|
|
194
|
+
(expectationLabel && interactionLabel && expectationLabel.includes(interactionLabel))) {
|
|
195
|
+
expectations.push(expectation);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return expectations;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Extract path from URL
|
|
206
|
+
*/
|
|
207
|
+
function extractPathFromUrl(url) {
|
|
208
|
+
if (!url || typeof url !== 'string') return '';
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const urlObj = new URL(url);
|
|
212
|
+
return urlObj.pathname;
|
|
213
|
+
} catch {
|
|
214
|
+
// Relative URL
|
|
215
|
+
const pathMatch = url.match(/^([^?#]+)/);
|
|
216
|
+
return pathMatch ? pathMatch[1] : url;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|