@odavl/guardian 2.0.0 → 2.0.1
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 +210 -210
- package/LICENSE +21 -21
- package/README.md +297 -184
- package/bin/guardian.js +2242 -2221
- package/config/README.md +59 -59
- package/config/guardian.config.json +54 -54
- package/config/guardian.policy.json +12 -12
- package/config/profiles/docs.yaml +18 -18
- package/config/profiles/ecommerce.yaml +17 -17
- package/config/profiles/landing-demo.yaml +16 -16
- package/config/profiles/marketing.yaml +18 -18
- package/config/profiles/saas.yaml +21 -21
- package/flows/example-login-flow.json +36 -36
- package/flows/example-signup-flow.json +44 -44
- package/package.json +124 -116
- package/policies/enterprise.json +12 -12
- package/policies/landing-demo.json +22 -22
- package/policies/saas.json +12 -12
- package/policies/startup.json +12 -12
- package/src/enterprise/audit-logger.js +166 -166
- package/src/enterprise/pdf-exporter.js +267 -267
- package/src/enterprise/rbac-gate.js +142 -142
- package/src/enterprise/rbac.js +239 -239
- package/src/enterprise/site-manager.js +180 -180
- package/src/founder/feedback-system.js +156 -156
- package/src/founder/founder-tracker.js +213 -213
- package/src/founder/usage-signals.js +141 -141
- package/src/guardian/action-hints.js +439 -439
- package/src/guardian/alert-ledger.js +121 -121
- package/src/guardian/artifact-sanitizer.js +56 -56
- package/src/guardian/attempt-engine.js +1069 -1029
- package/src/guardian/attempt-registry.js +267 -267
- package/src/guardian/attempt-relevance.js +106 -106
- package/src/guardian/attempt-reporter.js +513 -507
- package/src/guardian/attempt.js +274 -273
- package/src/guardian/attempts-filter.js +63 -63
- package/src/guardian/auto-attempt-builder.js +283 -283
- package/src/guardian/baseline-registry.js +177 -177
- package/src/guardian/baseline-reporter.js +143 -143
- package/src/guardian/baseline-storage.js +285 -285
- package/src/guardian/baseline.js +535 -534
- package/src/guardian/behavioral-signals.js +261 -261
- package/src/guardian/breakage-intelligence.js +224 -224
- package/src/guardian/browser-pool.js +131 -131
- package/src/guardian/browser.js +119 -119
- package/src/guardian/canonical-truth.js +308 -308
- package/src/guardian/ci-cli.js +121 -121
- package/src/guardian/ci-gate.js +96 -96
- package/src/guardian/ci-mode.js +15 -15
- package/src/guardian/ci-output.js +55 -38
- package/src/guardian/cli-summary.js +102 -102
- package/src/guardian/confidence-signals.js +251 -251
- package/src/guardian/config-loader.js +161 -161
- package/src/guardian/config-validator.js +285 -283
- package/src/guardian/coverage-model.js +239 -239
- package/src/guardian/coverage-packs.js +58 -58
- package/src/guardian/crawler.js +142 -142
- package/src/guardian/data-guardian-detector.js +189 -189
- package/src/guardian/decision-authority.js +746 -725
- package/src/guardian/detection-layers.js +271 -271
- package/src/guardian/determinism.js +146 -146
- package/src/guardian/discovery-engine.js +661 -661
- package/src/guardian/drift-detector.js +100 -100
- package/src/guardian/enhanced-html-reporter.js +522 -522
- package/src/guardian/env-guard.js +128 -127
- package/src/guardian/error-clarity.js +399 -399
- package/src/guardian/export-contract.js +196 -196
- package/src/guardian/fail-safe.js +212 -212
- package/src/guardian/failure-intelligence.js +173 -173
- package/src/guardian/failure-taxonomy.js +169 -169
- package/src/guardian/final-outcome.js +206 -206
- package/src/guardian/first-run-profile.js +89 -89
- package/src/guardian/first-run.js +65 -67
- package/src/guardian/flag-validator.js +111 -111
- package/src/guardian/flow-executor.js +641 -639
- package/src/guardian/flow-registry.js +67 -67
- package/src/guardian/honesty.js +394 -394
- package/src/guardian/html-reporter.js +416 -416
- package/src/guardian/human-intent-resolver.js +296 -296
- package/src/guardian/human-interaction-model.js +351 -351
- package/src/guardian/human-journey-context.js +184 -184
- package/src/guardian/human-navigator.js +544 -544
- package/src/guardian/human-reporter.js +435 -431
- package/src/guardian/index.js +226 -221
- package/src/guardian/init-command.js +143 -143
- package/src/guardian/intent-detector.js +148 -146
- package/src/guardian/journey-definitions.js +132 -132
- package/src/guardian/journey-scan-cli.js +142 -145
- package/src/guardian/journey-scanner.js +583 -583
- package/src/guardian/junit-reporter.js +281 -281
- package/src/guardian/language-detection.js +99 -99
- package/src/guardian/live-alert.js +56 -56
- package/src/guardian/live-baseline-compare.js +146 -146
- package/src/guardian/live-cli.js +95 -95
- package/src/guardian/live-guardian.js +210 -210
- package/src/guardian/live-scheduler-runner.js +137 -137
- package/src/guardian/live-scheduler-state.js +167 -168
- package/src/guardian/live-scheduler.js +146 -146
- package/src/guardian/live-state.js +110 -110
- package/src/guardian/market-criticality.js +335 -335
- package/src/guardian/market-reporter.js +577 -577
- package/src/guardian/network-trace.js +178 -178
- package/src/guardian/obs-logger.js +110 -110
- package/src/guardian/observed-capabilities.js +427 -427
- package/src/guardian/output-contract.js +154 -0
- package/src/guardian/output-readability.js +264 -264
- package/src/guardian/parallel-executor.js +116 -116
- package/src/guardian/path-safety.js +56 -56
- package/src/guardian/pattern-analyzer.js +348 -348
- package/src/guardian/policy.js +432 -434
- package/src/guardian/prelaunch-gate.js +193 -193
- package/src/guardian/prerequisite-checker.js +101 -101
- package/src/guardian/preset-loader.js +152 -157
- package/src/guardian/profile-loader.js +96 -96
- package/src/guardian/reality.js +3025 -2826
- package/src/guardian/realworld-scenarios.js +94 -94
- package/src/guardian/reporter.js +167 -167
- package/src/guardian/retry-policy.js +123 -123
- package/src/guardian/root-cause-analysis.js +171 -171
- package/src/guardian/rules-engine.js +558 -558
- package/src/guardian/run-artifacts.js +212 -212
- package/src/guardian/run-cleanup.js +207 -207
- package/src/guardian/run-export.js +522 -522
- package/src/guardian/run-latest.js +90 -90
- package/src/guardian/run-list.js +211 -211
- package/src/guardian/run-summary.js +20 -20
- package/src/guardian/runtime-root.js +246 -246
- package/src/guardian/safety.js +248 -248
- package/src/guardian/scan-presets.js +133 -149
- package/src/guardian/screenshot.js +152 -152
- package/src/guardian/secret-hygiene.js +44 -44
- package/src/guardian/selector-fallbacks.js +394 -394
- package/src/guardian/semantic-contact-detection.js +255 -255
- package/src/guardian/semantic-contact-finder.js +201 -201
- package/src/guardian/semantic-targets.js +234 -234
- package/src/guardian/site-intelligence.js +588 -588
- package/src/guardian/site-introspection.js +257 -257
- package/src/guardian/sitemap.js +225 -225
- package/src/guardian/smoke.js +283 -258
- package/src/guardian/snapshot-schema.js +177 -290
- package/src/guardian/snapshot.js +430 -397
- package/src/guardian/stability-scorer.js +169 -169
- package/src/guardian/success-evaluator.js +214 -214
- package/src/guardian/template-command.js +184 -184
- package/src/guardian/text-formatters.js +426 -426
- package/src/guardian/timeout-profiles.js +57 -57
- package/src/guardian/truth/attempt.contract.js +158 -0
- package/src/guardian/truth/decision.contract.js +275 -0
- package/src/guardian/truth/snapshot.contract.js +363 -0
- package/src/guardian/validators.js +323 -323
- package/src/guardian/verdict-card.js +474 -474
- package/src/guardian/verdict-clarity.js +298 -298
- package/src/guardian/verdict-policy.js +363 -363
- package/src/guardian/verdict.js +333 -333
- package/src/guardian/verdicts.js +79 -74
- package/src/guardian/visual-diff.js +247 -247
- package/src/guardian/wait-for-outcome.js +119 -119
- package/src/guardian/watch-runner.js +181 -181
- package/src/guardian/watchdog-diff.js +167 -167
- package/src/guardian/webhook.js +206 -206
- package/src/payments/stripe-checkout.js +169 -169
- package/src/plans/plan-definitions.js +148 -148
- package/src/plans/plan-manager.js +211 -211
- package/src/plans/usage-tracker.js +210 -210
- package/src/recipes/recipe-engine.js +188 -188
- package/src/recipes/recipe-failure-analysis.js +159 -159
- package/src/recipes/recipe-registry.js +134 -134
- package/src/recipes/recipe-runtime.js +507 -507
- package/src/recipes/recipe-store.js +410 -410
- package/SECURITY.md +0 -77
- package/VERSIONING.md +0 -100
- package/guardian-contract-v1.md +0 -502
|
@@ -1,296 +1,296 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Human Intent Resolver
|
|
3
|
-
*
|
|
4
|
-
* Determines what a real human visitor would realistically try to do on this site.
|
|
5
|
-
* This layer ensures Guardian only executes attempts that align with genuine human behavior.
|
|
6
|
-
*
|
|
7
|
-
* Core principle:
|
|
8
|
-
* - A human visits a site with a PRIMARY GOAL based on what they see
|
|
9
|
-
* - They will NOT attempt actions unrelated to that goal
|
|
10
|
-
* - Attempts that don't match the human goal should be marked NOT_APPLICABLE
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Human goal types (what a visitor would actually try to do)
|
|
15
|
-
*/
|
|
16
|
-
const HUMAN_GOALS = {
|
|
17
|
-
BUY: 'BUY', // Purchase products (ecommerce)
|
|
18
|
-
SIGN_UP: 'SIGN_UP', // Create account / subscribe (SaaS, apps)
|
|
19
|
-
READ: 'READ', // Read content (docs, blogs, news)
|
|
20
|
-
CONTACT: 'CONTACT', // Contact business (marketing sites)
|
|
21
|
-
USE_SERVICE: 'USE_SERVICE', // Use an existing account (login-first apps)
|
|
22
|
-
EXPLORE: 'EXPLORE' // Unclear intent (fallback)
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Mapping: which attempts align with which human goals
|
|
27
|
-
* If an attempt is not listed for a goal, it's FORBIDDEN for that goal
|
|
28
|
-
*/
|
|
29
|
-
const GOAL_TO_VALID_ATTEMPTS = {
|
|
30
|
-
[HUMAN_GOALS.BUY]: [
|
|
31
|
-
'site_smoke',
|
|
32
|
-
'primary_ctas',
|
|
33
|
-
'checkout',
|
|
34
|
-
'language_switch',
|
|
35
|
-
'universal_reality',
|
|
36
|
-
'contact_discovery_v2',
|
|
37
|
-
'signup', // Some shops require account
|
|
38
|
-
'login' // Returning customers
|
|
39
|
-
],
|
|
40
|
-
|
|
41
|
-
[HUMAN_GOALS.SIGN_UP]: [
|
|
42
|
-
'site_smoke',
|
|
43
|
-
'primary_ctas',
|
|
44
|
-
'signup',
|
|
45
|
-
'login',
|
|
46
|
-
'language_switch',
|
|
47
|
-
'universal_reality',
|
|
48
|
-
'contact_discovery_v2',
|
|
49
|
-
'newsletter_signup'
|
|
50
|
-
],
|
|
51
|
-
|
|
52
|
-
[HUMAN_GOALS.READ]: [
|
|
53
|
-
'site_smoke',
|
|
54
|
-
'primary_ctas',
|
|
55
|
-
'language_switch',
|
|
56
|
-
'universal_reality',
|
|
57
|
-
'contact_discovery_v2',
|
|
58
|
-
'contact_form',
|
|
59
|
-
'newsletter_signup'
|
|
60
|
-
],
|
|
61
|
-
|
|
62
|
-
[HUMAN_GOALS.CONTACT]: [
|
|
63
|
-
'site_smoke',
|
|
64
|
-
'primary_ctas',
|
|
65
|
-
'contact_form',
|
|
66
|
-
'contact_discovery_v2',
|
|
67
|
-
'language_switch',
|
|
68
|
-
'universal_reality',
|
|
69
|
-
'newsletter_signup'
|
|
70
|
-
],
|
|
71
|
-
|
|
72
|
-
[HUMAN_GOALS.USE_SERVICE]: [
|
|
73
|
-
'site_smoke',
|
|
74
|
-
'login',
|
|
75
|
-
'signup', // First time users
|
|
76
|
-
'language_switch',
|
|
77
|
-
'universal_reality',
|
|
78
|
-
'contact_discovery_v2'
|
|
79
|
-
],
|
|
80
|
-
|
|
81
|
-
[HUMAN_GOALS.EXPLORE]: [
|
|
82
|
-
// When intent is unclear, allow most attempts (conservative)
|
|
83
|
-
'site_smoke',
|
|
84
|
-
'primary_ctas',
|
|
85
|
-
'contact_discovery_v2',
|
|
86
|
-
'universal_reality',
|
|
87
|
-
'login',
|
|
88
|
-
'signup',
|
|
89
|
-
'checkout',
|
|
90
|
-
'contact_form',
|
|
91
|
-
'language_switch',
|
|
92
|
-
'newsletter_signup'
|
|
93
|
-
]
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Resolve human intent based on site profile and detected capabilities
|
|
98
|
-
*
|
|
99
|
-
* @param {Object} params
|
|
100
|
-
* @param {string} params.siteProfile - 'ecommerce', 'saas', 'content', 'unknown'
|
|
101
|
-
* @param {Object} params.introspection - Site capabilities from inspectSite()
|
|
102
|
-
* @param {string} params.entryUrl - The URL being tested
|
|
103
|
-
* @returns {Object} Intent resolution result
|
|
104
|
-
*/
|
|
105
|
-
function resolveHumanIntent({ siteProfile, introspection, entryUrl }) {
|
|
106
|
-
let primaryGoal = HUMAN_GOALS.EXPLORE;
|
|
107
|
-
const secondaryGoals = [];
|
|
108
|
-
let confidence = 0;
|
|
109
|
-
let reasoning = '';
|
|
110
|
-
|
|
111
|
-
// Rule 1: Ecommerce sites → primary goal is BUY
|
|
112
|
-
if (siteProfile === 'ecommerce' || introspection.hasCheckout) {
|
|
113
|
-
primaryGoal = HUMAN_GOALS.BUY;
|
|
114
|
-
confidence = 0.95;
|
|
115
|
-
reasoning = 'Site has checkout/cart capabilities → human intent is to BUY products';
|
|
116
|
-
|
|
117
|
-
// Secondary: might also sign up or contact support
|
|
118
|
-
if (introspection.hasSignup) secondaryGoals.push(HUMAN_GOALS.SIGN_UP);
|
|
119
|
-
if (introspection.hasContactForm) secondaryGoals.push(HUMAN_GOALS.CONTACT);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Rule 2: SaaS with signup (no checkout) → primary goal is SIGN_UP
|
|
123
|
-
else if (siteProfile === 'saas' && (introspection.hasSignup || introspection.hasLogin)) {
|
|
124
|
-
// Distinguish between signup-first vs login-first
|
|
125
|
-
if (introspection.hasSignup || (!introspection.hasLogin && introspection.hasSignup)) {
|
|
126
|
-
primaryGoal = HUMAN_GOALS.SIGN_UP;
|
|
127
|
-
confidence = 0.9;
|
|
128
|
-
reasoning = 'Site has signup capability → human intent is to SIGN_UP for service';
|
|
129
|
-
} else if (introspection.hasLogin && !introspection.hasSignup) {
|
|
130
|
-
primaryGoal = HUMAN_GOALS.USE_SERVICE;
|
|
131
|
-
confidence = 0.85;
|
|
132
|
-
reasoning = 'Site requires login (no visible signup) → human intent is to USE_SERVICE';
|
|
133
|
-
} else {
|
|
134
|
-
primaryGoal = HUMAN_GOALS.SIGN_UP;
|
|
135
|
-
confidence = 0.8;
|
|
136
|
-
reasoning = 'SaaS site with auth → human intent is to SIGN_UP';
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (introspection.hasContactForm) secondaryGoals.push(HUMAN_GOALS.CONTACT);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Rule 3: Content sites → primary goal is READ
|
|
143
|
-
else if (siteProfile === 'content' || introspection.hasContentSignals) {
|
|
144
|
-
primaryGoal = HUMAN_GOALS.READ;
|
|
145
|
-
confidence = 0.85;
|
|
146
|
-
reasoning = 'Content-focused site → human intent is to READ articles/documentation';
|
|
147
|
-
|
|
148
|
-
if (introspection.hasContactForm) secondaryGoals.push(HUMAN_GOALS.CONTACT);
|
|
149
|
-
// Note: Newsletter on content sites doesn't mean full account signup - handled in valid attempts
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Rule 4: Marketing/landing pages (contact form but no other strong signals)
|
|
153
|
-
else if (introspection.hasContactForm && !introspection.hasLogin && !introspection.hasSignup && !introspection.hasCheckout) {
|
|
154
|
-
primaryGoal = HUMAN_GOALS.CONTACT;
|
|
155
|
-
confidence = 0.8;
|
|
156
|
-
reasoning = 'Marketing site with contact form → human intent is to CONTACT business';
|
|
157
|
-
|
|
158
|
-
if (introspection.hasNewsletter) secondaryGoals.push(HUMAN_GOALS.SIGN_UP);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Rule 5: Login-only sites (no signup visible)
|
|
162
|
-
else if (introspection.hasLogin && !introspection.hasSignup && !introspection.hasCheckout) {
|
|
163
|
-
primaryGoal = HUMAN_GOALS.USE_SERVICE;
|
|
164
|
-
confidence = 0.75;
|
|
165
|
-
reasoning = 'Login-only site → human intent is to USE_SERVICE with existing account';
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Rule 6: Unknown/ambiguous → EXPLORE (allow most things)
|
|
169
|
-
else {
|
|
170
|
-
primaryGoal = HUMAN_GOALS.EXPLORE;
|
|
171
|
-
confidence = 0.3;
|
|
172
|
-
reasoning = 'Site intent unclear → human may EXPLORE various capabilities';
|
|
173
|
-
|
|
174
|
-
// For unknown sites, mark common actions as secondary
|
|
175
|
-
if (introspection.hasContactForm) secondaryGoals.push(HUMAN_GOALS.CONTACT);
|
|
176
|
-
if (introspection.hasSignup) secondaryGoals.push(HUMAN_GOALS.SIGN_UP);
|
|
177
|
-
if (introspection.hasCheckout) secondaryGoals.push(HUMAN_GOALS.BUY);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return {
|
|
181
|
-
primaryGoal,
|
|
182
|
-
secondaryGoals,
|
|
183
|
-
confidence,
|
|
184
|
-
reasoning,
|
|
185
|
-
siteProfile
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Determine if an attempt should be executed based on human intent
|
|
191
|
-
*
|
|
192
|
-
* @param {string} attemptId - The attempt to check
|
|
193
|
-
* @param {Object} intentResolution - Result from resolveHumanIntent()
|
|
194
|
-
* @returns {Object} Decision { shouldExecute: boolean, reason: string }
|
|
195
|
-
*/
|
|
196
|
-
function shouldExecuteAttempt(attemptId, intentResolution) {
|
|
197
|
-
const { primaryGoal, secondaryGoals } = intentResolution;
|
|
198
|
-
|
|
199
|
-
// Check if attempt is valid for primary goal
|
|
200
|
-
const validForPrimary = GOAL_TO_VALID_ATTEMPTS[primaryGoal]?.includes(attemptId);
|
|
201
|
-
|
|
202
|
-
if (validForPrimary) {
|
|
203
|
-
return {
|
|
204
|
-
shouldExecute: true,
|
|
205
|
-
reason: `Aligns with human goal: ${primaryGoal}`,
|
|
206
|
-
humanReason: getHumanReadableReason(attemptId, primaryGoal, true)
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Check secondary goals
|
|
211
|
-
for (const secondaryGoal of secondaryGoals) {
|
|
212
|
-
const validForSecondary = GOAL_TO_VALID_ATTEMPTS[secondaryGoal]?.includes(attemptId);
|
|
213
|
-
if (validForSecondary) {
|
|
214
|
-
return {
|
|
215
|
-
shouldExecute: true,
|
|
216
|
-
reason: `Aligns with secondary goal: ${secondaryGoal}`,
|
|
217
|
-
humanReason: getHumanReadableReason(attemptId, secondaryGoal, false)
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Not aligned with any goal
|
|
223
|
-
return {
|
|
224
|
-
shouldExecute: false,
|
|
225
|
-
reason: `Not aligned with human intent (primary: ${primaryGoal}, secondary: ${secondaryGoals.join(', ')})`,
|
|
226
|
-
humanReason: getHumanReadableReason(attemptId, primaryGoal, false)
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Generate human-readable explanation for why an attempt should/shouldn't execute
|
|
232
|
-
*/
|
|
233
|
-
function getHumanReadableReason(attemptId, goal, shouldExecute) {
|
|
234
|
-
const attemptLabels = {
|
|
235
|
-
'checkout': 'trying to purchase',
|
|
236
|
-
'login': 'logging in',
|
|
237
|
-
'signup': 'signing up',
|
|
238
|
-
'contact_form': 'contacting the business',
|
|
239
|
-
'newsletter_signup': 'subscribing to newsletter',
|
|
240
|
-
'language_switch': 'switching language',
|
|
241
|
-
'site_smoke': 'checking if site loads',
|
|
242
|
-
'primary_ctas': 'exploring main actions',
|
|
243
|
-
'universal_reality': 'basic site verification',
|
|
244
|
-
'contact_discovery_v2': 'finding contact information'
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
const goalExplanations = {
|
|
248
|
-
[HUMAN_GOALS.BUY]: 'buy something',
|
|
249
|
-
[HUMAN_GOALS.SIGN_UP]: 'sign up for the service',
|
|
250
|
-
[HUMAN_GOALS.READ]: 'read content',
|
|
251
|
-
[HUMAN_GOALS.CONTACT]: 'contact the business',
|
|
252
|
-
[HUMAN_GOALS.USE_SERVICE]: 'use an existing account',
|
|
253
|
-
[HUMAN_GOALS.EXPLORE]: 'explore the site'
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
const attemptLabel = attemptLabels[attemptId] || attemptId;
|
|
257
|
-
const goalLabel = goalExplanations[goal] || goal;
|
|
258
|
-
|
|
259
|
-
if (shouldExecute) {
|
|
260
|
-
return `A visitor wanting to ${goalLabel} would naturally try ${attemptLabel}`;
|
|
261
|
-
} else {
|
|
262
|
-
return `A visitor wanting to ${goalLabel} would NOT try ${attemptLabel}`;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Get list of forbidden attempts for a given intent resolution
|
|
268
|
-
*
|
|
269
|
-
* @param {Array} allAttempts - List of all attempt IDs
|
|
270
|
-
* @param {Object} intentResolution - Result from resolveHumanIntent()
|
|
271
|
-
* @returns {Array} List of {attemptId, reason} for forbidden attempts
|
|
272
|
-
*/
|
|
273
|
-
function getForbiddenAttempts(allAttempts, intentResolution) {
|
|
274
|
-
const forbidden = [];
|
|
275
|
-
|
|
276
|
-
for (const attemptId of allAttempts) {
|
|
277
|
-
const decision = shouldExecuteAttempt(attemptId, intentResolution);
|
|
278
|
-
if (!decision.shouldExecute) {
|
|
279
|
-
forbidden.push({
|
|
280
|
-
attemptId,
|
|
281
|
-
reason: decision.reason,
|
|
282
|
-
humanReason: decision.humanReason
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return forbidden;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
module.exports = {
|
|
291
|
-
HUMAN_GOALS,
|
|
292
|
-
resolveHumanIntent,
|
|
293
|
-
shouldExecuteAttempt,
|
|
294
|
-
getForbiddenAttempts,
|
|
295
|
-
getHumanReadableReason
|
|
296
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Human Intent Resolver
|
|
3
|
+
*
|
|
4
|
+
* Determines what a real human visitor would realistically try to do on this site.
|
|
5
|
+
* This layer ensures Guardian only executes attempts that align with genuine human behavior.
|
|
6
|
+
*
|
|
7
|
+
* Core principle:
|
|
8
|
+
* - A human visits a site with a PRIMARY GOAL based on what they see
|
|
9
|
+
* - They will NOT attempt actions unrelated to that goal
|
|
10
|
+
* - Attempts that don't match the human goal should be marked NOT_APPLICABLE
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Human goal types (what a visitor would actually try to do)
|
|
15
|
+
*/
|
|
16
|
+
const HUMAN_GOALS = {
|
|
17
|
+
BUY: 'BUY', // Purchase products (ecommerce)
|
|
18
|
+
SIGN_UP: 'SIGN_UP', // Create account / subscribe (SaaS, apps)
|
|
19
|
+
READ: 'READ', // Read content (docs, blogs, news)
|
|
20
|
+
CONTACT: 'CONTACT', // Contact business (marketing sites)
|
|
21
|
+
USE_SERVICE: 'USE_SERVICE', // Use an existing account (login-first apps)
|
|
22
|
+
EXPLORE: 'EXPLORE' // Unclear intent (fallback)
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Mapping: which attempts align with which human goals
|
|
27
|
+
* If an attempt is not listed for a goal, it's FORBIDDEN for that goal
|
|
28
|
+
*/
|
|
29
|
+
const GOAL_TO_VALID_ATTEMPTS = {
|
|
30
|
+
[HUMAN_GOALS.BUY]: [
|
|
31
|
+
'site_smoke',
|
|
32
|
+
'primary_ctas',
|
|
33
|
+
'checkout',
|
|
34
|
+
'language_switch',
|
|
35
|
+
'universal_reality',
|
|
36
|
+
'contact_discovery_v2',
|
|
37
|
+
'signup', // Some shops require account
|
|
38
|
+
'login' // Returning customers
|
|
39
|
+
],
|
|
40
|
+
|
|
41
|
+
[HUMAN_GOALS.SIGN_UP]: [
|
|
42
|
+
'site_smoke',
|
|
43
|
+
'primary_ctas',
|
|
44
|
+
'signup',
|
|
45
|
+
'login',
|
|
46
|
+
'language_switch',
|
|
47
|
+
'universal_reality',
|
|
48
|
+
'contact_discovery_v2',
|
|
49
|
+
'newsletter_signup'
|
|
50
|
+
],
|
|
51
|
+
|
|
52
|
+
[HUMAN_GOALS.READ]: [
|
|
53
|
+
'site_smoke',
|
|
54
|
+
'primary_ctas',
|
|
55
|
+
'language_switch',
|
|
56
|
+
'universal_reality',
|
|
57
|
+
'contact_discovery_v2',
|
|
58
|
+
'contact_form',
|
|
59
|
+
'newsletter_signup'
|
|
60
|
+
],
|
|
61
|
+
|
|
62
|
+
[HUMAN_GOALS.CONTACT]: [
|
|
63
|
+
'site_smoke',
|
|
64
|
+
'primary_ctas',
|
|
65
|
+
'contact_form',
|
|
66
|
+
'contact_discovery_v2',
|
|
67
|
+
'language_switch',
|
|
68
|
+
'universal_reality',
|
|
69
|
+
'newsletter_signup'
|
|
70
|
+
],
|
|
71
|
+
|
|
72
|
+
[HUMAN_GOALS.USE_SERVICE]: [
|
|
73
|
+
'site_smoke',
|
|
74
|
+
'login',
|
|
75
|
+
'signup', // First time users
|
|
76
|
+
'language_switch',
|
|
77
|
+
'universal_reality',
|
|
78
|
+
'contact_discovery_v2'
|
|
79
|
+
],
|
|
80
|
+
|
|
81
|
+
[HUMAN_GOALS.EXPLORE]: [
|
|
82
|
+
// When intent is unclear, allow most attempts (conservative)
|
|
83
|
+
'site_smoke',
|
|
84
|
+
'primary_ctas',
|
|
85
|
+
'contact_discovery_v2',
|
|
86
|
+
'universal_reality',
|
|
87
|
+
'login',
|
|
88
|
+
'signup',
|
|
89
|
+
'checkout',
|
|
90
|
+
'contact_form',
|
|
91
|
+
'language_switch',
|
|
92
|
+
'newsletter_signup'
|
|
93
|
+
]
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Resolve human intent based on site profile and detected capabilities
|
|
98
|
+
*
|
|
99
|
+
* @param {Object} params
|
|
100
|
+
* @param {string} params.siteProfile - 'ecommerce', 'saas', 'content', 'unknown'
|
|
101
|
+
* @param {Object} params.introspection - Site capabilities from inspectSite()
|
|
102
|
+
* @param {string} params.entryUrl - The URL being tested
|
|
103
|
+
* @returns {Object} Intent resolution result
|
|
104
|
+
*/
|
|
105
|
+
function resolveHumanIntent({ siteProfile, introspection, entryUrl }) {
|
|
106
|
+
let primaryGoal = HUMAN_GOALS.EXPLORE;
|
|
107
|
+
const secondaryGoals = [];
|
|
108
|
+
let confidence = 0;
|
|
109
|
+
let reasoning = '';
|
|
110
|
+
|
|
111
|
+
// Rule 1: Ecommerce sites → primary goal is BUY
|
|
112
|
+
if (siteProfile === 'ecommerce' || introspection.hasCheckout) {
|
|
113
|
+
primaryGoal = HUMAN_GOALS.BUY;
|
|
114
|
+
confidence = 0.95;
|
|
115
|
+
reasoning = 'Site has checkout/cart capabilities → human intent is to BUY products';
|
|
116
|
+
|
|
117
|
+
// Secondary: might also sign up or contact support
|
|
118
|
+
if (introspection.hasSignup) secondaryGoals.push(HUMAN_GOALS.SIGN_UP);
|
|
119
|
+
if (introspection.hasContactForm) secondaryGoals.push(HUMAN_GOALS.CONTACT);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Rule 2: SaaS with signup (no checkout) → primary goal is SIGN_UP
|
|
123
|
+
else if (siteProfile === 'saas' && (introspection.hasSignup || introspection.hasLogin)) {
|
|
124
|
+
// Distinguish between signup-first vs login-first
|
|
125
|
+
if (introspection.hasSignup || (!introspection.hasLogin && introspection.hasSignup)) {
|
|
126
|
+
primaryGoal = HUMAN_GOALS.SIGN_UP;
|
|
127
|
+
confidence = 0.9;
|
|
128
|
+
reasoning = 'Site has signup capability → human intent is to SIGN_UP for service';
|
|
129
|
+
} else if (introspection.hasLogin && !introspection.hasSignup) {
|
|
130
|
+
primaryGoal = HUMAN_GOALS.USE_SERVICE;
|
|
131
|
+
confidence = 0.85;
|
|
132
|
+
reasoning = 'Site requires login (no visible signup) → human intent is to USE_SERVICE';
|
|
133
|
+
} else {
|
|
134
|
+
primaryGoal = HUMAN_GOALS.SIGN_UP;
|
|
135
|
+
confidence = 0.8;
|
|
136
|
+
reasoning = 'SaaS site with auth → human intent is to SIGN_UP';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (introspection.hasContactForm) secondaryGoals.push(HUMAN_GOALS.CONTACT);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Rule 3: Content sites → primary goal is READ
|
|
143
|
+
else if (siteProfile === 'content' || introspection.hasContentSignals) {
|
|
144
|
+
primaryGoal = HUMAN_GOALS.READ;
|
|
145
|
+
confidence = 0.85;
|
|
146
|
+
reasoning = 'Content-focused site → human intent is to READ articles/documentation';
|
|
147
|
+
|
|
148
|
+
if (introspection.hasContactForm) secondaryGoals.push(HUMAN_GOALS.CONTACT);
|
|
149
|
+
// Note: Newsletter on content sites doesn't mean full account signup - handled in valid attempts
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Rule 4: Marketing/landing pages (contact form but no other strong signals)
|
|
153
|
+
else if (introspection.hasContactForm && !introspection.hasLogin && !introspection.hasSignup && !introspection.hasCheckout) {
|
|
154
|
+
primaryGoal = HUMAN_GOALS.CONTACT;
|
|
155
|
+
confidence = 0.8;
|
|
156
|
+
reasoning = 'Marketing site with contact form → human intent is to CONTACT business';
|
|
157
|
+
|
|
158
|
+
if (introspection.hasNewsletter) secondaryGoals.push(HUMAN_GOALS.SIGN_UP);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Rule 5: Login-only sites (no signup visible)
|
|
162
|
+
else if (introspection.hasLogin && !introspection.hasSignup && !introspection.hasCheckout) {
|
|
163
|
+
primaryGoal = HUMAN_GOALS.USE_SERVICE;
|
|
164
|
+
confidence = 0.75;
|
|
165
|
+
reasoning = 'Login-only site → human intent is to USE_SERVICE with existing account';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Rule 6: Unknown/ambiguous → EXPLORE (allow most things)
|
|
169
|
+
else {
|
|
170
|
+
primaryGoal = HUMAN_GOALS.EXPLORE;
|
|
171
|
+
confidence = 0.3;
|
|
172
|
+
reasoning = 'Site intent unclear → human may EXPLORE various capabilities';
|
|
173
|
+
|
|
174
|
+
// For unknown sites, mark common actions as secondary
|
|
175
|
+
if (introspection.hasContactForm) secondaryGoals.push(HUMAN_GOALS.CONTACT);
|
|
176
|
+
if (introspection.hasSignup) secondaryGoals.push(HUMAN_GOALS.SIGN_UP);
|
|
177
|
+
if (introspection.hasCheckout) secondaryGoals.push(HUMAN_GOALS.BUY);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
primaryGoal,
|
|
182
|
+
secondaryGoals,
|
|
183
|
+
confidence,
|
|
184
|
+
reasoning,
|
|
185
|
+
siteProfile
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Determine if an attempt should be executed based on human intent
|
|
191
|
+
*
|
|
192
|
+
* @param {string} attemptId - The attempt to check
|
|
193
|
+
* @param {Object} intentResolution - Result from resolveHumanIntent()
|
|
194
|
+
* @returns {Object} Decision { shouldExecute: boolean, reason: string }
|
|
195
|
+
*/
|
|
196
|
+
function shouldExecuteAttempt(attemptId, intentResolution) {
|
|
197
|
+
const { primaryGoal, secondaryGoals } = intentResolution;
|
|
198
|
+
|
|
199
|
+
// Check if attempt is valid for primary goal
|
|
200
|
+
const validForPrimary = GOAL_TO_VALID_ATTEMPTS[primaryGoal]?.includes(attemptId);
|
|
201
|
+
|
|
202
|
+
if (validForPrimary) {
|
|
203
|
+
return {
|
|
204
|
+
shouldExecute: true,
|
|
205
|
+
reason: `Aligns with human goal: ${primaryGoal}`,
|
|
206
|
+
humanReason: getHumanReadableReason(attemptId, primaryGoal, true)
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Check secondary goals
|
|
211
|
+
for (const secondaryGoal of secondaryGoals) {
|
|
212
|
+
const validForSecondary = GOAL_TO_VALID_ATTEMPTS[secondaryGoal]?.includes(attemptId);
|
|
213
|
+
if (validForSecondary) {
|
|
214
|
+
return {
|
|
215
|
+
shouldExecute: true,
|
|
216
|
+
reason: `Aligns with secondary goal: ${secondaryGoal}`,
|
|
217
|
+
humanReason: getHumanReadableReason(attemptId, secondaryGoal, false)
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Not aligned with any goal
|
|
223
|
+
return {
|
|
224
|
+
shouldExecute: false,
|
|
225
|
+
reason: `Not aligned with human intent (primary: ${primaryGoal}, secondary: ${secondaryGoals.join(', ')})`,
|
|
226
|
+
humanReason: getHumanReadableReason(attemptId, primaryGoal, false)
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Generate human-readable explanation for why an attempt should/shouldn't execute
|
|
232
|
+
*/
|
|
233
|
+
function getHumanReadableReason(attemptId, goal, shouldExecute) {
|
|
234
|
+
const attemptLabels = {
|
|
235
|
+
'checkout': 'trying to purchase',
|
|
236
|
+
'login': 'logging in',
|
|
237
|
+
'signup': 'signing up',
|
|
238
|
+
'contact_form': 'contacting the business',
|
|
239
|
+
'newsletter_signup': 'subscribing to newsletter',
|
|
240
|
+
'language_switch': 'switching language',
|
|
241
|
+
'site_smoke': 'checking if site loads',
|
|
242
|
+
'primary_ctas': 'exploring main actions',
|
|
243
|
+
'universal_reality': 'basic site verification',
|
|
244
|
+
'contact_discovery_v2': 'finding contact information'
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const goalExplanations = {
|
|
248
|
+
[HUMAN_GOALS.BUY]: 'buy something',
|
|
249
|
+
[HUMAN_GOALS.SIGN_UP]: 'sign up for the service',
|
|
250
|
+
[HUMAN_GOALS.READ]: 'read content',
|
|
251
|
+
[HUMAN_GOALS.CONTACT]: 'contact the business',
|
|
252
|
+
[HUMAN_GOALS.USE_SERVICE]: 'use an existing account',
|
|
253
|
+
[HUMAN_GOALS.EXPLORE]: 'explore the site'
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const attemptLabel = attemptLabels[attemptId] || attemptId;
|
|
257
|
+
const goalLabel = goalExplanations[goal] || goal;
|
|
258
|
+
|
|
259
|
+
if (shouldExecute) {
|
|
260
|
+
return `A visitor wanting to ${goalLabel} would naturally try ${attemptLabel}`;
|
|
261
|
+
} else {
|
|
262
|
+
return `A visitor wanting to ${goalLabel} would NOT try ${attemptLabel}`;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Get list of forbidden attempts for a given intent resolution
|
|
268
|
+
*
|
|
269
|
+
* @param {Array} allAttempts - List of all attempt IDs
|
|
270
|
+
* @param {Object} intentResolution - Result from resolveHumanIntent()
|
|
271
|
+
* @returns {Array} List of {attemptId, reason} for forbidden attempts
|
|
272
|
+
*/
|
|
273
|
+
function getForbiddenAttempts(allAttempts, intentResolution) {
|
|
274
|
+
const forbidden = [];
|
|
275
|
+
|
|
276
|
+
for (const attemptId of allAttempts) {
|
|
277
|
+
const decision = shouldExecuteAttempt(attemptId, intentResolution);
|
|
278
|
+
if (!decision.shouldExecute) {
|
|
279
|
+
forbidden.push({
|
|
280
|
+
attemptId,
|
|
281
|
+
reason: decision.reason,
|
|
282
|
+
humanReason: decision.humanReason
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return forbidden;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
module.exports = {
|
|
291
|
+
HUMAN_GOALS,
|
|
292
|
+
resolveHumanIntent,
|
|
293
|
+
shouldExecuteAttempt,
|
|
294
|
+
getForbiddenAttempts,
|
|
295
|
+
getHumanReadableReason
|
|
296
|
+
};
|