@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.
Files changed (172) hide show
  1. package/CHANGELOG.md +210 -210
  2. package/LICENSE +21 -21
  3. package/README.md +297 -184
  4. package/bin/guardian.js +2242 -2221
  5. package/config/README.md +59 -59
  6. package/config/guardian.config.json +54 -54
  7. package/config/guardian.policy.json +12 -12
  8. package/config/profiles/docs.yaml +18 -18
  9. package/config/profiles/ecommerce.yaml +17 -17
  10. package/config/profiles/landing-demo.yaml +16 -16
  11. package/config/profiles/marketing.yaml +18 -18
  12. package/config/profiles/saas.yaml +21 -21
  13. package/flows/example-login-flow.json +36 -36
  14. package/flows/example-signup-flow.json +44 -44
  15. package/package.json +124 -116
  16. package/policies/enterprise.json +12 -12
  17. package/policies/landing-demo.json +22 -22
  18. package/policies/saas.json +12 -12
  19. package/policies/startup.json +12 -12
  20. package/src/enterprise/audit-logger.js +166 -166
  21. package/src/enterprise/pdf-exporter.js +267 -267
  22. package/src/enterprise/rbac-gate.js +142 -142
  23. package/src/enterprise/rbac.js +239 -239
  24. package/src/enterprise/site-manager.js +180 -180
  25. package/src/founder/feedback-system.js +156 -156
  26. package/src/founder/founder-tracker.js +213 -213
  27. package/src/founder/usage-signals.js +141 -141
  28. package/src/guardian/action-hints.js +439 -439
  29. package/src/guardian/alert-ledger.js +121 -121
  30. package/src/guardian/artifact-sanitizer.js +56 -56
  31. package/src/guardian/attempt-engine.js +1069 -1029
  32. package/src/guardian/attempt-registry.js +267 -267
  33. package/src/guardian/attempt-relevance.js +106 -106
  34. package/src/guardian/attempt-reporter.js +513 -507
  35. package/src/guardian/attempt.js +274 -273
  36. package/src/guardian/attempts-filter.js +63 -63
  37. package/src/guardian/auto-attempt-builder.js +283 -283
  38. package/src/guardian/baseline-registry.js +177 -177
  39. package/src/guardian/baseline-reporter.js +143 -143
  40. package/src/guardian/baseline-storage.js +285 -285
  41. package/src/guardian/baseline.js +535 -534
  42. package/src/guardian/behavioral-signals.js +261 -261
  43. package/src/guardian/breakage-intelligence.js +224 -224
  44. package/src/guardian/browser-pool.js +131 -131
  45. package/src/guardian/browser.js +119 -119
  46. package/src/guardian/canonical-truth.js +308 -308
  47. package/src/guardian/ci-cli.js +121 -121
  48. package/src/guardian/ci-gate.js +96 -96
  49. package/src/guardian/ci-mode.js +15 -15
  50. package/src/guardian/ci-output.js +55 -38
  51. package/src/guardian/cli-summary.js +102 -102
  52. package/src/guardian/confidence-signals.js +251 -251
  53. package/src/guardian/config-loader.js +161 -161
  54. package/src/guardian/config-validator.js +285 -283
  55. package/src/guardian/coverage-model.js +239 -239
  56. package/src/guardian/coverage-packs.js +58 -58
  57. package/src/guardian/crawler.js +142 -142
  58. package/src/guardian/data-guardian-detector.js +189 -189
  59. package/src/guardian/decision-authority.js +746 -725
  60. package/src/guardian/detection-layers.js +271 -271
  61. package/src/guardian/determinism.js +146 -146
  62. package/src/guardian/discovery-engine.js +661 -661
  63. package/src/guardian/drift-detector.js +100 -100
  64. package/src/guardian/enhanced-html-reporter.js +522 -522
  65. package/src/guardian/env-guard.js +128 -127
  66. package/src/guardian/error-clarity.js +399 -399
  67. package/src/guardian/export-contract.js +196 -196
  68. package/src/guardian/fail-safe.js +212 -212
  69. package/src/guardian/failure-intelligence.js +173 -173
  70. package/src/guardian/failure-taxonomy.js +169 -169
  71. package/src/guardian/final-outcome.js +206 -206
  72. package/src/guardian/first-run-profile.js +89 -89
  73. package/src/guardian/first-run.js +65 -67
  74. package/src/guardian/flag-validator.js +111 -111
  75. package/src/guardian/flow-executor.js +641 -639
  76. package/src/guardian/flow-registry.js +67 -67
  77. package/src/guardian/honesty.js +394 -394
  78. package/src/guardian/html-reporter.js +416 -416
  79. package/src/guardian/human-intent-resolver.js +296 -296
  80. package/src/guardian/human-interaction-model.js +351 -351
  81. package/src/guardian/human-journey-context.js +184 -184
  82. package/src/guardian/human-navigator.js +544 -544
  83. package/src/guardian/human-reporter.js +435 -431
  84. package/src/guardian/index.js +226 -221
  85. package/src/guardian/init-command.js +143 -143
  86. package/src/guardian/intent-detector.js +148 -146
  87. package/src/guardian/journey-definitions.js +132 -132
  88. package/src/guardian/journey-scan-cli.js +142 -145
  89. package/src/guardian/journey-scanner.js +583 -583
  90. package/src/guardian/junit-reporter.js +281 -281
  91. package/src/guardian/language-detection.js +99 -99
  92. package/src/guardian/live-alert.js +56 -56
  93. package/src/guardian/live-baseline-compare.js +146 -146
  94. package/src/guardian/live-cli.js +95 -95
  95. package/src/guardian/live-guardian.js +210 -210
  96. package/src/guardian/live-scheduler-runner.js +137 -137
  97. package/src/guardian/live-scheduler-state.js +167 -168
  98. package/src/guardian/live-scheduler.js +146 -146
  99. package/src/guardian/live-state.js +110 -110
  100. package/src/guardian/market-criticality.js +335 -335
  101. package/src/guardian/market-reporter.js +577 -577
  102. package/src/guardian/network-trace.js +178 -178
  103. package/src/guardian/obs-logger.js +110 -110
  104. package/src/guardian/observed-capabilities.js +427 -427
  105. package/src/guardian/output-contract.js +154 -0
  106. package/src/guardian/output-readability.js +264 -264
  107. package/src/guardian/parallel-executor.js +116 -116
  108. package/src/guardian/path-safety.js +56 -56
  109. package/src/guardian/pattern-analyzer.js +348 -348
  110. package/src/guardian/policy.js +432 -434
  111. package/src/guardian/prelaunch-gate.js +193 -193
  112. package/src/guardian/prerequisite-checker.js +101 -101
  113. package/src/guardian/preset-loader.js +152 -157
  114. package/src/guardian/profile-loader.js +96 -96
  115. package/src/guardian/reality.js +3025 -2826
  116. package/src/guardian/realworld-scenarios.js +94 -94
  117. package/src/guardian/reporter.js +167 -167
  118. package/src/guardian/retry-policy.js +123 -123
  119. package/src/guardian/root-cause-analysis.js +171 -171
  120. package/src/guardian/rules-engine.js +558 -558
  121. package/src/guardian/run-artifacts.js +212 -212
  122. package/src/guardian/run-cleanup.js +207 -207
  123. package/src/guardian/run-export.js +522 -522
  124. package/src/guardian/run-latest.js +90 -90
  125. package/src/guardian/run-list.js +211 -211
  126. package/src/guardian/run-summary.js +20 -20
  127. package/src/guardian/runtime-root.js +246 -246
  128. package/src/guardian/safety.js +248 -248
  129. package/src/guardian/scan-presets.js +133 -149
  130. package/src/guardian/screenshot.js +152 -152
  131. package/src/guardian/secret-hygiene.js +44 -44
  132. package/src/guardian/selector-fallbacks.js +394 -394
  133. package/src/guardian/semantic-contact-detection.js +255 -255
  134. package/src/guardian/semantic-contact-finder.js +201 -201
  135. package/src/guardian/semantic-targets.js +234 -234
  136. package/src/guardian/site-intelligence.js +588 -588
  137. package/src/guardian/site-introspection.js +257 -257
  138. package/src/guardian/sitemap.js +225 -225
  139. package/src/guardian/smoke.js +283 -258
  140. package/src/guardian/snapshot-schema.js +177 -290
  141. package/src/guardian/snapshot.js +430 -397
  142. package/src/guardian/stability-scorer.js +169 -169
  143. package/src/guardian/success-evaluator.js +214 -214
  144. package/src/guardian/template-command.js +184 -184
  145. package/src/guardian/text-formatters.js +426 -426
  146. package/src/guardian/timeout-profiles.js +57 -57
  147. package/src/guardian/truth/attempt.contract.js +158 -0
  148. package/src/guardian/truth/decision.contract.js +275 -0
  149. package/src/guardian/truth/snapshot.contract.js +363 -0
  150. package/src/guardian/validators.js +323 -323
  151. package/src/guardian/verdict-card.js +474 -474
  152. package/src/guardian/verdict-clarity.js +298 -298
  153. package/src/guardian/verdict-policy.js +363 -363
  154. package/src/guardian/verdict.js +333 -333
  155. package/src/guardian/verdicts.js +79 -74
  156. package/src/guardian/visual-diff.js +247 -247
  157. package/src/guardian/wait-for-outcome.js +119 -119
  158. package/src/guardian/watch-runner.js +181 -181
  159. package/src/guardian/watchdog-diff.js +167 -167
  160. package/src/guardian/webhook.js +206 -206
  161. package/src/payments/stripe-checkout.js +169 -169
  162. package/src/plans/plan-definitions.js +148 -148
  163. package/src/plans/plan-manager.js +211 -211
  164. package/src/plans/usage-tracker.js +210 -210
  165. package/src/recipes/recipe-engine.js +188 -188
  166. package/src/recipes/recipe-failure-analysis.js +159 -159
  167. package/src/recipes/recipe-registry.js +134 -134
  168. package/src/recipes/recipe-runtime.js +507 -507
  169. package/src/recipes/recipe-store.js +410 -410
  170. package/SECURITY.md +0 -77
  171. package/VERSIONING.md +0 -100
  172. 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
+ };