@odavl/guardian 0.1.0-rc1 → 1.0.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/CHANGELOG.md +146 -0
- package/README.md +155 -97
- package/bin/guardian.js +1544 -55
- package/config/README.md +59 -0
- package/config/profiles/landing-demo.yaml +16 -0
- package/package.json +26 -11
- package/policies/landing-demo.json +22 -0
- package/src/enterprise/audit-logger.js +166 -0
- package/src/enterprise/pdf-exporter.js +267 -0
- package/src/enterprise/rbac-gate.js +142 -0
- package/src/enterprise/rbac.js +239 -0
- package/src/enterprise/site-manager.js +180 -0
- package/src/founder/feedback-system.js +156 -0
- package/src/founder/founder-tracker.js +213 -0
- package/src/founder/usage-signals.js +141 -0
- package/src/guardian/alert-ledger.js +121 -0
- package/src/guardian/attempt-engine.js +587 -12
- package/src/guardian/attempt-registry.js +42 -1
- package/src/guardian/attempt-relevance.js +106 -0
- package/src/guardian/attempt.js +85 -39
- package/src/guardian/attempts-filter.js +63 -0
- package/src/guardian/baseline.js +50 -8
- package/src/guardian/breakage-intelligence.js +1 -0
- package/src/guardian/browser-pool.js +131 -0
- package/src/guardian/browser.js +28 -1
- package/src/guardian/ci-cli.js +121 -0
- package/src/guardian/ci-mode.js +15 -0
- package/src/guardian/ci-output.js +38 -0
- package/src/guardian/cli-summary.js +167 -67
- package/src/guardian/config-loader.js +162 -0
- package/src/guardian/data-guardian-detector.js +189 -0
- package/src/guardian/detection-layers.js +271 -0
- package/src/guardian/drift-detector.js +100 -0
- package/src/guardian/enhanced-html-reporter.js +221 -4
- package/src/guardian/env-guard.js +127 -0
- package/src/guardian/failure-intelligence.js +173 -0
- package/src/guardian/first-run-profile.js +89 -0
- package/src/guardian/first-run.js +54 -0
- package/src/guardian/flag-validator.js +111 -0
- package/src/guardian/flow-executor.js +309 -44
- package/src/guardian/html-reporter.js +2 -0
- package/src/guardian/human-reporter.js +431 -0
- package/src/guardian/index.js +22 -19
- package/src/guardian/init-command.js +9 -5
- package/src/guardian/intent-detector.js +146 -0
- package/src/guardian/journey-definitions.js +132 -0
- package/src/guardian/journey-scan-cli.js +145 -0
- package/src/guardian/journey-scanner.js +583 -0
- package/src/guardian/junit-reporter.js +18 -1
- package/src/guardian/language-detection.js +99 -0
- package/src/guardian/live-cli.js +95 -0
- package/src/guardian/live-scheduler-runner.js +137 -0
- package/src/guardian/live-scheduler.js +146 -0
- package/src/guardian/market-reporter.js +357 -82
- package/src/guardian/parallel-executor.js +116 -0
- package/src/guardian/pattern-analyzer.js +348 -0
- package/src/guardian/policy.js +80 -3
- package/src/guardian/prerequisite-checker.js +101 -0
- package/src/guardian/preset-loader.js +27 -18
- package/src/guardian/profile-loader.js +96 -0
- package/src/guardian/reality.js +1612 -115
- package/src/guardian/reporter.js +27 -41
- package/src/guardian/run-artifacts.js +212 -0
- package/src/guardian/run-cleanup.js +207 -0
- package/src/guardian/run-latest.js +90 -0
- package/src/guardian/run-list.js +211 -0
- package/src/guardian/run-summary.js +20 -0
- package/src/guardian/scan-presets.js +100 -11
- package/src/guardian/selector-fallbacks.js +394 -0
- package/src/guardian/semantic-contact-detection.js +255 -0
- package/src/guardian/semantic-contact-finder.js +201 -0
- package/src/guardian/semantic-targets.js +234 -0
- package/src/guardian/site-introspection.js +257 -0
- package/src/guardian/smoke.js +258 -0
- package/src/guardian/snapshot-schema.js +25 -1
- package/src/guardian/snapshot.js +69 -3
- package/src/guardian/stability-scorer.js +169 -0
- package/src/guardian/success-evaluator.js +214 -0
- package/src/guardian/template-command.js +184 -0
- package/src/guardian/text-formatters.js +426 -0
- package/src/guardian/timeout-profiles.js +57 -0
- package/src/guardian/verdict.js +320 -0
- package/src/guardian/verdicts.js +74 -0
- package/src/guardian/wait-for-outcome.js +120 -0
- package/src/guardian/watch-runner.js +181 -0
- package/src/payments/stripe-checkout.js +169 -0
- package/src/plans/plan-definitions.js +148 -0
- package/src/plans/plan-manager.js +211 -0
- package/src/plans/usage-tracker.js +210 -0
- package/src/recipes/recipe-engine.js +188 -0
- package/src/recipes/recipe-failure-analysis.js +159 -0
- package/src/recipes/recipe-registry.js +134 -0
- package/src/recipes/recipe-runtime.js +507 -0
- package/src/recipes/recipe-store.js +410 -0
- package/guardian-contract-v1.md +0 -149
- /package/{guardian.config.json → config/guardian.config.json} +0 -0
- /package/{guardian.policy.json → config/guardian.policy.json} +0 -0
- /package/{guardian.profile.docs.yaml → config/profiles/docs.yaml} +0 -0
- /package/{guardian.profile.ecommerce.yaml → config/profiles/ecommerce.yaml} +0 -0
- /package/{guardian.profile.marketing.yaml → config/profiles/marketing.yaml} +0 -0
- /package/{guardian.profile.saas.yaml → config/profiles/saas.yaml} +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Journey Definitions - Preset journeys for different product types
|
|
3
|
+
* Each journey is a deterministic sequence of steps
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
function getJourneyDefinition(preset = 'saas') {
|
|
7
|
+
switch (preset) {
|
|
8
|
+
case 'saas':
|
|
9
|
+
return getSaasjJourney();
|
|
10
|
+
case 'shop':
|
|
11
|
+
case 'ecommerce':
|
|
12
|
+
return getShopJourney();
|
|
13
|
+
case 'landing':
|
|
14
|
+
return getLandingJourney();
|
|
15
|
+
default:
|
|
16
|
+
return getSaasjJourney();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* SAAS Journey: Homepage → Find CTA → Signup flow
|
|
22
|
+
*/
|
|
23
|
+
function getSaasjJourney() {
|
|
24
|
+
return {
|
|
25
|
+
name: 'SAAS Signup Flow',
|
|
26
|
+
description: 'Test critical path: homepage → locate signup CTA → click → verify signup page',
|
|
27
|
+
preset: 'saas',
|
|
28
|
+
steps: [
|
|
29
|
+
{
|
|
30
|
+
id: 'step-1-homepage',
|
|
31
|
+
name: 'Load homepage',
|
|
32
|
+
action: 'navigate',
|
|
33
|
+
target: '/',
|
|
34
|
+
description: 'User arrives at the homepage'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'step-2-find-cta',
|
|
38
|
+
name: 'Find signup CTA',
|
|
39
|
+
action: 'find_cta',
|
|
40
|
+
target: null,
|
|
41
|
+
description: 'Locate primary signup/CTA button (sign up, get started, start, register, pricing, etc)',
|
|
42
|
+
expectedIndicator: {
|
|
43
|
+
textMatch: ['sign up', 'signup', 'get started', 'start', 'register', 'pricing'],
|
|
44
|
+
tagNames: ['A', 'BUTTON']
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'step-3-verify-homepage',
|
|
49
|
+
name: 'Verify homepage loaded',
|
|
50
|
+
action: 'navigate',
|
|
51
|
+
target: '/pricing',
|
|
52
|
+
description: 'If CTA not found, try /pricing as fallback'
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Shop Journey: Homepage → Find product CTA → Checkout
|
|
60
|
+
*/
|
|
61
|
+
function getShopJourney() {
|
|
62
|
+
return {
|
|
63
|
+
name: 'Shop Checkout Flow',
|
|
64
|
+
description: 'Test critical path: homepage → find product/checkout CTA → click → verify flow',
|
|
65
|
+
preset: 'shop',
|
|
66
|
+
steps: [
|
|
67
|
+
{
|
|
68
|
+
id: 'step-1-homepage',
|
|
69
|
+
name: 'Load homepage',
|
|
70
|
+
action: 'navigate',
|
|
71
|
+
target: '/',
|
|
72
|
+
description: 'User arrives at the homepage'
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'step-2-find-product',
|
|
76
|
+
name: 'Find product CTA',
|
|
77
|
+
action: 'find_cta',
|
|
78
|
+
target: null,
|
|
79
|
+
description: 'Locate primary product/buy/checkout button',
|
|
80
|
+
expectedIndicator: {
|
|
81
|
+
textMatch: ['buy', 'shop', 'add to cart', 'checkout', 'order', 'purchase'],
|
|
82
|
+
tagNames: ['A', 'BUTTON']
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'step-3-navigate-shop',
|
|
87
|
+
name: 'Navigate to shop',
|
|
88
|
+
action: 'navigate',
|
|
89
|
+
target: '/shop',
|
|
90
|
+
description: 'If CTA not found, try /shop as fallback'
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Landing Page Journey: Simple homepage test
|
|
98
|
+
*/
|
|
99
|
+
function getLandingJourney() {
|
|
100
|
+
return {
|
|
101
|
+
name: 'Landing Page Journey',
|
|
102
|
+
description: 'Test: homepage → find contact/CTA → verify page load',
|
|
103
|
+
preset: 'landing',
|
|
104
|
+
steps: [
|
|
105
|
+
{
|
|
106
|
+
id: 'step-1-homepage',
|
|
107
|
+
name: 'Load homepage',
|
|
108
|
+
action: 'navigate',
|
|
109
|
+
target: '/',
|
|
110
|
+
description: 'User arrives at the homepage'
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: 'step-2-find-cta',
|
|
114
|
+
name: 'Find primary CTA',
|
|
115
|
+
action: 'find_cta',
|
|
116
|
+
target: null,
|
|
117
|
+
description: 'Locate primary call-to-action element',
|
|
118
|
+
expectedIndicator: {
|
|
119
|
+
textMatch: ['contact', 'get started', 'sign up', 'learn more', 'demo'],
|
|
120
|
+
tagNames: ['A', 'BUTTON']
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
getJourneyDefinition,
|
|
129
|
+
getSaasjJourney,
|
|
130
|
+
getShopJourney,
|
|
131
|
+
getLandingJourney
|
|
132
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Journey Scan CLI Command
|
|
3
|
+
* Entry point for running human journey scans from command line
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { JourneyScanner } = require('./journey-scanner');
|
|
9
|
+
const { getJourneyDefinition } = require('./journey-definitions');
|
|
10
|
+
const { HumanReporter } = require('./human-reporter');
|
|
11
|
+
|
|
12
|
+
async function runJourneyScanCLI(config) {
|
|
13
|
+
const {
|
|
14
|
+
baseUrl,
|
|
15
|
+
preset = 'saas',
|
|
16
|
+
artifactsDir = './.odavlguardian',
|
|
17
|
+
headless = true,
|
|
18
|
+
timeout = 20000,
|
|
19
|
+
presetProvided = false
|
|
20
|
+
} = config;
|
|
21
|
+
|
|
22
|
+
// Set a process timeout to ensure we don't hang forever
|
|
23
|
+
const processTimeout = setTimeout(() => {
|
|
24
|
+
console.error('\n❌ Scan timeout: Process took too long (>120s)');
|
|
25
|
+
process.exit(2);
|
|
26
|
+
}, 120000); // 120 second hard limit
|
|
27
|
+
|
|
28
|
+
// Validate URL
|
|
29
|
+
let url;
|
|
30
|
+
try {
|
|
31
|
+
url = new URL(baseUrl);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.error(`❌ Invalid URL: ${baseUrl}`);
|
|
34
|
+
clearTimeout(processTimeout);
|
|
35
|
+
process.exit(2);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log(`\n🛡️ ODAVL Guardian — Human Journey Scanner`);
|
|
39
|
+
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
40
|
+
console.log(`📍 Target URL: ${baseUrl}`);
|
|
41
|
+
console.log(`🎯 Preset: ${presetProvided ? preset : '(auto)'}`);
|
|
42
|
+
console.log(`📁 Output: ${artifactsDir}\n`);
|
|
43
|
+
process.stdout.write(''); // Flush stdout
|
|
44
|
+
|
|
45
|
+
// Create output directory
|
|
46
|
+
try {
|
|
47
|
+
if (!fs.existsSync(artifactsDir)) {
|
|
48
|
+
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const screenshotDir = path.join(artifactsDir, 'screenshots');
|
|
52
|
+
if (!fs.existsSync(screenshotDir)) {
|
|
53
|
+
fs.mkdirSync(screenshotDir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Detect site intent if preset not provided
|
|
57
|
+
let finalPreset = preset;
|
|
58
|
+
let intentDetection = null;
|
|
59
|
+
if (!presetProvided) {
|
|
60
|
+
const { detectIntent } = require('./intent-detector');
|
|
61
|
+
console.log('🔎 Detecting site intent...');
|
|
62
|
+
intentDetection = await detectIntent(baseUrl, { timeout, headless });
|
|
63
|
+
console.log(` Detected: ${intentDetection.intent.toUpperCase()} (confidence ${intentDetection.confidence}%)`);
|
|
64
|
+
if (intentDetection.intent === 'saas') finalPreset = 'saas';
|
|
65
|
+
else if (intentDetection.intent === 'shop') finalPreset = 'shop';
|
|
66
|
+
else if (intentDetection.intent === 'landing') finalPreset = 'landing';
|
|
67
|
+
else finalPreset = 'landing'; // default for unknown
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Get journey definition
|
|
71
|
+
const journey = getJourneyDefinition(finalPreset);
|
|
72
|
+
console.log(`🚀 Starting journey: ${journey.name}`);
|
|
73
|
+
console.log(` ${journey.description}\n`);
|
|
74
|
+
process.stdout.write(''); // Flush stdout
|
|
75
|
+
|
|
76
|
+
// Run scanner
|
|
77
|
+
const scanner = new JourneyScanner({
|
|
78
|
+
timeout,
|
|
79
|
+
headless,
|
|
80
|
+
screenshotDir
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
console.log('⏳ Executing journey steps...\n');
|
|
84
|
+
process.stdout.write(''); // Flush stdout
|
|
85
|
+
|
|
86
|
+
const result = await scanner.scan(baseUrl, journey);
|
|
87
|
+
if (intentDetection) {
|
|
88
|
+
result.intentDetection = intentDetection;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Generate reports
|
|
92
|
+
const reporter = new HumanReporter();
|
|
93
|
+
const humanReport = reporter.generateSummary(result, artifactsDir);
|
|
94
|
+
const jsonReport = reporter.generateJSON(result, artifactsDir);
|
|
95
|
+
|
|
96
|
+
// Print summary to console
|
|
97
|
+
console.log('\n' + humanReport.content);
|
|
98
|
+
process.stdout.write(''); // Flush stdout
|
|
99
|
+
|
|
100
|
+
// Determine canonical verdict and exit code
|
|
101
|
+
const { toCanonicalJourneyVerdict, mapExitCodeFromCanonical } = require('./verdicts');
|
|
102
|
+
const canonical = toCanonicalJourneyVerdict(result.finalDecision);
|
|
103
|
+
const exitCode = mapExitCodeFromCanonical(canonical);
|
|
104
|
+
|
|
105
|
+
// Save results metadata
|
|
106
|
+
const metadata = {
|
|
107
|
+
timestamp: new Date().toISOString(),
|
|
108
|
+
url: baseUrl,
|
|
109
|
+
preset: finalPreset,
|
|
110
|
+
decision: result.finalDecision,
|
|
111
|
+
decisionCanonical: canonical,
|
|
112
|
+
executedSteps: result.executedSteps.length,
|
|
113
|
+
failedSteps: result.failedSteps.length
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
fs.writeFileSync(
|
|
117
|
+
path.join(artifactsDir, 'metadata.json'),
|
|
118
|
+
JSON.stringify(metadata, null, 2),
|
|
119
|
+
'utf8'
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Exit with canonical-mapped code
|
|
123
|
+
|
|
124
|
+
console.log(`📁 Reports saved to: ${artifactsDir}`);
|
|
125
|
+
console.log(` - summary.txt (human readable)`);
|
|
126
|
+
console.log(` - summary.md (markdown)`);
|
|
127
|
+
console.log(` - report.json (full results)`);
|
|
128
|
+
console.log(` - screenshots/ (evidence images)`);
|
|
129
|
+
process.stdout.write(''); // Flush stdout
|
|
130
|
+
|
|
131
|
+
clearTimeout(processTimeout);
|
|
132
|
+
process.exit(exitCode);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.error(`\n❌ Scan failed: ${err.message}`);
|
|
135
|
+
if (process.env.DEBUG) {
|
|
136
|
+
console.error(err.stack);
|
|
137
|
+
}
|
|
138
|
+
clearTimeout(processTimeout);
|
|
139
|
+
process.exit(2);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = { runJourneyScanCLI };
|
|
144
|
+
|
|
145
|
+
module.exports = { runJourneyScanCLI };
|