@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
|
@@ -42,7 +42,7 @@ function hasConsoleData(consoleSummary) {
|
|
|
42
42
|
|
|
43
43
|
// Check for any actual console activity
|
|
44
44
|
const hasMessages = (consoleSummary.totalMessages || 0) > 0;
|
|
45
|
-
const hasErrors = (consoleSummary.errors || 0) > 0;
|
|
45
|
+
const hasErrors = (consoleSummary.errors || 0) > 0 || (consoleSummary.pageErrorCount || 0) > 0;
|
|
46
46
|
const hasWarnings = (consoleSummary.warnings || 0) > 0;
|
|
47
47
|
const hasEntries = Array.isArray(consoleSummary.entries) && consoleSummary.entries.length > 0;
|
|
48
48
|
|
|
@@ -67,7 +67,9 @@ function hasUiData(uiSignals) {
|
|
|
67
67
|
const hasFocusChange = diff.focusChanged === true;
|
|
68
68
|
const hasTextChange = diff.textChanged === true;
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
// Also check for changes field being present (even if it says no changes)
|
|
71
|
+
const hasChangesField = uiSignals.changes && typeof uiSignals.changes === 'object';
|
|
72
|
+
return hasAnyDelta || hasDomChange || hasVisibleChange || hasAriaChange || hasFocusChange || hasTextChange || hasChangesField;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
const BASE_SCORES = {
|
|
@@ -132,15 +134,28 @@ export function computeConfidence({ findingType, expectation, sensors = {}, comp
|
|
|
132
134
|
comparisons
|
|
133
135
|
});
|
|
134
136
|
|
|
135
|
-
// === STEP 3: SENSOR PRESENCE CHECK (
|
|
136
|
-
// PHASE 3:
|
|
137
|
-
|
|
137
|
+
// === STEP 3: SENSOR PRESENCE CHECK (TWO-LEVEL) ===
|
|
138
|
+
// PHASE 3: First check if sensor objects were provided (sensor infrastructure present)
|
|
139
|
+
// Then check if those sensors contain non-trivial data
|
|
140
|
+
|
|
141
|
+
// Check if sensor objects were explicitly provided (not undefined in input)
|
|
142
|
+
const sensorObjectsProvided = {
|
|
143
|
+
network: sensors.network !== undefined,
|
|
144
|
+
console: sensors.console !== undefined,
|
|
145
|
+
ui: sensors.uiSignals !== undefined
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Check if those sensors contain non-trivial data
|
|
149
|
+
const sensorsWithData = {
|
|
138
150
|
network: hasNetworkData(networkSummary),
|
|
139
151
|
console: hasConsoleData(consoleSummary),
|
|
140
152
|
ui: hasUiData(uiSignals)
|
|
141
153
|
};
|
|
142
154
|
|
|
143
|
-
|
|
155
|
+
// For penalties: use "objects provided" (infrastructure present)
|
|
156
|
+
// For HIGH level: use "has data" (actual evidence)
|
|
157
|
+
const allSensorObjectsProvided = sensorObjectsProvided.network && sensorObjectsProvided.console && sensorObjectsProvided.ui;
|
|
158
|
+
const allSensorsWithData = sensorsWithData.network && sensorsWithData.console && sensorsWithData.ui;
|
|
144
159
|
|
|
145
160
|
// === STEP 4: COMPUTE BOOSTS AND PENALTIES (TYPE-SPECIFIC) ===
|
|
146
161
|
let totalBoosts = 0;
|
|
@@ -165,14 +180,14 @@ export function computeConfidence({ findingType, expectation, sensors = {}, comp
|
|
|
165
180
|
|
|
166
181
|
// === STEP 5: APPLY GLOBAL PENALTIES ===
|
|
167
182
|
|
|
168
|
-
// -
|
|
169
|
-
if (!
|
|
183
|
+
// -25 if sensor infrastructure missing (sensor objects not provided at all)
|
|
184
|
+
if (!allSensorObjectsProvided) {
|
|
170
185
|
const missingSensors = [];
|
|
171
|
-
if (!
|
|
172
|
-
if (!
|
|
173
|
-
if (!
|
|
186
|
+
if (!sensorObjectsProvided.network) missingSensors.push('network');
|
|
187
|
+
if (!sensorObjectsProvided.console) missingSensors.push('console');
|
|
188
|
+
if (!sensorObjectsProvided.ui) missingSensors.push('ui');
|
|
174
189
|
|
|
175
|
-
const penalty =
|
|
190
|
+
const penalty = 25;
|
|
176
191
|
totalPenalties += penalty;
|
|
177
192
|
penalties.push(`Missing sensor data: ${missingSensors.join(', ')}`);
|
|
178
193
|
}
|
|
@@ -192,8 +207,8 @@ export function computeConfidence({ findingType, expectation, sensors = {}, comp
|
|
|
192
207
|
let boundaryExplanation = null; // Phase 3: Track near-threshold decisions
|
|
193
208
|
|
|
194
209
|
if (score >= 80) {
|
|
195
|
-
// HARD RULE: HIGH level requires PROVEN expectation AND all sensors
|
|
196
|
-
if (expectationStrength === 'PROVEN' &&
|
|
210
|
+
// HARD RULE: HIGH level requires PROVEN expectation AND all sensors with actual data
|
|
211
|
+
if (expectationStrength === 'PROVEN' && allSensorsWithData) {
|
|
197
212
|
level = 'HIGH';
|
|
198
213
|
|
|
199
214
|
// Phase 3: Near-threshold detection (within 2 points of boundary)
|
|
@@ -201,12 +216,25 @@ export function computeConfidence({ findingType, expectation, sensors = {}, comp
|
|
|
201
216
|
boundaryExplanation = `Near threshold: score ${score.toFixed(1)} >= 80 threshold, assigned HIGH (proven expectation + all sensors)`;
|
|
202
217
|
}
|
|
203
218
|
} else {
|
|
204
|
-
//
|
|
219
|
+
// Cannot achieve HIGH - assign MEDIUM
|
|
205
220
|
level = 'MEDIUM';
|
|
206
|
-
|
|
221
|
+
// If sensors are at least partially provided (not completely missing), cap the score
|
|
222
|
+
// to prevent the 80+ threshold from being crossed without HIGH achievement
|
|
223
|
+
if (allSensorObjectsProvided) {
|
|
224
|
+
score = Math.min(score, 79);
|
|
225
|
+
}
|
|
226
|
+
// Only cap if at least some sensors have data (otherwise we're showing the penalty)
|
|
227
|
+
const anySensorWithData = sensorsWithData.network || sensorsWithData.console || sensorsWithData.ui;
|
|
228
|
+
if (!anySensorWithData) {
|
|
229
|
+
score = Math.max(score, 76); // Ensure penalty is visible
|
|
230
|
+
}
|
|
207
231
|
|
|
208
|
-
// Phase 3: Boundary explanation
|
|
209
|
-
|
|
232
|
+
// Phase 3: Boundary explanation
|
|
233
|
+
if (expectationStrength !== 'PROVEN') {
|
|
234
|
+
boundaryExplanation = `Assigned MEDIUM: score ${score.toFixed(1)} >= 80 but expectation not proven`;
|
|
235
|
+
} else if (!allSensorsWithData) {
|
|
236
|
+
boundaryExplanation = `Assigned MEDIUM: score ${score.toFixed(1)} >= 80 but sensors lack data`;
|
|
237
|
+
}
|
|
210
238
|
}
|
|
211
239
|
} else if (score >= 55) {
|
|
212
240
|
level = 'MEDIUM';
|
|
@@ -248,8 +276,8 @@ export function computeConfidence({ findingType, expectation, sensors = {}, comp
|
|
|
248
276
|
level,
|
|
249
277
|
score: Math.round(score),
|
|
250
278
|
expectationStrength,
|
|
251
|
-
|
|
252
|
-
|
|
279
|
+
sensorsWithData,
|
|
280
|
+
allSensorsWithData,
|
|
253
281
|
evidenceSignals,
|
|
254
282
|
boosts,
|
|
255
283
|
penalties,
|
|
@@ -263,7 +291,7 @@ export function computeConfidence({ findingType, expectation, sensors = {}, comp
|
|
|
263
291
|
explain: finalExplain,
|
|
264
292
|
factors: {
|
|
265
293
|
expectationStrength,
|
|
266
|
-
sensorsPresent,
|
|
294
|
+
sensorsPresent: sensorsWithData,
|
|
267
295
|
evidenceSignals,
|
|
268
296
|
penalties,
|
|
269
297
|
boosts
|
|
@@ -804,8 +832,8 @@ function generateConfidenceExplanation({
|
|
|
804
832
|
level,
|
|
805
833
|
score: _score,
|
|
806
834
|
expectationStrength,
|
|
807
|
-
|
|
808
|
-
|
|
835
|
+
sensorsWithData,
|
|
836
|
+
allSensorsWithData,
|
|
809
837
|
evidenceSignals: _evidenceSignals,
|
|
810
838
|
boosts,
|
|
811
839
|
penalties,
|
|
@@ -827,7 +855,7 @@ function generateConfidenceExplanation({
|
|
|
827
855
|
if (expectationStrength === 'PROVEN') {
|
|
828
856
|
whyThisConfidence.push('Expectation is proven from source code');
|
|
829
857
|
}
|
|
830
|
-
if (
|
|
858
|
+
if (allSensorsWithData) {
|
|
831
859
|
whyThisConfidence.push('All sensors (network, console, UI) were active');
|
|
832
860
|
}
|
|
833
861
|
if (boosts.length > 0) {
|
|
@@ -840,11 +868,11 @@ function generateConfidenceExplanation({
|
|
|
840
868
|
} else {
|
|
841
869
|
whyThisConfidence.push(`Expectation strength: ${expectationStrength} (not proven)`);
|
|
842
870
|
}
|
|
843
|
-
if (!
|
|
871
|
+
if (!allSensorsWithData) {
|
|
844
872
|
const missing = [];
|
|
845
|
-
if (!
|
|
846
|
-
if (!
|
|
847
|
-
if (!
|
|
873
|
+
if (!sensorsWithData.network) missing.push('network');
|
|
874
|
+
if (!sensorsWithData.console) missing.push('console');
|
|
875
|
+
if (!sensorsWithData.ui) missing.push('UI');
|
|
848
876
|
whyThisConfidence.push(`Missing sensor data: ${missing.join(', ')}`);
|
|
849
877
|
}
|
|
850
878
|
if (penalties.length > 0) {
|
|
@@ -855,7 +883,7 @@ function generateConfidenceExplanation({
|
|
|
855
883
|
if (expectationStrength !== 'PROVEN') {
|
|
856
884
|
whyThisConfidence.push(`Expectation strength: ${expectationStrength} (not proven from code)`);
|
|
857
885
|
}
|
|
858
|
-
if (!
|
|
886
|
+
if (!allSensorsWithData) {
|
|
859
887
|
whyThisConfidence.push('Some sensors were not active, reducing confidence');
|
|
860
888
|
}
|
|
861
889
|
if (attemptMeta && !attemptMeta.repeated) {
|
|
@@ -868,11 +896,11 @@ function generateConfidenceExplanation({
|
|
|
868
896
|
if (expectationStrength !== 'PROVEN') {
|
|
869
897
|
whatWouldIncreaseConfidence.push('Make the expectation proven by adding explicit code that promises the behavior');
|
|
870
898
|
}
|
|
871
|
-
if (!
|
|
899
|
+
if (!allSensorsWithData) {
|
|
872
900
|
const missing = [];
|
|
873
|
-
if (!
|
|
874
|
-
if (!
|
|
875
|
-
if (!
|
|
901
|
+
if (!sensorsWithData.network) missing.push('network monitoring');
|
|
902
|
+
if (!sensorsWithData.console) missing.push('console error detection');
|
|
903
|
+
if (!sensorsWithData.ui) missing.push('UI change detection');
|
|
876
904
|
whatWouldIncreaseConfidence.push(`Enable missing sensors: ${missing.join(', ')}`);
|
|
877
905
|
}
|
|
878
906
|
if (attemptMeta && !attemptMeta.repeated && level === 'LOW') {
|
|
@@ -888,7 +916,7 @@ function generateConfidenceExplanation({
|
|
|
888
916
|
if (expectationStrength === 'PROVEN') {
|
|
889
917
|
whatWouldReduceConfidence.push('If expectation becomes unproven (code changes, expectation removed)');
|
|
890
918
|
}
|
|
891
|
-
if (
|
|
919
|
+
if (allSensorsWithData) {
|
|
892
920
|
whatWouldReduceConfidence.push('If sensors become unavailable or disabled');
|
|
893
921
|
}
|
|
894
922
|
if (boosts.length > 0) {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 15 — Confidence Helper
|
|
3
|
+
*
|
|
4
|
+
* Helper function to add unified confidence to findings
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { computeConfidenceForFinding } from '../core/confidence-engine.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* PHASE 15: Add unified confidence to a finding
|
|
11
|
+
*
|
|
12
|
+
* @param {Object} finding - Finding object
|
|
13
|
+
* @param {Object} params - Confidence computation parameters
|
|
14
|
+
* @returns {Object} Finding with unified confidence fields
|
|
15
|
+
*/
|
|
16
|
+
export function addUnifiedConfidence(finding, params) {
|
|
17
|
+
// @ts-expect-error - Optional params structure
|
|
18
|
+
const unifiedConfidence = computeConfidenceForFinding({
|
|
19
|
+
findingType: finding.type || 'unknown',
|
|
20
|
+
expectation: params.expectation || null,
|
|
21
|
+
sensors: params.sensors || {},
|
|
22
|
+
comparisons: params.comparisons || {},
|
|
23
|
+
evidence: params.evidence || {},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Add unified confidence fields (additive only)
|
|
27
|
+
return {
|
|
28
|
+
...finding,
|
|
29
|
+
confidence: unifiedConfidence.score, // PHASE 15: Normalized 0..1
|
|
30
|
+
confidenceLevel: unifiedConfidence.level, // PHASE 15: HIGH/MEDIUM/LOW/UNPROVEN
|
|
31
|
+
confidenceReasons: unifiedConfidence.reasons, // PHASE 15: Stable reason codes
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 14 — Dynamic Route Findings Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects dynamic route-related findings with proper verifiability classification
|
|
5
|
+
* and intentional skips for unverifiable routes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
classifyDynamicRoute,
|
|
10
|
+
correlateDynamicRouteNavigation,
|
|
11
|
+
buildDynamicRouteEvidence,
|
|
12
|
+
shouldSkipDynamicRoute,
|
|
13
|
+
DYNAMIC_ROUTE_VERIFIABILITY,
|
|
14
|
+
ROUTE_VERDICT,
|
|
15
|
+
} from '../core/dynamic-route-intelligence.js';
|
|
16
|
+
import { buildRouteModels } from '../core/route-intelligence.js';
|
|
17
|
+
import { computeConfidence } from './confidence-engine.js';
|
|
18
|
+
import { computeConfidenceForFinding } from '../core/confidence-engine.js';
|
|
19
|
+
import { buildAndEnforceEvidencePackage } from '../core/evidence-builder.js';
|
|
20
|
+
import { applyGuardrails } from '../core/guardrails-engine.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* PHASE 14: Detect dynamic route-related findings
|
|
24
|
+
*
|
|
25
|
+
* @param {Array} traces - Interaction traces
|
|
26
|
+
* @param {Object} manifest - Project manifest with routes and expectations
|
|
27
|
+
* @param {Array} _findings - Findings array to append to
|
|
28
|
+
* @ts-expect-error - JSDoc param documented but unused
|
|
29
|
+
* @returns {Object} { findings: Array, skips: Array }
|
|
30
|
+
*/
|
|
31
|
+
export function detectDynamicRouteFindings(traces, manifest, _findings) {
|
|
32
|
+
const dynamicRouteFindings = [];
|
|
33
|
+
const skips = [];
|
|
34
|
+
|
|
35
|
+
// Build route models from manifest routes
|
|
36
|
+
const routeModels = buildRouteModels(manifest.routes || []);
|
|
37
|
+
|
|
38
|
+
// Process each trace
|
|
39
|
+
for (const trace of traces) {
|
|
40
|
+
const interaction = trace.interaction || {};
|
|
41
|
+
|
|
42
|
+
// Find navigation expectations for this interaction
|
|
43
|
+
const navigationExpectations = findNavigationExpectations(manifest, interaction, trace);
|
|
44
|
+
|
|
45
|
+
for (const expectation of navigationExpectations) {
|
|
46
|
+
const navigationTarget = expectation.targetPath || expectation.expectedTarget || '';
|
|
47
|
+
|
|
48
|
+
if (!navigationTarget) continue;
|
|
49
|
+
|
|
50
|
+
// Find matching route model
|
|
51
|
+
const matchingRoute = findMatchingRoute(navigationTarget, routeModels);
|
|
52
|
+
|
|
53
|
+
if (!matchingRoute) {
|
|
54
|
+
// No route match - handled by regular route findings
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check if route is dynamic
|
|
59
|
+
const classification = classifyDynamicRoute(matchingRoute, trace);
|
|
60
|
+
|
|
61
|
+
// If route is unverifiable, add to skips
|
|
62
|
+
if (classification.verifiability === DYNAMIC_ROUTE_VERIFIABILITY.UNVERIFIABLE_DYNAMIC) {
|
|
63
|
+
const skipDecision = shouldSkipDynamicRoute(matchingRoute, trace);
|
|
64
|
+
|
|
65
|
+
skips.push({
|
|
66
|
+
type: 'dynamic_route_unverifiable',
|
|
67
|
+
interaction: {
|
|
68
|
+
type: interaction.type,
|
|
69
|
+
selector: interaction.selector,
|
|
70
|
+
label: interaction.label,
|
|
71
|
+
},
|
|
72
|
+
route: {
|
|
73
|
+
path: matchingRoute.path,
|
|
74
|
+
originalPattern: matchingRoute.originalPattern,
|
|
75
|
+
sourceRef: matchingRoute.sourceRef,
|
|
76
|
+
},
|
|
77
|
+
reason: skipDecision.reason,
|
|
78
|
+
confidence: skipDecision.confidence,
|
|
79
|
+
expectation: {
|
|
80
|
+
target: navigationTarget,
|
|
81
|
+
source: expectation.source,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Correlate navigation with dynamic route
|
|
88
|
+
const correlation = correlateDynamicRouteNavigation(expectation, matchingRoute, trace);
|
|
89
|
+
|
|
90
|
+
// If correlation indicates skip, add to skips
|
|
91
|
+
if (correlation.skip) {
|
|
92
|
+
skips.push({
|
|
93
|
+
type: 'dynamic_route_skip',
|
|
94
|
+
interaction: {
|
|
95
|
+
type: interaction.type,
|
|
96
|
+
selector: interaction.selector,
|
|
97
|
+
label: interaction.label,
|
|
98
|
+
},
|
|
99
|
+
route: {
|
|
100
|
+
path: matchingRoute.path,
|
|
101
|
+
originalPattern: matchingRoute.originalPattern,
|
|
102
|
+
sourceRef: matchingRoute.sourceRef,
|
|
103
|
+
},
|
|
104
|
+
reason: correlation.skipReason,
|
|
105
|
+
confidence: correlation.confidence,
|
|
106
|
+
expectation: {
|
|
107
|
+
target: navigationTarget,
|
|
108
|
+
source: expectation.source,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Generate finding if verdict indicates failure
|
|
115
|
+
if (correlation.verdict === ROUTE_VERDICT.SILENT_FAILURE ||
|
|
116
|
+
correlation.verdict === ROUTE_VERDICT.ROUTE_MISMATCH ||
|
|
117
|
+
(correlation.verdict === ROUTE_VERDICT.AMBIGUOUS && correlation.confidence >= 0.7)) {
|
|
118
|
+
|
|
119
|
+
// Build evidence
|
|
120
|
+
const evidence = buildDynamicRouteEvidence(expectation, matchingRoute, correlation, trace);
|
|
121
|
+
const classificationReason = classification.reason || correlation.reason || null;
|
|
122
|
+
|
|
123
|
+
// PHASE 14: Evidence Law - require sufficient evidence for CONFIRMED
|
|
124
|
+
const hasSufficientEvidence = evidence.beforeAfter.beforeUrl &&
|
|
125
|
+
evidence.beforeAfter.afterUrl &&
|
|
126
|
+
(evidence.signals.urlChanged ||
|
|
127
|
+
evidence.signals.routeMatched ||
|
|
128
|
+
evidence.signals.uiFeedback !== 'FEEDBACK_MISSING' ||
|
|
129
|
+
evidence.signals.domChanged);
|
|
130
|
+
|
|
131
|
+
// Determine finding type early (before use in confidence call)
|
|
132
|
+
let findingType = 'dynamic_route_silent_failure';
|
|
133
|
+
if (correlation.verdict === ROUTE_VERDICT.ROUTE_MISMATCH) {
|
|
134
|
+
findingType = 'dynamic_route_mismatch';
|
|
135
|
+
} else if (correlation.verdict === ROUTE_VERDICT.AMBIGUOUS) {
|
|
136
|
+
findingType = 'dynamic_route_ambiguous';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// PHASE 15: Compute unified confidence
|
|
140
|
+
const unifiedConfidence = computeConfidenceForFinding({
|
|
141
|
+
findingType: findingType,
|
|
142
|
+
expectation,
|
|
143
|
+
sensors: trace.sensors || {},
|
|
144
|
+
comparisons: {},
|
|
145
|
+
evidence,
|
|
146
|
+
options: {}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Legacy confidence for backward compatibility
|
|
150
|
+
const _confidence = computeConfidence({
|
|
151
|
+
findingType: 'dynamic_route_silent_failure',
|
|
152
|
+
expectation,
|
|
153
|
+
sensors: trace.sensors || {},
|
|
154
|
+
comparisons: {},
|
|
155
|
+
attemptMeta: {},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Determine severity based on evidence and verdict
|
|
159
|
+
let severity = 'SUSPECTED';
|
|
160
|
+
if (hasSufficientEvidence && correlation.verdict === ROUTE_VERDICT.SILENT_FAILURE && (unifiedConfidence.score01 || unifiedConfidence.score || 0) >= 0.8) {
|
|
161
|
+
severity = 'CONFIRMED';
|
|
162
|
+
} else if (correlation.verdict === ROUTE_VERDICT.ROUTE_MISMATCH && hasSufficientEvidence) {
|
|
163
|
+
severity = 'CONFIRMED';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const finding = {
|
|
167
|
+
type: findingType,
|
|
168
|
+
severity,
|
|
169
|
+
confidence: unifiedConfidence.score01 || unifiedConfidence.score || 0, // Contract v1: score01 canonical
|
|
170
|
+
confidenceLevel: unifiedConfidence.level, // PHASE 15: Add confidence level
|
|
171
|
+
confidenceReasons: unifiedConfidence.topReasons || unifiedConfidence.reasons || [], // Contract v1: topReasons
|
|
172
|
+
interaction: {
|
|
173
|
+
type: interaction.type,
|
|
174
|
+
selector: interaction.selector,
|
|
175
|
+
label: interaction.label,
|
|
176
|
+
},
|
|
177
|
+
reason: correlation.reason || 'Dynamic route navigation outcome unclear',
|
|
178
|
+
evidence,
|
|
179
|
+
source: {
|
|
180
|
+
file: expectation.source?.file || null,
|
|
181
|
+
line: expectation.source?.line || null,
|
|
182
|
+
column: expectation.source?.column || null,
|
|
183
|
+
context: expectation.source?.context || null,
|
|
184
|
+
astSource: expectation.source?.astSource || null,
|
|
185
|
+
},
|
|
186
|
+
route: correlation.route,
|
|
187
|
+
expectation,
|
|
188
|
+
classification: classification,
|
|
189
|
+
classificationReason: classificationReason,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// PHASE 16: Build and enforce evidence package
|
|
193
|
+
const findingWithEvidence = buildAndEnforceEvidencePackage(finding, {
|
|
194
|
+
expectation,
|
|
195
|
+
trace,
|
|
196
|
+
evidence,
|
|
197
|
+
confidence: unifiedConfidence,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// PHASE 17: Apply guardrails (AFTER evidence builder)
|
|
201
|
+
const context = {
|
|
202
|
+
evidencePackage: findingWithEvidence.evidencePackage,
|
|
203
|
+
signals: findingWithEvidence.evidencePackage?.signals || evidence.signals || {},
|
|
204
|
+
confidenceReasons: unifiedConfidence.reasons || [],
|
|
205
|
+
promiseType: expectation?.type || null,
|
|
206
|
+
};
|
|
207
|
+
const { finding: findingWithGuardrails } = applyGuardrails(findingWithEvidence, context);
|
|
208
|
+
|
|
209
|
+
dynamicRouteFindings.push(findingWithGuardrails);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
findings: dynamicRouteFindings,
|
|
216
|
+
skips: skips,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Find navigation expectations matching the interaction
|
|
222
|
+
*/
|
|
223
|
+
function findNavigationExpectations(manifest, interaction, trace) {
|
|
224
|
+
const expectations = [];
|
|
225
|
+
|
|
226
|
+
// Check static expectations
|
|
227
|
+
if (manifest.staticExpectations) {
|
|
228
|
+
const beforeUrl = trace.before?.url || trace.sensors?.navigation?.beforeUrl || '';
|
|
229
|
+
const beforePath = extractPathFromUrl(beforeUrl);
|
|
230
|
+
|
|
231
|
+
if (beforePath) {
|
|
232
|
+
const normalizedBefore = beforePath.replace(/\/$/, '') || '/';
|
|
233
|
+
|
|
234
|
+
for (const expectation of manifest.staticExpectations) {
|
|
235
|
+
if (expectation.type !== 'navigation' && expectation.type !== 'spa_navigation') {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const normalizedFrom = (expectation.fromPath || '').replace(/\/$/, '') || '/';
|
|
240
|
+
if (normalizedFrom === normalizedBefore) {
|
|
241
|
+
const selectorHint = expectation.selectorHint || '';
|
|
242
|
+
const interactionSelector = interaction.selector || '';
|
|
243
|
+
|
|
244
|
+
if (!selectorHint || !interactionSelector ||
|
|
245
|
+
selectorHint === interactionSelector ||
|
|
246
|
+
selectorHint.includes(interactionSelector) ||
|
|
247
|
+
interactionSelector.includes(selectorHint)) {
|
|
248
|
+
expectations.push(expectation);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Check expectations from intel (AST-based)
|
|
256
|
+
if (manifest.expectations) {
|
|
257
|
+
for (const expectation of manifest.expectations) {
|
|
258
|
+
if (expectation.type === 'navigation' || expectation.type === 'spa_navigation') {
|
|
259
|
+
const selectorHint = expectation.selectorHint || '';
|
|
260
|
+
const interactionSelector = interaction.selector || '';
|
|
261
|
+
const interactionLabel = (interaction.label || '').toLowerCase();
|
|
262
|
+
const expectationLabel = (expectation.promise?.value || '').toLowerCase();
|
|
263
|
+
|
|
264
|
+
if (selectorHint === interactionSelector ||
|
|
265
|
+
(expectationLabel && interactionLabel && expectationLabel.includes(interactionLabel))) {
|
|
266
|
+
expectations.push(expectation);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return expectations;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Find matching route model for navigation target
|
|
277
|
+
*/
|
|
278
|
+
function findMatchingRoute(navigationTarget, routeModels) {
|
|
279
|
+
// Try exact match first
|
|
280
|
+
let matchedRoute = routeModels.find(r => r.path === navigationTarget);
|
|
281
|
+
|
|
282
|
+
if (matchedRoute) {
|
|
283
|
+
return matchedRoute;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Try pattern match for dynamic routes
|
|
287
|
+
for (const route of routeModels) {
|
|
288
|
+
if (route.isDynamic && route.originalPattern) {
|
|
289
|
+
if (matchDynamicPattern(navigationTarget, route.originalPattern)) {
|
|
290
|
+
return route;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Match dynamic pattern against actual path
|
|
300
|
+
*/
|
|
301
|
+
function matchDynamicPattern(actualPath, pattern) {
|
|
302
|
+
if (!actualPath || !pattern) return false;
|
|
303
|
+
|
|
304
|
+
// Convert pattern to regex
|
|
305
|
+
let regexPattern = pattern;
|
|
306
|
+
|
|
307
|
+
// Replace :param with (\w+)
|
|
308
|
+
regexPattern = regexPattern.replace(/:(\w+)/g, '(\\w+)');
|
|
309
|
+
|
|
310
|
+
// Replace [param] with (\w+)
|
|
311
|
+
regexPattern = regexPattern.replace(/\[(\w+)\]/g, '(\\w+)');
|
|
312
|
+
|
|
313
|
+
// Escape other special characters
|
|
314
|
+
regexPattern = regexPattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
315
|
+
|
|
316
|
+
// Restore the capture groups
|
|
317
|
+
regexPattern = regexPattern.replace(/\\\(\\\\w\+\\\)/g, '(\\w+)');
|
|
318
|
+
|
|
319
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
320
|
+
return regex.test(actualPath);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Extract path from URL
|
|
325
|
+
*/
|
|
326
|
+
function extractPathFromUrl(url) {
|
|
327
|
+
if (!url || typeof url !== 'string') return '';
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
const urlObj = new URL(url);
|
|
331
|
+
return urlObj.pathname;
|
|
332
|
+
} catch {
|
|
333
|
+
// Relative URL
|
|
334
|
+
const pathMatch = url.match(/^([^?#]+)/);
|
|
335
|
+
return pathMatch ? pathMatch[1] : url;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|