@veraxhq/verax 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -18
- package/bin/verax.js +7 -0
- package/package.json +3 -3
- package/src/cli/commands/baseline.js +104 -0
- package/src/cli/commands/default.js +79 -25
- package/src/cli/commands/ga.js +243 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +131 -2
- package/src/cli/commands/release-check.js +213 -0
- package/src/cli/commands/run.js +246 -35
- package/src/cli/commands/security-check.js +211 -0
- package/src/cli/commands/truth.js +114 -0
- package/src/cli/entry.js +304 -67
- package/src/cli/util/angular-component-extractor.js +179 -0
- package/src/cli/util/angular-navigation-detector.js +141 -0
- package/src/cli/util/angular-network-detector.js +161 -0
- package/src/cli/util/angular-state-detector.js +162 -0
- package/src/cli/util/ast-interactive-detector.js +546 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/determinism-runner.js +123 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/env-url.js +4 -0
- package/src/cli/util/expectation-extractor.js +369 -73
- package/src/cli/util/findings-writer.js +126 -16
- package/src/cli/util/learn-writer.js +3 -1
- package/src/cli/util/observe-writer.js +3 -1
- package/src/cli/util/paths.js +3 -12
- package/src/cli/util/project-discovery.js +3 -0
- package/src/cli/util/project-writer.js +3 -1
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +1 -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 +147 -0
- package/src/cli/util/svelte-state-detector.js +243 -0
- package/src/cli/util/vue-navigation-detector.js +177 -0
- package/src/cli/util/vue-sfc-extractor.js +162 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/verax/cli/finding-explainer.js +56 -3
- package/src/verax/core/artifacts/registry.js +154 -0
- package/src/verax/core/artifacts/verifier.js +980 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +231 -0
- package/src/verax/core/capabilities/gates.js +499 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +137 -0
- package/src/verax/core/confidence/confidence-invariants.js +234 -0
- package/src/verax/core/confidence/confidence-report-writer.js +112 -0
- package/src/verax/core/confidence/confidence-weights.js +44 -0
- package/src/verax/core/confidence/confidence.defaults.js +65 -0
- package/src/verax/core/confidence/confidence.loader.js +79 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +484 -0
- package/src/verax/core/confidence-engine.js +486 -0
- package/src/verax/core/confidence-engine.js.backup +471 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +185 -0
- package/src/verax/core/contracts/validators.js +381 -0
- package/src/verax/core/decision-snapshot.js +30 -3
- package/src/verax/core/decisions/decision.trace.js +276 -0
- package/src/verax/core/determinism/contract-writer.js +89 -0
- package/src/verax/core/determinism/contract.js +139 -0
- package/src/verax/core/determinism/diff.js +364 -0
- package/src/verax/core/determinism/engine.js +221 -0
- package/src/verax/core/determinism/finding-identity.js +148 -0
- package/src/verax/core/determinism/normalize.js +438 -0
- package/src/verax/core/determinism/report-writer.js +92 -0
- package/src/verax/core/determinism/run-fingerprint.js +118 -0
- package/src/verax/core/dynamic-route-intelligence.js +528 -0
- package/src/verax/core/evidence/evidence-capture-service.js +307 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
- package/src/verax/core/evidence-builder.js +487 -0
- package/src/verax/core/execution-mode-context.js +77 -0
- package/src/verax/core/execution-mode-detector.js +190 -0
- package/src/verax/core/failures/exit-codes.js +86 -0
- package/src/verax/core/failures/failure-summary.js +76 -0
- package/src/verax/core/failures/failure.factory.js +225 -0
- package/src/verax/core/failures/failure.ledger.js +132 -0
- package/src/verax/core/failures/failure.types.js +196 -0
- package/src/verax/core/failures/index.js +10 -0
- package/src/verax/core/ga/ga-report-writer.js +43 -0
- package/src/verax/core/ga/ga.artifact.js +49 -0
- package/src/verax/core/ga/ga.contract.js +434 -0
- package/src/verax/core/ga/ga.enforcer.js +86 -0
- package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
- package/src/verax/core/guardrails/policy.defaults.js +210 -0
- package/src/verax/core/guardrails/policy.loader.js +83 -0
- package/src/verax/core/guardrails/policy.schema.js +110 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
- package/src/verax/core/guardrails-engine.js +505 -0
- package/src/verax/core/observe/run-timeline.js +316 -0
- package/src/verax/core/perf/perf.contract.js +186 -0
- package/src/verax/core/perf/perf.display.js +65 -0
- package/src/verax/core/perf/perf.enforcer.js +91 -0
- package/src/verax/core/perf/perf.monitor.js +209 -0
- package/src/verax/core/perf/perf.report.js +198 -0
- package/src/verax/core/pipeline-tracker.js +238 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +271 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +159 -0
- package/src/verax/core/release/reproducibility.check.js +221 -0
- package/src/verax/core/release/sbom.builder.js +283 -0
- package/src/verax/core/report/cross-index.js +192 -0
- package/src/verax/core/report/human-summary.js +222 -0
- package/src/verax/core/route-intelligence.js +419 -0
- package/src/verax/core/security/secrets.scan.js +326 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +124 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +326 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/truth/truth.certificate.js +250 -0
- package/src/verax/core/ui-feedback-intelligence.js +515 -0
- package/src/verax/detect/confidence-engine.js +628 -40
- package/src/verax/detect/confidence-helper.js +33 -0
- package/src/verax/detect/detection-engine.js +18 -1
- package/src/verax/detect/dynamic-route-findings.js +335 -0
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +3 -1
- package/src/verax/detect/findings-writer.js +141 -5
- package/src/verax/detect/index.js +229 -5
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/route-findings.js +218 -0
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/verdict-engine.js +57 -3
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/index.js +413 -45
- package/src/verax/learn/action-contract-extractor.js +682 -64
- package/src/verax/learn/route-validator.js +4 -1
- package/src/verax/observe/index.js +88 -843
- package/src/verax/observe/interaction-runner.js +25 -8
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +191 -0
- package/src/verax/observe/observe-runner.js +226 -0
- package/src/verax/observe/observers/budget-observer.js +185 -0
- package/src/verax/observe/observers/console-observer.js +102 -0
- package/src/verax/observe/observers/coverage-observer.js +107 -0
- package/src/verax/observe/observers/interaction-observer.js +471 -0
- package/src/verax/observe/observers/navigation-observer.js +132 -0
- package/src/verax/observe/observers/network-observer.js +87 -0
- package/src/verax/observe/observers/safety-observer.js +82 -0
- package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/observe/ui-signal-sensor.js +148 -2
- package/src/verax/scan-summary-writer.js +42 -8
- package/src/verax/shared/artifact-manager.js +8 -5
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/view-switch-rules.js +208 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 16 — Evidence Hardening: Mandatory Evidence Packages
|
|
3
|
+
* PHASE 21.1 — Evidence Law Hard Lock: Unbreakable invariant enforcement
|
|
4
|
+
*
|
|
5
|
+
* Central evidence builder that creates standardized Evidence Packages
|
|
6
|
+
* for all findings. Ensures completeness and enforces Evidence Law.
|
|
7
|
+
*
|
|
8
|
+
* HARD LOCK RULES:
|
|
9
|
+
* - buildEvidencePackage() MUST return complete evidencePackage or throw EvidenceBuildError
|
|
10
|
+
* - No silent failures allowed
|
|
11
|
+
* - CONFIRMED findings without complete evidencePackage is IMPOSSIBLE
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* PHASE 16: Evidence Package Schema
|
|
16
|
+
*
|
|
17
|
+
* @typedef {Object} EvidencePackage
|
|
18
|
+
* @property {Object} trigger - What triggered the finding (AST/DOM source + context)
|
|
19
|
+
* @property {Object} before - Before state (screenshot, URL, DOM signature)
|
|
20
|
+
* @property {Object} action - Action trace (interaction + timing)
|
|
21
|
+
* @property {Object} after - After state (screenshot, URL, DOM signature)
|
|
22
|
+
* @property {Object} signals - All sensor signals (network, console, ui feedback, route)
|
|
23
|
+
* @property {Object} justification - Confidence reasons and verdict rationale
|
|
24
|
+
* @property {Array<string>} missingEvidence - Fields that are missing (if any)
|
|
25
|
+
* @property {boolean} isComplete - Whether all required fields are present
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* PHASE 16: Required fields for CONFIRMED findings
|
|
30
|
+
* PHASE 21.1: Hard lock - these fields are MANDATORY for CONFIRMED
|
|
31
|
+
*/
|
|
32
|
+
const REQUIRED_FIELDS_CONFIRMED = [
|
|
33
|
+
'trigger.source',
|
|
34
|
+
'before.screenshot',
|
|
35
|
+
'after.screenshot',
|
|
36
|
+
'before.url',
|
|
37
|
+
'after.url',
|
|
38
|
+
'action.interaction',
|
|
39
|
+
'signals.network',
|
|
40
|
+
'signals.uiSignals',
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* PHASE 21.1: Evidence Build Error
|
|
45
|
+
* Thrown when evidence building fails - must NOT be silently caught
|
|
46
|
+
*/
|
|
47
|
+
export class EvidenceBuildError extends Error {
|
|
48
|
+
constructor(message, missingFields = [], evidencePackage = null) {
|
|
49
|
+
super(message);
|
|
50
|
+
this.name = 'EvidenceBuildError';
|
|
51
|
+
this.code = 'EVIDENCE_BUILD_FAILED';
|
|
52
|
+
this.missingFields = missingFields;
|
|
53
|
+
this.evidencePackage = evidencePackage;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* PHASE 16: Build standardized Evidence Package
|
|
59
|
+
*
|
|
60
|
+
* @param {Object} params - Evidence building parameters
|
|
61
|
+
* @param {Object} params.expectation - Promise/expectation
|
|
62
|
+
* @param {Object} params.trace - Interaction trace
|
|
63
|
+
* @param {Object} params.evidence - Existing evidence (optional, will be merged)
|
|
64
|
+
* @param {Object} params.confidence - Confidence result (optional)
|
|
65
|
+
* @returns {Object} Evidence Package
|
|
66
|
+
*/
|
|
67
|
+
export function buildEvidencePackage({ expectation, trace, evidence = {}, confidence = null }) {
|
|
68
|
+
const evidencePackage = {
|
|
69
|
+
trigger: buildTrigger(expectation, trace),
|
|
70
|
+
before: buildBeforeState(trace),
|
|
71
|
+
action: buildActionTrace(trace),
|
|
72
|
+
after: buildAfterState(trace),
|
|
73
|
+
signals: buildSignals(trace),
|
|
74
|
+
justification: buildJustification(expectation, trace, confidence),
|
|
75
|
+
missingEvidence: [],
|
|
76
|
+
isComplete: false,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Check completeness
|
|
80
|
+
const missing = checkMissingEvidence(evidencePackage);
|
|
81
|
+
evidencePackage.missingEvidence = missing;
|
|
82
|
+
evidencePackage.isComplete = missing.length === 0;
|
|
83
|
+
|
|
84
|
+
// Merge with existing evidence if provided
|
|
85
|
+
if (evidence && Object.keys(evidence).length > 0) {
|
|
86
|
+
evidencePackage.trigger = { ...evidencePackage.trigger, ...(evidence.trigger || {}) };
|
|
87
|
+
evidencePackage.before = { ...evidencePackage.before, ...(evidence.before || {}) };
|
|
88
|
+
evidencePackage.after = { ...evidencePackage.after, ...(evidence.after || {}) };
|
|
89
|
+
evidencePackage.signals = { ...evidencePackage.signals, ...(evidence.signals || {}) };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return evidencePackage;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Build trigger section (AST/DOM source + context)
|
|
97
|
+
*/
|
|
98
|
+
function buildTrigger(expectation, trace) {
|
|
99
|
+
const trigger = {
|
|
100
|
+
source: null,
|
|
101
|
+
context: null,
|
|
102
|
+
astSource: null,
|
|
103
|
+
domSelector: null,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
if (expectation) {
|
|
107
|
+
trigger.source = {
|
|
108
|
+
file: expectation.source?.file || null,
|
|
109
|
+
line: expectation.source?.line || null,
|
|
110
|
+
column: expectation.source?.column || null,
|
|
111
|
+
};
|
|
112
|
+
trigger.context = expectation.source?.context || null;
|
|
113
|
+
trigger.astSource = expectation.source?.astSource ||
|
|
114
|
+
expectation.metadata?.astSource || null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (trace.interaction) {
|
|
118
|
+
trigger.domSelector = trace.interaction.selector || null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return trigger;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Build before state (screenshot, URL, DOM signature)
|
|
126
|
+
*/
|
|
127
|
+
function buildBeforeState(trace) {
|
|
128
|
+
const before = {
|
|
129
|
+
screenshot: null,
|
|
130
|
+
url: null,
|
|
131
|
+
domSignature: null,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
if (trace.before) {
|
|
135
|
+
before.screenshot = trace.before.screenshot || null;
|
|
136
|
+
before.url = trace.before.url || null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Also check sensors for before state
|
|
140
|
+
if (trace.sensors?.navigation?.beforeUrl) {
|
|
141
|
+
before.url = trace.sensors.navigation.beforeUrl;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// DOM signature (hash)
|
|
145
|
+
if (trace.dom?.beforeHash) {
|
|
146
|
+
before.domSignature = trace.dom.beforeHash;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return before;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Build action trace (interaction + timing)
|
|
154
|
+
*/
|
|
155
|
+
function buildActionTrace(trace) {
|
|
156
|
+
const action = {
|
|
157
|
+
interaction: null,
|
|
158
|
+
timing: null,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (trace.interaction) {
|
|
162
|
+
action.interaction = {
|
|
163
|
+
type: trace.interaction.type || null,
|
|
164
|
+
selector: trace.interaction.selector || null,
|
|
165
|
+
label: trace.interaction.label || null,
|
|
166
|
+
href: trace.interaction.href || null,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Timing information
|
|
171
|
+
if (trace.timing) {
|
|
172
|
+
action.timing = trace.timing;
|
|
173
|
+
} else if (trace.sensors?.timing) {
|
|
174
|
+
action.timing = trace.sensors.timing;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return action;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Build after state (screenshot, URL, DOM signature)
|
|
182
|
+
*/
|
|
183
|
+
function buildAfterState(trace) {
|
|
184
|
+
const after = {
|
|
185
|
+
screenshot: null,
|
|
186
|
+
url: null,
|
|
187
|
+
domSignature: null,
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
if (trace.after) {
|
|
191
|
+
after.screenshot = trace.after.screenshot || null;
|
|
192
|
+
after.url = trace.after.url || null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Also check sensors for after state
|
|
196
|
+
if (trace.sensors?.navigation?.afterUrl) {
|
|
197
|
+
after.url = trace.sensors.navigation.afterUrl;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// DOM signature (hash)
|
|
201
|
+
if (trace.dom?.afterHash) {
|
|
202
|
+
after.domSignature = trace.dom.afterHash;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return after;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Build signals section (network, console, ui feedback, route)
|
|
210
|
+
*/
|
|
211
|
+
function buildSignals(trace) {
|
|
212
|
+
const signals = {
|
|
213
|
+
network: null,
|
|
214
|
+
console: null,
|
|
215
|
+
uiSignals: null,
|
|
216
|
+
uiFeedback: null,
|
|
217
|
+
navigation: null,
|
|
218
|
+
route: null,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const sensors = trace.sensors || {};
|
|
222
|
+
|
|
223
|
+
if (sensors.network) {
|
|
224
|
+
signals.network = {
|
|
225
|
+
totalRequests: sensors.network.totalRequests || 0,
|
|
226
|
+
failedRequests: sensors.network.failedRequests || 0,
|
|
227
|
+
successfulRequests: sensors.network.successfulRequests || 0,
|
|
228
|
+
topFailedUrls: sensors.network.topFailedUrls || [],
|
|
229
|
+
hasNetworkActivity: sensors.network.hasNetworkActivity || false,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (sensors.console) {
|
|
234
|
+
signals.console = {
|
|
235
|
+
errorCount: sensors.console.errorCount || 0,
|
|
236
|
+
errors: sensors.console.errors || 0,
|
|
237
|
+
warnings: sensors.console.warnings || 0,
|
|
238
|
+
hasErrors: sensors.console.hasErrors || false,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (sensors.uiSignals) {
|
|
243
|
+
signals.uiSignals = {
|
|
244
|
+
changed: sensors.uiSignals.diff?.changed || false,
|
|
245
|
+
hasLoadingIndicator: sensors.uiSignals.after?.hasLoadingIndicator || false,
|
|
246
|
+
hasDialog: sensors.uiSignals.after?.hasDialog || false,
|
|
247
|
+
hasErrorSignal: sensors.uiSignals.after?.hasErrorSignal || false,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (sensors.uiFeedback) {
|
|
252
|
+
signals.uiFeedback = {
|
|
253
|
+
overallUiFeedbackScore: sensors.uiFeedback.overallUiFeedbackScore || 0,
|
|
254
|
+
signals: sensors.uiFeedback.signals || {},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (sensors.navigation) {
|
|
259
|
+
signals.navigation = {
|
|
260
|
+
urlChanged: sensors.navigation.urlChanged || false,
|
|
261
|
+
routerStateChanged: sensors.navigation.routerStateChanged || false,
|
|
262
|
+
beforeUrl: sensors.navigation.beforeUrl || null,
|
|
263
|
+
afterUrl: sensors.navigation.afterUrl || null,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return signals;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Build justification (confidence reasons and verdict rationale)
|
|
272
|
+
*/
|
|
273
|
+
function buildJustification(expectation, trace, confidence) {
|
|
274
|
+
const justification = {
|
|
275
|
+
confidenceReasons: null,
|
|
276
|
+
verdictRationale: null,
|
|
277
|
+
confidenceScore: null,
|
|
278
|
+
confidenceLevel: null,
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
if (confidence) {
|
|
282
|
+
justification.confidenceReasons = confidence.reasons || [];
|
|
283
|
+
justification.confidenceScore = confidence.score || null;
|
|
284
|
+
justification.confidenceLevel = confidence.level || null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Verdict rationale from finding reason
|
|
288
|
+
if (trace.reason) {
|
|
289
|
+
justification.verdictRationale = trace.reason;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return justification;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Check for missing evidence fields
|
|
297
|
+
*/
|
|
298
|
+
function checkMissingEvidence(evidencePackage) {
|
|
299
|
+
const missing = [];
|
|
300
|
+
|
|
301
|
+
// Check required fields for CONFIRMED findings
|
|
302
|
+
if (!evidencePackage.trigger.source || !evidencePackage.trigger.source.file) {
|
|
303
|
+
missing.push('trigger.source');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!evidencePackage.before.screenshot) {
|
|
307
|
+
missing.push('before.screenshot');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (!evidencePackage.after.screenshot) {
|
|
311
|
+
missing.push('after.screenshot');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!evidencePackage.before.url) {
|
|
315
|
+
missing.push('before.url');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (!evidencePackage.after.url) {
|
|
319
|
+
missing.push('after.url');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (!evidencePackage.action.interaction || !evidencePackage.action.interaction.type) {
|
|
323
|
+
missing.push('action.interaction');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (!evidencePackage.signals.network) {
|
|
327
|
+
missing.push('signals.network');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (!evidencePackage.signals.uiSignals) {
|
|
331
|
+
missing.push('signals.uiSignals');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return missing;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* PHASE 16: Validate evidence package completeness for CONFIRMED findings
|
|
339
|
+
*
|
|
340
|
+
* @param {Object} evidencePackage - Evidence package to validate
|
|
341
|
+
* @param {string} severity - Finding severity (CONFIRMED or SUSPECTED)
|
|
342
|
+
* @returns {Object} { isComplete, missingFields, shouldDowngrade }
|
|
343
|
+
*/
|
|
344
|
+
export function validateEvidencePackage(evidencePackage, severity) {
|
|
345
|
+
const missing = checkMissingEvidence(evidencePackage);
|
|
346
|
+
const isComplete = missing.length === 0;
|
|
347
|
+
|
|
348
|
+
// CONFIRMED findings MUST have complete evidence
|
|
349
|
+
const shouldDowngrade = severity === 'CONFIRMED' && !isComplete;
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
isComplete,
|
|
353
|
+
missingFields: missing,
|
|
354
|
+
shouldDowngrade,
|
|
355
|
+
downgradeReason: shouldDowngrade
|
|
356
|
+
? `Evidence Law Violation: Missing required evidence fields: ${missing.join(', ')}`
|
|
357
|
+
: null,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* PHASE 21.1: Strict validation for CONFIRMED findings
|
|
363
|
+
*
|
|
364
|
+
* HARD LOCK: If finding is CONFIRMED and evidencePackage is incomplete,
|
|
365
|
+
* this function throws EvidenceBuildError (fail closed, not open).
|
|
366
|
+
*
|
|
367
|
+
* @param {Object} evidencePackage - Evidence package to validate
|
|
368
|
+
* @param {string} severity - Finding severity (must be CONFIRMED for strict validation)
|
|
369
|
+
* @throws {EvidenceBuildError} If CONFIRMED finding has incomplete evidencePackage
|
|
370
|
+
*/
|
|
371
|
+
export function validateEvidencePackageStrict(evidencePackage, severity) {
|
|
372
|
+
if (!evidencePackage || typeof evidencePackage !== 'object') {
|
|
373
|
+
throw new EvidenceBuildError(
|
|
374
|
+
'Evidence Law Violation: evidencePackage is missing or invalid',
|
|
375
|
+
REQUIRED_FIELDS_CONFIRMED,
|
|
376
|
+
null
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (severity === 'CONFIRMED' || severity === 'CONFIRMED') {
|
|
381
|
+
const missing = checkMissingEvidence(evidencePackage);
|
|
382
|
+
const isComplete = missing.length === 0;
|
|
383
|
+
|
|
384
|
+
if (!isComplete) {
|
|
385
|
+
throw new EvidenceBuildError(
|
|
386
|
+
`Evidence Law Violation: CONFIRMED finding requires complete evidencePackage. Missing fields: ${missing.join(', ')}`,
|
|
387
|
+
missing,
|
|
388
|
+
evidencePackage
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Additional strict check: evidencePackage.isComplete must be true
|
|
393
|
+
if (evidencePackage.isComplete !== true) {
|
|
394
|
+
throw new EvidenceBuildError(
|
|
395
|
+
`Evidence Law Violation: CONFIRMED finding has evidencePackage.isComplete !== true`,
|
|
396
|
+
missing.length > 0 ? missing : REQUIRED_FIELDS_CONFIRMED,
|
|
397
|
+
evidencePackage
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
isComplete: evidencePackage.isComplete === true,
|
|
404
|
+
missingFields: checkMissingEvidence(evidencePackage),
|
|
405
|
+
valid: true
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* PHASE 16: Build evidence package and enforce completeness
|
|
411
|
+
* PHASE 21.1: Hard lock - throws EvidenceBuildError on failure (no silent failures)
|
|
412
|
+
* PHASE 22: Evidence System Hardening - records evidence intent and capture failures
|
|
413
|
+
*
|
|
414
|
+
* @param {Object} finding - Finding object
|
|
415
|
+
* @param {Object} params - Evidence building parameters
|
|
416
|
+
* @param {Array<Object>} captureFailures - Array of EvidenceCaptureFailure objects (optional)
|
|
417
|
+
* @returns {Object} Finding with evidencePackage and potentially downgraded severity
|
|
418
|
+
* @throws {EvidenceBuildError} If evidence building fails or CONFIRMED finding has incomplete evidence
|
|
419
|
+
*/
|
|
420
|
+
export function buildAndEnforceEvidencePackage(finding, params, captureFailures = []) {
|
|
421
|
+
// PHASE 21.1: Build evidence package - throws if it fails
|
|
422
|
+
let evidencePackage;
|
|
423
|
+
try {
|
|
424
|
+
evidencePackage = buildEvidencePackage(params);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
// Re-throw as EvidenceBuildError if not already
|
|
427
|
+
if (error instanceof EvidenceBuildError) {
|
|
428
|
+
throw error;
|
|
429
|
+
}
|
|
430
|
+
throw new EvidenceBuildError(
|
|
431
|
+
`Evidence building failed: ${error.message}`,
|
|
432
|
+
REQUIRED_FIELDS_CONFIRMED,
|
|
433
|
+
null
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// PHASE 22: Record capture failures in evidence package metadata
|
|
438
|
+
if (captureFailures && captureFailures.length > 0) {
|
|
439
|
+
evidencePackage.captureFailures = captureFailures.map(f => f.toJSON ? f.toJSON() : f);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// PHASE 21.1: Strict validation for CONFIRMED findings
|
|
443
|
+
const severity = finding.severity || finding.status || 'SUSPECTED';
|
|
444
|
+
if (severity === 'CONFIRMED') {
|
|
445
|
+
// Hard lock: CONFIRMED findings MUST have complete evidencePackage
|
|
446
|
+
validateEvidencePackageStrict(evidencePackage, severity);
|
|
447
|
+
// If we get here, evidencePackage is complete
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// For SUSPECTED findings, allow partial evidence
|
|
451
|
+
const validation = validateEvidencePackage(evidencePackage, severity);
|
|
452
|
+
|
|
453
|
+
// PHASE 22: Downgrade CONFIRMED if evidence incomplete (with evidence intent tracking)
|
|
454
|
+
let finalSeverity = severity;
|
|
455
|
+
let downgradeReason = null;
|
|
456
|
+
|
|
457
|
+
if (validation.shouldDowngrade) {
|
|
458
|
+
finalSeverity = 'SUSPECTED';
|
|
459
|
+
downgradeReason = validation.downgradeReason;
|
|
460
|
+
|
|
461
|
+
// PHASE 22: Record evidence intent in downgrade reason
|
|
462
|
+
if (captureFailures && captureFailures.length > 0) {
|
|
463
|
+
const failureCodes = captureFailures.map(f => f.reasonCode || 'UNKNOWN').join(', ');
|
|
464
|
+
downgradeReason += ` [Evidence Intent: Capture failures: ${failureCodes}]`;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// PHASE 22: Record missing fields in downgrade reason
|
|
468
|
+
if (validation.missingFields && validation.missingFields.length > 0) {
|
|
469
|
+
downgradeReason += ` [Missing fields: ${validation.missingFields.join(', ')}]`;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Attach evidence package to finding
|
|
474
|
+
return {
|
|
475
|
+
...finding,
|
|
476
|
+
severity: finalSeverity,
|
|
477
|
+
evidencePackage,
|
|
478
|
+
evidenceCompleteness: {
|
|
479
|
+
isComplete: validation.isComplete,
|
|
480
|
+
missingFields: validation.missingFields,
|
|
481
|
+
downgraded: validation.shouldDowngrade,
|
|
482
|
+
downgradeReason,
|
|
483
|
+
captureFailures: captureFailures.length > 0 ? captureFailures.map(f => f.toJSON ? f.toJSON() : f) : []
|
|
484
|
+
},
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution Mode Context Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages execution mode detection and provides context throughout the scan pipeline.
|
|
5
|
+
* Responsible for:
|
|
6
|
+
* - Detecting mode at scan start
|
|
7
|
+
* - Passing mode through all phases
|
|
8
|
+
* - Applying ceilings to confidence calculations
|
|
9
|
+
* - Injecting mode explanations into output
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { detectExecutionMode, applyConfidenceCeiling, CONFIDENCE_CEILINGS } from './execution-mode-detector.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Execution mode context object
|
|
16
|
+
*/
|
|
17
|
+
class ExecutionModeContext {
|
|
18
|
+
constructor(mode, ceiling, explanation, reason) {
|
|
19
|
+
this.mode = mode;
|
|
20
|
+
this.ceiling = ceiling;
|
|
21
|
+
this.explanation = explanation;
|
|
22
|
+
this.reason = reason;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Apply ceiling to a confidence score
|
|
27
|
+
* @param {number} score - Confidence score (0..1)
|
|
28
|
+
* @returns {number} - Capped score
|
|
29
|
+
*/
|
|
30
|
+
applyCeiling(score) {
|
|
31
|
+
return applyConfidenceCeiling(score, this.mode);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get CLI explanation for output
|
|
36
|
+
* @returns {string} - Human-readable explanation
|
|
37
|
+
*/
|
|
38
|
+
getCliExplanation() {
|
|
39
|
+
const ceilingPct = Math.round(this.ceiling * 100);
|
|
40
|
+
if (this.mode === 'WEB_SCAN_LIMITED') {
|
|
41
|
+
return `Running ${this.mode} mode: analyzing URL without access to source code. Confidence limited to ${ceilingPct}%.`;
|
|
42
|
+
} else {
|
|
43
|
+
return `Running ${this.mode} mode: full project analysis with source code available.`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get JSON-serializable context
|
|
49
|
+
* @returns {Object}
|
|
50
|
+
*/
|
|
51
|
+
toJSON() {
|
|
52
|
+
return {
|
|
53
|
+
mode: this.mode,
|
|
54
|
+
ceiling: this.ceiling,
|
|
55
|
+
explanation: this.explanation,
|
|
56
|
+
reason: this.reason
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Detect and create execution mode context for a scan
|
|
63
|
+
* @param {string} srcPath - Source code path
|
|
64
|
+
* @param {string} url - Target URL
|
|
65
|
+
* @returns {ExecutionModeContext}
|
|
66
|
+
*/
|
|
67
|
+
export function createExecutionModeContext(srcPath, url) {
|
|
68
|
+
const modeInfo = detectExecutionMode(srcPath, url);
|
|
69
|
+
return new ExecutionModeContext(
|
|
70
|
+
modeInfo.mode,
|
|
71
|
+
modeInfo.ceiling,
|
|
72
|
+
modeInfo.explanation,
|
|
73
|
+
modeInfo.reason
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { ExecutionModeContext };
|