@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,314 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test: Failure Cause Inference (FCI)
|
|
5
|
+
*
|
|
6
|
+
* Tests:
|
|
7
|
+
* 1. No evidence -> no causes (Evidence Law enforcement)
|
|
8
|
+
* 2. Determinism: same input -> same causes, same ordering, same wording
|
|
9
|
+
* 3. Cause inference for C2 (state mutation no UI)
|
|
10
|
+
* 4. Cause inference for C3 (dead click)
|
|
11
|
+
* 5. Cause inference for C7 (network silent)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
inferCauses,
|
|
16
|
+
inferCausesForFindings,
|
|
17
|
+
findingsWithCauses,
|
|
18
|
+
attachCausesToFinding
|
|
19
|
+
} from './failure-cause-inference.js';
|
|
20
|
+
|
|
21
|
+
let testCount = 0;
|
|
22
|
+
let passCount = 0;
|
|
23
|
+
|
|
24
|
+
function test(name, fn) {
|
|
25
|
+
testCount++;
|
|
26
|
+
try {
|
|
27
|
+
fn();
|
|
28
|
+
console.log(`✓ Test ${testCount}: ${name}`);
|
|
29
|
+
passCount++;
|
|
30
|
+
} catch (e) {
|
|
31
|
+
console.error(`✗ Test ${testCount}: ${name}`);
|
|
32
|
+
console.error(` ${e.message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function assert(condition, message) {
|
|
37
|
+
if (!condition) {
|
|
38
|
+
throw new Error(message);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Test 1: No evidence -> no causes
|
|
43
|
+
test('No evidence => no causes (Evidence Law)', () => {
|
|
44
|
+
const finding = {
|
|
45
|
+
id: 'test-1',
|
|
46
|
+
type: 'silent_failure',
|
|
47
|
+
evidence: {}
|
|
48
|
+
};
|
|
49
|
+
const causes = inferCauses(finding);
|
|
50
|
+
assert(Array.isArray(causes), 'Should return array');
|
|
51
|
+
assert(causes.length === 0, `Expected 0 causes, got ${causes.length}`);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Test 2: Null/undefined finding
|
|
55
|
+
test('Null finding => no causes', () => {
|
|
56
|
+
const causes = inferCauses(null);
|
|
57
|
+
assert(causes.length === 0, 'Should return empty array for null');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Test 3: Missing evidence field
|
|
61
|
+
test('Missing evidence field => no causes', () => {
|
|
62
|
+
const finding = {
|
|
63
|
+
id: 'test-2',
|
|
64
|
+
type: 'silent_failure'
|
|
65
|
+
};
|
|
66
|
+
const causes = inferCauses(finding);
|
|
67
|
+
assert(causes.length === 0, 'Should return empty array');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Test 4: Determinism - run twice, get identical output
|
|
71
|
+
test('Determinism: same finding => same causes', () => {
|
|
72
|
+
const finding = {
|
|
73
|
+
id: 'test-det-1',
|
|
74
|
+
type: 'silent_failure',
|
|
75
|
+
evidence: {
|
|
76
|
+
stateMutation: true,
|
|
77
|
+
domChanged: false,
|
|
78
|
+
navigationOccurred: false,
|
|
79
|
+
uiFeedback: false
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const run1 = inferCauses(finding);
|
|
83
|
+
const run2 = inferCauses(finding);
|
|
84
|
+
|
|
85
|
+
assert(JSON.stringify(run1) === JSON.stringify(run2), 'Runs should be identical');
|
|
86
|
+
assert(run1.length > 0, 'Should detect C2 cause');
|
|
87
|
+
assert(run1[0].id === 'C2_STATE_MUTATION_NO_UI', 'Should detect C2');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Test 5: Determinism - ordering
|
|
91
|
+
test('Determinism: causes ordered by id', () => {
|
|
92
|
+
const finding = {
|
|
93
|
+
id: 'test-ord-1',
|
|
94
|
+
type: 'silent_failure',
|
|
95
|
+
evidence: {
|
|
96
|
+
stateMutation: true,
|
|
97
|
+
domChanged: false,
|
|
98
|
+
navigationOccurred: false,
|
|
99
|
+
uiFeedback: false,
|
|
100
|
+
interactionPerformed: true,
|
|
101
|
+
networkActivity: false,
|
|
102
|
+
userFeedback: false
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const causes = inferCauses(finding);
|
|
106
|
+
// Should have both C2 and C3
|
|
107
|
+
assert(causes.length >= 1, 'Should detect at least 1 cause');
|
|
108
|
+
|
|
109
|
+
// Check ordering
|
|
110
|
+
for (let i = 1; i < causes.length; i++) {
|
|
111
|
+
assert(
|
|
112
|
+
causes[i].id.localeCompare(causes[i-1].id) > 0,
|
|
113
|
+
`Causes not ordered: ${causes[i].id} should come after ${causes[i-1].id}`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Test 6: C2 inference - state mutation no UI
|
|
119
|
+
test('C2: State mutation + no DOM change + no feedback', () => {
|
|
120
|
+
const finding = {
|
|
121
|
+
id: 'c2-test',
|
|
122
|
+
type: 'state_action',
|
|
123
|
+
evidence: {
|
|
124
|
+
stateMutation: true,
|
|
125
|
+
domChanged: false,
|
|
126
|
+
navigationOccurred: false,
|
|
127
|
+
uiFeedback: false
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const causes = inferCauses(finding);
|
|
131
|
+
assert(causes.length >= 1, 'Should find at least one cause');
|
|
132
|
+
assert(causes.some(c => c.id === 'C2_STATE_MUTATION_NO_UI'), 'Should find C2');
|
|
133
|
+
|
|
134
|
+
const c2 = causes.find(c => c.id === 'C2_STATE_MUTATION_NO_UI');
|
|
135
|
+
assert(c2.statement.includes('Likely cause:'), 'Should start with "Likely cause:"');
|
|
136
|
+
assert(c2.confidence === 'MEDIUM', 'Should be MEDIUM confidence');
|
|
137
|
+
assert(Array.isArray(c2.evidence_refs), 'Should have evidence_refs array');
|
|
138
|
+
assert(c2.evidence_refs.length > 0, 'Should have evidence references');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Test 7: C3 inference - dead click
|
|
142
|
+
test('C3: Interaction but no network/nav/DOM/feedback', () => {
|
|
143
|
+
const finding = {
|
|
144
|
+
id: 'c3-test',
|
|
145
|
+
type: 'silent_failure',
|
|
146
|
+
evidence: {
|
|
147
|
+
interactionPerformed: true,
|
|
148
|
+
networkActivity: false,
|
|
149
|
+
navigationOccurred: false,
|
|
150
|
+
domChanged: false,
|
|
151
|
+
userFeedback: false
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const causes = inferCauses(finding);
|
|
155
|
+
assert(causes.some(c => c.id === 'C3_DEAD_CLICK'), 'Should find C3');
|
|
156
|
+
|
|
157
|
+
const c3 = causes.find(c => c.id === 'C3_DEAD_CLICK');
|
|
158
|
+
assert(c3.title.includes('dead'), 'Title should mention dead');
|
|
159
|
+
assert(c3.confidence === 'MEDIUM', 'Should be MEDIUM confidence');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Test 8: C7 inference - network silent
|
|
163
|
+
test('C7: Network failure + no feedback', () => {
|
|
164
|
+
const finding = {
|
|
165
|
+
id: 'c7-test',
|
|
166
|
+
type: 'network_silent_failure',
|
|
167
|
+
evidence: {
|
|
168
|
+
networkFailure: true,
|
|
169
|
+
uiFeedback: false,
|
|
170
|
+
domChanged: false
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const causes = inferCauses(finding);
|
|
174
|
+
assert(causes.some(c => c.id === 'C7_NETWORK_SILENT'), 'Should find C7');
|
|
175
|
+
|
|
176
|
+
const c7 = causes.find(c => c.id === 'C7_NETWORK_SILENT');
|
|
177
|
+
assert(c7.statement.includes('network'), 'Should mention network');
|
|
178
|
+
assert(c7.confidence === 'MEDIUM', 'Should be MEDIUM confidence');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Test 9: Batch inference
|
|
182
|
+
test('Batch inference on multiple findings', () => {
|
|
183
|
+
const findings = [
|
|
184
|
+
{
|
|
185
|
+
id: 'batch-1',
|
|
186
|
+
type: 'silent_failure',
|
|
187
|
+
evidence: { }
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: 'batch-2',
|
|
191
|
+
type: 'state_action',
|
|
192
|
+
evidence: {
|
|
193
|
+
stateMutation: true,
|
|
194
|
+
domChanged: false,
|
|
195
|
+
navigationOccurred: false,
|
|
196
|
+
uiFeedback: false
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
];
|
|
200
|
+
const causesMap = inferCausesForFindings(findings);
|
|
201
|
+
assert('batch-1' in causesMap === false, 'batch-1 has no evidence, should not be in map');
|
|
202
|
+
assert('batch-2' in causesMap, 'batch-2 should be in map');
|
|
203
|
+
assert(causesMap['batch-2'].some(c => c.id === 'C2_STATE_MUTATION_NO_UI'), 'batch-2 should have C2');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Test 10: findingsWithCauses filter
|
|
207
|
+
test('findingsWithCauses filters correctly', () => {
|
|
208
|
+
const findings = [
|
|
209
|
+
{
|
|
210
|
+
id: 'with-evidence',
|
|
211
|
+
type: 'state_action',
|
|
212
|
+
evidence: {
|
|
213
|
+
stateMutation: true,
|
|
214
|
+
domChanged: false,
|
|
215
|
+
navigationOccurred: false,
|
|
216
|
+
uiFeedback: false
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
id: 'no-evidence',
|
|
221
|
+
type: 'silent_failure',
|
|
222
|
+
evidence: { }
|
|
223
|
+
}
|
|
224
|
+
];
|
|
225
|
+
const filtered = findingsWithCauses(findings);
|
|
226
|
+
assert(filtered.length === 1, `Expected 1 finding with causes, got ${filtered.length}`);
|
|
227
|
+
assert(filtered[0].id === 'with-evidence', 'Should only include finding with causes');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Test 11: attachCausesToFinding mutation
|
|
231
|
+
test('attachCausesToFinding mutates finding', () => {
|
|
232
|
+
const finding = {
|
|
233
|
+
id: 'mutate-test',
|
|
234
|
+
type: 'state_action',
|
|
235
|
+
evidence: {
|
|
236
|
+
stateMutation: true,
|
|
237
|
+
domChanged: false,
|
|
238
|
+
navigationOccurred: false,
|
|
239
|
+
uiFeedback: false
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
assert(!('causes' in finding), 'Should not have causes before');
|
|
243
|
+
attachCausesToFinding(finding);
|
|
244
|
+
assert('causes' in finding, 'Should have causes after');
|
|
245
|
+
assert(finding.causes.length > 0, 'Should have non-empty causes array');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Test 12: Cause statement format
|
|
249
|
+
test('Cause statements start with "Likely cause:"', () => {
|
|
250
|
+
const finding = {
|
|
251
|
+
id: 'format-test',
|
|
252
|
+
type: 'state_action',
|
|
253
|
+
evidence: {
|
|
254
|
+
stateMutation: true,
|
|
255
|
+
domChanged: false,
|
|
256
|
+
navigationOccurred: false,
|
|
257
|
+
uiFeedback: false
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
const causes = inferCauses(finding);
|
|
261
|
+
causes.forEach(cause => {
|
|
262
|
+
assert(
|
|
263
|
+
cause.statement.startsWith('Likely cause:'),
|
|
264
|
+
`Statement should start with "Likely cause:", got: ${cause.statement}`
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Test 13: Confidence never HIGH
|
|
270
|
+
test('Confidence is never HIGH', () => {
|
|
271
|
+
const findings = [
|
|
272
|
+
{
|
|
273
|
+
id: 't1',
|
|
274
|
+
type: 'state_action',
|
|
275
|
+
evidence: {
|
|
276
|
+
stateMutation: true,
|
|
277
|
+
domChanged: false,
|
|
278
|
+
navigationOccurred: false,
|
|
279
|
+
uiFeedback: false
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: 't2',
|
|
284
|
+
type: 'silent_failure',
|
|
285
|
+
evidence: {
|
|
286
|
+
interactionPerformed: true,
|
|
287
|
+
networkActivity: false,
|
|
288
|
+
navigationOccurred: false,
|
|
289
|
+
domChanged: false,
|
|
290
|
+
userFeedback: false
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
];
|
|
294
|
+
|
|
295
|
+
findings.forEach(finding => {
|
|
296
|
+
const causes = inferCauses(finding);
|
|
297
|
+
causes.forEach(cause => {
|
|
298
|
+
assert(
|
|
299
|
+
cause.confidence === 'LOW' || cause.confidence === 'MEDIUM',
|
|
300
|
+
`Confidence should be LOW or MEDIUM, got ${cause.confidence}`
|
|
301
|
+
);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Results
|
|
307
|
+
console.log(`\n${passCount}/${testCount} tests passed`);
|
|
308
|
+
if (passCount === testCount) {
|
|
309
|
+
console.log('✓ All tests passed');
|
|
310
|
+
process.exit(0);
|
|
311
|
+
} else {
|
|
312
|
+
console.log(`✗ ${testCount - passCount} test(s) failed`);
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
@@ -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
|
+
* @ts-expect-error - JSDoc param documented but unused
|
|
27
|
+
* @returns {Array} UI feedback-related findings
|
|
28
|
+
*/
|
|
29
|
+
export function detectUIFeedbackFindings(traces, manifest, _findings) {
|
|
30
|
+
const feedbackFindings = [];
|
|
31
|
+
|
|
32
|
+
// Process each trace
|
|
33
|
+
for (const trace of traces) {
|
|
34
|
+
const interaction = trace.interaction || {};
|
|
35
|
+
|
|
36
|
+
// Find expectations for this interaction
|
|
37
|
+
const expectations = findExpectationsForInteraction(manifest, interaction, trace);
|
|
38
|
+
|
|
39
|
+
for (const expectation of expectations) {
|
|
40
|
+
// Detect UI feedback signals
|
|
41
|
+
const signals = detectUIFeedbackSignals(trace);
|
|
42
|
+
|
|
43
|
+
// Score feedback presence/absence
|
|
44
|
+
const feedbackScore = scoreUIFeedback(signals, expectation, trace);
|
|
45
|
+
|
|
46
|
+
// Correlate promise with feedback
|
|
47
|
+
const correlation = correlatePromiseWithFeedback(expectation, feedbackScore, trace);
|
|
48
|
+
|
|
49
|
+
// Generate finding if correlation indicates silent failure
|
|
50
|
+
if (correlation.outcome === 'CONFIRMED' || correlation.outcome === 'SUSPECTED') {
|
|
51
|
+
// Build evidence
|
|
52
|
+
const evidence = buildUIFeedbackEvidence(feedbackScore, correlation, trace, expectation);
|
|
53
|
+
|
|
54
|
+
// PHASE 13: Evidence Law - require sufficient evidence for CONFIRMED
|
|
55
|
+
const hasSufficientEvidence = evidence.beforeAfter.beforeScreenshot &&
|
|
56
|
+
evidence.beforeAfter.afterScreenshot &&
|
|
57
|
+
(evidence.feedback.signals.length > 0 ||
|
|
58
|
+
evidence.feedback.score === FEEDBACK_SCORE.MISSING);
|
|
59
|
+
|
|
60
|
+
// Determine finding type early (before use in confidence call)
|
|
61
|
+
let findingType = 'ui_feedback_silent_failure';
|
|
62
|
+
if (expectation.type === 'network_action' || expectation.type === 'network') {
|
|
63
|
+
findingType = 'network_feedback_missing';
|
|
64
|
+
} else if (expectation.type === 'navigation' || expectation.type === 'spa_navigation') {
|
|
65
|
+
findingType = 'navigation_feedback_missing';
|
|
66
|
+
} else if (expectation.type === 'validation' || expectation.type === 'form_submission') {
|
|
67
|
+
findingType = 'validation_feedback_missing';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// PHASE 15: Compute unified confidence
|
|
71
|
+
const unifiedConfidence = computeConfidenceForFinding({
|
|
72
|
+
findingType: findingType,
|
|
73
|
+
expectation,
|
|
74
|
+
sensors: trace.sensors || {},
|
|
75
|
+
comparisons: {},
|
|
76
|
+
evidence,
|
|
77
|
+
options: {}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Legacy confidence for backward compatibility
|
|
81
|
+
const _confidence = computeConfidence({
|
|
82
|
+
findingType: 'ui_feedback_silent_failure',
|
|
83
|
+
expectation,
|
|
84
|
+
sensors: trace.sensors || {},
|
|
85
|
+
comparisons: {},
|
|
86
|
+
attemptMeta: {},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Determine severity based on evidence
|
|
90
|
+
const severity = hasSufficientEvidence && correlation.outcome === 'CONFIRMED' && (unifiedConfidence.score01 || unifiedConfidence.score || 0) >= 0.8
|
|
91
|
+
? 'CONFIRMED'
|
|
92
|
+
: 'SUSPECTED';
|
|
93
|
+
|
|
94
|
+
const finding = {
|
|
95
|
+
type: findingType,
|
|
96
|
+
severity,
|
|
97
|
+
confidence: unifiedConfidence.score01 || unifiedConfidence.score || 0, // Contract v1: score01 canonical
|
|
98
|
+
confidenceLevel: unifiedConfidence.level, // PHASE 15: Add confidence level
|
|
99
|
+
confidenceReasons: unifiedConfidence.topReasons || unifiedConfidence.reasons || [], // Contract v1: topReasons
|
|
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
|
+
|