@veraxhq/verax 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -20
- package/bin/verax.js +11 -18
- package/package.json +28 -7
- package/src/cli/commands/baseline.js +1 -2
- package/src/cli/commands/default.js +72 -81
- package/src/cli/commands/doctor.js +29 -0
- package/src/cli/commands/ga.js +3 -0
- package/src/cli/commands/gates.js +1 -1
- package/src/cli/commands/inspect.js +6 -133
- package/src/cli/commands/release-check.js +2 -0
- package/src/cli/commands/run.js +74 -246
- package/src/cli/commands/security-check.js +2 -1
- package/src/cli/commands/truth.js +0 -1
- package/src/cli/entry.js +82 -309
- package/src/cli/util/angular-component-extractor.js +2 -2
- package/src/cli/util/angular-navigation-detector.js +2 -2
- package/src/cli/util/ast-interactive-detector.js +4 -6
- package/src/cli/util/ast-network-detector.js +3 -3
- package/src/cli/util/ast-promise-extractor.js +581 -0
- package/src/cli/util/ast-usestate-detector.js +3 -3
- package/src/cli/util/atomic-write.js +12 -1
- package/src/cli/util/console-reporter.js +72 -0
- package/src/cli/util/detection-engine.js +105 -41
- package/src/cli/util/determinism-runner.js +2 -1
- package/src/cli/util/determinism-writer.js +1 -1
- package/src/cli/util/digest-engine.js +359 -0
- package/src/cli/util/dom-diff.js +226 -0
- package/src/cli/util/env-url.js +0 -4
- package/src/cli/util/evidence-engine.js +287 -0
- package/src/cli/util/expectation-extractor.js +217 -367
- package/src/cli/util/findings-writer.js +19 -126
- package/src/cli/util/framework-detector.js +572 -0
- package/src/cli/util/idgen.js +1 -1
- package/src/cli/util/interaction-planner.js +529 -0
- package/src/cli/util/learn-writer.js +2 -2
- package/src/cli/util/ledger-writer.js +110 -0
- package/src/cli/util/monorepo-resolver.js +162 -0
- package/src/cli/util/observation-engine.js +127 -278
- package/src/cli/util/observe-writer.js +2 -2
- package/src/cli/util/paths.js +12 -3
- package/src/cli/util/project-discovery.js +284 -3
- package/src/cli/util/project-writer.js +2 -2
- package/src/cli/util/run-id.js +23 -27
- package/src/cli/util/run-result.js +778 -0
- package/src/cli/util/selector-resolver.js +235 -0
- package/src/cli/util/summary-writer.js +2 -1
- package/src/cli/util/svelte-navigation-detector.js +3 -3
- package/src/cli/util/svelte-sfc-extractor.js +0 -1
- package/src/cli/util/svelte-state-detector.js +1 -2
- package/src/cli/util/trust-activation-integration.js +496 -0
- package/src/cli/util/trust-activation-wrapper.js +85 -0
- package/src/cli/util/trust-integration-hooks.js +164 -0
- package/src/cli/util/types.js +153 -0
- package/src/cli/util/url-validation.js +40 -0
- package/src/cli/util/vue-navigation-detector.js +4 -3
- package/src/cli/util/vue-sfc-extractor.js +1 -2
- package/src/cli/util/vue-state-detector.js +1 -1
- package/src/types/fs-augment.d.ts +23 -0
- package/src/types/global.d.ts +137 -0
- package/src/types/internal-types.d.ts +35 -0
- package/src/verax/cli/finding-explainer.js +3 -56
- package/src/verax/cli/init.js +4 -18
- package/src/verax/core/action-classifier.js +4 -3
- package/src/verax/core/artifacts/registry.js +0 -15
- package/src/verax/core/artifacts/verifier.js +18 -8
- package/src/verax/core/baseline/baseline.snapshot.js +2 -0
- package/src/verax/core/capabilities/gates.js +7 -1
- package/src/verax/core/confidence/confidence-compute.js +14 -7
- package/src/verax/core/confidence/confidence.loader.js +1 -0
- package/src/verax/core/confidence-engine-refactor.js +8 -3
- package/src/verax/core/confidence-engine.js +162 -23
- package/src/verax/core/contracts/types.js +1 -0
- package/src/verax/core/contracts/validators.js +79 -4
- package/src/verax/core/decision-snapshot.js +3 -30
- package/src/verax/core/decisions/decision.trace.js +2 -0
- package/src/verax/core/determinism/contract-writer.js +2 -2
- package/src/verax/core/determinism/contract.js +1 -1
- package/src/verax/core/determinism/diff.js +42 -1
- package/src/verax/core/determinism/engine.js +7 -6
- package/src/verax/core/determinism/finding-identity.js +3 -2
- package/src/verax/core/determinism/normalize.js +32 -4
- package/src/verax/core/determinism/report-writer.js +1 -0
- package/src/verax/core/determinism/run-fingerprint.js +7 -2
- package/src/verax/core/dynamic-route-intelligence.js +8 -7
- package/src/verax/core/evidence/evidence-capture-service.js +1 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +2 -1
- package/src/verax/core/evidence-builder.js +2 -2
- package/src/verax/core/execution-mode-context.js +1 -1
- package/src/verax/core/execution-mode-detector.js +5 -3
- package/src/verax/core/failures/exit-codes.js +39 -37
- package/src/verax/core/failures/failure-summary.js +1 -1
- package/src/verax/core/failures/failure.factory.js +3 -3
- package/src/verax/core/failures/failure.ledger.js +3 -2
- package/src/verax/core/ga/ga.artifact.js +1 -1
- package/src/verax/core/ga/ga.contract.js +3 -2
- package/src/verax/core/ga/ga.enforcer.js +1 -0
- package/src/verax/core/guardrails/policy.loader.js +1 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +1 -1
- package/src/verax/core/guardrails-engine.js +2 -2
- package/src/verax/core/incremental-store.js +1 -0
- package/src/verax/core/integrity/budget.js +138 -0
- package/src/verax/core/integrity/determinism.js +342 -0
- package/src/verax/core/integrity/integrity.js +208 -0
- package/src/verax/core/integrity/poisoning.js +108 -0
- package/src/verax/core/integrity/transaction.js +140 -0
- package/src/verax/core/observe/run-timeline.js +2 -0
- package/src/verax/core/perf/perf.report.js +2 -0
- package/src/verax/core/pipeline-tracker.js +5 -0
- package/src/verax/core/release/provenance.builder.js +73 -214
- package/src/verax/core/release/release.enforcer.js +14 -9
- package/src/verax/core/release/reproducibility.check.js +1 -0
- package/src/verax/core/release/sbom.builder.js +32 -23
- package/src/verax/core/replay-validator.js +2 -0
- package/src/verax/core/replay.js +4 -0
- package/src/verax/core/report/cross-index.js +6 -3
- package/src/verax/core/report/human-summary.js +141 -1
- package/src/verax/core/route-intelligence.js +4 -3
- package/src/verax/core/run-id.js +6 -3
- package/src/verax/core/run-manifest.js +4 -3
- package/src/verax/core/security/secrets.scan.js +10 -7
- package/src/verax/core/security/security.enforcer.js +4 -0
- package/src/verax/core/security/supplychain.policy.js +9 -1
- package/src/verax/core/security/vuln.scan.js +2 -2
- package/src/verax/core/truth/truth.certificate.js +3 -1
- package/src/verax/core/ui-feedback-intelligence.js +12 -46
- package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
- package/src/verax/detect/confidence-engine.js +100 -660
- package/src/verax/detect/confidence-helper.js +1 -0
- package/src/verax/detect/detection-engine.js +1 -18
- package/src/verax/detect/dynamic-route-findings.js +17 -14
- package/src/verax/detect/expectation-chain-detector.js +1 -1
- package/src/verax/detect/expectation-model.js +3 -5
- package/src/verax/detect/failure-cause-inference.js +293 -0
- package/src/verax/detect/findings-writer.js +126 -166
- package/src/verax/detect/flow-detector.js +2 -2
- package/src/verax/detect/form-silent-failure.js +98 -0
- package/src/verax/detect/index.js +51 -234
- package/src/verax/detect/invariants-enforcer.js +147 -0
- package/src/verax/detect/journey-stall-detector.js +4 -4
- package/src/verax/detect/navigation-silent-failure.js +82 -0
- package/src/verax/detect/problem-aggregator.js +361 -0
- package/src/verax/detect/route-findings.js +7 -6
- package/src/verax/detect/summary-writer.js +477 -0
- package/src/verax/detect/test-failure-cause-inference.js +314 -0
- package/src/verax/detect/ui-feedback-findings.js +18 -18
- package/src/verax/detect/verdict-engine.js +3 -57
- package/src/verax/detect/view-switch-correlator.js +2 -2
- package/src/verax/flow/flow-engine.js +2 -1
- package/src/verax/flow/flow-spec.js +0 -6
- package/src/verax/index.js +48 -412
- package/src/verax/intel/ts-program.js +1 -0
- package/src/verax/intel/vue-navigation-extractor.js +3 -0
- package/src/verax/learn/action-contract-extractor.js +67 -682
- package/src/verax/learn/ast-contract-extractor.js +1 -1
- package/src/verax/learn/flow-extractor.js +1 -0
- package/src/verax/learn/project-detector.js +5 -0
- package/src/verax/learn/react-router-extractor.js +2 -0
- package/src/verax/learn/route-validator.js +1 -4
- package/src/verax/learn/source-instrumenter.js +1 -0
- package/src/verax/learn/state-extractor.js +2 -1
- package/src/verax/learn/static-extractor.js +1 -0
- package/src/verax/observe/coverage-gaps.js +132 -0
- package/src/verax/observe/expectation-handler.js +126 -0
- package/src/verax/observe/incremental-skip.js +46 -0
- package/src/verax/observe/index.js +735 -84
- package/src/verax/observe/interaction-executor.js +192 -0
- package/src/verax/observe/interaction-runner.js +782 -530
- package/src/verax/observe/network-firewall.js +86 -0
- package/src/verax/observe/observation-builder.js +169 -0
- package/src/verax/observe/observe-context.js +1 -1
- package/src/verax/observe/observe-helpers.js +2 -1
- package/src/verax/observe/observe-runner.js +28 -24
- package/src/verax/observe/observers/budget-observer.js +3 -3
- package/src/verax/observe/observers/console-observer.js +4 -4
- package/src/verax/observe/observers/coverage-observer.js +4 -4
- package/src/verax/observe/observers/interaction-observer.js +3 -3
- package/src/verax/observe/observers/navigation-observer.js +4 -4
- package/src/verax/observe/observers/network-observer.js +4 -4
- package/src/verax/observe/observers/safety-observer.js +1 -1
- package/src/verax/observe/observers/ui-feedback-observer.js +4 -4
- package/src/verax/observe/page-traversal.js +138 -0
- package/src/verax/observe/snapshot-ops.js +94 -0
- package/src/verax/observe/ui-signal-sensor.js +2 -148
- package/src/verax/scan-summary-writer.js +10 -42
- package/src/verax/shared/artifact-manager.js +30 -13
- package/src/verax/shared/caching.js +1 -0
- package/src/verax/shared/expectation-tracker.js +1 -0
- package/src/verax/shared/zip-artifacts.js +6 -0
- package/src/verax/core/confidence-engine.js.backup +0 -471
- package/src/verax/shared/config-loader.js +0 -169
- /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
|
@@ -3,7 +3,7 @@ import traverse from '@babel/traverse';
|
|
|
3
3
|
import { readFileSync } from 'fs';
|
|
4
4
|
import { glob } from 'glob';
|
|
5
5
|
import { resolve } from 'path';
|
|
6
|
-
import { ExpectationProof } from '../shared/expectation-
|
|
6
|
+
import { ExpectationProof } from '../shared/expectation-validation.js';
|
|
7
7
|
import { normalizeTemplateLiteral } from '../shared/dynamic-route-utils.js';
|
|
8
8
|
|
|
9
9
|
const MAX_FILES_TO_SCAN = 200;
|
|
@@ -14,6 +14,7 @@ import { isProvenExpectation } from '../shared/expectation-prover.js';
|
|
|
14
14
|
function generateFlowId(steps) {
|
|
15
15
|
const hashInput = steps.map(s => `${s.expectationType}:${s.source || s.handlerRef}`).join('|');
|
|
16
16
|
const hash = createHash('sha256').update(hashInput).digest('hex');
|
|
17
|
+
// @ts-expect-error - digest returns string
|
|
17
18
|
return `flow-${hash.substring(0, 8)}`;
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -7,6 +7,7 @@ async function hasReactDependency(projectDir) {
|
|
|
7
7
|
const packageJsonPath = resolve(projectDir, 'package.json');
|
|
8
8
|
if (!existsSync(packageJsonPath)) return false;
|
|
9
9
|
|
|
10
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
10
11
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
11
12
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
12
13
|
|
|
@@ -21,6 +22,7 @@ async function hasNextJs(projectDir) {
|
|
|
21
22
|
const packageJsonPath = resolve(projectDir, 'package.json');
|
|
22
23
|
if (!existsSync(packageJsonPath)) return false;
|
|
23
24
|
|
|
25
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
24
26
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
25
27
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
26
28
|
|
|
@@ -35,6 +37,7 @@ async function hasReactRouter(projectDir) {
|
|
|
35
37
|
const packageJsonPath = resolve(projectDir, 'package.json');
|
|
36
38
|
if (!existsSync(packageJsonPath)) return false;
|
|
37
39
|
|
|
40
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
38
41
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
39
42
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
40
43
|
|
|
@@ -49,6 +52,7 @@ async function hasVue(projectDir) {
|
|
|
49
52
|
const packageJsonPath = resolve(projectDir, 'package.json');
|
|
50
53
|
if (!existsSync(packageJsonPath)) return false;
|
|
51
54
|
|
|
55
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
52
56
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
53
57
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
54
58
|
|
|
@@ -63,6 +67,7 @@ async function hasVueRouter(projectDir) {
|
|
|
63
67
|
const packageJsonPath = resolve(projectDir, 'package.json');
|
|
64
68
|
if (!existsSync(packageJsonPath)) return false;
|
|
65
69
|
|
|
70
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
66
71
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
67
72
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
68
73
|
|
|
@@ -26,11 +26,13 @@ export async function extractReactRouterRoutes(projectDir) {
|
|
|
26
26
|
|
|
27
27
|
for (const pattern of routePatterns) {
|
|
28
28
|
let match;
|
|
29
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
29
30
|
while ((match = pattern.exec(content)) !== null) {
|
|
30
31
|
let path = match[1];
|
|
31
32
|
|
|
32
33
|
if (!path) {
|
|
33
34
|
if (match[0].includes('createBrowserRouter')) {
|
|
35
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
34
36
|
const routesMatch = content.match(/createBrowserRouter\s*\(\s*\[([^\]]+)\]/s);
|
|
35
37
|
if (routesMatch) {
|
|
36
38
|
const routesContent = routesMatch[1];
|
|
@@ -15,10 +15,7 @@ export async function validateRoutes(manifest, baseUrl) {
|
|
|
15
15
|
routesReachable: 0,
|
|
16
16
|
routesUnreachable: 0,
|
|
17
17
|
details: [],
|
|
18
|
-
warnings: [
|
|
19
|
-
code: 'INVALID_BASE_URL',
|
|
20
|
-
message: `Cannot parse base URL for validation: ${error.message}`
|
|
21
|
-
}]
|
|
18
|
+
warnings: []
|
|
22
19
|
};
|
|
23
20
|
}
|
|
24
21
|
|
|
@@ -152,6 +152,7 @@ export async function instrumentFile(inputPath, outputPath, workspaceRoot) {
|
|
|
152
152
|
const { mkdirSync } = await import('fs');
|
|
153
153
|
|
|
154
154
|
const code = readFileSync(inputPath, 'utf-8');
|
|
155
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
155
156
|
const instrumented = instrumentJSX(code, inputPath, workspaceRoot);
|
|
156
157
|
|
|
157
158
|
// Ensure output directory exists
|
|
@@ -3,7 +3,7 @@ import traverse from '@babel/traverse';
|
|
|
3
3
|
import { readFileSync } from 'fs';
|
|
4
4
|
import { glob } from 'glob';
|
|
5
5
|
import { resolve } from 'path';
|
|
6
|
-
import { ExpectationProof } from '../shared/expectation-
|
|
6
|
+
import { ExpectationProof } from '../shared/expectation-validation.js';
|
|
7
7
|
|
|
8
8
|
const MAX_FILES_TO_SCAN = 200;
|
|
9
9
|
|
|
@@ -141,6 +141,7 @@ function detectStateStores(projectDir) {
|
|
|
141
141
|
try {
|
|
142
142
|
const pkgPath = resolve(projectDir, 'package.json');
|
|
143
143
|
const pkgContent = readFileSync(pkgPath, 'utf-8');
|
|
144
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
144
145
|
const pkg = JSON.parse(pkgContent);
|
|
145
146
|
|
|
146
147
|
const allDeps = {
|
|
@@ -276,6 +276,7 @@ export async function extractStaticExpectations(projectDir, routes) {
|
|
|
276
276
|
|
|
277
277
|
try {
|
|
278
278
|
const content = readFileSync(filePath, 'utf-8');
|
|
279
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
279
280
|
const root = parse(content);
|
|
280
281
|
|
|
281
282
|
const links = root.querySelectorAll('a[href]');
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage Gap Accumulation & Warning Generation Module
|
|
3
|
+
* Extracted from observe/index.js (STAGE D2.2)
|
|
4
|
+
*
|
|
5
|
+
* Responsibility: Manage coverage gap collection from multiple sources
|
|
6
|
+
* and generate corresponding warning messages about incomplete coverage.
|
|
7
|
+
*
|
|
8
|
+
* This module encapsulates the logic for:
|
|
9
|
+
* 1. Accumulating coverage gaps from remaining unexecuted interactions
|
|
10
|
+
* 2. Detecting and recording frontier capping events
|
|
11
|
+
* 3. Building the coverage metrics object
|
|
12
|
+
* 4. Generating appropriate warning messages
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Accumulate coverage gaps from remaining interactions and frontier capping.
|
|
17
|
+
*
|
|
18
|
+
* When the interaction execution loop exits early (due to budget constraints),
|
|
19
|
+
* any unexecuted interactions are collected as coverage gaps with metadata
|
|
20
|
+
* about why they couldn't be executed.
|
|
21
|
+
*
|
|
22
|
+
* @param {Array} remainingInteractionsGaps - Array of unexecuted interactions with reasons
|
|
23
|
+
* @param {Object} frontier - Frontier object with tracking metadata (frontierCapped, pagesVisited, pagesDiscovered)
|
|
24
|
+
* @param {string} pageUrl - Current page URL from page.url() for gap location context
|
|
25
|
+
* @param {Object} scanBudget - Scan budget configuration (maxUniqueUrls, maxTotalInteractions)
|
|
26
|
+
* @returns {Array} Array of coverage gap objects in expectationCoverageGaps format
|
|
27
|
+
*/
|
|
28
|
+
export function accumulateCoverageGaps(remainingInteractionsGaps, frontier, pageUrl, scanBudget) {
|
|
29
|
+
const gaps = [];
|
|
30
|
+
|
|
31
|
+
// Add remaining interactions as coverage gaps
|
|
32
|
+
// These are interactions that existed but couldn't be executed due to budget constraints
|
|
33
|
+
if (remainingInteractionsGaps.length > 0) {
|
|
34
|
+
gaps.push(...remainingInteractionsGaps.map(gap => ({
|
|
35
|
+
expectationId: null,
|
|
36
|
+
type: gap.interaction.type,
|
|
37
|
+
reason: gap.reason,
|
|
38
|
+
fromPath: gap.url,
|
|
39
|
+
source: null,
|
|
40
|
+
evidence: {
|
|
41
|
+
interaction: gap.interaction
|
|
42
|
+
}
|
|
43
|
+
})));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Record frontier capping as coverage gap if it occurred
|
|
47
|
+
// This indicates that the scan encountered the URL discovery limit
|
|
48
|
+
if (frontier.frontierCapped) {
|
|
49
|
+
gaps.push({
|
|
50
|
+
expectationId: null,
|
|
51
|
+
type: 'navigation',
|
|
52
|
+
reason: 'frontier_capped',
|
|
53
|
+
fromPath: pageUrl,
|
|
54
|
+
source: null,
|
|
55
|
+
evidence: {
|
|
56
|
+
message: `Frontier capped at ${scanBudget.maxUniqueUrls || 'unlimited'} unique URLs`
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return gaps;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build the coverage metrics object.
|
|
66
|
+
*
|
|
67
|
+
* The coverage object captures quantitative metrics about what was discovered
|
|
68
|
+
* versus what was actually executed, enabling downstream analysis of coverage
|
|
69
|
+
* completeness and bottlenecks.
|
|
70
|
+
*
|
|
71
|
+
* @param {number} totalInteractionsDiscovered - Total interactions found across all pages
|
|
72
|
+
* @param {number} totalInteractionsExecuted - Interactions actually executed
|
|
73
|
+
* @param {Object} scanBudget - Scan budget configuration
|
|
74
|
+
* @param {Object} frontier - Frontier object with pagesVisited and pagesDiscovered
|
|
75
|
+
* @param {Array} skippedInteractions - Array of interactions that were skipped for safety
|
|
76
|
+
* @param {Array} remainingInteractionsGaps - Array of unexecuted interactions
|
|
77
|
+
* @returns {Object} Coverage metrics object
|
|
78
|
+
*/
|
|
79
|
+
export function buildCoverageObject(
|
|
80
|
+
totalInteractionsDiscovered,
|
|
81
|
+
totalInteractionsExecuted,
|
|
82
|
+
scanBudget,
|
|
83
|
+
frontier,
|
|
84
|
+
skippedInteractions,
|
|
85
|
+
remainingInteractionsGaps
|
|
86
|
+
) {
|
|
87
|
+
return {
|
|
88
|
+
candidatesDiscovered: totalInteractionsDiscovered,
|
|
89
|
+
candidatesSelected: totalInteractionsExecuted,
|
|
90
|
+
cap: scanBudget.maxTotalInteractions,
|
|
91
|
+
capped: totalInteractionsExecuted >= scanBudget.maxTotalInteractions || remainingInteractionsGaps.length > 0,
|
|
92
|
+
pagesVisited: frontier.pagesVisited,
|
|
93
|
+
pagesDiscovered: frontier.pagesDiscovered,
|
|
94
|
+
skippedInteractions: skippedInteractions.length,
|
|
95
|
+
interactionsDiscovered: totalInteractionsDiscovered,
|
|
96
|
+
interactionsExecuted: totalInteractionsExecuted
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Generate warning messages based on coverage metrics.
|
|
102
|
+
*
|
|
103
|
+
* Warnings communicate to the user what limitations were encountered during
|
|
104
|
+
* observation, allowing them to understand coverage completeness and any
|
|
105
|
+
* safety-related interaction filtering.
|
|
106
|
+
*
|
|
107
|
+
* @param {Object} coverage - Coverage metrics object from buildCoverageObject()
|
|
108
|
+
* @param {Array} skippedInteractions - Array of interactions that were skipped for safety
|
|
109
|
+
* @returns {Array} Array of warning objects with code and message properties
|
|
110
|
+
*/
|
|
111
|
+
export function generateCoverageWarnings(coverage, skippedInteractions) {
|
|
112
|
+
const warnings = [];
|
|
113
|
+
|
|
114
|
+
// Warn if coverage was incomplete due to capping
|
|
115
|
+
if (coverage.capped) {
|
|
116
|
+
warnings.push({
|
|
117
|
+
code: 'INTERACTIONS_CAPPED',
|
|
118
|
+
message: `Interaction execution capped. Visited ${coverage.pagesVisited} pages, discovered ${coverage.pagesDiscovered}, executed ${coverage.candidatesSelected} of ${coverage.candidatesDiscovered} interactions. Coverage incomplete.`
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Warn if interactions were skipped for safety
|
|
123
|
+
if (skippedInteractions.length > 0) {
|
|
124
|
+
warnings.push({
|
|
125
|
+
code: 'INTERACTIONS_SKIPPED',
|
|
126
|
+
message: `Skipped ${skippedInteractions.length} dangerous interactions`,
|
|
127
|
+
details: skippedInteractions
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return warnings;
|
|
132
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EXPECTATION HANDLER
|
|
3
|
+
*
|
|
4
|
+
* Manages manifest loading, snapshot comparison, and proven expectation execution.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync as readFileSyncWithEncoding, existsSync, writeFileSync, mkdirSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Load and execute proven expectations from manifest
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} page - Playwright page
|
|
14
|
+
* @param {string} manifestPath - Path to manifest file
|
|
15
|
+
* @param {string} projectDir - Project directory
|
|
16
|
+
* @param {Object} silenceTracker - Silence tracker
|
|
17
|
+
* @returns {Promise<{success: boolean, results: Array|null}>}
|
|
18
|
+
*/
|
|
19
|
+
export async function loadAndExecuteProvenExpectations(page, manifestPath, projectDir, silenceTracker) {
|
|
20
|
+
try {
|
|
21
|
+
if (!existsSync(manifestPath)) {
|
|
22
|
+
silenceTracker.record('expectation_manifest_not_found');
|
|
23
|
+
return { success: false, results: null };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const manifestContent = readFileSyncWithEncoding(manifestPath, 'utf8');
|
|
27
|
+
const manifest = JSON.parse(typeof manifestContent === 'string' ? manifestContent : manifestContent.toString());
|
|
28
|
+
|
|
29
|
+
if (!manifest.expectations || !Array.isArray(manifest.expectations)) {
|
|
30
|
+
silenceTracker.record('expectation_manifest_invalid_format');
|
|
31
|
+
return { success: false, results: null };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Return empty results for now (expectation execution handled in main observe function)
|
|
35
|
+
return { success: true, results: [] };
|
|
36
|
+
} catch (error) {
|
|
37
|
+
silenceTracker.record('expectation_manifest_load_error');
|
|
38
|
+
return { success: false, results: null };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Load and compare snapshot with previous observation
|
|
44
|
+
*
|
|
45
|
+
* @param {string} projectDir - Project directory
|
|
46
|
+
* @param {Object} silenceTracker - Silence tracker
|
|
47
|
+
* @returns {Promise<{currentSnapshot: Object|null, previousSnapshot: Object|null}>}
|
|
48
|
+
*/
|
|
49
|
+
export async function loadAndCompareSnapshot(projectDir, silenceTracker) {
|
|
50
|
+
try {
|
|
51
|
+
const snapshotDir = join(projectDir, '.verax', 'snapshots');
|
|
52
|
+
|
|
53
|
+
if (!existsSync(snapshotDir)) {
|
|
54
|
+
mkdirSync(snapshotDir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Load previous snapshot if exists
|
|
58
|
+
const previousSnapshotPath = join(snapshotDir, 'previous.json');
|
|
59
|
+
let previousSnapshot = null;
|
|
60
|
+
if (existsSync(previousSnapshotPath)) {
|
|
61
|
+
try {
|
|
62
|
+
const content = readFileSyncWithEncoding(previousSnapshotPath, 'utf8');
|
|
63
|
+
previousSnapshot = JSON.parse(typeof content === 'string' ? content : content.toString());
|
|
64
|
+
} catch {
|
|
65
|
+
silenceTracker.record('snapshot_previous_load_error');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Load current snapshot if exists
|
|
70
|
+
const currentSnapshotPath = join(snapshotDir, 'current.json');
|
|
71
|
+
let currentSnapshot = null;
|
|
72
|
+
if (existsSync(currentSnapshotPath)) {
|
|
73
|
+
try {
|
|
74
|
+
const content = readFileSyncWithEncoding(currentSnapshotPath, 'utf8');
|
|
75
|
+
currentSnapshot = JSON.parse(typeof content === 'string' ? content : content.toString());
|
|
76
|
+
} catch {
|
|
77
|
+
silenceTracker.record('snapshot_current_load_error');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { currentSnapshot, previousSnapshot };
|
|
82
|
+
} catch (error) {
|
|
83
|
+
silenceTracker.record('snapshot_comparison_error');
|
|
84
|
+
return { currentSnapshot: null, previousSnapshot: null };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Build snapshot object from observation data
|
|
90
|
+
*
|
|
91
|
+
* @param {Array} traces - Array of interaction traces
|
|
92
|
+
* @param {string} baseOrigin - Base origin URL
|
|
93
|
+
* @returns {Object}
|
|
94
|
+
*/
|
|
95
|
+
export function buildSnapshot(traces, baseOrigin) {
|
|
96
|
+
const snapshot = {
|
|
97
|
+
timestamp: new Date().toISOString(),
|
|
98
|
+
baseOrigin,
|
|
99
|
+
totalTraces: traces.length,
|
|
100
|
+
verifiedExpectations: traces.filter(t => t.expectationDriven).length,
|
|
101
|
+
observedExpectations: traces.filter(t => t.observedExpectation).length,
|
|
102
|
+
unprovable: traces.filter(t => t.unprovenResult).length
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return snapshot;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Save snapshot for future comparison
|
|
110
|
+
*
|
|
111
|
+
* @param {Object} snapshot - Snapshot object
|
|
112
|
+
* @param {string} projectDir - Project directory
|
|
113
|
+
*/
|
|
114
|
+
export function saveSnapshot(snapshot, projectDir) {
|
|
115
|
+
try {
|
|
116
|
+
const snapshotDir = join(projectDir, '.verax', 'snapshots');
|
|
117
|
+
if (!existsSync(snapshotDir)) {
|
|
118
|
+
mkdirSync(snapshotDir, { recursive: true });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const snapshotPath = join(snapshotDir, 'current.json');
|
|
122
|
+
writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2));
|
|
123
|
+
} catch (error) {
|
|
124
|
+
// Silently fail on snapshot save
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Incremental Skip Phantom Trace Builder
|
|
3
|
+
*
|
|
4
|
+
* Extracted from observe/index.js (STAGE D2.4)
|
|
5
|
+
*
|
|
6
|
+
* Builds the minimal phantom trace object that represents a skipped
|
|
7
|
+
* interaction in incremental mode. The phantom trace preserves interaction
|
|
8
|
+
* metadata but has no execution result (before/after URLs are identical).
|
|
9
|
+
*
|
|
10
|
+
* Preserves 100% of original behavior:
|
|
11
|
+
* - Same 5 top-level properties
|
|
12
|
+
* - Same nested object shapes
|
|
13
|
+
* - incremental flag always true
|
|
14
|
+
* - resultType always 'INCREMENTAL_SKIP'
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Build a phantom trace for an incremental skip.
|
|
19
|
+
*
|
|
20
|
+
* When an interaction is skipped in incremental mode (because it was
|
|
21
|
+
* unchanged from the previous run), a phantom trace is created to:
|
|
22
|
+
* 1. Preserve the interaction metadata for the observation record
|
|
23
|
+
* 2. Mark the trace as incremental (via incremental: true) for filtering
|
|
24
|
+
* 3. Enable transparent output (trace appears in JSON but marked as skipped)
|
|
25
|
+
*
|
|
26
|
+
* @param {Object} params - Function parameters
|
|
27
|
+
* @param {Object} params.interaction - The interaction that was skipped
|
|
28
|
+
* @param {string} params.interaction.type - Interaction type (e.g., 'click')
|
|
29
|
+
* @param {string} params.interaction.selector - CSS selector
|
|
30
|
+
* @param {string} params.interaction.label - Human-readable label
|
|
31
|
+
* @param {string} params.currentUrl - Current page URL (used for before/after)
|
|
32
|
+
* @returns {Object} Phantom trace object with incremental flag set
|
|
33
|
+
*/
|
|
34
|
+
export function buildIncrementalPhantomTrace({ interaction, currentUrl }) {
|
|
35
|
+
return {
|
|
36
|
+
interaction: {
|
|
37
|
+
type: interaction.type,
|
|
38
|
+
selector: interaction.selector,
|
|
39
|
+
label: interaction.label
|
|
40
|
+
},
|
|
41
|
+
before: { url: currentUrl },
|
|
42
|
+
after: { url: currentUrl },
|
|
43
|
+
incremental: true,
|
|
44
|
+
resultType: 'INCREMENTAL_SKIP'
|
|
45
|
+
};
|
|
46
|
+
}
|