@veraxhq/verax 0.3.0 → 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 +28 -20
- package/bin/verax.js +11 -18
- package/package.json +28 -7
- package/src/cli/commands/baseline.js +1 -2
- package/src/cli/commands/default.js +72 -81
- package/src/cli/commands/doctor.js +29 -0
- package/src/cli/commands/ga.js +3 -0
- package/src/cli/commands/gates.js +1 -1
- package/src/cli/commands/inspect.js +6 -133
- package/src/cli/commands/release-check.js +2 -0
- package/src/cli/commands/run.js +74 -246
- package/src/cli/commands/security-check.js +2 -1
- package/src/cli/commands/truth.js +0 -1
- package/src/cli/entry.js +82 -309
- package/src/cli/util/angular-component-extractor.js +2 -2
- package/src/cli/util/angular-navigation-detector.js +2 -2
- package/src/cli/util/ast-interactive-detector.js +4 -6
- package/src/cli/util/ast-network-detector.js +3 -3
- package/src/cli/util/ast-promise-extractor.js +581 -0
- package/src/cli/util/ast-usestate-detector.js +3 -3
- package/src/cli/util/atomic-write.js +12 -1
- 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 +2 -1
- package/src/cli/util/determinism-writer.js +1 -1
- package/src/cli/util/digest-engine.js +359 -0
- package/src/cli/util/dom-diff.js +226 -0
- package/src/cli/util/env-url.js +0 -4
- package/src/cli/util/evidence-engine.js +287 -0
- package/src/cli/util/expectation-extractor.js +217 -367
- package/src/cli/util/findings-writer.js +19 -126
- 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 -2
- 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 -2
- package/src/cli/util/paths.js +12 -3
- package/src/cli/util/project-discovery.js +284 -3
- package/src/cli/util/project-writer.js +2 -2
- package/src/cli/util/run-id.js +23 -27
- package/src/cli/util/run-result.js +778 -0
- package/src/cli/util/selector-resolver.js +235 -0
- package/src/cli/util/summary-writer.js +2 -1
- package/src/cli/util/svelte-navigation-detector.js +3 -3
- package/src/cli/util/svelte-sfc-extractor.js +0 -1
- package/src/cli/util/svelte-state-detector.js +1 -2
- 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 +4 -3
- package/src/cli/util/vue-sfc-extractor.js +1 -2
- package/src/cli/util/vue-state-detector.js +1 -1
- 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/finding-explainer.js +3 -56
- package/src/verax/cli/init.js +4 -18
- package/src/verax/core/action-classifier.js +4 -3
- package/src/verax/core/artifacts/registry.js +0 -15
- package/src/verax/core/artifacts/verifier.js +18 -8
- package/src/verax/core/baseline/baseline.snapshot.js +2 -0
- package/src/verax/core/capabilities/gates.js +7 -1
- package/src/verax/core/confidence/confidence-compute.js +14 -7
- package/src/verax/core/confidence/confidence.loader.js +1 -0
- package/src/verax/core/confidence-engine-refactor.js +8 -3
- package/src/verax/core/confidence-engine.js +162 -23
- package/src/verax/core/contracts/types.js +1 -0
- package/src/verax/core/contracts/validators.js +79 -4
- package/src/verax/core/decision-snapshot.js +3 -30
- package/src/verax/core/decisions/decision.trace.js +2 -0
- package/src/verax/core/determinism/contract-writer.js +2 -2
- package/src/verax/core/determinism/contract.js +1 -1
- package/src/verax/core/determinism/diff.js +42 -1
- package/src/verax/core/determinism/engine.js +7 -6
- package/src/verax/core/determinism/finding-identity.js +3 -2
- package/src/verax/core/determinism/normalize.js +32 -4
- package/src/verax/core/determinism/report-writer.js +1 -0
- package/src/verax/core/determinism/run-fingerprint.js +7 -2
- package/src/verax/core/dynamic-route-intelligence.js +8 -7
- package/src/verax/core/evidence/evidence-capture-service.js +1 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +2 -1
- package/src/verax/core/evidence-builder.js +2 -2
- package/src/verax/core/execution-mode-context.js +1 -1
- package/src/verax/core/execution-mode-detector.js +5 -3
- package/src/verax/core/failures/exit-codes.js +39 -37
- package/src/verax/core/failures/failure-summary.js +1 -1
- package/src/verax/core/failures/failure.factory.js +3 -3
- package/src/verax/core/failures/failure.ledger.js +3 -2
- package/src/verax/core/ga/ga.artifact.js +1 -1
- package/src/verax/core/ga/ga.contract.js +3 -2
- package/src/verax/core/ga/ga.enforcer.js +1 -0
- package/src/verax/core/guardrails/policy.loader.js +1 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +1 -1
- package/src/verax/core/guardrails-engine.js +2 -2
- 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 +2 -0
- package/src/verax/core/perf/perf.report.js +2 -0
- package/src/verax/core/pipeline-tracker.js +5 -0
- package/src/verax/core/release/provenance.builder.js +73 -214
- package/src/verax/core/release/release.enforcer.js +14 -9
- package/src/verax/core/release/reproducibility.check.js +1 -0
- package/src/verax/core/release/sbom.builder.js +32 -23
- 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 +6 -3
- package/src/verax/core/report/human-summary.js +141 -1
- package/src/verax/core/route-intelligence.js +4 -3
- 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 +10 -7
- package/src/verax/core/security/security.enforcer.js +4 -0
- package/src/verax/core/security/supplychain.policy.js +9 -1
- package/src/verax/core/security/vuln.scan.js +2 -2
- package/src/verax/core/truth/truth.certificate.js +3 -1
- package/src/verax/core/ui-feedback-intelligence.js +12 -46
- package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
- package/src/verax/detect/confidence-engine.js +100 -660
- package/src/verax/detect/confidence-helper.js +1 -0
- package/src/verax/detect/detection-engine.js +1 -18
- package/src/verax/detect/dynamic-route-findings.js +17 -14
- package/src/verax/detect/expectation-chain-detector.js +1 -1
- package/src/verax/detect/expectation-model.js +3 -5
- package/src/verax/detect/failure-cause-inference.js +293 -0
- package/src/verax/detect/findings-writer.js +126 -166
- 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 +51 -234
- package/src/verax/detect/invariants-enforcer.js +147 -0
- package/src/verax/detect/journey-stall-detector.js +4 -4
- 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 +7 -6
- 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 +18 -18
- package/src/verax/detect/verdict-engine.js +3 -57
- package/src/verax/detect/view-switch-correlator.js +2 -2
- package/src/verax/flow/flow-engine.js +2 -1
- package/src/verax/flow/flow-spec.js +0 -6
- package/src/verax/index.js +48 -412
- 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 +67 -682
- 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/route-validator.js +1 -4
- 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 +735 -84
- package/src/verax/observe/interaction-executor.js +192 -0
- package/src/verax/observe/interaction-runner.js +782 -530
- 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 +1 -1
- package/src/verax/observe/observe-helpers.js +2 -1
- package/src/verax/observe/observe-runner.js +28 -24
- package/src/verax/observe/observers/budget-observer.js +3 -3
- package/src/verax/observe/observers/console-observer.js +4 -4
- package/src/verax/observe/observers/coverage-observer.js +4 -4
- package/src/verax/observe/observers/interaction-observer.js +3 -3
- package/src/verax/observe/observers/navigation-observer.js +4 -4
- package/src/verax/observe/observers/network-observer.js +4 -4
- package/src/verax/observe/observers/safety-observer.js +1 -1
- package/src/verax/observe/observers/ui-feedback-observer.js +4 -4
- package/src/verax/observe/page-traversal.js +138 -0
- package/src/verax/observe/snapshot-ops.js +94 -0
- package/src/verax/observe/ui-signal-sensor.js +2 -148
- package/src/verax/scan-summary-writer.js +10 -42
- package/src/verax/shared/artifact-manager.js +30 -13
- package/src/verax/shared/caching.js +1 -0
- package/src/verax/shared/expectation-tracker.js +1 -0
- package/src/verax/shared/zip-artifacts.js +6 -0
- package/src/verax/core/confidence-engine.js.backup +0 -471
- 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
|
+
}
|
|
@@ -22,10 +22,11 @@ import { applyGuardrails } from '../core/guardrails-engine.js';
|
|
|
22
22
|
*
|
|
23
23
|
* @param {Array} traces - Interaction traces
|
|
24
24
|
* @param {Object} manifest - Project manifest with expectations
|
|
25
|
-
* @param {Array}
|
|
25
|
+
* @param {Array} _findings - Findings array to append to
|
|
26
|
+
* @ts-expect-error - JSDoc param documented but unused
|
|
26
27
|
* @returns {Array} UI feedback-related findings
|
|
27
28
|
*/
|
|
28
|
-
export function detectUIFeedbackFindings(traces, manifest,
|
|
29
|
+
export function detectUIFeedbackFindings(traces, manifest, _findings) {
|
|
29
30
|
const feedbackFindings = [];
|
|
30
31
|
|
|
31
32
|
// Process each trace
|
|
@@ -56,6 +57,16 @@ export function detectUIFeedbackFindings(traces, manifest, findings) {
|
|
|
56
57
|
(evidence.feedback.signals.length > 0 ||
|
|
57
58
|
evidence.feedback.score === FEEDBACK_SCORE.MISSING);
|
|
58
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
|
+
|
|
59
70
|
// PHASE 15: Compute unified confidence
|
|
60
71
|
const unifiedConfidence = computeConfidenceForFinding({
|
|
61
72
|
findingType: findingType,
|
|
@@ -63,10 +74,11 @@ export function detectUIFeedbackFindings(traces, manifest, findings) {
|
|
|
63
74
|
sensors: trace.sensors || {},
|
|
64
75
|
comparisons: {},
|
|
65
76
|
evidence,
|
|
77
|
+
options: {}
|
|
66
78
|
});
|
|
67
79
|
|
|
68
80
|
// Legacy confidence for backward compatibility
|
|
69
|
-
const
|
|
81
|
+
const _confidence = computeConfidence({
|
|
70
82
|
findingType: 'ui_feedback_silent_failure',
|
|
71
83
|
expectation,
|
|
72
84
|
sensors: trace.sensors || {},
|
|
@@ -75,28 +87,16 @@ export function detectUIFeedbackFindings(traces, manifest, findings) {
|
|
|
75
87
|
});
|
|
76
88
|
|
|
77
89
|
// Determine severity based on evidence
|
|
78
|
-
const severity = hasSufficientEvidence && correlation.outcome === 'CONFIRMED' && unifiedConfidence.score >= 0.8
|
|
90
|
+
const severity = hasSufficientEvidence && correlation.outcome === 'CONFIRMED' && (unifiedConfidence.score01 || unifiedConfidence.score || 0) >= 0.8
|
|
79
91
|
? 'CONFIRMED'
|
|
80
92
|
: 'SUSPECTED';
|
|
81
93
|
|
|
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
94
|
const finding = {
|
|
95
95
|
type: findingType,
|
|
96
96
|
severity,
|
|
97
|
-
confidence: unifiedConfidence.score, //
|
|
97
|
+
confidence: unifiedConfidence.score01 || unifiedConfidence.score || 0, // Contract v1: score01 canonical
|
|
98
98
|
confidenceLevel: unifiedConfidence.level, // PHASE 15: Add confidence level
|
|
99
|
-
confidenceReasons: unifiedConfidence.reasons, //
|
|
99
|
+
confidenceReasons: unifiedConfidence.topReasons || unifiedConfidence.reasons || [], // Contract v1: topReasons
|
|
100
100
|
interaction: {
|
|
101
101
|
type: interaction.type,
|
|
102
102
|
selector: interaction.selector,
|
|
@@ -55,20 +55,19 @@ export function computeObservationSummary(findings, observeTruth, learnTruth, co
|
|
|
55
55
|
);
|
|
56
56
|
const skippedCount = coverage.skippedInteractions || 0;
|
|
57
57
|
|
|
58
|
-
//
|
|
58
|
+
// Count findings by confidence (for transparency, not judgment)
|
|
59
59
|
const findingsByConfidence = {
|
|
60
60
|
HIGH: 0,
|
|
61
61
|
MEDIUM: 0,
|
|
62
62
|
LOW: 0,
|
|
63
|
-
|
|
63
|
+
UNKNOWN: 0
|
|
64
64
|
};
|
|
65
65
|
const findingsByType = {};
|
|
66
66
|
const findingsByOutcome = {}; // PHASE 2: Added outcome tracking
|
|
67
67
|
const findingsByPromise = {}; // PHASE 3: Added promise tracking
|
|
68
68
|
|
|
69
69
|
for (const finding of (findings || [])) {
|
|
70
|
-
|
|
71
|
-
const confidence = finding.confidenceLevel || finding.confidence?.level || 'UNPROVEN';
|
|
70
|
+
const confidence = finding.confidence?.level || 'UNKNOWN';
|
|
72
71
|
const type = finding.type || 'unknown';
|
|
73
72
|
const outcome = finding.outcome || CANONICAL_OUTCOMES.SILENT_FAILURE; // Default for legacy findings
|
|
74
73
|
const promiseType = finding.promise?.type || 'UNKNOWN_PROMISE'; // PHASE 3
|
|
@@ -79,39 +78,6 @@ export function computeObservationSummary(findings, observeTruth, learnTruth, co
|
|
|
79
78
|
findingsByType[type] = (findingsByType[type] || 0) + 1;
|
|
80
79
|
findingsByOutcome[outcome] = (findingsByOutcome[outcome] || 0) + 1; // PHASE 2
|
|
81
80
|
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
|
-
}
|
|
115
81
|
}
|
|
116
82
|
|
|
117
83
|
// Calculate ratios (factual, not judgmental)
|
|
@@ -128,24 +94,6 @@ export function computeObservationSummary(findings, observeTruth, learnTruth, co
|
|
|
128
94
|
unprovenResults: unprovenTraces.length
|
|
129
95
|
};
|
|
130
96
|
|
|
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
|
-
|
|
149
97
|
// Build gap details
|
|
150
98
|
const gapDetails = [];
|
|
151
99
|
if (isBudgetExceeded) {
|
|
@@ -202,8 +150,6 @@ export function computeObservationSummary(findings, observeTruth, learnTruth, co
|
|
|
202
150
|
discrepanciesByPromise: findingsByPromise, // PHASE 3: Promise types
|
|
203
151
|
findings: findings || []
|
|
204
152
|
},
|
|
205
|
-
evidenceCompleteness: evidenceCompleteness, // PHASE 16: Evidence completeness summary
|
|
206
|
-
guardrailsSummary: guardrailsSummary, // PHASE 17: Guardrails summary
|
|
207
153
|
coverage: {
|
|
208
154
|
pagesEvaluated,
|
|
209
155
|
pagesDiscovered,
|
|
@@ -39,8 +39,8 @@ export function correlateViewSwitch(expectation, trace, beforeUrl, afterUrl) {
|
|
|
39
39
|
|
|
40
40
|
const sensors = trace.sensors || {};
|
|
41
41
|
const navigation = sensors.navigation || {};
|
|
42
|
-
const
|
|
43
|
-
const
|
|
42
|
+
const _uiSignals = sensors.uiSignals || {};
|
|
43
|
+
const _stateUi = sensors.stateUi || {};
|
|
44
44
|
const uiFeedback = sensors.uiFeedback || {};
|
|
45
45
|
|
|
46
46
|
// Check if URL changed (if so, this is not a state-driven navigation)
|
|
@@ -195,7 +195,8 @@ async function stepClick(page, step, result, spec) {
|
|
|
195
195
|
|
|
196
196
|
const fullContent = `${elementText} ${elementAriaLabel} ${elementValue}`.toLowerCase();
|
|
197
197
|
|
|
198
|
-
|
|
198
|
+
const denyKeywords = spec.denyKeywords || [];
|
|
199
|
+
for (const keyword of denyKeywords) {
|
|
199
200
|
if (fullContent.includes(keyword.toLowerCase())) {
|
|
200
201
|
result.reason = `Click blocked by safety gate: denyKeyword "${keyword}" found in element`;
|
|
201
202
|
result.findingType = 'blocked_by_safety_gate';
|
|
@@ -41,11 +41,6 @@ export function validateFlowSpec(spec) {
|
|
|
41
41
|
allowlist.pathsPrefix = ['/'];
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// Validate denyKeywords
|
|
45
|
-
const denyKeywords = spec.denyKeywords || [];
|
|
46
|
-
if (!Array.isArray(denyKeywords)) {
|
|
47
|
-
throw new Error('denyKeywords must be an array');
|
|
48
|
-
}
|
|
49
44
|
|
|
50
45
|
// Validate steps
|
|
51
46
|
const steps = spec.steps.map((step, idx) => {
|
|
@@ -100,7 +95,6 @@ export function validateFlowSpec(spec) {
|
|
|
100
95
|
name: spec.name,
|
|
101
96
|
baseUrl: spec.baseUrl,
|
|
102
97
|
allowlist,
|
|
103
|
-
denyKeywords,
|
|
104
98
|
secrets: spec.secrets || {},
|
|
105
99
|
steps
|
|
106
100
|
};
|