@veraxhq/verax 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -6
- package/bin/verax.js +11 -11
- package/package.json +29 -8
- package/src/cli/commands/baseline.js +103 -0
- package/src/cli/commands/default.js +51 -6
- package/src/cli/commands/doctor.js +29 -0
- package/src/cli/commands/ga.js +246 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +4 -2
- package/src/cli/commands/release-check.js +215 -0
- package/src/cli/commands/run.js +45 -6
- package/src/cli/commands/security-check.js +212 -0
- package/src/cli/commands/truth.js +113 -0
- package/src/cli/entry.js +30 -20
- package/src/cli/util/angular-component-extractor.js +179 -0
- package/src/cli/util/angular-navigation-detector.js +141 -0
- package/src/cli/util/angular-network-detector.js +161 -0
- package/src/cli/util/angular-state-detector.js +162 -0
- package/src/cli/util/ast-interactive-detector.js +544 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-promise-extractor.js +581 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/atomic-write.js +12 -1
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/console-reporter.js +72 -0
- package/src/cli/util/detection-engine.js +105 -41
- package/src/cli/util/determinism-runner.js +124 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/digest-engine.js +359 -0
- package/src/cli/util/dom-diff.js +226 -0
- package/src/cli/util/evidence-engine.js +287 -0
- package/src/cli/util/expectation-extractor.js +151 -5
- package/src/cli/util/findings-writer.js +3 -0
- package/src/cli/util/framework-detector.js +572 -0
- package/src/cli/util/idgen.js +1 -1
- package/src/cli/util/interaction-planner.js +529 -0
- package/src/cli/util/learn-writer.js +2 -0
- package/src/cli/util/ledger-writer.js +110 -0
- package/src/cli/util/monorepo-resolver.js +162 -0
- package/src/cli/util/observation-engine.js +127 -278
- package/src/cli/util/observe-writer.js +2 -0
- package/src/cli/util/project-discovery.js +284 -0
- package/src/cli/util/project-writer.js +2 -0
- package/src/cli/util/run-id.js +23 -27
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/run-result.js +778 -0
- package/src/cli/util/selector-resolver.js +235 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +2 -0
- package/src/cli/util/svelte-navigation-detector.js +163 -0
- package/src/cli/util/svelte-network-detector.js +80 -0
- package/src/cli/util/svelte-sfc-extractor.js +146 -0
- package/src/cli/util/svelte-state-detector.js +242 -0
- package/src/cli/util/trust-activation-integration.js +496 -0
- package/src/cli/util/trust-activation-wrapper.js +85 -0
- package/src/cli/util/trust-integration-hooks.js +164 -0
- package/src/cli/util/types.js +153 -0
- package/src/cli/util/url-validation.js +40 -0
- package/src/cli/util/vue-navigation-detector.js +178 -0
- package/src/cli/util/vue-sfc-extractor.js +161 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/types/fs-augment.d.ts +23 -0
- package/src/types/global.d.ts +137 -0
- package/src/types/internal-types.d.ts +35 -0
- package/src/verax/cli/init.js +4 -18
- package/src/verax/core/action-classifier.js +4 -3
- package/src/verax/core/artifacts/registry.js +139 -0
- package/src/verax/core/artifacts/verifier.js +990 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +233 -0
- package/src/verax/core/capabilities/gates.js +505 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +144 -0
- package/src/verax/core/confidence/confidence-invariants.js +234 -0
- package/src/verax/core/confidence/confidence-report-writer.js +112 -0
- package/src/verax/core/confidence/confidence-weights.js +44 -0
- package/src/verax/core/confidence/confidence.defaults.js +65 -0
- package/src/verax/core/confidence/confidence.loader.js +80 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +489 -0
- package/src/verax/core/confidence-engine.js +625 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +186 -0
- package/src/verax/core/contracts/validators.js +456 -0
- package/src/verax/core/decisions/decision.trace.js +278 -0
- package/src/verax/core/determinism/contract-writer.js +89 -0
- package/src/verax/core/determinism/contract.js +139 -0
- package/src/verax/core/determinism/diff.js +405 -0
- package/src/verax/core/determinism/engine.js +222 -0
- package/src/verax/core/determinism/finding-identity.js +149 -0
- package/src/verax/core/determinism/normalize.js +466 -0
- package/src/verax/core/determinism/report-writer.js +93 -0
- package/src/verax/core/determinism/run-fingerprint.js +123 -0
- package/src/verax/core/dynamic-route-intelligence.js +529 -0
- package/src/verax/core/evidence/evidence-capture-service.js +308 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +166 -0
- package/src/verax/core/evidence-builder.js +487 -0
- package/src/verax/core/execution-mode-context.js +77 -0
- package/src/verax/core/execution-mode-detector.js +192 -0
- package/src/verax/core/failures/exit-codes.js +88 -0
- package/src/verax/core/failures/failure-summary.js +76 -0
- package/src/verax/core/failures/failure.factory.js +225 -0
- package/src/verax/core/failures/failure.ledger.js +133 -0
- package/src/verax/core/failures/failure.types.js +196 -0
- package/src/verax/core/failures/index.js +10 -0
- package/src/verax/core/ga/ga-report-writer.js +43 -0
- package/src/verax/core/ga/ga.artifact.js +49 -0
- package/src/verax/core/ga/ga.contract.js +435 -0
- package/src/verax/core/ga/ga.enforcer.js +87 -0
- package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
- package/src/verax/core/guardrails/policy.defaults.js +210 -0
- package/src/verax/core/guardrails/policy.loader.js +84 -0
- package/src/verax/core/guardrails/policy.schema.js +110 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
- package/src/verax/core/guardrails-engine.js +505 -0
- package/src/verax/core/incremental-store.js +1 -0
- package/src/verax/core/integrity/budget.js +138 -0
- package/src/verax/core/integrity/determinism.js +342 -0
- package/src/verax/core/integrity/integrity.js +208 -0
- package/src/verax/core/integrity/poisoning.js +108 -0
- package/src/verax/core/integrity/transaction.js +140 -0
- package/src/verax/core/observe/run-timeline.js +318 -0
- package/src/verax/core/perf/perf.contract.js +186 -0
- package/src/verax/core/perf/perf.display.js +65 -0
- package/src/verax/core/perf/perf.enforcer.js +91 -0
- package/src/verax/core/perf/perf.monitor.js +209 -0
- package/src/verax/core/perf/perf.report.js +200 -0
- package/src/verax/core/pipeline-tracker.js +243 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +130 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +164 -0
- package/src/verax/core/release/reproducibility.check.js +222 -0
- package/src/verax/core/release/sbom.builder.js +292 -0
- package/src/verax/core/replay-validator.js +2 -0
- package/src/verax/core/replay.js +4 -0
- package/src/verax/core/report/cross-index.js +195 -0
- package/src/verax/core/report/human-summary.js +362 -0
- package/src/verax/core/route-intelligence.js +420 -0
- package/src/verax/core/run-id.js +6 -3
- package/src/verax/core/run-manifest.js +4 -3
- package/src/verax/core/security/secrets.scan.js +329 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +128 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +334 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/truth/truth.certificate.js +252 -0
- package/src/verax/core/ui-feedback-intelligence.js +481 -0
- package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
- package/src/verax/detect/confidence-engine.js +62 -34
- package/src/verax/detect/confidence-helper.js +34 -0
- package/src/verax/detect/dynamic-route-findings.js +338 -0
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +2 -2
- package/src/verax/detect/failure-cause-inference.js +293 -0
- package/src/verax/detect/findings-writer.js +131 -35
- package/src/verax/detect/flow-detector.js +2 -2
- package/src/verax/detect/form-silent-failure.js +98 -0
- package/src/verax/detect/index.js +46 -5
- package/src/verax/detect/invariants-enforcer.js +147 -0
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/navigation-silent-failure.js +82 -0
- package/src/verax/detect/problem-aggregator.js +361 -0
- package/src/verax/detect/route-findings.js +219 -0
- package/src/verax/detect/summary-writer.js +477 -0
- package/src/verax/detect/test-failure-cause-inference.js +314 -0
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/flow/flow-engine.js +2 -1
- package/src/verax/flow/flow-spec.js +0 -6
- package/src/verax/index.js +4 -0
- package/src/verax/intel/ts-program.js +1 -0
- package/src/verax/intel/vue-navigation-extractor.js +3 -0
- package/src/verax/learn/action-contract-extractor.js +3 -0
- package/src/verax/learn/ast-contract-extractor.js +1 -1
- package/src/verax/learn/flow-extractor.js +1 -0
- package/src/verax/learn/project-detector.js +5 -0
- package/src/verax/learn/react-router-extractor.js +2 -0
- package/src/verax/learn/source-instrumenter.js +1 -0
- package/src/verax/learn/state-extractor.js +2 -1
- package/src/verax/learn/static-extractor.js +1 -0
- package/src/verax/observe/coverage-gaps.js +132 -0
- package/src/verax/observe/expectation-handler.js +126 -0
- package/src/verax/observe/incremental-skip.js +46 -0
- package/src/verax/observe/index.js +51 -155
- package/src/verax/observe/interaction-executor.js +192 -0
- package/src/verax/observe/interaction-runner.js +782 -513
- package/src/verax/observe/network-firewall.js +86 -0
- package/src/verax/observe/observation-builder.js +169 -0
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +192 -0
- package/src/verax/observe/observe-runner.js +230 -0
- package/src/verax/observe/observers/budget-observer.js +185 -0
- package/src/verax/observe/observers/console-observer.js +102 -0
- package/src/verax/observe/observers/coverage-observer.js +107 -0
- package/src/verax/observe/observers/interaction-observer.js +471 -0
- package/src/verax/observe/observers/navigation-observer.js +132 -0
- package/src/verax/observe/observers/network-observer.js +87 -0
- package/src/verax/observe/observers/safety-observer.js +82 -0
- package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
- package/src/verax/observe/page-traversal.js +138 -0
- package/src/verax/observe/snapshot-ops.js +94 -0
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/scan-summary-writer.js +2 -0
- package/src/verax/shared/artifact-manager.js +25 -5
- package/src/verax/shared/caching.js +1 -0
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/expectation-tracker.js +1 -0
- package/src/verax/shared/view-switch-rules.js +208 -0
- package/src/verax/shared/zip-artifacts.js +6 -0
- package/src/verax/shared/config-loader.js +0 -169
- /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.3 — Safety Observer
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Network interception (cross-origin blocking, write method blocking)
|
|
6
|
+
* - NO file I/O
|
|
7
|
+
* - NO side effects outside its scope
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Setup network interception firewall
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} context - Observe context
|
|
14
|
+
* @returns {Promise<void>}
|
|
15
|
+
*/
|
|
16
|
+
export async function setupNetworkInterception(context) {
|
|
17
|
+
const { page, baseOrigin, safetyFlags, silenceTracker, blockedNetworkWrites, blockedCrossOrigin } = context;
|
|
18
|
+
const { allowWrites = false, allowCrossOrigin = false } = safetyFlags;
|
|
19
|
+
|
|
20
|
+
await page.route('**/*', (route) => {
|
|
21
|
+
const request = route.request();
|
|
22
|
+
const method = request.method();
|
|
23
|
+
const requestUrl = request.url();
|
|
24
|
+
const resourceType = request.resourceType();
|
|
25
|
+
|
|
26
|
+
// Check cross-origin blocking (skip for file:// URLs)
|
|
27
|
+
if (!allowCrossOrigin && !requestUrl.startsWith('file://')) {
|
|
28
|
+
try {
|
|
29
|
+
const reqOrigin = new URL(requestUrl).origin;
|
|
30
|
+
if (reqOrigin !== baseOrigin) {
|
|
31
|
+
blockedCrossOrigin.push({
|
|
32
|
+
url: requestUrl,
|
|
33
|
+
origin: reqOrigin,
|
|
34
|
+
method,
|
|
35
|
+
resourceType,
|
|
36
|
+
timestamp: Date.now()
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
silenceTracker.record({
|
|
40
|
+
scope: 'safety',
|
|
41
|
+
reason: 'cross_origin_blocked',
|
|
42
|
+
description: `Cross-origin request blocked: ${method} ${requestUrl}`,
|
|
43
|
+
context: { url: requestUrl, origin: reqOrigin, method, baseOrigin },
|
|
44
|
+
impact: 'request_blocked'
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return route.abort('blockedbyclient');
|
|
48
|
+
}
|
|
49
|
+
} catch (e) {
|
|
50
|
+
// Invalid URL, allow and let browser handle
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check write method blocking
|
|
55
|
+
if (!allowWrites && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
|
|
56
|
+
// Check if it's a GraphQL mutation (best-effort)
|
|
57
|
+
const isGraphQLMutation = requestUrl.includes('/graphql') && method === 'POST';
|
|
58
|
+
|
|
59
|
+
blockedNetworkWrites.push({
|
|
60
|
+
url: requestUrl,
|
|
61
|
+
method,
|
|
62
|
+
resourceType,
|
|
63
|
+
isGraphQLMutation,
|
|
64
|
+
timestamp: Date.now()
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
silenceTracker.record({
|
|
68
|
+
scope: 'safety',
|
|
69
|
+
reason: 'blocked_network_write',
|
|
70
|
+
description: `Network write blocked: ${method} ${requestUrl}${isGraphQLMutation ? ' (GraphQL mutation)' : ''}`,
|
|
71
|
+
context: { url: requestUrl, method, resourceType, isGraphQLMutation },
|
|
72
|
+
impact: 'write_blocked'
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return route.abort('blockedbyclient');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Allow request
|
|
79
|
+
route.continue();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.3 — UI Feedback Observer
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - DOM mutation observation
|
|
6
|
+
* - Loading / disabled / feedback signals
|
|
7
|
+
* - UI settle signals (NO adaptive waiting - that's in settle.js)
|
|
8
|
+
*
|
|
9
|
+
* NO file I/O
|
|
10
|
+
* NO side effects outside its scope
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { UISignalSensor } from '../ui-signal-sensor.js';
|
|
14
|
+
import { captureDomSignature } from '../dom-signature.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Observe UI feedback and DOM state on current page
|
|
18
|
+
*
|
|
19
|
+
* @param {Object} context - Observe context
|
|
20
|
+
* @param {Object} _runState - Current run state
|
|
21
|
+
* @returns {Promise<Array<Object>>} Array of UI feedback observations
|
|
22
|
+
*/
|
|
23
|
+
export async function observe(context, _runState) {
|
|
24
|
+
const { page, currentUrl, timestamp } = context;
|
|
25
|
+
const observations = [];
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// Capture current UI signals
|
|
29
|
+
const uiSignalSensor = new UISignalSensor();
|
|
30
|
+
const uiSignals = await uiSignalSensor.snapshot(page);
|
|
31
|
+
|
|
32
|
+
// Capture DOM signature for mutation tracking
|
|
33
|
+
const domSignature = await captureDomSignature(page);
|
|
34
|
+
|
|
35
|
+
// Create observation for UI signals
|
|
36
|
+
observations.push({
|
|
37
|
+
type: 'ui_feedback',
|
|
38
|
+
scope: 'page',
|
|
39
|
+
data: {
|
|
40
|
+
hasLoadingIndicator: uiSignals.hasLoadingIndicator,
|
|
41
|
+
hasDialog: uiSignals.hasDialog,
|
|
42
|
+
hasErrorSignal: uiSignals.hasErrorSignal,
|
|
43
|
+
hasStatusSignal: uiSignals.hasStatusSignal,
|
|
44
|
+
hasLiveRegion: uiSignals.hasLiveRegion,
|
|
45
|
+
validationFeedbackDetected: uiSignals.validationFeedbackDetected,
|
|
46
|
+
disabledElementsCount: uiSignals.disabledElements?.length || 0,
|
|
47
|
+
explanation: uiSignals.explanation || []
|
|
48
|
+
},
|
|
49
|
+
timestamp,
|
|
50
|
+
url: currentUrl
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Create observation for DOM state
|
|
54
|
+
observations.push({
|
|
55
|
+
type: 'dom_state',
|
|
56
|
+
scope: 'page',
|
|
57
|
+
data: {
|
|
58
|
+
domHash: domSignature,
|
|
59
|
+
hasDom: !!domSignature
|
|
60
|
+
},
|
|
61
|
+
timestamp,
|
|
62
|
+
url: currentUrl
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// If there are loading indicators, create specific observation
|
|
66
|
+
if (uiSignals.hasLoadingIndicator) {
|
|
67
|
+
observations.push({
|
|
68
|
+
type: 'ui_loading',
|
|
69
|
+
scope: 'page',
|
|
70
|
+
data: {
|
|
71
|
+
loading: true,
|
|
72
|
+
explanation: uiSignals.explanation?.filter(e => e.includes('loading') || e.includes('busy')) || []
|
|
73
|
+
},
|
|
74
|
+
timestamp,
|
|
75
|
+
url: currentUrl
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// If there are disabled elements, create observation
|
|
80
|
+
if (uiSignals.disabledElements && uiSignals.disabledElements.length > 0) {
|
|
81
|
+
observations.push({
|
|
82
|
+
type: 'ui_disabled',
|
|
83
|
+
scope: 'page',
|
|
84
|
+
data: {
|
|
85
|
+
disabledCount: uiSignals.disabledElements.length,
|
|
86
|
+
disabledElements: uiSignals.disabledElements.slice(0, 10) // Limit to 10
|
|
87
|
+
},
|
|
88
|
+
timestamp,
|
|
89
|
+
url: currentUrl
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
// Propagate error - no silent catch
|
|
94
|
+
throw new Error(`UI feedback observer failed: ${error.message}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return observations;
|
|
98
|
+
}
|
|
99
|
+
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PAGE TRAVERSAL ENGINE
|
|
3
|
+
*
|
|
4
|
+
* Manages page frontier, link discovery, and page-to-page navigation.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { isExternalUrl } from './domain-boundary.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Discover links on current page
|
|
11
|
+
*
|
|
12
|
+
* @param {Object} page - Playwright page
|
|
13
|
+
* @param {string} baseOrigin - Base origin URL
|
|
14
|
+
* @param {Object} silenceTracker - Silence tracker
|
|
15
|
+
* @returns {Promise<Array>}
|
|
16
|
+
*/
|
|
17
|
+
export async function discoverPageLinks(page, baseOrigin, silenceTracker) {
|
|
18
|
+
try {
|
|
19
|
+
// Discover all links on the page
|
|
20
|
+
const links = await page.locator('a[href]').all();
|
|
21
|
+
const linkData = [];
|
|
22
|
+
|
|
23
|
+
for (const link of links) {
|
|
24
|
+
const href = await link.getAttribute('href');
|
|
25
|
+
if (href) {
|
|
26
|
+
linkData.push({ href });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Filter to same-origin links
|
|
31
|
+
const sameOriginLinks = linkData.filter(link => {
|
|
32
|
+
if (!link.href) return false;
|
|
33
|
+
return !isExternalUrl(link.href, baseOrigin);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return sameOriginLinks;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
silenceTracker.record('link_discovery_error');
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get next page URL from frontier
|
|
45
|
+
*
|
|
46
|
+
* @param {Object} frontier - Page frontier object
|
|
47
|
+
* @returns {string|null}
|
|
48
|
+
*/
|
|
49
|
+
export function getNextPageUrl(frontier) {
|
|
50
|
+
if (!frontier || !frontier.queue || frontier.queue.length === 0) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const nextUrl = frontier.queue[0];
|
|
55
|
+
return nextUrl;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Mark page as visited in frontier
|
|
60
|
+
*
|
|
61
|
+
* @param {Object} frontier - Page frontier object
|
|
62
|
+
* @param {string} url - URL that was visited
|
|
63
|
+
*/
|
|
64
|
+
export function markPageVisited(frontier, url) {
|
|
65
|
+
if (!frontier) return;
|
|
66
|
+
|
|
67
|
+
// Remove from queue
|
|
68
|
+
if (frontier.queue && frontier.queue.length > 0) {
|
|
69
|
+
frontier.queue = frontier.queue.filter(u => u !== url);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Add to visited
|
|
73
|
+
if (!frontier.visited) {
|
|
74
|
+
frontier.visited = [];
|
|
75
|
+
}
|
|
76
|
+
if (!frontier.visited.includes(url)) {
|
|
77
|
+
frontier.visited.push(url);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Add discovered links to frontier queue
|
|
83
|
+
*
|
|
84
|
+
* @param {Object} frontier - Page frontier object
|
|
85
|
+
* @param {Array} links - Links to add
|
|
86
|
+
*/
|
|
87
|
+
export function addLinksToFrontier(frontier, links) {
|
|
88
|
+
if (!frontier || !links || links.length === 0) return;
|
|
89
|
+
|
|
90
|
+
if (!frontier.queue) {
|
|
91
|
+
frontier.queue = [];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const link of links) {
|
|
95
|
+
if (link.href && !frontier.queue.includes(link.href) &&
|
|
96
|
+
(!frontier.visited || !frontier.visited.includes(link.href))) {
|
|
97
|
+
frontier.queue.push(link.href);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if page limit has been reached
|
|
104
|
+
*
|
|
105
|
+
* @param {number} pagesVisited - Number of pages visited
|
|
106
|
+
* @param {number} pageLimit - Maximum pages to visit
|
|
107
|
+
* @returns {boolean}
|
|
108
|
+
*/
|
|
109
|
+
export function isPageLimitReached(pagesVisited, pageLimit) {
|
|
110
|
+
return pagesVisited >= pageLimit;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Cap frontier to maximum size
|
|
115
|
+
*
|
|
116
|
+
* @param {Object} frontier - Page frontier object
|
|
117
|
+
* @param {number} maxFrontierSize - Maximum frontier queue size
|
|
118
|
+
*/
|
|
119
|
+
export function capFrontier(frontier, maxFrontierSize) {
|
|
120
|
+
if (!frontier || !frontier.queue) return;
|
|
121
|
+
|
|
122
|
+
if (frontier.queue.length > maxFrontierSize) {
|
|
123
|
+
frontier.queue = frontier.queue.slice(0, maxFrontierSize);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Initialize frontier for traversal
|
|
129
|
+
*
|
|
130
|
+
* @param {string} baseUrl - Starting URL
|
|
131
|
+
* @returns {Object}
|
|
132
|
+
*/
|
|
133
|
+
export function initializeFrontier(baseUrl) {
|
|
134
|
+
return {
|
|
135
|
+
queue: [baseUrl],
|
|
136
|
+
visited: []
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SNAPSHOT OPERATIONS MODULE
|
|
3
|
+
*
|
|
4
|
+
* Encapsulates snapshot loading, comparison, building, and saving logic.
|
|
5
|
+
* Extracted from observe/index.js (STAGE D2.1).
|
|
6
|
+
*
|
|
7
|
+
* Preserves 100% of original behavior:
|
|
8
|
+
* - Same incremental mode conditions
|
|
9
|
+
* - Same snapshot JSON shape
|
|
10
|
+
* - Same load/save timing
|
|
11
|
+
* - Same trace filtering
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
loadPreviousSnapshot,
|
|
16
|
+
buildSnapshot,
|
|
17
|
+
compareSnapshots,
|
|
18
|
+
saveSnapshot
|
|
19
|
+
} from '../core/incremental-store.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Initialize snapshot operations at the beginning of observation
|
|
23
|
+
*
|
|
24
|
+
* Loads previous snapshot, compares with baseline, determines incremental mode.
|
|
25
|
+
* Extracted from lines 108-111 of observe/index.js
|
|
26
|
+
*
|
|
27
|
+
* @param {string} projectDir - Project directory
|
|
28
|
+
* @param {Object} manifest - Loaded manifest object
|
|
29
|
+
* @returns {Promise<{oldSnapshot: Object|null, snapshotDiff: Object|null, incrementalMode: boolean}>}
|
|
30
|
+
*/
|
|
31
|
+
export async function initializeSnapshot(projectDir, manifest) {
|
|
32
|
+
let oldSnapshot = null;
|
|
33
|
+
let snapshotDiff = null;
|
|
34
|
+
let incrementalMode = false;
|
|
35
|
+
|
|
36
|
+
// SCALE INTELLIGENCE: Load previous snapshot for incremental mode
|
|
37
|
+
oldSnapshot = loadPreviousSnapshot(projectDir);
|
|
38
|
+
if (oldSnapshot) {
|
|
39
|
+
const currentSnapshot = buildSnapshot(manifest, []);
|
|
40
|
+
snapshotDiff = compareSnapshots(oldSnapshot, currentSnapshot);
|
|
41
|
+
incrementalMode = !snapshotDiff.hasChanges; // Use incremental if nothing changed
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
oldSnapshot,
|
|
46
|
+
snapshotDiff,
|
|
47
|
+
incrementalMode
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Finalize snapshot operations at the end of observation
|
|
53
|
+
*
|
|
54
|
+
* Builds current snapshot from observed interactions, saves for next run,
|
|
55
|
+
* and creates incremental metadata for observation object.
|
|
56
|
+
* Extracted from lines 789-811 of observe/index.js
|
|
57
|
+
*
|
|
58
|
+
* @param {Object} manifest - Loaded manifest object (or null if not provided)
|
|
59
|
+
* @param {Array} traces - All collected traces
|
|
60
|
+
* @param {Array} skippedInteractions - Array of skipped interactions
|
|
61
|
+
* @param {boolean} incrementalMode - Whether incremental mode was enabled
|
|
62
|
+
* @param {Object} snapshotDiff - Snapshot diff from initialization (or null)
|
|
63
|
+
* @param {string} projectDir - Project directory
|
|
64
|
+
* @param {string} runId - Run identifier
|
|
65
|
+
* @param {string} url - Initial URL (fallback for trace.before.url)
|
|
66
|
+
* @returns {Promise<{enabled: boolean, snapshotDiff: Object, skippedInteractionsCount: number}|null>}
|
|
67
|
+
*/
|
|
68
|
+
export async function finalizeSnapshot(manifest, traces, skippedInteractions, incrementalMode, snapshotDiff, projectDir, runId, url) {
|
|
69
|
+
let incrementalMetadata = null;
|
|
70
|
+
|
|
71
|
+
// SCALE INTELLIGENCE: Save snapshot for next incremental run
|
|
72
|
+
if (manifest) {
|
|
73
|
+
// Build snapshot from current run (extract interactions from traces)
|
|
74
|
+
const observedInteractions = traces
|
|
75
|
+
.filter(t => t.interaction && !t.incremental)
|
|
76
|
+
.map(t => ({
|
|
77
|
+
type: t.interaction?.type,
|
|
78
|
+
selector: t.interaction?.selector,
|
|
79
|
+
url: t.before?.url || url
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
const currentSnapshot = buildSnapshot(manifest, observedInteractions);
|
|
83
|
+
saveSnapshot(projectDir, currentSnapshot, runId);
|
|
84
|
+
|
|
85
|
+
// Add incremental mode metadata to observation
|
|
86
|
+
incrementalMetadata = {
|
|
87
|
+
enabled: incrementalMode,
|
|
88
|
+
snapshotDiff: snapshotDiff,
|
|
89
|
+
skippedInteractionsCount: skippedInteractions.filter(s => s.reason === 'incremental_unchanged').length
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return incrementalMetadata;
|
|
94
|
+
}
|