@veraxhq/verax 0.2.0 → 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 +15 -5
- package/src/cli/commands/baseline.js +104 -0
- package/src/cli/commands/default.js +323 -111
- package/src/cli/commands/doctor.js +36 -4
- 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 +498 -103
- package/src/cli/commands/security-check.js +211 -0
- package/src/cli/commands/truth.js +114 -0
- package/src/cli/entry.js +305 -68
- 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/detection-engine.js +4 -3
- 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/events.js +76 -0
- package/src/cli/util/expectation-extractor.js +380 -74
- package/src/cli/util/findings-writer.js +126 -15
- package/src/cli/util/learn-writer.js +3 -1
- package/src/cli/util/observation-engine.js +69 -23
- package/src/cli/util/observe-writer.js +3 -1
- package/src/cli/util/paths.js +6 -14
- package/src/cli/util/project-discovery.js +23 -0
- package/src/cli/util/project-writer.js +3 -1
- package/src/cli/util/redact.js +2 -2
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/runtime-budget.js +147 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +13 -1
- 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/types/global.d.ts +28 -0
- package/src/types/ts-ast.d.ts +24 -0
- package/src/verax/cli/doctor.js +2 -2
- package/src/verax/cli/finding-explainer.js +56 -3
- package/src/verax/cli/init.js +1 -1
- package/src/verax/cli/url-safety.js +12 -2
- package/src/verax/cli/wizard.js +13 -2
- 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/budget-engine.js +1 -1
- 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 +31 -4
- 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/determinism-model.js +35 -6
- 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/incremental-store.js +15 -7
- 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/replay-validator.js +4 -4
- package/src/verax/core/replay.js +1 -1
- 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/silence-impact.js +1 -1
- package/src/verax/core/silence-model.js +9 -7
- package/src/verax/core/truth/truth.certificate.js +250 -0
- package/src/verax/core/ui-feedback-intelligence.js +515 -0
- package/src/verax/detect/comparison.js +8 -3
- package/src/verax/detect/confidence-engine.js +645 -57
- package/src/verax/detect/confidence-helper.js +33 -0
- package/src/verax/detect/detection-engine.js +19 -2
- package/src/verax/detect/dynamic-route-findings.js +335 -0
- package/src/verax/detect/evidence-index.js +15 -65
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +56 -3
- package/src/verax/detect/explanation-helpers.js +1 -1
- package/src/verax/detect/finding-detector.js +2 -2
- package/src/verax/detect/findings-writer.js +149 -20
- package/src/verax/detect/flow-detector.js +4 -4
- package/src/verax/detect/index.js +265 -15
- package/src/verax/detect/interactive-findings.js +3 -4
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/route-findings.js +218 -0
- package/src/verax/detect/signal-mapper.js +2 -2
- package/src/verax/detect/skip-classifier.js +4 -4
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/verdict-engine.js +61 -9
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/flow/flow-engine.js +3 -2
- package/src/verax/flow/flow-spec.js +1 -2
- package/src/verax/index.js +413 -33
- package/src/verax/intel/effect-detector.js +1 -1
- package/src/verax/intel/index.js +2 -2
- package/src/verax/intel/route-extractor.js +3 -3
- package/src/verax/intel/vue-navigation-extractor.js +81 -18
- package/src/verax/intel/vue-router-extractor.js +4 -2
- package/src/verax/learn/action-contract-extractor.js +684 -66
- package/src/verax/learn/ast-contract-extractor.js +53 -1
- package/src/verax/learn/index.js +36 -2
- package/src/verax/learn/manifest-writer.js +28 -14
- package/src/verax/learn/route-extractor.js +1 -1
- package/src/verax/learn/route-validator.js +12 -8
- package/src/verax/learn/state-extractor.js +1 -1
- package/src/verax/learn/static-extractor-navigation.js +1 -1
- package/src/verax/learn/static-extractor-validation.js +2 -2
- package/src/verax/learn/static-extractor.js +8 -7
- package/src/verax/learn/ts-contract-resolver.js +14 -12
- package/src/verax/observe/browser.js +22 -3
- package/src/verax/observe/console-sensor.js +2 -2
- package/src/verax/observe/expectation-executor.js +2 -1
- package/src/verax/observe/focus-sensor.js +1 -1
- package/src/verax/observe/human-driver.js +29 -10
- package/src/verax/observe/index.js +92 -844
- package/src/verax/observe/interaction-discovery.js +27 -15
- package/src/verax/observe/interaction-runner.js +31 -14
- package/src/verax/observe/loading-sensor.js +6 -0
- package/src/verax/observe/navigation-sensor.js +1 -1
- 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/settle.js +1 -0
- package/src/verax/observe/state-sensor.js +8 -4
- package/src/verax/observe/state-ui-sensor.js +7 -1
- package/src/verax/observe/traces-writer.js +27 -16
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/observe/ui-signal-sensor.js +155 -2
- package/src/verax/scan-summary-writer.js +46 -9
- package/src/verax/shared/artifact-manager.js +9 -6
- package/src/verax/shared/budget-profiles.js +2 -2
- package/src/verax/shared/caching.js +1 -1
- package/src/verax/shared/config-loader.js +1 -2
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/dynamic-route-utils.js +12 -6
- package/src/verax/shared/retry-policy.js +1 -6
- package/src/verax/shared/root-artifacts.js +1 -1
- package/src/verax/shared/view-switch-rules.js +208 -0
- package/src/verax/shared/zip-artifacts.js +1 -0
- package/src/verax/validate/context-validator.js +1 -1
- package/src/verax/observe/index.js.backup +0 -1
- package/src/verax/validate/context-validator.js.bak +0 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EVIDENCE CAPTURE RELIABILITY LAYER
|
|
3
|
+
*
|
|
4
|
+
* Centralized evidence capture service with retries and failure tracking.
|
|
5
|
+
* Ensures evidence capture is resilient and failures are never silent.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { captureScreenshot } from '../../observe/evidence-capture.js';
|
|
9
|
+
import { captureDomSignature } from '../../observe/dom-signature.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Evidence capture failure reason codes (stable)
|
|
13
|
+
*/
|
|
14
|
+
export const EVIDENCE_CAPTURE_FAILURE_CODES = {
|
|
15
|
+
SCREENSHOT_FAILED: 'EVIDENCE_SCREENSHOT_FAILED',
|
|
16
|
+
SCREENSHOT_TIMEOUT: 'EVIDENCE_SCREENSHOT_TIMEOUT',
|
|
17
|
+
DOM_SIGNATURE_FAILED: 'EVIDENCE_DOM_SIGNATURE_FAILED',
|
|
18
|
+
URL_CAPTURE_FAILED: 'EVIDENCE_URL_CAPTURE_FAILED',
|
|
19
|
+
UISIGNALS_CAPTURE_FAILED: 'EVIDENCE_UISIGNALS_CAPTURE_FAILED',
|
|
20
|
+
NETWORK_CAPTURE_FAILED: 'EVIDENCE_NETWORK_CAPTURE_FAILED',
|
|
21
|
+
UNKNOWN_ERROR: 'EVIDENCE_UNKNOWN_ERROR'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Evidence capture stage codes (stable)
|
|
26
|
+
*/
|
|
27
|
+
export const EVIDENCE_CAPTURE_STAGE = {
|
|
28
|
+
BEFORE_SCREENSHOT: 'BEFORE_SCREENSHOT',
|
|
29
|
+
AFTER_SCREENSHOT: 'AFTER_SCREENSHOT',
|
|
30
|
+
DOM_SIGNATURE: 'DOM_SIGNATURE',
|
|
31
|
+
URL: 'URL',
|
|
32
|
+
UISIGNALS: 'UISIGNALS',
|
|
33
|
+
NETWORK: 'NETWORK'
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* EvidenceCaptureFailure object (structured failure record)
|
|
38
|
+
*/
|
|
39
|
+
export class EvidenceCaptureFailure {
|
|
40
|
+
constructor(stage, reasonCode, reason, stackSummary = null, attemptCount = 1) {
|
|
41
|
+
this.stage = stage;
|
|
42
|
+
this.reasonCode = reasonCode;
|
|
43
|
+
this.reason = reason;
|
|
44
|
+
this.stackSummary = stackSummary || this._extractStackSummary(new Error().stack);
|
|
45
|
+
this.attemptCount = attemptCount;
|
|
46
|
+
this.timestamp = new Date().toISOString();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_extractStackSummary(stack) {
|
|
50
|
+
if (!stack) return null;
|
|
51
|
+
const lines = stack.split('\n').slice(0, 5);
|
|
52
|
+
return lines.join('\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
toJSON() {
|
|
56
|
+
return {
|
|
57
|
+
stage: this.stage,
|
|
58
|
+
reasonCode: this.reasonCode,
|
|
59
|
+
reason: this.reason,
|
|
60
|
+
stackSummary: this.stackSummary,
|
|
61
|
+
attemptCount: this.attemptCount,
|
|
62
|
+
timestamp: this.timestamp
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Screenshot capture with retries
|
|
69
|
+
*
|
|
70
|
+
* @param {Object} page - Playwright page object
|
|
71
|
+
* @param {string} filepath - Path to save screenshot
|
|
72
|
+
* @param {Object} options - Options { maxRetries: 2, retryDelayMs: 100 }
|
|
73
|
+
* @returns {Promise<{ success: boolean, filepath: string | null, failure: EvidenceCaptureFailure | null }>}
|
|
74
|
+
*/
|
|
75
|
+
export async function captureScreenshotWithRetry(page, filepath, options = {}) {
|
|
76
|
+
const maxRetries = options.maxRetries || 2;
|
|
77
|
+
const retryDelayMs = options.retryDelayMs || 100;
|
|
78
|
+
|
|
79
|
+
let lastError = null;
|
|
80
|
+
let attemptCount = 0;
|
|
81
|
+
|
|
82
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
83
|
+
attemptCount = attempt + 1;
|
|
84
|
+
try {
|
|
85
|
+
await captureScreenshot(page, filepath);
|
|
86
|
+
// Verify file was created
|
|
87
|
+
const { existsSync } = await import('fs');
|
|
88
|
+
if (existsSync(filepath)) {
|
|
89
|
+
return { success: true, filepath, failure: null };
|
|
90
|
+
}
|
|
91
|
+
throw new Error('Screenshot file was not created');
|
|
92
|
+
} catch (error) {
|
|
93
|
+
lastError = error;
|
|
94
|
+
|
|
95
|
+
// If not last attempt, wait before retry
|
|
96
|
+
if (attempt < maxRetries) {
|
|
97
|
+
await new Promise(resolve => setTimeout(resolve, retryDelayMs));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// All retries failed
|
|
103
|
+
const failure = new EvidenceCaptureFailure(
|
|
104
|
+
filepath.includes('before') ? EVIDENCE_CAPTURE_STAGE.BEFORE_SCREENSHOT : EVIDENCE_CAPTURE_STAGE.AFTER_SCREENSHOT,
|
|
105
|
+
lastError?.message?.includes('timeout') ? EVIDENCE_CAPTURE_FAILURE_CODES.SCREENSHOT_TIMEOUT : EVIDENCE_CAPTURE_FAILURE_CODES.SCREENSHOT_FAILED,
|
|
106
|
+
lastError?.message || 'Screenshot capture failed after retries',
|
|
107
|
+
lastError?.stack,
|
|
108
|
+
attemptCount
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
return { success: false, filepath: null, failure };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Capture DOM signature with error handling
|
|
116
|
+
*
|
|
117
|
+
* @param {Object} page - Playwright page object
|
|
118
|
+
* @returns {Promise<{ success: boolean, domSignature: string | null, failure: EvidenceCaptureFailure | null }>}
|
|
119
|
+
*/
|
|
120
|
+
export async function captureDomSignatureSafe(page) {
|
|
121
|
+
try {
|
|
122
|
+
const domSignature = await captureDomSignature(page);
|
|
123
|
+
return { success: true, domSignature, failure: null };
|
|
124
|
+
} catch (error) {
|
|
125
|
+
const failure = new EvidenceCaptureFailure(
|
|
126
|
+
EVIDENCE_CAPTURE_STAGE.DOM_SIGNATURE,
|
|
127
|
+
EVIDENCE_CAPTURE_FAILURE_CODES.DOM_SIGNATURE_FAILED,
|
|
128
|
+
error.message || 'DOM signature capture failed',
|
|
129
|
+
error.stack
|
|
130
|
+
);
|
|
131
|
+
return { success: false, domSignature: null, failure };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Capture URL with error handling
|
|
137
|
+
*
|
|
138
|
+
* @param {Object} page - Playwright page object
|
|
139
|
+
* @returns {Promise<{ success: boolean, url: string | null, failure: EvidenceCaptureFailure | null }>}
|
|
140
|
+
*/
|
|
141
|
+
export async function captureUrlSafe(page) {
|
|
142
|
+
try {
|
|
143
|
+
const url = page.url();
|
|
144
|
+
return { success: true, url, failure: null };
|
|
145
|
+
} catch (error) {
|
|
146
|
+
const failure = new EvidenceCaptureFailure(
|
|
147
|
+
EVIDENCE_CAPTURE_STAGE.URL,
|
|
148
|
+
EVIDENCE_CAPTURE_FAILURE_CODES.URL_CAPTURE_FAILED,
|
|
149
|
+
error.message || 'URL capture failed',
|
|
150
|
+
error.stack
|
|
151
|
+
);
|
|
152
|
+
return { success: false, url: null, failure };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Capture UI signals snapshot with error handling
|
|
158
|
+
*
|
|
159
|
+
* @param {Object} uiSignalSensor - UISignalSensor instance
|
|
160
|
+
* @param {Object} page - Playwright page object
|
|
161
|
+
* @param {number} interactionTime - Optional interaction timestamp
|
|
162
|
+
* @param {Object} beforeSnapshot - Optional before snapshot
|
|
163
|
+
* @returns {Promise<{ success: boolean, uiSignals: Object | null, failure: EvidenceCaptureFailure | null }>}
|
|
164
|
+
*/
|
|
165
|
+
export async function captureUiSignalsSafe(uiSignalSensor, page, interactionTime = null, beforeSnapshot = null) {
|
|
166
|
+
try {
|
|
167
|
+
const uiSignals = await uiSignalSensor.snapshot(page, interactionTime, beforeSnapshot);
|
|
168
|
+
return { success: true, uiSignals, failure: null };
|
|
169
|
+
} catch (error) {
|
|
170
|
+
const failure = new EvidenceCaptureFailure(
|
|
171
|
+
EVIDENCE_CAPTURE_STAGE.UISIGNALS,
|
|
172
|
+
EVIDENCE_CAPTURE_FAILURE_CODES.UISIGNALS_CAPTURE_FAILED,
|
|
173
|
+
error.message || 'UI signals capture failed',
|
|
174
|
+
error.stack
|
|
175
|
+
);
|
|
176
|
+
return { success: false, uiSignals: null, failure };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Capture network snapshot with error handling
|
|
182
|
+
*
|
|
183
|
+
* @param {Object} networkSensor - NetworkSensor instance
|
|
184
|
+
* @param {string} windowId - Network window ID
|
|
185
|
+
* @returns {Promise<{ success: boolean, networkSummary: Object | null, failure: EvidenceCaptureFailure | null }>}
|
|
186
|
+
*/
|
|
187
|
+
export async function captureNetworkSafe(networkSensor, windowId) {
|
|
188
|
+
try {
|
|
189
|
+
const networkSummary = networkSensor.stopWindow(windowId);
|
|
190
|
+
return { success: true, networkSummary, failure: null };
|
|
191
|
+
} catch (error) {
|
|
192
|
+
const failure = new EvidenceCaptureFailure(
|
|
193
|
+
EVIDENCE_CAPTURE_STAGE.NETWORK,
|
|
194
|
+
EVIDENCE_CAPTURE_FAILURE_CODES.NETWORK_CAPTURE_FAILED,
|
|
195
|
+
error.message || 'Network capture failed',
|
|
196
|
+
error.stack
|
|
197
|
+
);
|
|
198
|
+
return { success: false, networkSummary: null, failure };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Comprehensive evidence capture for a finding
|
|
204
|
+
*
|
|
205
|
+
* @param {Object} params - Capture parameters
|
|
206
|
+
* @param {Object} params.page - Playwright page object
|
|
207
|
+
* @param {string} params.beforeScreenshotPath - Before screenshot path
|
|
208
|
+
* @param {string} params.afterScreenshotPath - After screenshot path
|
|
209
|
+
* @param {Object} params.uiSignalSensor - UISignalSensor instance
|
|
210
|
+
* @param {Object} params.networkSensor - NetworkSensor instance
|
|
211
|
+
* @param {string} params.networkWindowId - Network window ID
|
|
212
|
+
* @param {number} params.interactionTime - Interaction timestamp
|
|
213
|
+
* @param {Object} params.beforeSnapshot - Before snapshot
|
|
214
|
+
* @returns {Promise<{ evidence: Object, failures: Array<EvidenceCaptureFailure> }>}
|
|
215
|
+
*/
|
|
216
|
+
export async function captureEvidenceComprehensive(params) {
|
|
217
|
+
const {
|
|
218
|
+
page,
|
|
219
|
+
beforeScreenshotPath,
|
|
220
|
+
afterScreenshotPath,
|
|
221
|
+
uiSignalSensor,
|
|
222
|
+
networkSensor,
|
|
223
|
+
networkWindowId,
|
|
224
|
+
interactionTime,
|
|
225
|
+
beforeSnapshot
|
|
226
|
+
} = params;
|
|
227
|
+
|
|
228
|
+
const failures = [];
|
|
229
|
+
const evidence = {
|
|
230
|
+
before: {},
|
|
231
|
+
after: {},
|
|
232
|
+
signals: {}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Capture before screenshot
|
|
236
|
+
if (beforeScreenshotPath) {
|
|
237
|
+
const beforeScreenshot = await captureScreenshotWithRetry(page, beforeScreenshotPath);
|
|
238
|
+
if (beforeScreenshot.success) {
|
|
239
|
+
evidence.before.screenshot = beforeScreenshotPath;
|
|
240
|
+
} else {
|
|
241
|
+
failures.push(beforeScreenshot.failure);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Capture after screenshot
|
|
246
|
+
if (afterScreenshotPath) {
|
|
247
|
+
const afterScreenshot = await captureScreenshotWithRetry(page, afterScreenshotPath);
|
|
248
|
+
if (afterScreenshot.success) {
|
|
249
|
+
evidence.after.screenshot = afterScreenshotPath;
|
|
250
|
+
} else {
|
|
251
|
+
failures.push(afterScreenshot.failure);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Capture URLs
|
|
256
|
+
const beforeUrl = await captureUrlSafe(page);
|
|
257
|
+
if (beforeUrl.success) {
|
|
258
|
+
evidence.before.url = beforeUrl.url;
|
|
259
|
+
} else {
|
|
260
|
+
failures.push(beforeUrl.failure);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const afterUrl = await captureUrlSafe(page);
|
|
264
|
+
if (afterUrl.success) {
|
|
265
|
+
evidence.after.url = afterUrl.url;
|
|
266
|
+
} else {
|
|
267
|
+
failures.push(afterUrl.failure);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Capture DOM signatures
|
|
271
|
+
const beforeDom = await captureDomSignatureSafe(page);
|
|
272
|
+
if (beforeDom.success) {
|
|
273
|
+
evidence.before.domSignature = beforeDom.domSignature;
|
|
274
|
+
} else {
|
|
275
|
+
failures.push(beforeDom.failure);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const afterDom = await captureDomSignatureSafe(page);
|
|
279
|
+
if (afterDom.success) {
|
|
280
|
+
evidence.after.domSignature = afterDom.domSignature;
|
|
281
|
+
} else {
|
|
282
|
+
failures.push(afterDom.failure);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Capture UI signals
|
|
286
|
+
if (uiSignalSensor) {
|
|
287
|
+
const uiSignals = await captureUiSignalsSafe(uiSignalSensor, page, interactionTime, beforeSnapshot);
|
|
288
|
+
if (uiSignals.success) {
|
|
289
|
+
evidence.signals.uiSignals = uiSignals.uiSignals;
|
|
290
|
+
} else {
|
|
291
|
+
failures.push(uiSignals.failure);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Capture network
|
|
296
|
+
if (networkSensor && networkWindowId) {
|
|
297
|
+
const network = await captureNetworkSafe(networkSensor, networkWindowId);
|
|
298
|
+
if (network.success) {
|
|
299
|
+
evidence.signals.network = network.networkSummary;
|
|
300
|
+
} else {
|
|
301
|
+
failures.push(network.failure);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return { evidence, failures };
|
|
306
|
+
}
|
|
307
|
+
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EVIDENCE INTENT LEDGER
|
|
3
|
+
*
|
|
4
|
+
* Audit trail of evidence capture attempts per finding.
|
|
5
|
+
* Records what evidence was required, what was captured, and what failed.
|
|
6
|
+
* This is NOT proof; it is an audit trail proving we attempted to capture evidence.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { writeFileSync, readFileSync, existsSync } from 'fs';
|
|
10
|
+
import { resolve } from 'path';
|
|
11
|
+
import { EVIDENCE_CAPTURE_STAGE, EVIDENCE_CAPTURE_FAILURE_CODES } from './evidence-capture-service.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Required evidence fields for CONFIRMED findings (stable)
|
|
15
|
+
*/
|
|
16
|
+
export const REQUIRED_EVIDENCE_FIELDS = [
|
|
17
|
+
'trigger.source',
|
|
18
|
+
'before.screenshot',
|
|
19
|
+
'after.screenshot',
|
|
20
|
+
'before.url',
|
|
21
|
+
'after.url',
|
|
22
|
+
'action.interaction',
|
|
23
|
+
'signals.network',
|
|
24
|
+
'signals.uiSignals'
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Build evidence intent entry for a finding
|
|
29
|
+
*
|
|
30
|
+
* @param {Object} finding - Finding object
|
|
31
|
+
* @param {Object} evidencePackage - Evidence package
|
|
32
|
+
* @param {Array<Object>} captureFailures - Array of EvidenceCaptureFailure objects
|
|
33
|
+
* @returns {Object} Evidence intent entry
|
|
34
|
+
*/
|
|
35
|
+
export function buildEvidenceIntentEntry(finding, evidencePackage, captureFailures = []) {
|
|
36
|
+
const findingIdentity = finding.findingId || finding.id || `finding-${Date.now()}`;
|
|
37
|
+
|
|
38
|
+
// Determine required fields based on finding severity
|
|
39
|
+
const requiredFields = finding.severity === 'CONFIRMED' || finding.status === 'CONFIRMED'
|
|
40
|
+
? REQUIRED_EVIDENCE_FIELDS
|
|
41
|
+
: [];
|
|
42
|
+
|
|
43
|
+
// Check capture outcomes for each required field
|
|
44
|
+
const captureOutcomes = {};
|
|
45
|
+
const missingFields = [];
|
|
46
|
+
|
|
47
|
+
for (const field of requiredFields) {
|
|
48
|
+
const [section, key] = field.split('.');
|
|
49
|
+
|
|
50
|
+
let captured = false;
|
|
51
|
+
let failure = null;
|
|
52
|
+
|
|
53
|
+
// Check if field exists in evidence package
|
|
54
|
+
if (section === 'trigger' && evidencePackage.trigger?.[key]) {
|
|
55
|
+
captured = true;
|
|
56
|
+
} else if (section === 'before' && evidencePackage.before?.[key]) {
|
|
57
|
+
captured = true;
|
|
58
|
+
} else if (section === 'after' && evidencePackage.after?.[key]) {
|
|
59
|
+
captured = true;
|
|
60
|
+
} else if (section === 'action' && evidencePackage.action?.[key]) {
|
|
61
|
+
captured = true;
|
|
62
|
+
} else if (section === 'signals' && evidencePackage.signals?.[key]) {
|
|
63
|
+
captured = true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check if there was a capture failure for this field
|
|
67
|
+
if (!captured) {
|
|
68
|
+
const fieldFailure = captureFailures.find(f => {
|
|
69
|
+
if (field.includes('screenshot') && f.stage.includes('SCREENSHOT')) return true;
|
|
70
|
+
if (field.includes('domSignature') && f.stage === EVIDENCE_CAPTURE_STAGE.DOM_SIGNATURE) return true;
|
|
71
|
+
if (field.includes('url') && f.stage === EVIDENCE_CAPTURE_STAGE.URL) return true;
|
|
72
|
+
if (field.includes('uiSignals') && f.stage === EVIDENCE_CAPTURE_STAGE.UISIGNALS) return true;
|
|
73
|
+
if (field.includes('network') && f.stage === EVIDENCE_CAPTURE_STAGE.NETWORK) return true;
|
|
74
|
+
return false;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (fieldFailure) {
|
|
78
|
+
failure = {
|
|
79
|
+
stage: fieldFailure.stage,
|
|
80
|
+
reasonCode: fieldFailure.reasonCode,
|
|
81
|
+
reason: fieldFailure.reason
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
missingFields.push(field);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
captureOutcomes[field] = {
|
|
89
|
+
required: true,
|
|
90
|
+
captured,
|
|
91
|
+
failure
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
findingIdentity,
|
|
97
|
+
findingType: finding.type || 'finding',
|
|
98
|
+
severity: finding.severity || finding.status || 'SUSPECTED',
|
|
99
|
+
requiredFields,
|
|
100
|
+
captureOutcomes,
|
|
101
|
+
missingFields,
|
|
102
|
+
evidencePackageComplete: evidencePackage.isComplete === true,
|
|
103
|
+
timestamp: new Date().toISOString()
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Write evidence intent ledger
|
|
109
|
+
*
|
|
110
|
+
* @param {string} runDir - Run directory
|
|
111
|
+
* @param {Array<Object>} findings - Array of findings with evidence packages
|
|
112
|
+
* @param {Map<string, Array<Object>>} captureFailuresMap - Map of findingId -> capture failures
|
|
113
|
+
* @returns {string} Path to written evidence.intent.json
|
|
114
|
+
*/
|
|
115
|
+
export function writeEvidenceIntentLedger(runDir, findings, captureFailuresMap = new Map()) {
|
|
116
|
+
const intentPath = resolve(runDir, 'evidence.intent.json');
|
|
117
|
+
|
|
118
|
+
// Build intent entries (deterministic ordering by findingIdentity)
|
|
119
|
+
const entries = [];
|
|
120
|
+
|
|
121
|
+
for (const finding of findings || []) {
|
|
122
|
+
const findingIdentity = finding.findingId || finding.id || `finding-${findings.indexOf(finding)}`;
|
|
123
|
+
const captureFailures = captureFailuresMap.get(findingIdentity) || [];
|
|
124
|
+
const evidencePackage = finding.evidencePackage || {};
|
|
125
|
+
|
|
126
|
+
const entry = buildEvidenceIntentEntry(finding, evidencePackage, captureFailures);
|
|
127
|
+
entries.push(entry);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Sort by findingIdentity for deterministic ordering
|
|
131
|
+
entries.sort((a, b) => a.findingIdentity.localeCompare(b.findingIdentity));
|
|
132
|
+
|
|
133
|
+
const ledger = {
|
|
134
|
+
version: 1,
|
|
135
|
+
generatedAt: new Date().toISOString(),
|
|
136
|
+
totalFindings: entries.length,
|
|
137
|
+
entries
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
writeFileSync(intentPath, JSON.stringify(ledger, null, 2) + '\n');
|
|
141
|
+
|
|
142
|
+
return intentPath;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Read evidence intent ledger
|
|
147
|
+
*
|
|
148
|
+
* @param {string} runDir - Run directory
|
|
149
|
+
* @returns {Object | null} Evidence intent ledger or null if not found
|
|
150
|
+
*/
|
|
151
|
+
export function readEvidenceIntentLedger(runDir) {
|
|
152
|
+
const intentPath = resolve(runDir, 'evidence.intent.json');
|
|
153
|
+
|
|
154
|
+
if (!existsSync(intentPath)) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const content = readFileSync(intentPath, 'utf-8');
|
|
160
|
+
return JSON.parse(content);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|