mcp-scraper 0.89.5 → 0.89.8

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 (57) hide show
  1. package/CHANGELOG.md +65 -15
  2. package/README.md +5 -5
  3. package/dist/{analytics-repository-RG7CFK6W.js → analytics-repository-UX6VU22H.js} +1 -1
  4. package/dist/bin/api-server.js +1 -1
  5. package/dist/bin/mcp-scraper-cli.js +1 -1
  6. package/dist/bin/mcp-scraper-core.js +1 -1
  7. package/dist/bin/mcp-scraper-install.js +1 -1
  8. package/dist/bin/mcp-stdio-server.js +1 -1
  9. package/dist/bin/paa-harvest.js +1 -1
  10. package/dist/{chunk-DBDMD7V7.js → chunk-2BS7MMOS.js} +2 -2
  11. package/dist/{chunk-NGQYSQQA.js → chunk-2GRE4BAO.js} +1 -1
  12. package/dist/chunk-2TZBO52D.js +1 -0
  13. package/dist/chunk-4FROKQJN.js +1 -1
  14. package/dist/chunk-52XUWR3X.js +820 -0
  15. package/dist/chunk-5B42QVDC.js +19 -0
  16. package/dist/chunk-7AC66V3T.js +1 -0
  17. package/dist/{chunk-7JAMFXNL.js → chunk-7BEWBRMT.js} +153 -86
  18. package/dist/{chunk-4A5F7IUA.js → chunk-AGK756UE.js} +210 -210
  19. package/dist/chunk-D6BTAT5Y.js +1 -0
  20. package/dist/{chunk-53HIX3X7.js → chunk-DKQZNSYM.js} +15 -15
  21. package/dist/{chunk-MZN4U5BL.js → chunk-E4ME7J6G.js} +1 -1
  22. package/dist/{chunk-A2CZ7WRV.js → chunk-HR2A2WTL.js} +1 -1
  23. package/dist/{chunk-XLWNEVUZ.js → chunk-JHMI6HEO.js} +3 -3
  24. package/dist/{chunk-UB5N2ICD.js → chunk-JSNFZPPB.js} +1 -1
  25. package/dist/{chunk-WYCXAFID.js → chunk-JVU2THSY.js} +1 -1
  26. package/dist/{chunk-SEHBXTMX.js → chunk-KX72IORC.js} +6 -6
  27. package/dist/{chunk-RZSJC7FX.js → chunk-LZTDANNZ.js} +1 -1
  28. package/dist/{chunk-QPWPR5XG.js → chunk-M5QHXNFZ.js} +3 -3
  29. package/dist/chunk-MM6QBDPC.js +1 -0
  30. package/dist/{chunk-3UPA3XHF.js → chunk-PHBU6X5B.js} +1 -1
  31. package/dist/chunk-Q4A2KIPZ.js +41 -0
  32. package/dist/{chunk-4SQ4RKDX.js → chunk-TI3KH3YW.js} +1 -1
  33. package/dist/{chunk-JJDYKM5I.js → chunk-VT6SLBOU.js} +1 -1
  34. package/dist/{chunk-YQZGZBB4.js → chunk-XG6GCEUE.js} +1 -1
  35. package/dist/chunk-Z3WXA4ZU.js +1 -0
  36. package/dist/db-UKQ74OVT.js +1 -0
  37. package/dist/{extract-bundle-RVOBMGU2.js → extract-bundle-DI7OVLT5.js} +5 -5
  38. package/dist/gmail-service-ASX7YBC3.js +1 -0
  39. package/dist/index.cjs +47 -47
  40. package/dist/index.d.cts +14 -14
  41. package/dist/index.d.ts +14 -14
  42. package/dist/index.js +1 -1
  43. package/dist/{lead-list-enrichment-repository-GKVCX7BM.js → lead-list-enrichment-repository-GPDHJVM3.js} +1 -1
  44. package/dist/{location-data-repository-WK27TSHZ.js → location-data-repository-MXUNVDHW.js} +1 -1
  45. package/dist/{server-7TSXB24M.js → server-PQB2EITA.js} +732 -1551
  46. package/dist/{site-extract-repository-FAHOTQGX.js → site-extract-repository-WADCAIYB.js} +1 -1
  47. package/dist/stripe-event-worker-ZPNWXX2T.js +1 -0
  48. package/dist/worker-GTIKQJIN.js +1 -0
  49. package/package.json +137 -17
  50. package/THIRD_PARTY_NOTICES.html +0 -203
  51. package/dist/chunk-2ICI4TIX.js +0 -59
  52. package/dist/chunk-3GP5CYZX.js +0 -1
  53. package/dist/chunk-O2QKRYLS.js +0 -1
  54. package/dist/chunk-SCHTLEMZ.js +0 -1
  55. package/dist/db-DV4HHCYP.js +0 -1
  56. package/dist/gmail-service-FJTBCZPH.js +0 -1
  57. package/dist/worker-ROG7SO3R.js +0 -1
@@ -0,0 +1,820 @@
1
+ import{a as Jt,s as Yt}from"./chunk-5B42QVDC.js";import{T as tn,a as de,b as he,c as lt,d as Qt,e as en,q as f,r as v,sa as G,ta as Ne,v as U,w as J}from"./chunk-HR2A2WTL.js";import{a as Me,e as zt}from"./chunk-2GRE4BAO.js";import{d as Xt,g as Ue,i as Fe,k as ot,m as ct,o as Gt,r as je,t as Zt,u as dt}from"./chunk-D6BTAT5Y.js";import{B as at,F as D,G as Wt,P as Bt,Q as Vt,S as X,U as ce,W as Kt,r as ke,s as Ht,x as qt}from"./chunk-E4ME7J6G.js";import{Ab as Ut,Eb as oe,G as kt,Hb as Ft,Ib as st,Q as $e,Rb as jt,S as Mt,t as ae,ub as M,vb as Pe,xb as nt,yb as it,zb as rt}from"./chunk-7BEWBRMT.js";import{createCipheriv as Ai,createDecipheriv as Ii,randomBytes as Si,scryptSync as hi}from"crypto";import{setScheduleEntitlementTool as Ni}from"mcpscraper-memory-tools/tools/schedule/set-schedule-entitlement";import{createHash as pi,randomUUID as yi}from"crypto";import{exportJWK as gi,importPKCS8 as fi,SignJWT as vi}from"jose";function Ei(e=process.env.OAUTH_ISSUER){let t=e?.trim()||"https://mcpscraper.dev",n=new URL(t);if(n.protocol!=="https:"||n.username||n.password||n.search||n.hash||n.pathname!=="/"&&n.pathname!=="")throw new Error("OAUTH_ISSUER must be an HTTPS origin without credentials, path, query, or fragment");return n.origin}var bi=Ei(),He=null;async function nn(){if(He)return He;let e=process.env.OAUTH_SIGNING_KEY;if(!e?.trim())throw new Error("OAUTH_SIGNING_KEY is not set");let t=e.includes("BEGIN")?e:Buffer.from(e,"base64").toString("utf8"),n=await fi(t,"RS256",{extractable:!0}),i=await gi(n),r={kty:i.kty,n:i.n,e:i.e},s=pi("sha256").update(JSON.stringify({e:r.e,kty:r.kty,n:r.n})).digest("base64url").slice(0,16);return r.kid=s,r.alg="RS256",r.use="sig",He={privateKey:n,publicJwk:r,kid:s},He}async function Is(){return(await nn()).publicJwk}async function rn(e){let{privateKey:t,kid:n}=await nn();return new vi({scope:e.scope,plan:e.plan}).setProtectedHeader({alg:"RS256",kid:n}).setIssuer(bi).setSubject(e.identity).setAudience(e.audience).setIssuedAt().setJti(yi()).setExpirationTime(`${e.ttlSeconds}s`).sign(t)}var Ci=()=>(process.env.MCP_MEMORY_BASE_URL??"https://memory.mcpscraper.dev").replace(/\/$/,""),Ri=()=>(process.env.MCP_MEMORY_RESOURCE_URL??"https://memory.mcpscraper.dev/mcp").replace(/\/$/,""),wi=()=>process.env.MCP_MEMORY_ADMIN_KEY?.trim()??"",Oi=300;function Di(e){let t=e.split(`
2
+ `).find(i=>i.startsWith("data:")),n=t?t.slice(5).trim():e;return JSON.parse(n)}function ut(e){return e?(process.env.MEMORY_OPERATOR_IDENTITIES??"").split(",").map(n=>n.trim().toLowerCase()).filter(Boolean).includes(e.trim().toLowerCase()):!1}function sn(){return hi(Me(),"mcp-memory-key-v1",32)}function Ds(e){let t=Si(12),n=Ai("aes-256-gcm",sn(),t),i=Buffer.concat([n.update(e,"utf8"),n.final()]),r=n.getAuthTag();return`${t.toString("base64")}:${r.toString("base64")}:${i.toString("base64")}`}function Ts(e){try{let[t,n,i]=e.split(":"),r=Ii("aes-256-gcm",sn(),Buffer.from(t,"base64"));return r.setAuthTag(Buffer.from(n,"base64")),Buffer.concat([r.update(Buffer.from(i,"base64")),r.final()]).toString("utf8")}catch{return null}}async function an(e,t,n){try{let i=await fetch(`${Ci()}/mcp`,{method:"POST",headers:{"content-type":"application/json",accept:"application/json, text/event-stream",authorization:`Bearer ${n}`},body:JSON.stringify({jsonrpc:"2.0",id:1,method:"tools/call",params:{name:e,arguments:t}})}),r=await i.text();if(!i.ok)return{ok:!1,error:`memory ${e} failed (${i.status})`};let s=Di(r);if(s.error)return{ok:!1,error:s.error.message??`memory ${e} failed`};if(s.result?.structuredContent)return s.result.structuredContent;let a=s.result?.content?.find(o=>o.type==="text")?.text;if(a)try{return JSON.parse(a)}catch{return{ok:!1,error:a}}return{ok:!1,error:`memory ${e} returned no result`}}catch(i){return{ok:!1,error:i?.message??"memory call failed"}}}function on(e){return e.email}function Ti(e){return on(e)}function xs(e){return ut(e.email)?"unlimited":e.memory_plan==="pro"||e.memory_plan==="team"?e.memory_plan:"free"}async function Ls(e){try{let{key:t}=await cn(e);if(!t)return;(await an("listVaultsTool",{},t)).ok&&await Ft(e.id,!0)}catch{}}function xi(e){return e?!!Wt[e]?.includes_memory:!1}function F(e){return e.memory_plan==="team"?"team":xi(e.subscription_tier)||e.memory_plan==="pro"?"pro":"free"}async function cn(e,t){try{let n=ut(e.email),i=n?"unlimited":t??F(e),r=n?"memory.read memory.write memory.admin":"memory.read memory.write";return{key:await rn({identity:on(e),scope:r,plan:i,audience:Ri(),ttlSeconds:Oi})}}catch(n){return{key:null,error:n instanceof Error?n.message:"memory service authorization unavailable"}}}async function z(e,t){let n=ut(e.email)?"unlimited":t,{key:i,error:r}=await cn(e,n);if(!i)return{ok:!1,error:r??"memory unavailable"};let s=await an("listVaultsTool",{},i);return{ok:s.ok,error:s.error}}async function qe(e){let t=wi();if(!t)return{ok:!1,error:"memory not configured (MCP_MEMORY_ADMIN_KEY missing)"};let n=Ti(e),i=await Ni.execute({apiKey:t,granteeIdentity:n,mcpScraperApiKey:e.api_key},{});return{ok:i.ok,error:i.error}}var dn=["active","trialing","past_due"];function Ce(e){return dn.includes(e)}function ln(e,t){for(let n of dn){let i=e.find(r=>r.status===n&&t(r));if(i)return i}}var Li="price_1TtYPAS8aAcsk3TGHOgrDZiQ";function $i(e=new Date){return`${e.getUTCFullYear()}-${String(e.getUTCMonth()+1).padStart(2,"0")}`}var Pi=new Set(["connected","needs_reauth"]);function ki(e){return Pi.has(String(e.lifecycleStatus??""))}async function Mi(e,t){let n=await ae().execute({sql:"SELECT connection_id FROM connected_account_charges WHERE user_id = ? AND period_start = ?",args:[Number(e),t]});return new Set(n.rows.map(i=>String(i.connection_id)))}async function Ui(e,t,n){await zt();let i=Number(e),r=ae();return await r.batch([{sql:`
3
+ INSERT OR IGNORE INTO connected_account_charge_transfers (
4
+ user_id, period_start, from_connection_id, to_connection_id, provider_config_key
5
+ )
6
+ SELECT ?, ?, previous.id, replacement.id, replacement.provider_config_key
7
+ FROM service_connections AS replacement
8
+ JOIN service_connections AS previous
9
+ ON previous.user_id = replacement.user_id
10
+ AND previous.transport = 'nango'
11
+ AND previous.provider_config_key = replacement.provider_config_key
12
+ AND previous.lifecycle_status = 'disconnected'
13
+ JOIN connected_account_charges AS charge
14
+ ON charge.user_id = previous.user_id
15
+ AND charge.connection_id = previous.id
16
+ AND charge.period_start = ?
17
+ WHERE replacement.id = ?
18
+ AND replacement.user_id = ?
19
+ AND replacement.transport = 'nango'
20
+ AND replacement.lifecycle_status IN ('connected', 'needs_reauth')
21
+ AND NOT EXISTS (
22
+ SELECT 1 FROM connected_account_charges AS existing
23
+ WHERE existing.user_id = replacement.user_id
24
+ AND existing.connection_id = replacement.id
25
+ AND existing.period_start = ?
26
+ )
27
+ ORDER BY previous.updated_at DESC, previous.id ASC
28
+ LIMIT 1
29
+ `,args:[i,n,n,t,i,n]},{sql:`
30
+ UPDATE connected_account_charges
31
+ SET connection_id = ?
32
+ WHERE user_id = ?
33
+ AND period_start = ?
34
+ AND connection_id = (
35
+ SELECT transfer.from_connection_id
36
+ FROM connected_account_charge_transfers AS transfer
37
+ JOIN connected_account_charges AS current_charge
38
+ ON current_charge.user_id = transfer.user_id
39
+ AND current_charge.period_start = transfer.period_start
40
+ AND current_charge.connection_id = transfer.from_connection_id
41
+ WHERE transfer.user_id = ?
42
+ AND transfer.period_start = ?
43
+ AND transfer.to_connection_id = ?
44
+ ORDER BY transfer.transferred_at DESC, transfer.id DESC
45
+ LIMIT 1
46
+ )
47
+ AND NOT EXISTS (
48
+ SELECT 1 FROM connected_account_charges AS existing
49
+ WHERE existing.user_id = ?
50
+ AND existing.connection_id = ?
51
+ AND existing.period_start = ?
52
+ )
53
+ `,args:[t,i,n,i,n,t,i,t,n]}],"write"),(await r.execute({sql:"SELECT 1 FROM connected_account_charges WHERE user_id = ? AND connection_id = ? AND period_start = ? LIMIT 1",args:[i,t,n]})).rows.length>0}async function pt(e){let t=await ae().execute({sql:"SELECT * FROM connected_account_billing WHERE user_id = ? LIMIT 1",args:[Number(e)]});return t.rows[0]?t.rows[0]:null}async function _t(e){await ae().execute({sql:`INSERT INTO connected_account_billing
54
+ (user_id, stripe_subscription_id, stripe_subscription_item_id, price_id, quantity, status, last_error_code, checked_at, synced_at, updated_at)
55
+ VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), CASE WHEN ? THEN datetime('now') ELSE NULL END, datetime('now'))
56
+ ON CONFLICT(user_id) DO UPDATE SET
57
+ stripe_subscription_id = excluded.stripe_subscription_id,
58
+ stripe_subscription_item_id = excluded.stripe_subscription_item_id,
59
+ price_id = excluded.price_id,
60
+ quantity = excluded.quantity,
61
+ status = excluded.status,
62
+ last_error_code = excluded.last_error_code,
63
+ checked_at = datetime('now'),
64
+ synced_at = CASE WHEN ? THEN datetime('now') ELSE connected_account_billing.synced_at END,
65
+ updated_at = datetime('now')`,args:[Number(e.userId),e.stripeSubscriptionId,e.stripeSubscriptionItemId,e.priceId,e.quantity,e.status,e.lastErrorCode,e.synced?1:0,e.synced?1:0]});let t=await pt(e.userId);if(!t)throw new Error("connected account billing state was not persisted");return t}var j=class extends Error{constructor(n,i,r=409,s){super(n);this.code=i;this.status=r;this.details=s;this.name="ConnectedAccountBillingError"}code;status;details};function yt(){let e=process.env.CONNECTED_ACCOUNT_PRICE_ID?.trim();return e||Li||null}function mt(e,t){let n=Math.max(0,Math.round(e));return{provider:"nango",billingMode:"flat_recurring_credits",priceId:yt(),unitAmountCredits:ke,interval:"month",quantity:n,projectedMonthlyCredits:n*ke,status:t?.status??"pending",lastErrorCode:t?.last_error_code??null,syncedAt:t?.synced_at??null,usage:qt.usage}}function le(e){return e.items.data.find(t=>!!t.price?.id&&t.price.id in D)}function Hs(e){return ln(e,t=>!!le(t))}function Fi(e){return e instanceof j?e.code:"stripe_connection_billing_failed"}async function We(e,t,n){await _t({userId:e.id,stripeSubscriptionId:e.subscription_id,stripeSubscriptionItemId:null,priceId:yt(),quantity:t,status:n instanceof j&&n.code!=="stripe_connection_billing_failed"?"blocked":"error",lastErrorCode:Fi(n),synced:!1})}async function qs(e,t){let n=[...new Set(t.filter(ki).map(c=>c.connectionId))],i=n.length,r=yt();if(!r){let c=new j("Connected-account billing is not configured.","connected_account_price_not_configured",503);throw await We(e,i,c),c}if(!e.subscription_id||!(e.subscription_tier??"")||!Object.values(D).some(c=>c.tier===e.subscription_tier)){if(i===0){let d=await _t({userId:e.id,stripeSubscriptionId:null,stripeSubscriptionItemId:null,priceId:r,quantity:0,status:"synced",lastErrorCode:null,synced:!0});return mt(0,d)}let c=new j("An active paid plan is required before connected accounts can be billed.","paid_plan_required",403);throw await We(e,i,c),c}let s=await pt(e.id),a=Math.max(0,s?.quantity??0),o=$i();try{let c=await Mi(e.id,o),d=n.filter(u=>!c.has(u));for(let u of d){if(await Ui(e.id,u,o))continue;let y=Ht,E=await jt(e.id,y,Kt.CONNECTED_ACCOUNT_FEE,`1 connected account at ${ke} credits/month (${o})`,`connection-fee:${u}:${o}`);if(!E.ok){let b=new j(`Insufficient credits to connect. Balance: ${X(E.balance_mc)} credits. Connecting requires ${X(y)} credits (${X(y-E.balance_mc)} short). Upgrade your plan or wait for your credits to refresh.`,"insufficient_credits",402,{balanceCredits:X(E.balance_mc),requiredCredits:X(y),shortByCredits:X(Math.max(0,y-E.balance_mc))});throw await We(e,a,b),b}await ae().execute({sql:`INSERT INTO connected_account_charges (user_id, connection_id, period_start, amount_mc)
66
+ VALUES (?, ?, ?, ?) ON CONFLICT(user_id, connection_id, period_start) DO NOTHING`,args:[Number(e.id),u,o,y]}),E.duplicate||await Jt(e.id,1)}let _=await _t({userId:e.id,stripeSubscriptionId:e.subscription_id,stripeSubscriptionItemId:null,priceId:r,quantity:i,status:"synced",lastErrorCode:null,synced:!0});return mt(i,_)}catch(c){throw c instanceof j?c:(await We(e,a,c),new j("Unable to update connected-account billing.","stripe_connection_billing_failed",503))}}async function Ws(e,t){return mt(t,await pt(e))}import{createHash as Yi}from"crypto";import{z as q}from"zod";import{createHash as ji,randomUUID as Z}from"crypto";function I(e){return e??v()}function Be(e){return ji("sha256").update(JSON.stringify(e)).digest("hex")}async function Q(e){let t=await I(e.db).query("SELECT id::text AS id FROM analytics_connections WHERE id::text=$1 AND site_id=$2",[e.connectionId,e.siteId]);if(!t.rowCount)throw new f("analytics_crm_resource_not_found","CRM resource was not found.",404);return t.rows[0].id}async function zs(e){let t=Xt.parse(e.capability),n=I(e.db),i=await Q({siteId:e.siteId,connectionId:e.connectionId,db:n});await n.query(`INSERT INTO analytics_crm_connection_capabilities(
67
+ site_id,connection_id,provider,contract_version,mode,schema_fingerprint,allowed_operations,evidence,verified_at,updated_at
68
+ ) VALUES($1,$2,$3,1,$4,$5,$6::jsonb,$7::jsonb,$8,now())
69
+ ON CONFLICT(connection_id,contract_version) DO UPDATE SET
70
+ mode=EXCLUDED.mode,schema_fingerprint=EXCLUDED.schema_fingerprint,
71
+ allowed_operations=EXCLUDED.allowed_operations,evidence=EXCLUDED.evidence,
72
+ verified_at=EXCLUDED.verified_at,updated_at=now()`,[e.siteId,i,t.provider,t.mode,t.evidence.tenantSchemaFingerprint,JSON.stringify([...new Set(e.allowedOperations)].sort()),JSON.stringify(t.evidence),t.evidence.evaluatedAt])}async function Ys(e){let t=Ue.parse(e.plan),n=I(e.db),i=await Q({siteId:e.siteId,connectionId:t.connectionId,db:n}),r=await n.query("SELECT plan,request_fingerprint FROM analytics_crm_provisioning_plans WHERE site_id=$1 AND public_plan_id=$2",[e.siteId,t.planId]);if(r.rowCount){if(r.rows[0].request_fingerprint!==t.requestFingerprint)throw new f("idempotency_conflict","This plan identifier was already used for different arguments.",409);return{plan:Ue.parse(r.rows[0].plan),reused:!0}}return await n.query(`INSERT INTO analytics_crm_provisioning_plans(
73
+ id,site_id,connection_id,provider,contract_version,tenant_schema_version,plan,status,
74
+ created_by_user_id,public_plan_id,schema_fingerprint,request_fingerprint,created_at,updated_at
75
+ ) VALUES($1,$2,$3,$4,1,$5,$6::jsonb,'preview',$7,$8,$9,$10,$11,$11)`,[Z(),e.siteId,i,t.provider,t.schemaFingerprint,JSON.stringify(t),e.createdByUserId,t.planId,t.schemaFingerprint,t.requestFingerprint,t.createdAt]),{plan:t,reused:!1}}async function Xs(e){let t=await I(e.db).query("SELECT plan FROM analytics_crm_provisioning_plans WHERE site_id=$1 AND public_plan_id=$2",[e.siteId,e.planId]);if(!t.rowCount)throw new f("analytics_crm_resource_not_found","CRM resource was not found.",404);return Ue.parse(t.rows[0].plan)}async function Gs(e){let t=Fe.parse(e.receipt),n=I(e.db),i=await n.query("SELECT id::text AS id,request_fingerprint FROM analytics_crm_provisioning_plans WHERE site_id=$1 AND public_plan_id=$2",[e.siteId,t.planId]);if(!i.rowCount)throw new f("analytics_crm_resource_not_found","CRM resource was not found.",404);if(i.rows[0].request_fingerprint!==t.planFingerprint)throw new f("idempotency_conflict","The approved plan no longer matches the persisted plan.",409);let r=await n.query(`SELECT r.receipt FROM analytics_crm_provisioning_receipts r
76
+ JOIN analytics_crm_provisioning_plans p ON p.id=r.plan_id
77
+ WHERE p.site_id=$1 AND r.public_receipt_id=$2`,[e.siteId,t.receiptId]);if(r.rowCount&&Fe.parse(r.rows[0].receipt).planFingerprint!==t.planFingerprint)throw new f("idempotency_conflict","This receipt identifier was already used for different arguments.",409);let s=t.status==="tested"?"test_write":"create_field",a=t.status==="failed"?"failed":"accepted";return await n.query(`INSERT INTO analytics_crm_provisioning_receipts(
78
+ id,plan_id,operation,status,safe_error_code,warnings,created_at,public_receipt_id,receipt
79
+ ) VALUES($1,$2,$3,$4,$5,'[]'::jsonb,$6,$7,$8::jsonb)
80
+ ON CONFLICT (public_receipt_id) WHERE public_receipt_id IS NOT NULL
81
+ DO UPDATE SET operation=EXCLUDED.operation,status=EXCLUDED.status,safe_error_code=EXCLUDED.safe_error_code,receipt=EXCLUDED.receipt`,[Z(),i.rows[0].id,s,a,t.errorCode??null,t.occurredAt,t.receiptId,JSON.stringify(t)]),await n.query(`UPDATE analytics_crm_provisioning_plans
82
+ SET status=$3,approved_by_user_id=$4,approved_at=COALESCE(approved_at,$5),
83
+ approved_field_ids=$6::jsonb,updated_at=$5
84
+ WHERE site_id=$1 AND public_plan_id=$2`,[e.siteId,t.planId,t.status==="tested"?"verified":t.status==="failed"?"failed":"approved",e.approvedByUserId,t.occurredAt,JSON.stringify([...new Set(e.approvedFieldInternalNames)].sort())]),t}async function Zs(e){let t=await I(e.db).query(`SELECT r.receipt FROM analytics_crm_provisioning_receipts r
85
+ JOIN analytics_crm_provisioning_plans p ON p.id=r.plan_id
86
+ WHERE p.site_id=$1 AND r.public_receipt_id=$2`,[e.siteId,e.receiptId]);if(!t.rowCount)throw new f("analytics_crm_resource_not_found","CRM resource was not found.",404);return Fe.parse(t.rows[0].receipt)}var gt=e=>e instanceof Date?e.toISOString():e;function ft(e){return Gt.parse({syncId:e.sync_id,siteId:e.site_id,connectionId:e.connection_id,provider:e.provider,state:e.state,mode:e.mode,cursor:typeof e.cursor=="string"?e.cursor:e.cursor==null?null:JSON.stringify(e.cursor),lastStartedAt:gt(e.last_started_at),lastSuccessAt:gt(e.last_success_at),nextRunAt:gt(e.next_run_at),imported:Number(e.imported),deduplicated:Number(e.deduplicated),rejected:Number(e.rejected),lastErrorCode:e.last_error_code,requestFingerprint:e.request_fingerprint})}async function Qs(e){let t=I(e.db),n=await Q({siteId:e.siteId,connectionId:e.connectionId,db:t}),i=Be({siteId:e.siteId,connectionId:n,provider:e.provider,mode:e.mode,mapping:e.mapping,mappingVersion:e.mappingVersion??1,capabilityEvidence:e.capabilityEvidence??{},backfillFrom:e.backfillFrom??null}),r=await t.query("SELECT * FROM analytics_crm_sync_runs WHERE site_id=$1 AND idempotency_key=$2",[e.siteId,e.idempotencyKey]);if(r.rowCount){if(r.rows[0].request_fingerprint!==i)throw new f("idempotency_conflict","This Idempotency-Key was already used for different CRM sync arguments.",409);return{reused:!0,status:ft(r.rows[0])}}let s=await t.query(`INSERT INTO analytics_crm_sync_runs(
87
+ sync_id,site_id,connection_id,provider,mode,state,mapping,mapping_version,capability_evidence,idempotency_key,request_fingerprint,last_started_at
88
+ ) VALUES($1,$2,$3,$4,$5,'queued',$6::jsonb,$7,$8::jsonb,$9,$10,now()) RETURNING *`,[e.syncId,e.siteId,n,e.provider,e.mode,JSON.stringify(e.mapping),e.mappingVersion??1,JSON.stringify(e.capabilityEvidence??{}),e.idempotencyKey,i]);return await t.query(`INSERT INTO analytics_crm_sync_state(connection_id,status,last_started_at,updated_at)
89
+ VALUES($1,'pending',now(),now()) ON CONFLICT(connection_id)
90
+ DO UPDATE SET status='pending',last_started_at=now(),updated_at=now()`,[n]),{reused:!1,status:ft(s.rows[0])}}async function ea(e){let t=await I(e.db).query("SELECT * FROM analytics_crm_sync_runs WHERE site_id=$1 AND sync_id=$2",[e.siteId,e.syncId]);return t.rowCount?ft(t.rows[0]):null}async function ta(e){let t=I(e.db);return(await t.query(`UPDATE analytics_crm_sync_runs SET next_run_at=$4,interval_seconds=$5,updated_at=now()
91
+ WHERE site_id=$1 AND sync_id=$2 AND connection_id::text=$3 AND state <> 'cancelled'`,[e.siteId,e.syncId,e.connectionId,e.nextRunAt,e.intervalSeconds])).rowCount?(e.intervalSeconds===null?await t.query(`DELETE FROM analytics_reconciliation_jobs
92
+ WHERE connection_id::text=$1 AND status='pending'`,[e.connectionId]):await t.query(`INSERT INTO analytics_reconciliation_jobs(id,connection_id,status,next_attempt_at)
93
+ VALUES($1,$2,'pending',$3)
94
+ ON CONFLICT(connection_id) DO UPDATE SET
95
+ status=CASE WHEN analytics_reconciliation_jobs.status='leased' THEN 'leased' ELSE 'pending' END,
96
+ next_attempt_at=CASE WHEN analytics_reconciliation_jobs.status='leased'
97
+ THEN analytics_reconciliation_jobs.next_attempt_at ELSE EXCLUDED.next_attempt_at END,
98
+ updated_at=now()`,[Z(),e.connectionId,e.nextRunAt]),!0):!1}async function na(e){let t=I(e.db),n=await t.query(`UPDATE analytics_crm_sync_runs SET state='cancelled',next_run_at=NULL,updated_at=now()
99
+ WHERE site_id=$1 AND sync_id=$2 AND state <> 'cancelled' RETURNING connection_id::text AS connection_id`,[e.siteId,e.syncId]);return n.rowCount?(await t.query("UPDATE analytics_crm_sync_state SET status='disabled',next_sync_at=NULL,updated_at=now() WHERE connection_id::text=$1",[n.rows[0].connection_id]),!0):!1}async function ia(e){let t=I(e.db);await Q({siteId:e.siteId,connectionId:e.connectionId,db:t}),await t.query(`INSERT INTO analytics_crm_sync_state(connection_id,direction,status,last_started_at,updated_at)
100
+ VALUES($1,'inbound','running',now(),now())
101
+ ON CONFLICT(connection_id) DO UPDATE SET
102
+ direction=CASE WHEN analytics_crm_sync_state.direction='outbound' THEN 'bidirectional' ELSE analytics_crm_sync_state.direction END,
103
+ status='running',last_started_at=now(),last_error_code=NULL,updated_at=now()`,[e.connectionId]),await t.query(`UPDATE analytics_crm_sync_runs SET state='running',last_started_at=now(),last_error_code=NULL,updated_at=now()
104
+ WHERE sync_id=(SELECT sync_id FROM analytics_crm_sync_runs
105
+ WHERE site_id=$1 AND connection_id::text=$2 AND state NOT IN ('cancelled','webhook_only')
106
+ ORDER BY created_at DESC LIMIT 1)`,[e.siteId,e.connectionId])}async function ra(e){let t=I(e.db);await Q({siteId:e.siteId,connectionId:e.connectionId,db:t}),await t.query(`INSERT INTO analytics_crm_sync_state(
107
+ connection_id,cursor,direction,status,last_started_at,last_completed_at,last_success_at,next_sync_at,updated_at
108
+ ) VALUES($1,$2::jsonb,'inbound','healthy',now(),now(),now(),$3,now())
109
+ ON CONFLICT(connection_id) DO UPDATE SET
110
+ cursor=EXCLUDED.cursor,
111
+ direction=CASE WHEN analytics_crm_sync_state.direction='outbound' THEN 'bidirectional' ELSE analytics_crm_sync_state.direction END,
112
+ status='healthy',last_completed_at=now(),last_success_at=now(),last_error_code=NULL,
113
+ next_sync_at=EXCLUDED.next_sync_at,updated_at=now()`,[e.connectionId,JSON.stringify(e.cursor??null),e.nextRunAt]),await t.query(`UPDATE analytics_crm_sync_runs SET
114
+ state=$3,cursor=$4::jsonb,last_success_at=now(),next_run_at=$5,
115
+ imported=imported+$6,deduplicated=deduplicated+$7,rejected=rejected+$8,
116
+ last_error_code=NULL,updated_at=now()
117
+ WHERE sync_id=(SELECT sync_id FROM analytics_crm_sync_runs
118
+ WHERE site_id=$1 AND connection_id::text=$2 AND state <> 'cancelled'
119
+ ORDER BY created_at DESC LIMIT 1)`,[e.siteId,e.connectionId,e.hasMore?"queued":"completed",JSON.stringify(e.cursor??null),e.nextRunAt,e.imported,e.deduplicated,e.rejected]),await t.query(`INSERT INTO analytics_crm_sync_receipts(
120
+ id,site_id,connection_id,direction,operation,subject_ref,external_receipt_id,status,occurred_at
121
+ ) VALUES($1,$2,$3,'inbound','historical_pull',NULL,$4,'accepted',now())`,[Z(),e.siteId,e.connectionId,e.receiptFingerprint])}async function sa(e){let t=I(e.db);await Q({siteId:e.siteId,connectionId:e.connectionId,db:t});let n=e.terminal?"failed":"retrying";await t.query(`INSERT INTO analytics_crm_sync_state(connection_id,direction,status,last_completed_at,last_error_code,next_sync_at,updated_at)
122
+ VALUES($1,'inbound','degraded',now(),$2,$3,now())
123
+ ON CONFLICT(connection_id) DO UPDATE SET
124
+ direction=CASE WHEN analytics_crm_sync_state.direction='outbound' THEN 'bidirectional' ELSE analytics_crm_sync_state.direction END,
125
+ status='degraded',last_completed_at=now(),last_error_code=EXCLUDED.last_error_code,
126
+ next_sync_at=EXCLUDED.next_sync_at,updated_at=now()`,[e.connectionId,e.safeErrorCode,e.nextRunAt]),await t.query(`UPDATE analytics_crm_sync_runs SET state='degraded',last_error_code=$3,next_run_at=$4,
127
+ rejected=rejected+1,updated_at=now()
128
+ WHERE sync_id=(SELECT sync_id FROM analytics_crm_sync_runs
129
+ WHERE site_id=$1 AND connection_id::text=$2 AND state <> 'cancelled'
130
+ ORDER BY created_at DESC LIMIT 1)`,[e.siteId,e.connectionId,e.safeErrorCode,e.nextRunAt]),await t.query(`INSERT INTO analytics_crm_sync_receipts(
131
+ id,site_id,connection_id,direction,operation,subject_ref,external_receipt_id,status,safe_error_code,occurred_at
132
+ ) VALUES($1,$2,$3,'inbound','historical_pull',NULL,$4,$5,$6,now())`,[Z(),e.siteId,e.connectionId,e.receiptFingerprint,n,e.safeErrorCode])}function un(e){return je.parse({policyId:e.policy_id,siteId:e.site_id,provider:e.provider,connectionId:e.connection_id,kind:e.kind,enabled:e.enabled,version:Number(e.version),provisioningReceiptId:e.provisioning_receipt_id,schemaFingerprint:e.schema_fingerprint,mappingVersion:e.mapping_version==null?null:Number(e.mapping_version),mapping:e.mapping??null,allowedFields:e.allowed_fields,selectedEvents:e.selected_events,purpose:e.purpose,updatedAt:e.updated_at instanceof Date?e.updated_at.toISOString():new Date(e.updated_at).toISOString()})}async function aa(e){let t=je.parse(e.policy),n=I(e.db),i=await Q({siteId:t.siteId,connectionId:t.connectionId,db:n});if(t.enabled){let a=(await n.query(`SELECT r.receipt->>'status' receipt_status,p.status plan_status,p.schema_fingerprint plan_schema_fingerprint,
133
+ c.schema_fingerprint current_schema_fingerprint
134
+ FROM analytics_crm_provisioning_receipts r
135
+ JOIN analytics_crm_provisioning_plans p ON p.id=r.plan_id
136
+ LEFT JOIN analytics_crm_connection_capabilities c ON c.connection_id=p.connection_id AND c.contract_version=1
137
+ WHERE p.site_id=$1 AND p.connection_id=$2 AND r.public_receipt_id=$3`,[t.siteId,i,t.provisioningReceiptId])).rows[0];if(!a||a.receipt_status!=="tested"||a.plan_status!=="verified")throw new f("analytics_crm_outbound_test_required","A successful provisioning test is required before outbound automation can be enabled.",409);if(a.plan_schema_fingerprint!==t.schemaFingerprint||a.current_schema_fingerprint!==t.schemaFingerprint)throw new f("analytics_crm_schema_changed","The CRM schema changed after testing. Test the exact schema again before enabling outbound automation.",409)}if(t.kind==="pipeline_event"&&(!t.mapping||t.mapping.version!==t.mappingVersion))throw new f("analytics_crm_mapping_version_mismatch","The pipeline mapping and mapping version must match exactly.",409);let r=await n.query(`INSERT INTO analytics_crm_outbound_policies(
138
+ policy_id,site_id,connection_id,provider,kind,enabled,version,provisioning_receipt_id,
139
+ schema_fingerprint,mapping_version,mapping,allowed_fields,selected_events,purpose,created_by_user_id,updated_at
140
+ ) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12::jsonb,$13::jsonb,$14,$15,now())
141
+ ON CONFLICT(policy_id) DO UPDATE SET
142
+ enabled=EXCLUDED.enabled,provisioning_receipt_id=EXCLUDED.provisioning_receipt_id,
143
+ schema_fingerprint=EXCLUDED.schema_fingerprint,mapping_version=EXCLUDED.mapping_version,
144
+ mapping=EXCLUDED.mapping,allowed_fields=EXCLUDED.allowed_fields,selected_events=EXCLUDED.selected_events,
145
+ purpose=EXCLUDED.purpose,updated_at=now()
146
+ WHERE analytics_crm_outbound_policies.site_id=EXCLUDED.site_id
147
+ AND analytics_crm_outbound_policies.connection_id=EXCLUDED.connection_id
148
+ AND analytics_crm_outbound_policies.kind=EXCLUDED.kind
149
+ AND analytics_crm_outbound_policies.version=EXCLUDED.version
150
+ RETURNING *`,[t.policyId,t.siteId,i,t.provider,t.kind,t.enabled,t.version,t.provisioningReceiptId,t.schemaFingerprint,t.mappingVersion,JSON.stringify(t.mapping),JSON.stringify([...new Set(t.allowedFields)].sort()),JSON.stringify([...new Set(t.selectedEvents)].sort()),t.purpose,e.createdByUserId]);if(!r.rowCount)throw new f("idempotency_conflict","This policy identifier is already bound to another immutable policy version.",409);return un(r.rows[0])}async function _n(e){return(await I(e.db).query("SELECT * FROM analytics_crm_outbound_policies WHERE site_id=$1 ORDER BY updated_at DESC,policy_id",[e.siteId])).rows.map(un)}function vt(e){return Zt.parse({jobId:e.job_id,policyId:e.policy_id,siteId:e.site_id,personId:e.person_id,kind:e.kind,state:e.state,policyVersion:Number(e.policy_version),mappingVersion:e.mapping_version==null?null:Number(e.mapping_version),attempts:Number(e.attempts),nextAttemptAt:e.next_attempt_at==null?null:e.next_attempt_at instanceof Date?e.next_attempt_at.toISOString():e.next_attempt_at,safeErrorCode:e.safe_error_code,requestFingerprint:e.request_fingerprint})}async function mn(e){let t=je.parse(e.policy);if(!t.enabled)throw new f("analytics_crm_outbound_policy_disabled","The CRM outbound policy is disabled.",409);let n=I(e.db);if(!(await n.query(`SELECT p.id::text id FROM analytics_people p
151
+ WHERE p.id=$1 AND p.site_id=$2 AND
152
+ (p.crm_person_ref IS NOT NULL OR EXISTS (
153
+ SELECT 1 FROM analytics_identity_edges e JOIN analytics_identity_nodes n ON n.id=e.identity_node_id
154
+ WHERE e.person_id=p.id AND n.kind IN ('email','phone','customer_id') AND e.confidence >= 0.99))`,[e.personId,t.siteId])).rowCount)throw new f("analytics_crm_outbound_confirmed_person_required","CRM outbound delivery requires a confirmed person.",409);let r=await n.query("SELECT * FROM analytics_crm_outbound_jobs WHERE site_id=$1 AND idempotency_key=$2",[t.siteId,e.idempotencyKey]);if(r.rowCount){if(r.rows[0].request_fingerprint!==e.requestFingerprint)throw new f("idempotency_conflict","This idempotency key was already used for another CRM outbound request.",409);return{job:vt(r.rows[0]),reused:!0}}let s=await n.query(`INSERT INTO analytics_crm_outbound_jobs(
155
+ job_id,policy_id,site_id,person_id,kind,policy_version,mapping_version,source_event_id,
156
+ payload_ciphertext,idempotency_key,request_fingerprint,next_attempt_at
157
+ ) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,now()) RETURNING *`,[e.jobId,t.policyId,t.siteId,e.personId,t.kind,t.version,t.mappingVersion,e.sourceEventId??null,e.payloadCiphertext,e.idempotencyKey,e.requestFingerprint]);return{job:vt(s.rows[0]),reused:!1}}async function pn(e){let t=await I(e.db).query(`WITH due AS (
158
+ SELECT j.job_id FROM analytics_crm_outbound_jobs j
159
+ JOIN analytics_crm_outbound_policies p ON p.policy_id=j.policy_id
160
+ WHERE p.enabled=true AND p.provider NOT IN ('salesforce','pipedrive')
161
+ AND ((j.state IN ('pending','retrying') AND j.next_attempt_at <= now()) OR
162
+ (j.state='running' AND j.lease_expires_at < now()))
163
+ ORDER BY coalesce(j.next_attempt_at,j.lease_expires_at),j.created_at FOR UPDATE OF j SKIP LOCKED LIMIT $1
164
+ )
165
+ UPDATE analytics_crm_outbound_jobs j SET state='running',attempts=j.attempts+1,
166
+ lease_expires_at=now()+($2::text || ' seconds')::interval,updated_at=now()
167
+ FROM due,analytics_crm_outbound_policies p,analytics_sites s
168
+ WHERE j.job_id=due.job_id AND p.policy_id=j.policy_id AND s.id=j.site_id
169
+ RETURNING j.*,p.provider,p.connection_id::text,p.mapping,p.allowed_fields,p.purpose,s.owner_user_id,
170
+ EXISTS(SELECT 1 FROM analytics_crm_connection_capabilities c
171
+ WHERE c.connection_id=p.connection_id AND c.contract_version=1 AND c.schema_fingerprint=p.schema_fingerprint) schema_current`,[Math.max(1,Math.min(50,e.limit)),e.leaseSeconds??90]);for(let n of t.rows)await I(e.db).query(`INSERT INTO analytics_crm_outbound_attempts(id,job_id,attempt,status,started_at)
172
+ VALUES($1,$2,$3,'started',now()) ON CONFLICT(job_id,attempt) DO NOTHING`,[Z(),n.job_id,Number(n.attempts)]);return t.rows.map(n=>({job:vt(n),provider:n.provider,connectionId:n.connection_id,ownerUserId:Number(n.owner_user_id),payloadCiphertext:n.payload_ciphertext,mapping:n.mapping,allowedFields:n.allowed_fields,purpose:n.purpose,schemaCurrent:n.schema_current}))}async function yn(e){return(await I(e.db).query(`SELECT EXISTS(
173
+ SELECT 1
174
+ FROM analytics_crm_outbound_jobs j
175
+ JOIN analytics_crm_outbound_policies p ON p.policy_id=j.policy_id
176
+ JOIN analytics_people person ON person.id=j.person_id AND person.site_id=j.site_id
177
+ WHERE j.job_id=$1 AND j.state='running' AND j.attempts=$2
178
+ AND j.lease_expires_at>now() AND j.payload_ciphertext<>''
179
+ AND p.enabled=true AND p.version=j.policy_version AND p.purpose=$3
180
+ AND EXISTS (
181
+ SELECT 1 FROM analytics_purpose_policies purpose_policy
182
+ WHERE purpose_policy.site_id=j.site_id
183
+ AND purpose_policy.purpose=CASE $3
184
+ WHEN 'necessary_form_fulfillment' THEN 'necessary'
185
+ WHEN 'site_analytics' THEN 'site_analytics'
186
+ ELSE 'advertising_measurement' END
187
+ AND purpose_policy.enabled=true AND purpose_policy.retired_at IS NULL
188
+ )
189
+ AND NOT EXISTS (
190
+ SELECT 1 FROM analytics_identity_tombstones tombstone
191
+ WHERE tombstone.site_id=j.site_id AND tombstone.person_id=j.person_id
192
+ )
193
+ AND ($3='necessary_form_fulfillment' OR EXISTS (
194
+ SELECT 1
195
+ FROM analytics_consent_current_state consent
196
+ JOIN analytics_consent_receipts receipt
197
+ ON receipt.id=consent.latest_receipt_id AND receipt.site_id=consent.site_id
198
+ WHERE consent.site_id=j.site_id
199
+ AND consent.latest_receipt_id::text=$4
200
+ AND consent.gpc=false
201
+ AND receipt.action NOT IN ('withdraw','reject_all','gpc')
202
+ AND (consent.expires_at IS NULL OR consent.expires_at>now())
203
+ AND EXISTS (
204
+ SELECT 1 FROM analytics_purpose_policies current_policy
205
+ WHERE current_policy.site_id=consent.site_id
206
+ AND current_policy.purpose=CASE $3
207
+ WHEN 'site_analytics' THEN 'site_analytics'
208
+ ELSE 'advertising_measurement' END
209
+ AND current_policy.jurisdiction_profile=consent.jurisdiction_profile
210
+ AND current_policy.enabled=true AND current_policy.retired_at IS NULL
211
+ )
212
+ AND (($3='site_analytics' AND consent.site_analytics=true) OR
213
+ ($3='advertising_measurement' AND consent.advertising_measurement=true))
214
+ ))
215
+ ) authorized`,[e.jobId,e.attempt,e.purpose,e.consentReceiptRef])).rows[0]?.authorized===!0}async function gn(e){let t=I(e.db);return(await t.query(`UPDATE analytics_crm_outbound_jobs
216
+ SET state='cancelled',payload_ciphertext='',next_attempt_at=NULL,
217
+ lease_expires_at=NULL,cancelled_at=now(),safe_error_code=$3,updated_at=now()
218
+ WHERE job_id=$1 AND state='running' AND attempts=$2`,[e.jobId,e.attempt,e.safeErrorCode.slice(0,80)])).rowCount?(await t.query(`UPDATE analytics_crm_outbound_attempts
219
+ SET status='cancelled',safe_error_code=$3,completed_at=now()
220
+ WHERE job_id=$1 AND attempt=$2 AND status='started'`,[e.jobId,e.attempt,e.safeErrorCode.slice(0,80)]),!0):!1}async function Ve(e){let t=I(e.db),n=await t.query("SELECT attempts,max_attempts FROM analytics_crm_outbound_jobs WHERE job_id=$1",[e.jobId]);if(!n.rowCount)throw new f("analytics_crm_resource_not_found","CRM outbound job was not found.",404);let i=Number(n.rows[0].attempts),r=e.status!=="retrying"||i>=Number(n.rows[0].max_attempts),s=e.status==="accepted"?"delivered":e.status==="retrying"&&!r?"retrying":"dead_letter",a=`crmout_${Be({jobId:e.jobId,attempt:i,status:e.status}).slice(0,24)}`;return await t.query(`UPDATE analytics_crm_outbound_jobs SET state=$2,safe_error_code=$3,next_attempt_at=$4,
221
+ lease_expires_at=NULL,delivered_at=CASE WHEN $2='delivered' THEN now() ELSE delivered_at END,updated_at=now()
222
+ WHERE job_id=$1 AND state='running'`,[e.jobId,s,e.safeErrorCode??null,s==="retrying"?e.retryAt??null:null]),await t.query(`INSERT INTO analytics_crm_outbound_attempts(id,job_id,attempt,status,safe_error_code,completed_at)
223
+ VALUES($1,$2,$3,$4,$5,now()) ON CONFLICT(job_id,attempt) DO UPDATE SET status=EXCLUDED.status,safe_error_code=EXCLUDED.safe_error_code,completed_at=now()`,[Z(),e.jobId,i,e.status==="accepted"?"accepted":s==="retrying"?"retrying":"failed",e.safeErrorCode??null]),await t.query(`INSERT INTO analytics_crm_outbound_receipts(receipt_id,job_id,status,external_receipt_id,safe_error_code)
224
+ VALUES($1,$2,$3,$4,$5) ON CONFLICT(receipt_id) DO NOTHING`,[a,e.jobId,s==="retrying"?"retrying":e.status,e.externalReceiptId??null,e.safeErrorCode??null]),dt.parse({receiptId:a,jobId:e.jobId,status:s==="retrying"?"retrying":e.status,...e.externalReceiptId?{externalReceiptId:e.externalReceiptId}:{},...e.safeErrorCode?{safeErrorCode:e.safeErrorCode}:{},occurredAt:new Date().toISOString()})}async function oa(e){return(await I(e.db).query(`SELECT r.* FROM analytics_crm_outbound_receipts r
225
+ JOIN analytics_crm_outbound_jobs j ON j.job_id=r.job_id
226
+ JOIN analytics_crm_outbound_policies p ON p.policy_id=j.policy_id
227
+ WHERE j.site_id=$1 AND p.provider NOT IN ('salesforce','pipedrive')
228
+ ORDER BY r.occurred_at DESC LIMIT $2`,[e.siteId,Math.max(1,Math.min(100,e.limit??25))])).rows.map(n=>dt.parse({receiptId:n.receipt_id,jobId:n.job_id,status:n.status,...n.external_receipt_id?{externalReceiptId:n.external_receipt_id}:{},...n.safe_error_code?{safeErrorCode:n.safe_error_code}:{},occurredAt:n.occurred_at instanceof Date?n.occurred_at.toISOString():n.occurred_at}))}import{createHash as vn}from"crypto";var qi=["hubspot","salesforce","highlevel","zoho","pipedrive","keap"],ca=["hubspot","highlevel","zoho","keap"];var H=e=>{let t={describe:!1,discover_schema:!1,incremental_read:!1,webhook:!1,create_fields:!1,upsert_person:!1,append_activity:!1,test_write:!1,list_outcomes:!1,pipeline_write:!1};for(let n of e)t[n]=!0;return Object.freeze(t)},ue=["describe","discover_schema","incremental_read","webhook","upsert_person","append_activity","test_write","list_outcomes","pipeline_write"],fn=Object.freeze({hubspot:{provider:"hubspot",displayName:"HubSpot",providerConfigKeys:["hubspot"],auth:"oauth2",operations:H([...ue,"create_fields"]),personObject:"contacts",pipelineObject:"deals",activityObject:"notes",supportedObjects:["contacts","companies","deals","notes"],metadataEndpoint:"/crm/v3/properties/{objectType}",customFieldEndpoint:"/crm/v3/properties/{objectType}",externalIdStrategy:"email or an approved unique contact property",writablePersonFields:["email","firstname","lastname","phone"],requiredPersonFields:[],stageField:"dealstage",valueField:"amount",associations:["contact_to_company","contact_to_deal","note_to_contact"],pagination:"cursor",batchLimit:100,rateLimitHeaders:["x-hubspot-ratelimit-remaining","retry-after"],caveats:["Effective properties and writable scopes are tenant-specific."],safeFallback:"note"},salesforce:{provider:"salesforce",displayName:"Salesforce",providerConfigKeys:["salesforce"],auth:"oauth2",operations:H([...ue,"create_fields"]),personObject:"Contact",pipelineObject:"Opportunity",activityObject:"Task",supportedObjects:["Lead","Contact","Account","Opportunity","Task"],metadataEndpoint:"/services/data/{version}/sobjects/{object}/describe",customFieldEndpoint:"Metadata API",externalIdStrategy:"approved External ID field",writablePersonFields:["Email","FirstName","LastName","Phone"],requiredPersonFields:["LastName"],stageField:"StageName",valueField:"Amount",associations:["Contact.AccountId","Opportunity.AccountId","OpportunityContactRole"],pagination:"cursor",batchLimit:200,rateLimitHeaders:["sforce-limit-info","retry-after"],caveats:["Required fields, validation rules, record types and field-level security are tenant-specific."],safeFallback:"activity"},highlevel:{provider:"highlevel",displayName:"HighLevel",providerConfigKeys:["highlevel","gohighlevel"],auth:"oauth2",operations:H([...ue,"create_fields"]),personObject:"contacts",pipelineObject:"opportunities",activityObject:"notes",supportedObjects:["contacts","companies","opportunities","notes"],metadataEndpoint:"/locations/{locationId}/customFields",customFieldEndpoint:"/locations/{locationId}/customFields",externalIdStrategy:"location-scoped email/phone or approved custom field",writablePersonFields:["email","firstName","lastName","phone"],requiredPersonFields:[],stageField:"pipelineStageId",valueField:"monetaryValue",associations:["contact.location","opportunity.contact"],pagination:"cursor",batchLimit:100,rateLimitHeaders:["x-ratelimit-remaining","retry-after"],caveats:["Location context and installed OAuth scopes determine effective access."],safeFallback:"note"},zoho:{provider:"zoho",displayName:"Zoho CRM",providerConfigKeys:["zoho","zoho-crm"],auth:"oauth2",operations:H([...ue,"create_fields"]),personObject:"Contacts",pipelineObject:"Deals",activityObject:"Notes",supportedObjects:["Leads","Contacts","Accounts","Deals","Notes"],metadataEndpoint:"/crm/v7/settings/fields",customFieldEndpoint:"/crm/v7/settings/fields",externalIdStrategy:"approved unique field and duplicate-check preference",writablePersonFields:["Email","First_Name","Last_Name","Phone"],requiredPersonFields:["Last_Name"],stageField:"Stage",valueField:"Amount",associations:["Contact_Name","Account_Name","Parent_Id"],pagination:"page",batchLimit:100,rateLimitHeaders:["x-api-credits-remaining","retry-after"],caveats:["Custom field APIs, editions and API credits vary by tenant."],safeFallback:"note"},pipedrive:{provider:"pipedrive",displayName:"Pipedrive",providerConfigKeys:["pipedrive"],auth:"oauth2",operations:H([...ue,"create_fields"]),personObject:"persons",pipelineObject:"deals",activityObject:"notes",supportedObjects:["persons","organizations","deals","activities","notes"],metadataEndpoint:"/v1/personFields",customFieldEndpoint:"/v1/personFields",externalIdStrategy:"approved namespaced custom field; custom-field keys are tenant-specific",writablePersonFields:["name","email","phone"],requiredPersonFields:["name"],stageField:"stage_id",valueField:"value",associations:["person_id","org_id","deal_id"],pagination:"cursor",batchLimit:100,rateLimitHeaders:["x-ratelimit-remaining","retry-after"],caveats:["Custom-field hashes must be discovered from the connected tenant."],safeFallback:"note"},keap:{provider:"keap",displayName:"Keap",providerConfigKeys:["keap","infusionsoft"],auth:"oauth2",operations:H([...ue,"create_fields"]),personObject:"contacts",pipelineObject:"opportunities",activityObject:"notes",supportedObjects:["contacts","companies","opportunities","notes","tags"],metadataEndpoint:"/v1/contactCustomFields",customFieldEndpoint:"/v1/contactCustomFields",externalIdStrategy:"email or approved custom field",writablePersonFields:["email_addresses","given_name","family_name","phone_numbers"],requiredPersonFields:[],stageField:"stage_id",valueField:"projected_revenue",associations:["contact.company","opportunity.contact"],pagination:"offset",batchLimit:100,rateLimitHeaders:["x-ratelimit-remaining","retry-after"],caveats:["Custom fields, opportunity access and throttling depend on account entitlements."],safeFallback:"note"},callrail:{provider:"callrail",displayName:"CallRail",providerConfigKeys:["callrail"],auth:"api_key",operations:H(["describe","incremental_read","webhook","list_outcomes"]),personObject:null,pipelineObject:null,activityObject:"calls",supportedObjects:["calls"],metadataEndpoint:null,customFieldEndpoint:null,externalIdStrategy:null,writablePersonFields:[],requiredPersonFields:[],stageField:null,valueField:null,associations:[],pagination:"cursor",batchLimit:100,rateLimitHeaders:["x-ratelimit-remaining","retry-after"],caveats:["Read and webhook call outcomes only; no CRM person or pipeline writes."],safeFallback:"webhook_only"},twilio:{provider:"twilio",displayName:"Twilio",providerConfigKeys:["twilio"],auth:"account_token",operations:H(["describe","incremental_read","webhook","list_outcomes"]),personObject:null,pipelineObject:null,activityObject:"calls",supportedObjects:["calls"],metadataEndpoint:null,customFieldEndpoint:null,externalIdStrategy:null,writablePersonFields:[],requiredPersonFields:[],stageField:null,valueField:null,associations:[],pagination:"page",batchLimit:100,rateLimitHeaders:["twilio-concurrent-requests","retry-after"],caveats:["Read and webhook call outcomes only; no CRM person or pipeline writes."],safeFallback:"webhook_only"},ctm:{provider:"ctm",displayName:"CallTrackingMetrics",providerConfigKeys:["ctm","calltrackingmetrics"],auth:"api_key",operations:H(["describe","webhook"]),personObject:null,pipelineObject:null,activityObject:"calls",supportedObjects:["calls"],metadataEndpoint:null,customFieldEndpoint:null,externalIdStrategy:null,writablePersonFields:[],requiredPersonFields:[],stageField:null,valueField:null,associations:[],pagination:"webhook_only",batchLimit:null,rateLimitHeaders:["retry-after"],caveats:["Webhook-only until incremental cursor semantics are documented and proven."],safeFallback:"webhook_only"}}),Wi=Object.freeze({gohighlevel:"highlevel",ghl:"highlevel","high-level":"highlevel",infusionsoft:"keap","zoho-crm":"zoho",calltrackingmetrics:"ctm","call-tracking-metrics":"ctm"});function ee(e){let t=e.trim().toLowerCase().replace(/[ _]+/g,"-"),n=Wi[t]??t;if(!(n in fn))throw new Error("analytics_provider_unsupported");return n}function Ke(e){return fn[ee(e)]}function _e(e,t){let n=Ke(e);if(!n.operations[t])throw new Error(`analytics_provider_operation_unsupported:${n.provider}:${t}`);return n}var x=Object.freeze({describe:"describe-xray-crm-capabilities",discoverSchema:"discover-xray-crm-schema",upsertPerson:"upsert-xray-person",appendActivity:"append-xray-journey-activity",testWrite:"test-xray-write",listOutcomes:"list-xray-outcomes",createFields:"create-xray-custom-fields",pipelineEvent:"apply-xray-pipeline-event"}),Bi=Object.freeze({hubspot:["crm.objects.deals.read"],salesforce:["api"],highlevel:["opportunities.readonly"],zoho:["ZohoCRM.modules.deals.READ"],pipedrive:["deals:read"],keap:["full"]});function da(e){let t=ee(e.provider);if(!qi.includes(t))throw new Error("analytics_crm_provider_required");let n=t,i=Ke(n),r=new Set(e.compiledActions??[]),s=new Set(e.advertisedTools??[]),a=new Set(e.grantedScopes??[]),o=e.documented===!0,c=e.normalizedAdapter===!0,d=x.listOutcomes,_=c&&r.has(d),u=s.has(d),y=Bi[n],E=y.every(w=>a.has(w)),b=new Set(e.tenantObjects??[]),V=!!(i.pipelineObject&&b.has(i.pipelineObject)),P=o&&_&&u&&E&&V,h=i.operations.webhook&&e.webhookAdvertised===!0,T=[...o?[]:["official_documentation"],...c?[]:["normalized_adapter"],...r.has(d)?[]:[`compiled_action:${d}`],...u?[]:[`connection_tool:${d}`],...y.filter(w=>!a.has(w)).map(w=>`scope:${w}`),...!V&&i.pipelineObject?[`tenant_object:${i.pipelineObject}`]:[]],m=P&&h?"incremental_and_webhook":P?"incremental_only":h?"webhook_only":e.manualBackfillAvailable===!0?"manual_backfill":"unavailable",K=(w,A)=>i.operations[w]&&r.has(A)&&s.has(A);return{provider:n,mode:m,canSyncNow:P,canSchedule:P,canProvisionFields:K("create_fields",x.createFields),canUpsertPerson:K("upsert_person",x.upsertPerson),canApplyPipelineEvent:K("pipeline_write",x.pipelineEvent),supportedObjects:e.tenantObjects?i.supportedObjects.filter(w=>b.has(w)):[],evidence:{documented:o,normalizedAdapter:c,advertisedTools:[...s].sort(),grantedScopes:[...a].sort(),missingRequirements:T,tenantSchemaFingerprint:e.tenantSchemaFingerprint??null,evaluatedAt:e.evaluatedAt??new Date().toISOString()},caveats:i.caveats}}var En=[{internalName:"xray_person_id",label:"X-Ray Person ID",type:"string",writable:!0},{internalName:"xray_journey_url",label:"X-Ray Detailed Journey",type:"url",writable:!0},{internalName:"xray_first_touch",label:"X-Ray First Touch",type:"string",writable:!0},{internalName:"xray_last_non_direct_touch",label:"X-Ray Last Non-Direct Touch",type:"string",writable:!0},{internalName:"xray_converting_touch",label:"X-Ray Converting Touch",type:"string",writable:!0},{internalName:"xray_landing_page",label:"X-Ray Landing Page",type:"url",writable:!0},{internalName:"xray_conversion_page",label:"X-Ray Conversion Page",type:"url",writable:!0},{internalName:"xray_session_count",label:"X-Ray Session Count",type:"number",writable:!0},{internalName:"xray_event_count",label:"X-Ray Event Count",type:"number",writable:!0},{internalName:"xray_consent_receipt_ref",label:"X-Ray Consent Receipt",type:"string",writable:!0}];function te(e,t){return`${e}_${vn("sha256").update(JSON.stringify(t)).digest("hex").slice(0,24)}`}function bt(e){return vn("sha256").update(JSON.stringify(e)).digest("hex")}function Vi(e){return bt([...e].sort((t,n)=>`${t.object}:${t.internalName}`.localeCompare(`${n.object}:${n.internalName}`)))}function N(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function C(e,t=300){return typeof e=="string"&&e.trim()?e.trim().slice(0,t):void 0}function Ki(e){let t=C(e,80)?.toLowerCase()??"";return["number","integer","double","currency","decimal","percent","monetary"].some(n=>t.includes(n))?"number":["datetime","date","timestamp"].some(n=>t.includes(n))?"datetime":["bool","checkbox"].some(n=>t.includes(n))?"boolean":["url","uri"].some(n=>t.includes(n))?"url":"string"}function me(e){let t=C(e.object,100),n=C(e.internalName,160),i=C(e.label,160);return!t||!n||!i?null:{object:t,internalName:n,label:i,type:Ki(e.type),writable:e.writable,...e.required?{required:!0}:{}}}function Et(e){return Array.isArray(e)?e.flatMap(t=>{if(!N(t))return[];let n=C(t.id,240),i=C(t.name??t.label,240);if(!n||!i)return[];let r=Array.isArray(t.stages)?t.stages.flatMap(s=>{if(!N(s))return[];let a=C(s.id,240),o=C(s.name??s.label,240);return a&&o?[{id:a,name:o}]:[]}):[];return[{id:n,name:i,stages:r}]}):[]}function Ji(e,t){let n=N(t)?t:{},i=[],r=[],s=Et(n.pipelines),a=o=>{o&&i.push(o)};if(e==="hubspot"){for(let[o,c]of[["contacts","contacts"],["deals","deals"]])if(Array.isArray(n[o])){r.push(c);for(let d of n[o])N(d)&&a(me({object:c,internalName:d.name,label:d.label,type:d.type,writable:d.archived!==!0&&d.providerDefined!==!0}))}}else if(e==="salesforce"){if(Array.isArray(n.objects))for(let o of n.objects){if(!N(o))continue;let c=C(o.object,100);if(!c)continue;r.push(c);let d=N(o.permissions)?o.permissions:{};if(Array.isArray(o.fields))for(let _ of o.fields)N(_)&&a(me({object:c,internalName:_.name,label:_.label,type:_.type,writable:d.update===!0&&_.updateable===!0,required:_.required===!0}))}}else if(e==="highlevel"){if(Array.isArray(n.customFields)){for(let o of n.customFields)if(N(o)){let c=o.model==="opportunity"?"opportunities":"contacts";r.push(c),a(me({object:c,internalName:o.fieldKey??o.id,label:o.name,type:o.dataType,writable:!0}))}}}else if(e==="zoho"){let o=Array.isArray(n.moduleResults)?n.moduleResults:[n];for(let c of o){if(!N(c))continue;let d=C(c.module,100);if(d&&(r.push(d),Array.isArray(c.fields)))for(let _ of c.fields)N(_)&&a(me({object:d,internalName:_.apiName,label:_.label,type:_.dataType,writable:_.writable===!0,required:_.required===!0}))}}else if(e==="pipedrive"){if(Array.isArray(n.fields)){for(let c of n.fields)if(N(c)){let d=c.objectType==="deal"?"deals":"persons";r.push(d),a(me({object:d,internalName:c.code,label:c.name,type:c.fieldType,writable:c.writable===!0}))}}let o=Array.isArray(n.stages)?n.stages:[];s=Et(Array.isArray(n.pipelines)?n.pipelines.map(c=>N(c)?{...c,id:String(c.id??""),stages:o.filter(d=>N(d)&&String(d.pipelineId)===String(c.id)).map(d=>({...d,id:String(d.id??"")}))}:c):[])}else if(e==="keap"){if(r.push("contacts"),Array.isArray(n.contactFields))for(let c of n.contactFields)N(c)&&a(me({object:"contacts",internalName:c.fieldName??c.id,label:c.label,type:c.fieldType,writable:!0}));let o=Array.isArray(n.opportunityStages)?n.opportunityStages:[];o.length&&(s=[{id:"opportunities",name:"Opportunities",stages:Et([{id:"opportunities",name:"Opportunities",stages:o}])[0]?.stages??[]}])}return{fields:i,objects:[...new Set(r)],pipelines:s,...Array.isArray(n.allowedOperations)?{allowedOperations:n.allowedOperations.filter(o=>typeof o=="string")}:{}}}function zi(e){return!N(e)||!Array.isArray(e.fields)?[]:e.fields.flatMap(t=>{if(!N(t))return[];let n=C(t.object,100),i=C(t.internalName??t.internal_name,160);if(!n||!i)return[];let r=C(t.id??t.providerFieldId,240);return[{object:n,internalName:i,...r?{providerFieldId:r}:{}}]})}function Je(e){let t=ee(e);if(t==="callrail"||t==="twilio"||t==="ctm")throw new Error("analytics_crm_provider_required");return t}function ma(e){let t=Je(e.provider),n=_e(t,"discover_schema");if(!n.personObject||!e.connectionId.trim())throw new Error("analytics_crm_provisioning_invalid_scope");let i={},r=[];for(let d of En){let _=e.fields.find(u=>u.object===n.personObject&&(u.internalName.toLowerCase()===d.internalName||u.label.toLowerCase()===d.label.toLowerCase()));_?_.writable&&(i[d.internalName]=_.internalName):r.push({...d,object:n.personObject})}let s=e.now??new Date().toISOString(),a=Vi(e.fields),o={provider:t,connectionId:e.connectionId,existingMappings:i,proposedFields:r,schemaFingerprint:a},c=bt(o);return{planId:te("crmplan",o),provider:t,connectionId:e.connectionId,personObject:n.personObject,existingMappings:i,proposedFields:r,fallback:n.safeFallback==="activity"?"activity":"note",permissionRequirements:["schema:read",...r.length?["schema:write"]:[],"records:write"],schemaFingerprint:a,requestFingerprint:c,createdAt:s}}async function pa(e){let t=_e(e.plan.provider,"create_fields"),n=te("crmplan",{provider:e.plan.provider,connectionId:e.plan.connectionId,existingMappings:e.plan.existingMappings,proposedFields:e.plan.proposedFields,schemaFingerprint:e.plan.schemaFingerprint}),i=bt({provider:e.plan.provider,connectionId:e.plan.connectionId,existingMappings:e.plan.existingMappings,proposedFields:e.plan.proposedFields,schemaFingerprint:e.plan.schemaFingerprint});if(e.plan.planId!==n||e.plan.personObject!==t.personObject||e.plan.requestFingerprint!==i)throw new Error("analytics_crm_provisioning_plan_tampered");let r=new Set(En.map(c=>c.internalName));if(e.plan.proposedFields.some(c=>c.object!==t.personObject||!r.has(c.internalName)))throw new Error("analytics_crm_provisioning_plan_tampered");if(!Number.isInteger(e.approvedByUserId)||e.approvedByUserId<=0)throw new Error("analytics_crm_provisioning_approval_required");let s=new Set(e.approvedFieldInternalNames);if([...s].some(c=>!e.plan.proposedFields.some(d=>d.internalName===c)))throw new Error("analytics_crm_provisioning_field_not_in_plan");let a=e.plan.proposedFields.filter(c=>s.has(c.internalName)),o=te("crmreceipt",{planId:e.plan.planId,fields:[...s].sort()});try{let c=a.length?await e.dependencies.callAction({connectionId:e.plan.connectionId,action:x.createFields,payload:{provider:e.plan.provider,fields:a},idempotencyKey:o}):{fields:[]};return{receiptId:o,planId:e.plan.planId,provider:e.plan.provider,connectionId:e.plan.connectionId,status:"approved",createdFields:zi(c),existingMappings:e.plan.existingMappings,cleanup:{supported:!1,instructions:"Created empty X-Ray fields may be removed manually after confirming they are not populated."},planFingerprint:e.plan.requestFingerprint,occurredAt:e.now??new Date().toISOString()}}catch(c){return{receiptId:o,planId:e.plan.planId,provider:e.plan.provider,connectionId:e.plan.connectionId,status:"failed",createdFields:[],existingMappings:e.plan.existingMappings,cleanup:{supported:!1,instructions:"No cleanup was attempted."},planFingerprint:e.plan.requestFingerprint,occurredAt:e.now??new Date().toISOString(),errorCode:C(c instanceof Error?c.message:c,80)?.replace(/[^a-zA-Z0-9_:-]/g,"_")||"analytics_crm_provisioning_failed"}}}async function ya(e){if(e.receipt.status!=="approved")throw new Error("analytics_crm_provisioning_not_approved");_e(e.receipt.provider,"test_write");let t=te("crmtest",e.receipt.receiptId),n=await e.dependencies.callAction({connectionId:e.receipt.connectionId,action:x.testWrite,payload:{provider:e.receipt.provider,marker:"X-Ray reversible connection test",cleanupRequested:!0},idempotencyKey:t});if(!N(n)||n.ok!==!0)throw new Error("analytics_crm_test_write_not_verified");return{...e.receipt,status:"tested",occurredAt:e.now??new Date().toISOString()}}async function bn(e){let t=Je(e.provider);if(_e(t,"upsert_person"),e.person.identityTier!=="confirmed"||!e.person.deterministicIdentity.value.trim())throw new Error("analytics_crm_person_requires_confirmed_identity");let n=te("crmperson",{provider:t,connectionId:e.connectionId,personId:e.person.personId,identity:e.person.deterministicIdentity,consent:e.person.consentReceiptRef}),i={provider:t,identity:e.person.deterministicIdentity,contact:e.person.contact,journey:{personId:e.person.personId,firstTouch:e.person.firstTouch,lastNonDirectTouch:e.person.lastNonDirectTouch,convertingTouch:e.person.convertingTouch,landingPage:e.person.landingPage,conversionPage:e.person.conversionPage,sessionCount:e.person.sessionCount,eventCount:e.person.eventCount,campaignClickCoverage:e.person.campaignClickCoverage,consentReceiptRef:e.person.consentReceiptRef,detailedJourneyUrl:e.person.detailedJourneyUrl},mapping:e.mapping??{},createDeal:!1},r=await e.dependencies.callAction({connectionId:e.connectionId,action:x.upsertPerson,payload:i,idempotencyKey:n}),s=N(r)?C(r.recordRef??r.id,240):void 0;return{receiptId:n,provider:t,operation:"person_upsert",dealCreated:!1,...s?{providerRecordRef:s}:{}}}async function An(e){let t=Je(e.provider);if(_e(t,"pipeline_write"),e.person.identityTier!=="confirmed")throw new Error("analytics_crm_pipeline_requires_confirmed_identity");if(e.event.eventName!==e.mapping.eventName)throw new Error("analytics_crm_pipeline_event_not_mapped");if(e.mapping.valueSource==="verified_revenue"&&(!Number.isSafeInteger(e.event.verifiedRevenueMinor)||e.event.verifiedRevenueMinor<0))throw new Error("analytics_crm_pipeline_verified_revenue_required");let n=te("crmpipeline",{provider:t,connectionId:e.connectionId,eventId:e.event.eventId,mappingVersion:e.mapping.version});return await e.dependencies.callAction({connectionId:e.connectionId,action:x.pipelineEvent,payload:{provider:t,command:"apply_pipeline_event",person:{personId:e.person.personId,identity:e.person.deterministicIdentity},event:e.event,mapping:e.mapping},idempotencyKey:n}),{receiptId:n,provider:t,operation:"pipeline_event",dealCreatedOrUpdated:!0}}async function ga(e){let t=Je(e.provider),n=Ke(t),i=t==="highlevel"?{locationId:e.discoveryContext?.locationId}:t==="zoho"?{module:e.discoveryContext?.zohoModules?.[0]??"Contacts"}:{};if(t==="highlevel"&&!C(i.locationId,128))throw new Error("analytics_crm_highlevel_location_required");let r=(y,E)=>e.dependencies.callAction({connectionId:e.connectionId,action:x.discoverSchema,payload:y,idempotencyKey:te("crmdiscover",{provider:t,connectionId:e.connectionId,actionPayload:y,suffix:E})}),s=await r(i);if(t==="zoho"){let y=[...new Set(e.discoveryContext?.zohoModules??["Contacts","Deals"])],E=[s];for(let b of y.slice(1))E.push(await r({module:b},b));s={moduleResults:E}}let a=Ji(t,s),o=a.fields,c=Object.entries(n.operations).filter(([,y])=>y).map(([y])=>y),d=a.allowedOperations?new Set(a.allowedOperations.map(y=>C(y,80)).filter(y=>!!y)):null,_=d?c.filter(y=>d.has(y)):c,u=new Set(n.supportedObjects);return{provider:t,publicProfile:n,connectionId:e.connectionId,fields:o,objects:a.objects.length?a.objects.filter(y=>u.has(y)):n.supportedObjects,pipelines:a.pipelines,allowedOperations:_,discoveredAt:e.now??new Date().toISOString()}}var Xi=25,Gi=25e3,In=q.object({person:ot,event:ct.nullable(),mapping:q.object({version:q.number().int().positive(),eventName:q.string(),pipelineId:q.string(),stageId:q.string(),operation:q.enum(["create_or_update_deal","update_stage"]),valueSource:q.enum(["none","verified_revenue"]),activationEligible:q.boolean()}).strict().nullable()}).strict();function Zi(e,t){return`${e}_${Yi("sha256").update(JSON.stringify(t)).digest("hex").slice(0,24)}`}function ze(e){return tn(e)??void 0}function Qi(e,t,n){return e.purpose==="necessary_form_fulfillment"?t.eventKind==="form":e.purpose==="site_analytics"?n.siteAnalytics:n.advertisingMeasurement}function L(e,t){return e.has(t)}function er(e){let t=new Set(e.policy.allowedFields),n=e.contact?{...L(t,"contact.email")&&e.contact.email?{email:e.contact.email}:{},...L(t,"contact.phone")&&e.contact.phone?{phone:e.contact.phone}:{},...L(t,"contact.firstName")&&e.contact.firstName?{firstName:e.contact.firstName}:{},...L(t,"contact.lastName")&&e.contact.lastName?{lastName:e.contact.lastName}:{}}:void 0,i=[e.event.sourceName??e.event.source,e.event.medium,e.event.campaign].filter(Boolean).map(a=>String(a).slice(0,240)).join(" / "),r=e.policy.purpose==="advertising_measurement"&&e.consent.advertisingMeasurement?G(e.event.clickIds):{},s=Object.entries(r).filter(([a])=>L(t,`clickIds.${a}`)).map(([a])=>a).sort().join(",");return ot.parse({identityTier:"confirmed",personId:e.personId,deterministicIdentity:{kind:"crm_id",value:e.crmPersonRef},...n&&Object.keys(n).length?{contact:n}:{},...L(t,"attribution.firstTouch")&&i?{firstTouch:i}:{},...L(t,"attribution.lastNonDirectTouch")&&i?{lastNonDirectTouch:i}:{},...L(t,"attribution.convertingTouch")&&i?{convertingTouch:i}:{},...L(t,"attribution.landingPage")&&ze(e.event.canonicalUrl)?{landingPage:ze(e.event.canonicalUrl)}:{},...L(t,"attribution.conversionPage")&&ze(e.event.canonicalUrl)?{conversionPage:ze(e.event.canonicalUrl)}:{},sessionCount:e.sessionCount,eventCount:e.eventCount,...s?{campaignClickCoverage:s}:{},consentReceiptRef:e.consent.receiptId??`purpose:${e.policy.purpose}`,detailedJourneyUrl:`https://mcpscraper.dev/dashboard/analytics/people/${encodeURIComponent(e.personId)}`})}async function Sn(e){let t=(await _n({siteId:e.siteId,db:e.db})).filter(s=>s.enabled&&Qi(s,e.event,e.consent)).filter(s=>s.selectedEvents.length===0||s.selectedEvents.includes(e.event.eventName));if(!t.length)return{queued:0,reused:0};let n=await e.db.query(`SELECT p.crm_person_ref,
229
+ (SELECT count(DISTINCT s.id)::text FROM analytics_sessions s WHERE s.site_id=p.site_id AND s.person_id=p.id) session_count,
230
+ ((SELECT count(*) FROM analytics_events e WHERE e.site_id=p.site_id AND e.person_id=p.id)+
231
+ (SELECT count(*) FROM analytics_external_events x WHERE x.site_id=p.site_id AND x.person_id=p.id))::text event_count
232
+ FROM analytics_people p WHERE p.site_id=$1 AND p.id=$2`,[e.siteId,e.personId]);if(!n.rows[0])return{queued:0,reused:0};let i=0,r=0;for(let s of t){if(s.kind==="pipeline_event"&&(!s.mapping||s.mapping.eventName!==e.event.eventName))continue;let a=er({personId:e.personId,crmPersonRef:n.rows[0].crm_person_ref,event:e.event,policy:s,consent:e.consent,sessionCount:Number(n.rows[0].session_count),eventCount:Number(n.rows[0].event_count)}),o=s.kind==="pipeline_event"?ct.parse({eventId:e.canonicalEventId,eventName:e.event.eventName,occurredAt:e.event.occurredAt,...s.mapping?.valueSource==="verified_revenue"&&e.event.valueMinor!==void 0?{verifiedRevenueMinor:e.event.valueMinor,currency:e.event.currency?.toUpperCase()}:{}}):null;if(s.kind==="pipeline_event"&&s.mapping?.valueSource==="verified_revenue"&&o?.verifiedRevenueMinor===void 0)continue;let c=In.parse({person:a,event:o,mapping:s.mapping}),d=`${s.policyId}:${s.version}:${e.canonicalEventId}`,_=Be({policy:s,personId:e.personId,event:o,projection:a});(await mn({jobId:Zi("crmjob",{idempotencyKey:d}),policy:s,personId:e.personId,sourceEventId:e.canonicalEventId,payloadCiphertext:de(JSON.stringify(c),"crm-outbound-payload"),idempotencyKey:d,requestFingerprint:_,db:e.db})).reused?r+=1:i+=1}return{queued:i,reused:r}}var Ye={async resolveIdentity(e){return(await Mt(e))?.email??null},async callAction(e){return Yt({identity:e.identity,connectionId:e.connectionId,tool:e.action,input:e.payload,classification:"action",operationKind:"sync",requestId:e.idempotencyKey})},authorizeSend:e=>yn(e),cancelUnauthorized:e=>gn(e)};function tr(e){let t=e instanceof Error?e.message:String(e);return/schema_changed|mapping_version|not_mapped|verified_revenue|required|disabled/i.test(t)?{code:"crm_outbound_contract_invalid",retryable:!1,rateLimited:!1}:/429|rate.?limit|too many/i.test(t)?{code:"crm_outbound_rate_limited",retryable:!0,rateLimited:!0}:/401|403|scope|permission|unauthorized|forbidden/i.test(t)?{code:"crm_outbound_authorization_required",retryable:!1,rateLimited:!1}:{code:"crm_outbound_provider_unavailable",retryable:!0,rateLimited:!1}}async function Ra(e=10,t=Date.now()+Gi,n=Ye){let i=Math.max(1,Math.min(Xi,e)),r=v();await r.query(`INSERT INTO analytics_crm_outbound_worker_state(worker_key,last_started_at,last_error_code,updated_at)
233
+ VALUES('default',now(),NULL,now()) ON CONFLICT(worker_key) DO UPDATE SET last_started_at=now(),last_error_code=NULL,updated_at=now()`);try{let s=await pn({limit:i,leaseSeconds:90}),a=0,o=0,c=0;for(let u of s){if(Date.now()>=t){await Ve({jobId:u.job.jobId,status:"retrying",safeErrorCode:"crm_outbound_tick_deadline",retryAt:new Date(Date.now()+6e4).toISOString()}),o+=1;continue}try{if(!u.schemaCurrent)throw new Error("analytics_crm_schema_changed");let y=he(u.payloadCiphertext,"crm-outbound-payload");if(!y)throw new Error("crm_outbound_payload_unreadable");let E=In.parse(JSON.parse(y)),b=await n.resolveIdentity(u.ownerUserId);if(!b)throw new Error("crm_outbound_owner_identity_unavailable");if(!await(n.authorizeSend??(n===Ye?Ye.authorizeSend:async()=>!0))({jobId:u.job.jobId,attempt:u.job.attempts,purpose:u.purpose,consentReceiptRef:E.person.consentReceiptRef})){await(n.cancelUnauthorized??Ye.cancelUnauthorized)({jobId:u.job.jobId,attempt:u.job.attempts,safeErrorCode:"crm_outbound_privacy_fence_closed"}),c+=1;continue}let h={callAction:({connectionId:m,action:K,payload:w,idempotencyKey:A})=>n.callAction({identity:b,connectionId:m,action:K,payload:w,idempotencyKey:A})},T=u.job.kind==="person_summary"?await bn({provider:u.provider,connectionId:u.connectionId,person:E.person,dependencies:h}):await An({provider:u.provider,connectionId:u.connectionId,person:E.person,event:E.event,mapping:E.mapping,dependencies:h});await Ve({jobId:u.job.jobId,status:"accepted",externalReceiptId:T.receiptId}),a+=1}catch(y){let E=tr(y),b=new Date(Date.now()+(E.rateLimited?30*6e4:Math.min(360*6e4,3e4*2**u.job.attempts))).toISOString();await Ve({jobId:u.job.jobId,status:E.retryable?"retrying":"failed",safeErrorCode:E.code,...E.retryable?{retryAt:b}:{}}),E.retryable&&u.job.attempts<5?o+=1:c+=1}}let d=await r.query("SELECT count(*)::int due FROM analytics_crm_outbound_jobs WHERE state IN ('pending','retrying') AND next_attempt_at <= now()"),_={claimed:s.length,delivered:a,retried:o,deadLettered:c,remainingDue:Number(d.rows[0]?.due??0)};return await r.query(`UPDATE analytics_crm_outbound_worker_state SET last_completed_at=now(),last_success_at=now(),
234
+ last_error_code=NULL,last_claimed=$1,last_remaining_due=$2,updated_at=now() WHERE worker_key='default'`,[_.claimed,_.remainingDue]),_}catch(s){throw await r.query(`UPDATE analytics_crm_outbound_worker_state SET last_completed_at=now(),last_error_code='crm_outbound_worker_failed',updated_at=now()
235
+ WHERE worker_key='default'`).catch(()=>{}),s}}function hn(e,t){return e&&(t??0)>0}import{createHash as Co,createHmac as Dt,generateKeyPairSync as Wr,randomUUID as se,sign as Br}from"crypto";import Vr from"twilio";import{z as l}from"zod";import{z as R}from"zod";var nr=new Set(["AED","AFN","ALL","AMD","ANG","AOA","ARS","AUD","AWG","AZN","BAM","BBD","BDT","BGN","BHD","BIF","BMD","BND","BOB","BOV","BRL","BSD","BTN","BWP","BYN","BZD","CAD","CDF","CHE","CHF","CHW","CLF","CLP","CNY","COP","COU","CRC","CUP","CVE","CZK","DJF","DKK","DOP","DZD","EGP","ERN","ETB","EUR","FJD","FKP","GBP","GEL","GHS","GIP","GMD","GNF","GTQ","GYD","HKD","HNL","HTG","HUF","IDR","ILS","INR","IQD","IRR","ISK","JMD","JOD","JPY","KES","KGS","KHR","KMF","KPW","KRW","KWD","KYD","KZT","LAK","LBP","LKR","LRD","LSL","LYD","MAD","MDL","MGA","MKD","MMK","MNT","MOP","MRU","MUR","MVR","MWK","MXN","MXV","MYR","MZN","NAD","NGN","NIO","NOK","NPR","NZD","OMR","PAB","PEN","PGK","PHP","PKR","PLN","PYG","QAR","RON","RSD","RUB","RWF","SAR","SBD","SCR","SDG","SEK","SGD","SHP","SLE","SOS","SRD","SSP","STN","SVC","SYP","SZL","THB","TJS","TMT","TND","TOP","TRY","TTD","TWD","TZS","UAH","UGX","USD","USN","UYI","UYU","UYW","UZS","VED","VES","VND","VUV","WST","XAF","XAG","XAU","XBA","XBB","XBC","XBD","XCD","XDR","XOF","XPD","XPF","XPT","XSU","XTS","XUA","XXX","YER","ZAR","ZMW","ZWG"]),Re=R.string().trim().length(3).transform(e=>e.toUpperCase()).refine(e=>nr.has(e),"Provide a valid ISO-4217 currency."),Xe=R.number().int("Money must be represented as integer minor units.").min(0,"Money cannot be negative.").max(Number.MAX_SAFE_INTEGER),Ge=R.string().trim().min(1).max(80).default("1"),ir=R.enum(["semantic_markup","source_event","crm_field","transaction"]),rr=R.object({mode:R.literal("none"),ruleVersion:Ge}).strict(),sr=R.object({mode:R.literal("fixed"),valueMinor:Xe,currency:Re,ruleVersion:Ge}).strict(),ar=R.object({mode:R.literal("dynamic"),source:ir,currency:Re,fallbackValueMinor:Xe.nullable().default(null),ruleVersion:Ge}).strict(),Nn=R.discriminatedUnion("mode",[rr,sr,ar]),or=R.object({linkedEventName:R.string().trim().regex(/^[a-z][a-z0-9_]{1,79}$/),relation:R.enum(["before","after","either"]),intervalSeconds:R.number().int().min(0).max(10*365*24*60*60).nullable(),overrideValueMinor:Xe,currency:Re}).strict(),Ze=R.object({targetEventName:R.string().trim().regex(/^[a-z][a-z0-9_]{1,79}$/),base:Nn,conditionalOverride:or.nullable().default(null),ruleVersion:Ge}).strict().superRefine((e,t)=>{let n=e.conditionalOverride;n&&e.base.mode!=="none"&&n.currency!==e.base.currency&&t.addIssue({code:"custom",path:["conditionalOverride","currency"],message:"Conditional override currency must match the base value currency."})});function ne(e,t){let n=Date.parse(e);if(!Number.isFinite(n))throw new Error(`${t} must be an ISO timestamp.`);return n}function cr(e,t){let n=Xe.parse(e.valueMinor),i=Cn(t,e.currency);return ne(e.observedAt,"observedAt"),{valueMinor:n,currency:i,observedAt:new Date(e.observedAt).toISOString()}}function dr(e){return Nn.parse(e)}function Cn(e,t){let i=(Array.isArray(e)?e:[e,t]).filter(r=>typeof r=="string"&&r.trim().length>0).map(r=>Re.parse(r));if(i.length===0)throw new Error("A currency is required for a monetary value.");if(new Set(i).size!==1)throw new Error(`Mixed currencies are not compatible: ${[...new Set(i)].join(", ")}.`);return i[0]}function lr(e,t={}){let n=dr(e);if(n.mode==="none")return{valueMinor:null,currency:null,provenance:{mode:"none",source:null,observedAt:null,fallback:!1,ruleVersion:n.ruleVersion,resolution:"absent",baseValueMinor:null,resolvedValueMinor:null,currency:null,linkedEvent:null}};if(n.mode==="fixed"){let s=t.observedAt?new Date(ne(t.observedAt,"observedAt")).toISOString():null;return{valueMinor:n.valueMinor,currency:n.currency,provenance:{mode:"fixed",source:null,observedAt:s,fallback:!1,ruleVersion:n.ruleVersion,resolution:"fixed",baseValueMinor:n.valueMinor,resolvedValueMinor:n.valueMinor,currency:n.currency,linkedEvent:null}}}let i=t.observations?.[n.source];if(i){let s=cr(i,n.currency);return{valueMinor:s.valueMinor,currency:s.currency,provenance:{mode:"dynamic",source:n.source,observedAt:s.observedAt,fallback:!1,ruleVersion:n.ruleVersion,resolution:"dynamic",baseValueMinor:s.valueMinor,resolvedValueMinor:s.valueMinor,currency:s.currency,linkedEvent:null}}}let r=n.fallbackValueMinor;return{valueMinor:r,currency:r===null?null:n.currency,provenance:{mode:"dynamic",source:n.source,observedAt:null,fallback:r!==null,ruleVersion:n.ruleVersion,resolution:r===null?"absent":"fallback",baseValueMinor:r,resolvedValueMinor:r,currency:r===null?null:n.currency,linkedEvent:null}}}function ur(e,t,n){let i=ne(t.occurredAt,"targetEvent.occurredAt"),r=n.filter(s=>{if(s.eventName!==e.linkedEventName)return!1;let o=(ne(s.occurredAt,"linkedEvent.occurredAt")-i)/1e3;return e.relation==="before"&&o>0||e.relation==="after"&&o<0?!1:e.intervalSeconds===null||Math.abs(o)<=e.intervalSeconds});return r.sort((s,a)=>{let o=Math.abs(ne(s.occurredAt,"linkedEvent.occurredAt")-i),c=Math.abs(ne(a.occurredAt,"linkedEvent.occurredAt")-i);return o-c||s.eventId.localeCompare(a.eventId)}),r[0]??null}function Rn(e,t){let n=Ze.parse(e);if(t.targetEvent.eventName!==n.targetEventName)throw new Error(`Value rule targets ${n.targetEventName}, not ${t.targetEvent.eventName}.`);let i=lr({...n.base,ruleVersion:n.ruleVersion},t);if(!n.conditionalOverride)return i;let r=ur(n.conditionalOverride,t.targetEvent,t.linkedEvents);if(!r)return i;if(t.providerOperation==="restatement"&&r.identityTier!=="confirmed")throw new Error("Candidate-only evidence cannot be used for a provider restatement.");let s=i.currency?Cn(i.currency,n.conditionalOverride.currency):Re.parse(n.conditionalOverride.currency);return{valueMinor:n.conditionalOverride.overrideValueMinor,currency:s,provenance:{...i.provenance,ruleVersion:n.ruleVersion,resolution:"conditional_override",resolvedValueMinor:n.conditionalOverride.overrideValueMinor,currency:s,linkedEvent:{eventId:r.eventId,eventName:r.eventName,occurredAt:new Date(ne(r.occurredAt,"linkedEvent.occurredAt")).toISOString(),identityTier:r.identityTier??"candidate"}}}}var wn=l.object({gclid:l.string().trim().max(500).optional(),gbraid:l.string().trim().max(500).optional(),wbraid:l.string().trim().max(500).optional(),fbclid:l.string().trim().max(500).optional(),fbp:l.string().trim().max(500).optional(),fbc:l.string().trim().max(500).optional(),ttclid:l.string().trim().max(500).optional(),rdt_cid:l.string().trim().max(500).optional(),msclkid:l.string().trim().max(500).optional(),li_fat_id:l.string().trim().max(500).optional(),snapclid:l.string().trim().max(500).optional()}).strict(),_r=l.object({site_analytics:l.enum(["granted","denied","unknown"]),enhanced_matching:l.enum(["granted","denied","unknown"]),advertising_measurement:l.enum(["granted","denied","unknown"])}).strict(),mr=l.object({analytics:l.enum(["granted","denied","unknown"]).default("unknown"),adUserData:l.enum(["granted","denied","unknown"]).default("unknown"),adPersonalization:l.enum(["granted","denied","unknown"]).default("unknown"),globalPrivacyControl:l.boolean().default(!1),occurredAt:l.string().datetime(),receiptId:l.string().uuid().optional(),policyVersion:l.number().int().positive().optional(),purposes:_r.optional()}).strict().transform(e=>({...e,purposes:e.purposes??{site_analytics:e.analytics,enhanced_matching:e.adUserData,advertising_measurement:e.adPersonalization}})),On=l.enum(["browser","form","call","crm","transaction","custom_server","conversion","delivery"]),pr=l.object({schemaVersion:l.literal(1).default(1),eventId:l.string().trim().min(8).max(200),sourceEventId:l.string().trim().min(1).max(300).optional(),source:l.string().trim().min(1).max(80).default("xray"),sourceAccountRef:l.string().trim().max(240).optional(),eventKind:On,eventName:l.string().trim().regex(/^[a-z][a-z0-9_]{1,79}$/),occurredAt:l.string().datetime(),visitorId:l.string().trim().max(160).optional(),sessionId:l.string().trim().max(160).optional(),personRef:l.string().trim().max(240).optional(),orderId:l.string().trim().max(240).optional(),callId:l.string().trim().max(240).optional(),dealId:l.string().trim().max(240).optional(),path:l.string().trim().max(2e3).optional(),canonicalUrl:l.string().url().max(3e3).optional(),sourceName:l.string().trim().max(180).optional(),medium:l.string().trim().max(180).optional(),campaign:l.string().trim().max(240).optional(),clickIds:wn.optional(),consent:mr.optional(),valueMinor:l.number().int().min(0).max(Number.MAX_SAFE_INTEGER).optional(),currency:l.string().trim().regex(/^[A-Za-z]{3}$/).optional(),properties:l.record(l.string().max(80),l.unknown()).default({})}).strict(),Qe=pr.extend({sourceEventId:l.string().trim().min(1).max(300),source:l.string().trim().min(1).max(80),sourceAccountRef:l.string().trim().min(1).max(240)}),$a=l.object({sourceEventId:l.string().trim().min(1).max(300),source:l.string().trim().min(1).max(80),sourceAccountRef:l.string().trim().min(1).max(240),contactId:l.string().trim().max(240).optional(),dealId:l.string().trim().min(1).max(240),pipelineId:l.string().trim().max(240).optional(),stageId:l.string().trim().min(1).max(240),stageName:l.string().trim().max(240).optional(),occurredAt:l.string().datetime(),valueMinor:l.number().int().min(0).max(Number.MAX_SAFE_INTEGER).optional(),currency:l.string().trim().regex(/^[A-Za-z]{3}$/).optional(),visitorId:l.string().trim().max(160).optional(),sessionId:l.string().trim().max(160).optional(),clickIds:wn.optional(),email:l.string().email().max(320).optional(),phone:l.string().trim().max(80).optional(),orderId:l.string().trim().max(240).optional(),status:l.enum(["open","won","lost","spam","unknown"]).default("unknown")}).strict(),yr=l.object({eventName:l.string().regex(/^[a-z][a-z0-9_]{1,79}$/).optional(),eventKind:On.optional(),source:l.string().trim().max(80).optional(),stageId:l.string().trim().max(240).optional(),status:l.enum(["open","won","lost","spam","unknown"]).optional(),answered:l.boolean().optional(),minimumCallDurationSeconds:l.number().int().min(0).max(86400).optional(),excludedTags:l.array(l.string().trim().max(80)).max(30).default([]),requiredTags:l.array(l.string().trim().max(80)).max(30).default([])}).strict(),Dn=l.object({name:l.string().trim().min(1).max(120),conversionKind:l.string().trim().regex(/^[a-z][a-z0-9_]{1,79}$/),condition:yr,defaultValueMinor:l.number().int().min(0).max(Number.MAX_SAFE_INTEGER).optional(),defaultCurrency:l.string().trim().regex(/^[A-Za-z]{3}$/).default("USD"),valueRule:Ze.optional(),enabled:l.boolean().default(!0)}).strict();function gr(e){let t=e.trim().toLowerCase().replace(/\.$/,"");if(!t||t.includes("://")||/[/?#]/.test(t))return"";try{let n=new URL(`https://${t}`).hostname.toLowerCase().replace(/\.$/,"");return!n||n.length>253||n!=="localhost"&&!n.includes(".")||n.split(".").some(i=>!i||i.length>63||!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(i))?"":n}catch{return""}}var fr=l.string().trim().max(253).transform(gr).refine(Boolean,"Provide a valid bare hostname."),we=l.string().trim().max(80).regex(/^[A-Za-z][A-Za-z0-9_-]{0,79}$/).refine(e=>(e.match(/\d/g)?.length??0)<7,"Semantic dimensions cannot contain phone-like values.").transform(e=>e.toLowerCase()),vr=l.object({value:l.number().finite().min(0).max(1e12).optional(),currency:l.string().trim().regex(/^[A-Za-z]{3}$/).transform(e=>e.toUpperCase()).optional(),orderId:l.string().trim().min(1).max(200).optional(),contentId:l.string().trim().min(1).max(200).optional(),contentType:l.string().trim().min(1).max(80).optional(),placement:we.optional(),component:we.optional(),action:we.optional(),destination:we.optional(),plan:we.optional()}).strict(),At=l.object({name:l.string().trim().min(1).max(120),eventName:l.string().trim().regex(/^[a-z][a-z0-9_]{1,79}$/),triggerKind:l.enum(["page","click","form","semantic"]),path:l.string().trim().startsWith("/").max(2e3).optional(),pathMatch:l.enum(["exact","prefix"]).default("exact"),selector:l.string().trim().min(1).max(500).optional(),hostnames:l.array(fr).max(25).transform(e=>[...new Set(e)]).default([]),metadata:vr.optional(),valueRule:Ze.optional(),oncePerSession:l.boolean().default(!1),enabled:l.boolean().default(!0)}).strict().superRefine((e,t)=>{e.triggerKind==="page"&&!e.path&&t.addIssue({code:"custom",path:["path"],message:"Page triggers require a path."}),["click","form"].includes(e.triggerKind)&&!e.selector&&t.addIssue({code:"custom",path:["selector"],message:"Click and form triggers require a selector."}),e.triggerKind==="semantic"&&(e.path||e.selector)&&t.addIssue({code:"custom",path:["triggerKind"],message:"Semantic triggers use data-xray-event and cannot include a path or selector."})});import{createHmac as Er,randomUUID as It}from"crypto";import Va from"ipaddr.js";import{randomUUID as Tn}from"crypto";async function Ua(e){return(await v().query(`SELECT id, site_id, name, status, version, approved_at
236
+ FROM analytics_identity_namespaces
237
+ WHERE site_id=$1 AND status IN ('draft','approved','disabled')
238
+ ORDER BY CASE status WHEN 'approved' THEN 0 WHEN 'draft' THEN 1 ELSE 2 END,
239
+ version DESC, name, id`,[e.siteId])).rows.map(n=>({id:n.id,siteId:n.site_id,name:n.name,status:n.status,version:n.version,approvedAt:n.approved_at===null?null:new Date(n.approved_at).toISOString()}))}var xn={domain:"host",application:"server",phone:"call",crm:"crm"},Ln={host:"domain",server:"application",call:"phone",crm:"crm"};function $n(e,t){let n=t.trim().toLowerCase();if(!n||n.length>500)throw new f("analytics_namespace_source_invalid","Provide a valid namespace source.",400);if(e==="domain"&&(n.includes("://")||/[/?#]/.test(n)||!n.includes(".")))throw new f("analytics_namespace_domain_invalid","Provide a bare approved domain.",400);return n}async function Fa(e){let t=e.name.trim();if(!t||t.length>160)throw new f("analytics_namespace_name_invalid","Provide a namespace name.",400);let n=await v().connect();await n.query("BEGIN");try{await n.query("SELECT pg_advisory_xact_lock(hashtext($1))",[e.siteId]);let r=(await n.query(`SELECT id, site_id, name, status, version, approved_at
240
+ FROM analytics_identity_namespaces
241
+ WHERE site_id=$1 AND status IN ('draft','approved','disabled')
242
+ ORDER BY version DESC LIMIT 1`,[e.siteId])).rows[0];if(r){if(r.name!==t)throw new f("analytics_namespace_already_exists","This site already belongs to an identity namespace.",409);return await n.query("COMMIT"),{id:r.id,siteId:r.site_id,name:r.name,status:r.status,version:r.version,approvedAt:r.approved_at?.toISOString()??null}}let a=(await n.query(`INSERT INTO analytics_identity_namespaces(
243
+ id, site_id, name, status, approved_hosts, approved_sources, version, approved_by_user_id
244
+ ) VALUES ($1,$2,$3,'draft','[]'::jsonb,'[]'::jsonb,1,$4)
245
+ RETURNING id, site_id, name, status, version, approved_at`,[Tn(),e.siteId,t,e.createdByUserId])).rows[0];return await n.query("COMMIT"),{id:a.id,siteId:a.site_id,name:a.name,status:a.status,version:a.version,approvedAt:a.approved_at?.toISOString()??null}}catch(i){throw await n.query("ROLLBACK"),i}finally{n.release()}}async function ja(e){let t=$n(e.sourceKind,e.sourceRef),n=await v().connect();await n.query("BEGIN");try{let i=await n.query(`SELECT id, status FROM analytics_identity_namespaces
246
+ WHERE id=$1 AND site_id=$2 FOR UPDATE`,[e.namespaceId,e.siteId]);if(!i.rows[0])throw new f("analytics_namespace_not_found","Identity namespace not found.",404);if(["archived","disabled"].includes(i.rows[0].status))throw new f("analytics_namespace_inactive","Identity namespace is not active.",409);let r=xn[e.sourceKind],s=await n.query(`INSERT INTO analytics_identity_sources(id, namespace_id, site_id, source_kind, source_ref, status)
247
+ VALUES ($1,$2,$3,$4,$5,'approved')
248
+ ON CONFLICT(namespace_id, source_kind, source_ref) DO UPDATE
249
+ SET status='approved', revoked_at=NULL
250
+ RETURNING id, namespace_id, site_id, source_kind, source_ref, status, created_at, revoked_at`,[Tn(),e.namespaceId,e.siteId,r,t]);await n.query(`UPDATE analytics_identity_namespaces
251
+ SET status='approved', approved_by_user_id=$3, approved_at=COALESCE(approved_at, now()),
252
+ approved_hosts=CASE WHEN $4='host'
253
+ THEN (SELECT jsonb_agg(DISTINCT value) FROM jsonb_array_elements_text(approved_hosts || to_jsonb($5::text)) value)
254
+ ELSE approved_hosts END,
255
+ approved_sources=(SELECT jsonb_agg(DISTINCT value) FROM jsonb_array_elements_text(approved_sources || to_jsonb(($4 || ':' || $5)::text)) value),
256
+ updated_at=now()
257
+ WHERE id=$1 AND site_id=$2`,[e.namespaceId,e.siteId,e.approvedByUserId,r,t]),await n.query("COMMIT");let a=s.rows[0];return{id:a.id,namespaceId:a.namespace_id,siteId:a.site_id,sourceKind:Ln[a.source_kind],sourceRef:a.source_ref,status:a.status,createdAt:a.created_at instanceof Date?a.created_at.toISOString():new Date(a.created_at).toISOString(),revokedAt:a.revoked_at==null?null:a.revoked_at instanceof Date?a.revoked_at.toISOString():new Date(a.revoked_at).toISOString()}}catch(i){throw await n.query("ROLLBACK"),i}finally{n.release()}}async function et(e){let t=v();if(!(await t.query(`SELECT id FROM analytics_identity_namespaces
258
+ WHERE id=$1 AND site_id=$2 AND status='approved'`,[e.namespaceId,e.siteId])).rows[0])throw new f("analytics_namespace_boundary","The identity namespace is not approved for this site.",403);let i=[...new Set(e.identityNodeIds??[])];if(i.length&&(await t.query(`SELECT id FROM analytics_identity_nodes
259
+ WHERE site_id=$1 AND namespace_id=$2 AND id = ANY($3::uuid[])`,[e.siteId,e.namespaceId,i])).rows.length!==i.length)throw new f("analytics_namespace_boundary","Identity evidence cannot cross namespaces.",403);if(e.personId&&!(await t.query("SELECT id FROM analytics_people WHERE id=$1 AND site_id=$2 AND namespace_id=$3",[e.personId,e.siteId,e.namespaceId])).rows[0])throw new f("analytics_namespace_boundary","The person does not belong to this namespace.",403);if(e.source){let r=$n(e.source.kind,e.source.ref);if(!(await t.query(`SELECT id FROM analytics_identity_sources
260
+ WHERE namespace_id=$1 AND site_id=$2 AND source_kind=$3 AND source_ref=$4 AND status='approved'`,[e.namespaceId,e.siteId,xn[e.source.kind],r])).rows[0])throw new f("analytics_namespace_source_unapproved","This identity source is not approved.",403)}}async function Ha(e){return await et(e),(await v().query(`SELECT id, namespace_id, site_id, source_kind, source_ref, status, created_at, revoked_at
261
+ FROM analytics_identity_sources
262
+ WHERE namespace_id=$1 AND site_id=$2
263
+ ORDER BY source_kind, source_ref`,[e.namespaceId,e.siteId])).rows.map(n=>({id:n.id,namespaceId:n.namespace_id,siteId:n.site_id,sourceKind:Ln[n.source_kind],sourceRef:n.source_ref,status:n.status,createdAt:n.created_at.toISOString(),revokedAt:n.revoked_at?.toISOString()??null}))}function br(){let e=process.env.ANALYTICS_IDENTITY_SECRET?.trim()||process.env.SESSION_SECRET?.trim();if(!e)throw new f("analytics_identity_secret_missing","Candidate identity derivation is not configured.",503);return e}function St(e,t){return Er("sha256",br()).update(`xray-candidate:v1:${e}:${t}`).digest("hex")}async function za(e){await et(e);let t=Math.max(1,Math.min(200,Math.floor(e.limit??50)));return(await v().query(`SELECT a.id, a.model_version, a.first_observed_at, a.last_observed_at, a.expires_at,
264
+ array_remove(array_agg(DISTINCT e.feature_kind) FILTER (WHERE e.contribution > 0), NULL) supporting,
265
+ array_remove(array_agg(DISTINCT e.feature_kind) FILTER (WHERE e.contribution < 0), NULL) conflicting
266
+ FROM analytics_candidate_associations a
267
+ LEFT JOIN analytics_candidate_evidence e ON e.association_id=a.id AND e.namespace_id=a.namespace_id
268
+ WHERE a.site_id=$1 AND a.namespace_id=$2 AND a.status='active' AND a.confidence_band='medium'
269
+ AND a.expires_at > $3::timestamptz
270
+ GROUP BY a.id
271
+ ORDER BY a.updated_at DESC LIMIT $4`,[e.siteId,e.namespaceId,e.now??new Date().toISOString(),t])).rows.map(i=>({associationId:i.id,band:"medium",supportingFeatures:i.supporting??[],conflictingFeatures:i.conflicting??[],modelVersion:i.model_version,firstObservedAt:i.first_observed_at.toISOString(),lastObservedAt:i.last_observed_at.toISOString(),expiresAt:i.expires_at.toISOString()}))}async function Pn(e){await et({siteId:e.siteId,namespaceId:e.namespaceId,personId:e.personId});let t=new Date(e.occurredAt??Date.now());if(!e.evidenceRef.trim())throw new f("analytics_candidate_promotion_evidence_required","Deterministic evidence is required.",400);let n=e.promotedBy??"system";if(n==="user"&&!e.promotedByUserId)throw new f("analytics_candidate_promotion_actor_required","User-confirmed promotion requires an actor.",400);let i=St("journey_pattern",`${e.evidenceKind}:${e.evidenceRef.trim().toLowerCase()}`),s=await v().connect();await s.query("BEGIN");try{let a=await s.query(`UPDATE analytics_candidate_associations
272
+ SET status='promoted', promoted_person_id=$4, promoted_at=$5, updated_at=now()
273
+ WHERE id=$1 AND site_id=$2 AND namespace_id=$3
274
+ AND (status IN ('active','expired') OR (status='promoted' AND promoted_person_id=$4))
275
+ RETURNING id,left_identity_node_id,right_identity_node_id`,[e.associationId,e.siteId,e.namespaceId,e.personId,t.toISOString()]);if(!a.rows[0])throw new f("analytics_candidate_not_found","Candidate association not found in this namespace.",404);return await s.query(`INSERT INTO analytics_candidate_promotions(
276
+ id, site_id, namespace_id, association_id, person_id, evidence_kind,
277
+ evidence_ref_hmac, promoted_by, promoted_by_user_id, promoted_at
278
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)
279
+ ON CONFLICT(association_id, person_id) DO NOTHING`,[It(),e.siteId,e.namespaceId,e.associationId,e.personId,e.evidenceKind,i,n,e.promotedByUserId??null,t.toISOString()]),await s.query(`INSERT INTO analytics_privacy_audits(
280
+ id, site_id, namespace_ref, subject_ref, action, idempotency_key,
281
+ actor_user_id, result_code, affected_counts, completed_at
282
+ ) VALUES ($1,$2,$3,$4,'candidate_promoted',$5,$6,'promoted',$7::jsonb,$8)
283
+ ON CONFLICT(site_id, idempotency_key) DO NOTHING`,[It(),e.siteId,e.namespaceId,e.associationId,`candidate-promotion:${e.associationId}:${e.personId}`,e.promotedByUserId??null,JSON.stringify({candidateAssociations:1,deterministicPeople:1}),t.toISOString()]),await s.query(`INSERT INTO analytics_identity_promotion_receipts(
284
+ id,site_id,namespace_id,canonical_person_id,canonical_person_ref_hmac,
285
+ evidence_kind,evidence_ref_hmac,prior_person_ids,prior_identity_node_ids,
286
+ candidate_association_ids,affected_history_counts,schema_version,idempotency_key,promoted_at
287
+ ) VALUES($1,$2,$3,$4,$5,$6,$7,'{}'::uuid[],$8::uuid[],$9::uuid[],$10::jsonb,
288
+ 'xray.identity-promotion.v1',$11,$12)
289
+ ON CONFLICT(site_id,idempotency_key) DO NOTHING`,[It(),e.siteId,e.namespaceId,e.personId,St("journey_pattern",`person:${e.personId}`),e.evidenceKind,i,[a.rows[0].left_identity_node_id,a.rows[0].right_identity_node_id],[e.associationId],JSON.stringify({events:0,sessions:0,touches:0,conversions:0,candidateTouches:0}),St("journey_pattern",`promotion:${e.siteId}:${e.associationId}:${e.personId}:${e.evidenceKind}:${e.evidenceRef}`),t.toISOString()]),await s.query("COMMIT"),{associationId:e.associationId,personId:e.personId,promotedAt:t.toISOString(),reason:e.evidenceKind}}catch(a){throw await s.query("ROLLBACK"),a}finally{s.release()}}import{createHash as Ar,randomUUID as kn}from"crypto";import{z as g}from"zod";var ie=g.string().trim().min(1).max(160).regex(/^[a-zA-Z0-9_.:-]+$/),ht=g.string().trim().min(1).max(160).regex(/^[a-zA-Z][a-zA-Z0-9_.:-]*$/),Ir=g.discriminatedUnion("kind",[g.object({kind:g.literal("event_completed"),eventName:ie,withinDays:g.number().int().min(1).max(3650).optional()}).strict(),g.object({kind:g.literal("event_frequency"),eventName:ie,minimum:g.number().int().min(1).max(1e3),withinDays:g.number().int().min(1).max(3650).optional()}).strict(),g.object({kind:g.literal("event_recency"),eventName:ie,withinDays:g.number().int().min(1).max(3650)}).strict(),g.object({kind:g.literal("page_visited"),path:g.string().trim().startsWith("/").max(2e3),withinDays:g.number().int().min(1).max(3650).optional()}).strict(),g.object({kind:g.literal("referrer_equals"),value:g.string().trim().min(1).max(500)}).strict(),g.object({kind:g.literal("source_equals"),value:g.string().trim().min(1).max(180)}).strict(),g.object({kind:g.literal("call_outcome"),outcome:ie,withinDays:g.number().int().min(1).max(3650).optional()}).strict(),g.object({kind:g.literal("crm_stage"),stage:ie}).strict(),g.object({kind:g.literal("property_equals"),property:ht,value:g.union([g.string().max(500),g.number().finite(),g.boolean()])}).strict(),g.object({kind:g.literal("property_present"),property:ht}).strict()]),Sr=g.object({id:ie,label:g.string().trim().min(1).max(160),operation:g.enum(["add","subtract"]),points:g.number().int().min(0).max(1e5),cap:g.number().int().min(0).max(1e5).optional(),predicates:g.array(Ir).min(1).max(50)}).strict(),Mn=g.object({namespaceId:ie,name:g.string().trim().min(1).max(160),kind:g.enum(["fit","engagement","combined"]),targetObject:g.enum(["person","visitor"]),minimum:g.number().int().min(-1e6).max(1e6),maximum:g.number().int().min(-1e6).max(1e6),groups:g.array(Sr).min(1).max(100),permittedProperties:g.array(ht).max(200).default([]),enabled:g.boolean().default(!0)}).strict().superRefine((e,t)=>{e.minimum>=e.maximum&&t.addIssue({code:"custom",path:["maximum"],message:"maximum must be greater than minimum"});let n=new Set(e.permittedProperties);for(let[i,r]of e.groups.entries())for(let[s,a]of r.predicates.entries())(a.kind==="property_equals"||a.kind==="property_present")&&!n.has(a.property)&&t.addIssue({code:"custom",path:["groups",i,"predicates",s,"property"],message:"property is not allowlisted"})});function Un(){return{definitions:new Map,evaluations:new Map,evaluationHistory:[]}}var Y=class extends Error{constructor(n,i){super(i);this.code=n;this.name="AnalyticsLeadScoringError"}code};function Nt(e){let t=e instanceof Date?e.toISOString():e??new Date().toISOString();if(!Number.isFinite(Date.parse(t)))throw new Y("analytics_score_invalid_time","A valid evaluation time is required.");return new Date(t).toISOString()}function Oe(e,t){return`${e}:${t}`}function Ct(e){return{...e,groups:e.groups.map(t=>({...t,predicates:t.predicates.map(n=>({...n}))})),permittedProperties:[...e.permittedProperties]}}function hr(e,t,n){let i=Date.parse(e),r=Date.parse(t);return!Number.isFinite(i)||i>r?!1:n===void 0||r-i<=n*864e5}function Nr(e,t,n){let i=(r,s,a)=>(r??[]).filter(o=>o.name===s&&hr(o.occurredAt,n,a));switch(e.kind){case"event_completed":{let r=i(t.events,e.eventName,e.withinDays);return r.length?r.map(s=>s.ref):null}case"event_frequency":{let r=i(t.events,e.eventName,e.withinDays);return r.length>=e.minimum?r.map(s=>s.ref):null}case"event_recency":{let r=i(t.events,e.eventName,e.withinDays);return r.length?r.map(s=>s.ref):null}case"page_visited":{let r=i(t.pages,e.path,e.withinDays);return r.length?r.map(s=>s.ref):null}case"referrer_equals":{let r=(t.referrers??[]).filter(s=>s.name===e.value);return r.length?r.map(s=>s.ref):null}case"source_equals":{let r=(t.sources??[]).filter(s=>s.name===e.value);return r.length?r.map(s=>s.ref):null}case"call_outcome":{let r=i(t.calls,e.outcome,e.withinDays);return r.length?r.map(s=>s.ref):null}case"crm_stage":{let r=i(t.crmStages,e.stage);return r.length?r.map(s=>s.ref):null}case"property_equals":return t.permittedProperties?.[e.property]===e.value?[`property:${e.property}`]:null;case"property_present":{let r=t.permittedProperties?.[e.property];return r!=null?[`property:${e.property}`]:null}}}function Cr(e){let t=n=>Array.isArray(n)?n.map(t):n&&typeof n=="object"?Object.fromEntries(Object.entries(n).sort(([i],[r])=>i.localeCompare(r)).map(([i,r])=>[i,t(r)])):n;return Ar("sha256").update(JSON.stringify(t(e))).digest("hex")}function Fn(e){let t=Mn.parse(e.definition),n=e.id??kn();if(e.tables.definitions.has(Oe(t.namespaceId,n)))throw new Y("analytics_score_exists","A lead score with this ID already exists.");let i=Nt(e.now),r={id:n,...t,groups:t.groups.map(s=>({...s,predicates:s.predicates.map(a=>({...a}))})),version:1,createdAt:i,updatedAt:i};return e.tables.definitions.set(Oe(t.namespaceId,n),r),Ct(r)}function Za(e){let t=e.tables.definitions.get(Oe(e.namespaceId,e.scoreId));if(!t)throw new Y("analytics_score_not_found","Lead score not found.");if(t.version!==e.expectedVersion)throw new Y("analytics_score_version_conflict","The lead score changed; reload before updating.");let n=Mn.parse(e.definition);if(n.namespaceId!==e.namespaceId)throw new Y("analytics_score_namespace_mismatch","Lead scores cannot move between namespaces.");let i={...t,...n,groups:n.groups.map(r=>({...r,predicates:r.predicates.map(s=>({...s}))})),version:t.version+1,updatedAt:Nt(e.now)};return e.tables.definitions.set(Oe(e.namespaceId,e.scoreId),i),Ct(i)}function Qa(e){return[...e.tables.definitions.values()].filter(t=>t.namespaceId===e.namespaceId).sort((t,n)=>t.name.localeCompare(n.name)).map(Ct)}function jn(e){let t=e.tables.definitions.get(Oe(e.namespaceId,e.scoreId));if(!t||!t.enabled)throw new Y("analytics_score_unavailable","Lead score is missing or disabled.");let n=Nt(e.now),i=t.groups.map(_=>{let u=_.predicates.map(b=>Nr(b,e.facts,n)),y=u.every(b=>b!==null),E=y?Math.min(_.points,_.cap??_.points):0;return{groupId:_.id,operation:_.operation,matched:y,points:_.operation==="subtract"?-E:E,evidenceRefs:y?[...new Set(u.flatMap(b=>b??[]))].sort():[]}}),r=i.reduce((_,u)=>_+u.points,0),s=Math.max(t.minimum,Math.min(t.maximum,r)),a=[...new Set(i.flatMap(_=>_.evidenceRefs))].sort(),o=Cr({namespaceId:e.namespaceId,scoreId:e.scoreId,version:t.version,subjectRef:e.subjectRef,identityTier:e.identityTier,components:i,value:s}),c=e.tables.evaluations.get(o);if(c)return{...c,components:c.components.map(_=>({..._,evidenceRefs:[..._.evidenceRefs]})),evidenceRefs:[...c.evidenceRefs]};let d={id:kn(),namespaceId:e.namespaceId,subjectRef:e.subjectRef,identityTier:e.identityTier,scoreId:t.id,scoreVersion:t.version,scoreKind:t.kind,value:s,components:i,evidenceRefs:a,evaluatedAt:n,idempotencyKey:o,providerEligible:e.identityTier==="confirmed"};return e.tables.evaluations.set(o,d),e.tables.evaluationHistory.push(d),{...d,components:d.components.map(_=>({..._,evidenceRefs:[..._.evidenceRefs]})),evidenceRefs:[...d.evidenceRefs]}}import{createHmac as Rr,timingSafeEqual as wr}from"crypto";function p(e,t=500){if(typeof e!="string"&&typeof e!="number")return;let n=String(e).trim();return n?n.slice(0,t):void 0}function W(e){let t=Number(e);return Number.isFinite(t)?t:void 0}function re(e){if(typeof e=="boolean")return e;if(e==="true"||e==="1"||e===1)return!0;if(e==="false"||e==="0"||e===0)return!1}function De(e){if(Array.isArray(e))return e.map(n=>p(n,80)).filter(n=>!!n).slice(0,30);let t=p(e,1e3);return t?t.split(",").map(n=>n.trim()).filter(Boolean).slice(0,30):[]}function B(...e){for(let t of e){let n=p(t,100);if(!n)continue;let i=new Date(n);if(Number.isFinite(i.getTime()))return i.toISOString()}return new Date().toISOString()}function pe(e,t,n){return Rr(e,t).update(n).digest("base64")}function ye(e,t){if(!e||e.length!==t.length)return!1;try{return wr(Buffer.from(e),Buffer.from(t))}catch{return!1}}function ge(e,t=Date.now(),n=300*1e3){let i=p(e,100);if(!i)return!1;let r=Number(i),s=Number.isFinite(r)?r>1e10?r:r*1e3:Date.parse(i);return Number.isFinite(s)&&Math.abs(t-s)<=n}function no(e){let t=new URL(e.requestUrl);for(let s of e.internalRewriteQueryKeys??[])t.searchParams.delete(s);let n=e.publicOrigin?.trim();if(n){let s=new URL(n);if(s.protocol!=="https:"||s.username||s.password)throw new Error("analytics webhook public origin must be an HTTPS origin");return s.pathname=t.pathname,s.search=t.search,s.hash="",s.toString()}let i=e.forwardedProto?.split(",")[0]?.trim(),r=e.forwardedHost?.split(",")[0]?.trim();return i&&(t.protocol=`${i}:`),r&&(t.host=r),t.toString()}function Hn(e){return ge(e.payloadTimestamp,e.now)&&ye(e.signature,pe("sha1",e.secret,e.rawBody))}function qn(e){let t=e.payload,n=p(t.resource_id??t.id,240)||"unknown",i=B(t.timestamp,t.start_time,t.created_at),r=De(t.tags??t.tag),s=p(t.lead_status,80),a=s==="not_a_lead"||r.some(c=>c.toLowerCase()==="spam")||re(t.spam)===!0,o={...p(t.gclid)?{gclid:p(t.gclid)}:{},...p(t.gbraid)?{gbraid:p(t.gbraid)}:{},...p(t.wbraid)?{wbraid:p(t.wbraid)}:{},...p(t.fbclid)?{fbclid:p(t.fbclid)}:{},...p(t.msclkid)?{msclkid:p(t.msclkid)}:{}};return{schemaVersion:1,eventId:`callrail_${n}_${e.phase}`,sourceEventId:`${n}:${e.phase}:${i}`,source:"callrail",sourceAccountRef:e.sourceAccountRef,eventKind:"call",eventName:e.phase==="pre_call"?"call_started":e.phase==="routing_complete"?"call_routed":e.phase==="call_modified"?"call_modified":"call_completed",occurredAt:i,callId:n,personRef:p(t.person_resource_id,240),visitorId:p(t.visitor_id??t.browser_id??t.ga,160),canonicalUrl:p(t.landing_page_url,3e3),sourceName:p(t.utm_source??t.source_name,180),medium:p(t.utm_medium??t.medium,180),campaign:p(t.utm_campaign??t.campaign,240),clickIds:o,properties:{answered:re(t.answered)??!1,durationSeconds:W(t.duration)??0,status:a?"spam":s||"unknown",tags:r,direction:p(t.direction,40)||"unknown",trackingNumber:p(t.tracking_phone_number,80)||null,changes:De(t.changes)}}}function Wn(e){return ge(e.requestTime,e.now)&&ye(e.signature.trim(),pe("sha1",e.secret,`${e.requestTime}${e.rawBody}`))}function Bn(e){let t=e.payload,n=p(t.activity_id??t.call_id??t.id,240)||"unknown",i=B(t.timestamp,t.event_time,t.started_at,t.created_at),r=De(t.tags??t.tag_list),s=p(t.status??t.disposition,80)||"unknown",a=s.toLowerCase().includes("spam")||r.some(o=>o.toLowerCase()==="spam");return{schemaVersion:1,eventId:`ctm_${n}_${e.phase}`,sourceEventId:`${n}:${e.phase}:${i}`,source:"calltrackingmetrics",sourceAccountRef:e.sourceAccountRef,eventKind:"call",eventName:e.phase.includes("start")?"call_started":e.phase.includes("answer")?"call_answered":"call_completed",occurredAt:i,callId:n,personRef:p(t.contact_id??t.visitor_id,240),visitorId:p(t.visitor_id??t.cookie_id,160),sessionId:p(t.session_id,160),canonicalUrl:p(t.landing_page_url??t.landing_url,3e3),sourceName:p(t.source??t.utm_source,180),medium:p(t.medium??t.utm_medium,180),campaign:p(t.campaign??t.utm_campaign,240),clickIds:{...p(t.gclid)?{gclid:p(t.gclid)}:{},...p(t.fbclid)?{fbclid:p(t.fbclid)}:{},...p(t.msclkid)?{msclkid:p(t.msclkid)}:{}},properties:{answered:re(t.answered)??e.phase.includes("answer"),durationSeconds:W(t.duration??t.talk_time)??0,status:a?"spam":s,tags:r,direction:p(t.direction,40)||"unknown",trackingNumber:p(t.tracking_number??t.dialed_number,80)||null,score:W(t.score)??null}}}import Or from"twilio";function Vn(e){return Or.validateRequest(e.authToken,e.signature,e.url,e.params)}function Kn(e){let t=e.params,n=p(t.CallSid,240)||"unknown",i=(p(t.CallStatus,80)||"unknown").toLowerCase(),r=B(t.Timestamp,t.CallTimestamp,t.StartTime),s=["completed","busy","failed","no-answer","canceled"].includes(i),a=["in-progress","answered","completed"].includes(i);return{schemaVersion:1,eventId:`twilio_${n}_${i}`,sourceEventId:`${n}:${i}`,source:"twilio",sourceAccountRef:e.sourceAccountRef,eventKind:"call",eventName:s?"call_completed":a?"call_answered":"call_started",occurredAt:r,callId:n,visitorId:p(t.XRayVisitorId,160),sessionId:p(t.XRaySessionId,160),personRef:p(t.ContactId,240),properties:{answered:re(t.Answered)??a,durationSeconds:W(t.CallDuration??t.Duration)??0,status:i,direction:p(t.Direction,40)||"unknown",trackingNumber:p(t.To,80)||null,attributionLink:t.XRaySessionId?"explicit_session":"unattributed"}}}import{createCipheriv as Dr,createDecipheriv as Tr,createHash as xr,createHmac as Lr}from"crypto";var Te={session:"xrs_",visitor:"xrv_",person:"xrp_",cursor:"xrc_",candidate:"xri_"};function wt(e){return xr("sha256").update(`xray-report:${e}:`).update(Me()).digest()}function O(e,t,n){let i=wt("reference"),r=Buffer.from(`v1:${e}:${t}`),s=Buffer.from(n),a=Lr("sha256",wt("reference-iv")).update(r).update(s).digest().subarray(0,12),o=Dr("aes-256-gcm",i,a);o.setAAD(r);let c=Buffer.concat([o.update(s),o.final()]);return`${Te[e]}${Buffer.concat([a,c,o.getAuthTag()]).toString("base64url")}`}function tt(e,t,n){let i=Te[e];if(!n.startsWith(i))throw new Error("analytics_reference_invalid");try{let r=Buffer.from(n.slice(i.length),"base64url");if(r.length<29)throw new Error("short reference");let s=r.subarray(0,12),a=r.subarray(r.length-16),o=r.subarray(12,r.length-16),c=Tr("aes-256-gcm",wt("reference"),s);return c.setAAD(Buffer.from(`v1:${e}:${t}`)),c.setAuthTag(a),Buffer.concat([c.update(o),c.final()]).toString("utf8")}catch{throw new Error("analytics_reference_invalid")}}function $r(e,t){return O("cursor",e,JSON.stringify(t))}function ve(e,t){if(!t)return null;try{let n=JSON.parse(tt("cursor",e,t));if(!n.at||!n.id||Number.isNaN(Date.parse(n.at)))throw new Error("invalid");return n}catch{throw new Error("analytics_cursor_invalid")}}function Ee(e){if(e===void 0)return 50;if(!Number.isInteger(e)||e<1||e>100)throw new Error("analytics_limit_invalid");return e}function Rt(e){if(typeof e!="string"||!e)return null;try{let t=new URL(e);return`${t.origin}${t.pathname}`.slice(0,2e3)}catch{return null}}function fe(e){if(e==null)return null;let t=Number(e);return Number.isFinite(t)?t:null}function S(e){let t=e instanceof Date?e:new Date(e);if(!Number.isFinite(t.getTime()))throw new Error("analytics_timestamp_invalid");return t.toISOString()}function Jn(e){return e===null?null:S(e)}function be(e,t,n,i){let r=t.length>n,s=t.slice(0,n),a=s.at(-1);return{items:s.map(i),pageInfo:{hasNextPage:r,nextCursor:r&&a?$r(e,{at:a._cursorAt,id:a._cursorId}):null}}}async function Ae(e,t){await U(t,e.siteId,e.userId)}async function yo(e,t=v()){await Ae(e,t);let n=Ee(e.limit),i=ve(e.siteId,e.cursor),s=(await t.query(`/* analyticsSessions:keyset */
290
+ WITH ranked AS (
291
+ SELECT s.*,
292
+ row_number() OVER (PARTITION BY s.visitor_hmac ORDER BY s.started_at, s.id)::int AS session_number
293
+ FROM analytics_sessions s
294
+ WHERE s.site_id = $1
295
+ ), facts AS (
296
+ SELECT DISTINCT ON (e.session_hmac)
297
+ e.session_hmac, e.country_code, e.region_code, e.device_class,
298
+ e.source, e.medium, e.campaign
299
+ FROM analytics_events e
300
+ WHERE e.site_id = $1
301
+ ORDER BY e.session_hmac, e.occurred_at
302
+ ), conversions AS (
303
+ SELECT c.session_hmac, count(*)::int AS conversion_count,
304
+ sum(c.value_minor)::bigint AS revenue_minor,
305
+ CASE WHEN count(DISTINCT c.currency) = 1 THEN min(c.currency) END AS currency
306
+ FROM analytics_conversions c WHERE c.site_id = $1 GROUP BY c.session_hmac
307
+ )
308
+ SELECT r.id AS internal_id, r.visitor_hmac, r.person_id, r.started_at, r.last_active_at,
309
+ r.ended_at, r.duration_ms, r.landing_path, r.exit_path, r.pageview_count,
310
+ r.event_count, r.session_number, f.country_code, f.region_code, f.device_class,
311
+ f.source, f.medium, f.campaign, COALESCE(c.conversion_count, 0)::int AS conversion_count,
312
+ c.revenue_minor, c.currency
313
+ FROM ranked r
314
+ LEFT JOIN facts f ON f.session_hmac = r.session_hmac
315
+ LEFT JOIN conversions c ON c.session_hmac = r.session_hmac
316
+ WHERE ($2::timestamptz IS NULL OR (r.started_at, r.id) < ($2::timestamptz, $3::uuid))
317
+ AND ($4::boolean IS NULL OR (r.session_number > 1) = $4)
318
+ AND ($5::boolean IS NULL OR (COALESCE(c.conversion_count, 0) > 0) = $5)
319
+ AND ($6::bigint IS NULL OR r.duration_ms >= $6)
320
+ AND ($7::bigint IS NULL OR r.duration_ms <= $7)
321
+ AND ($8::text IS NULL OR EXISTS (
322
+ SELECT 1 FROM analytics_events ee
323
+ WHERE ee.site_id = $1 AND ee.session_hmac = r.session_hmac AND ee.event_name = $8
324
+ ))
325
+ ORDER BY r.started_at DESC, r.id DESC LIMIT $9`,[e.siteId,i?.at??null,i?.id??null,e.returning??null,e.converted??null,e.minimumDurationMs??null,e.maximumDurationMs??null,e.eventName?.slice(0,160)??null,n+1])).rows.map(a=>({...a,_cursorAt:S(a.started_at),_cursorId:a.internal_id}));return be(e.siteId,s,n,a=>({sessionRef:O("session",e.siteId,a.internal_id),visitorRef:O("visitor",e.siteId,`visitor:${a.visitor_hmac}`),personRef:a.person_id?O("person",e.siteId,a.person_id):null,returning:a.session_number>1,sessionNumber:a.session_number,startedAt:S(a.started_at),lastActiveAt:S(a.last_active_at),endedAt:Jn(a.ended_at),durationMs:fe(a.duration_ms),landingPath:a.landing_path,exitPath:a.exit_path,pages:a.pageview_count,events:a.event_count,device:a.device_class,location:{country:a.country_code,region:a.region_code},acquisition:{source:a.source,medium:a.medium,campaign:a.campaign},conversions:a.conversion_count,revenue:a.revenue_minor===null?null:{valueMinor:Number(a.revenue_minor),currency:a.currency}}))}async function go(e,t=v()){await Ae(e,t);let n=Ee(e.limit),i=ve(e.siteId,e.cursor),s=(await t.query(`/* analyticsVisitors:keyset */
326
+ WITH visitor_sessions AS (
327
+ SELECT COALESCE(s.person_id::text, s.visitor_hmac) AS identity_key, s.person_id,
328
+ min(s.started_at) AS first_seen_at, max(s.last_active_at) AS last_seen_at,
329
+ count(*)::int AS session_count, sum(s.duration_ms)::bigint AS duration_ms,
330
+ sum(s.pageview_count)::int AS pageview_count, sum(s.event_count)::int AS event_count,
331
+ (array_agg(s.id ORDER BY s.last_active_at DESC, s.id DESC))[1] AS last_session_id
332
+ FROM analytics_sessions s WHERE s.site_id = $1
333
+ GROUP BY COALESCE(s.person_id::text, s.visitor_hmac), s.person_id
334
+ ), conversions AS (
335
+ SELECT COALESCE(c.person_id::text, s.visitor_hmac) AS identity_key,
336
+ count(*)::int AS conversion_count, sum(c.value_minor)::bigint AS revenue_minor,
337
+ CASE WHEN count(DISTINCT c.currency) = 1 THEN min(c.currency) END AS currency
338
+ FROM analytics_conversions c
339
+ LEFT JOIN analytics_sessions s ON s.site_id=c.site_id AND s.session_hmac=c.session_hmac
340
+ WHERE c.site_id=$1 GROUP BY COALESCE(c.person_id::text, s.visitor_hmac)
341
+ )
342
+ SELECT v.*, COALESCE(c.conversion_count,0)::int AS conversion_count, c.revenue_minor, c.currency
343
+ FROM visitor_sessions v LEFT JOIN conversions c USING(identity_key)
344
+ WHERE ($2::timestamptz IS NULL OR (v.last_seen_at, v.last_session_id) < ($2::timestamptz, $3::uuid))
345
+ AND ($4::boolean IS NULL OR (v.session_count > 1) = $4)
346
+ AND ($5::boolean IS NULL OR (COALESCE(c.conversion_count,0) > 0) = $5)
347
+ ORDER BY v.last_seen_at DESC, v.last_session_id DESC LIMIT $6`,[e.siteId,i?.at??null,i?.id??null,e.returning??null,e.converted??null,n+1])).rows.map(a=>({...a,_cursorAt:S(a.last_seen_at),_cursorId:a.last_session_id}));return be(e.siteId,s,n,a=>({visitorRef:O("visitor",e.siteId,a.person_id?`person:${a.person_id}`:`visitor:${a.identity_key}`),personRef:a.person_id?O("person",e.siteId,a.person_id):null,known:!!a.person_id,returning:a.session_count>1,sessions:a.session_count,firstSeenAt:S(a.first_seen_at),lastSeenAt:S(a.last_seen_at),durationMs:fe(a.duration_ms),pages:a.pageview_count,events:a.event_count,conversions:a.conversion_count,revenue:a.revenue_minor===null?null:{valueMinor:Number(a.revenue_minor),currency:a.currency}}))}async function Pr(e,t){if(t.startsWith(Te.person))return{kind:"person",personId:tt("person",e,t),visitorHmac:null,sessionId:null};if(t.startsWith(Te.visitor)){let n=tt("visitor",e,t);if(n.startsWith("person:"))return{kind:"visitor",personId:n.slice(7),visitorHmac:null,sessionId:null};if(n.startsWith("visitor:"))return{kind:"visitor",personId:null,visitorHmac:n.slice(8),sessionId:null};throw new Error("analytics_reference_invalid")}if(t.startsWith(Te.session))return{kind:"session",personId:null,visitorHmac:null,sessionId:tt("session",e,t)};throw new Error("analytics_reference_invalid")}var kr=2160*60*60*1e3,Mr=366*24*60*60*1e3;function Ur(e,t){let n=t===void 0?Date.now():Date.parse(t),i=e===void 0?n-kr:Date.parse(e);if(!Number.isFinite(i)||!Number.isFinite(n)||i>=n||n-i>Mr)throw new f("analytics_journey_date_range_invalid","Choose a valid from-inclusive, to-exclusive date range of at most 366 days.",400);return{from:new Date(i).toISOString(),to:new Date(n).toISOString()}}async function fo(e,t=v()){await Ae(e,t);let n=Ee(e.limit),i=ve(e.siteId,e.cursor),r;try{r=await Pr(e.siteId,e.reference)}catch{throw new f("analytics_journey_not_found","Journey not found.",404)}if(!(await t.query(`/* analyticsEnhancedJourney:subject */
348
+ SELECT 1 WHERE
349
+ ($2::uuid IS NOT NULL AND EXISTS (SELECT 1 FROM analytics_people p WHERE p.site_id=$1 AND p.id=$2))
350
+ OR ($3::text IS NOT NULL AND EXISTS (SELECT 1 FROM analytics_sessions s WHERE s.site_id=$1 AND s.visitor_hmac=$3))
351
+ OR ($4::uuid IS NOT NULL AND EXISTS (SELECT 1 FROM analytics_sessions s WHERE s.site_id=$1 AND s.id=$4))
352
+ LIMIT 1`,[e.siteId,r.personId,r.visitorHmac,r.sessionId])).rowCount)throw new f("analytics_journey_not_found","Journey not found.",404);let a=Ur(e.from,e.to),o=[e.siteId,r.personId,r.visitorHmac,r.sessionId,a.from,a.to],c=`
353
+ SELECT s.id, s.session_hmac, s.visitor_hmac, s.person_id, s.started_at,
354
+ s.last_active_at, s.ended_at, s.duration_ms, s.landing_path, s.exit_path
355
+ FROM analytics_sessions s
356
+ WHERE s.site_id=$1
357
+ AND ($2::uuid IS NULL OR s.person_id=$2)
358
+ AND ($3::text IS NULL OR s.visitor_hmac=$3 OR s.person_id=(SELECT person_id FROM analytics_sessions WHERE site_id=$1 AND visitor_hmac=$3 AND person_id IS NOT NULL LIMIT 1))
359
+ AND ($4::uuid IS NULL OR s.id=$4)`,[d,_,u,y,E,b]=await Promise.all([t.query(`/* analyticsSessionTimeline:keyset */
360
+ WITH subject_sessions AS (
361
+ ${c}
362
+ ), entries AS (
363
+ SELECT s.started_at AS occurred_at, s.id::text||':start' AS stable_id,
364
+ 'session' AS entry_type, 'session_started' AS name, s.landing_path AS path,
365
+ NULL::text AS referrer, NULL::text AS source, NULL::text AS medium,
366
+ NULL::text AS campaign, NULL::bigint AS value_minor, NULL::text AS currency,
367
+ 'deterministic'::text AS evidence_class, 'persisted_session_bounds'::text AS provenance
368
+ FROM subject_sessions s
369
+ UNION ALL
370
+ SELECT COALESCE(s.ended_at,s.last_active_at), s.id::text||':end', 'session', 'session_ended',
371
+ s.exit_path, NULL, NULL, NULL, NULL, NULL, NULL, 'deterministic', 'persisted_session_bounds'
372
+ FROM analytics_sessions s JOIN subject_sessions ss ON ss.id=s.id
373
+ UNION ALL
374
+ SELECT e.occurred_at, e.id::text AS stable_id,
375
+ CASE WHEN e.event_name='page_view' THEN 'page' ELSE 'event' END AS entry_type,
376
+ e.event_name AS name, e.path, e.referrer, e.source, e.medium, e.campaign,
377
+ NULL::bigint AS value_minor, NULL::text AS currency,
378
+ 'deterministic'::text AS evidence_class, 'pixel_event'::text AS provenance
379
+ FROM analytics_events e JOIN subject_sessions s ON s.session_hmac=e.session_hmac AND e.site_id=$1
380
+ UNION ALL
381
+ SELECT t.occurred_at, t.id::text, 'touch', COALESCE(t.touch_kind,'touch'), t.landing_path,
382
+ t.referrer, t.source, t.medium, t.campaign, NULL, NULL, 'deterministic', 'attribution_touch'
383
+ FROM analytics_attribution_touches t JOIN subject_sessions s ON s.id=t.session_id AND t.site_id=$1
384
+ UNION ALL
385
+ SELECT x.occurred_at, x.id::text, x.event_kind, x.event_name, NULL, NULL, x.source, NULL, NULL,
386
+ x.value_minor, x.currency, 'deterministic', 'verified_external_event'
387
+ FROM analytics_external_events x
388
+ WHERE x.site_id=$1 AND (x.person_id=$2 OR x.session_hmac IN (SELECT session_hmac FROM subject_sessions))
389
+ UNION ALL
390
+ SELECT c.occurred_at, c.id::text, 'conversion', c.conversion_kind, NULL, NULL, NULL, NULL, NULL,
391
+ c.value_minor, c.currency, 'deterministic', COALESCE(c.value_source,'conversion')
392
+ FROM analytics_conversions c
393
+ WHERE c.site_id=$1 AND (c.person_id=$2 OR c.session_hmac IN (SELECT session_hmac FROM subject_sessions))
394
+ UNION ALL
395
+ SELECT e.occurred_at, ve.id::text, 'value', e.event_name, e.path, NULL, e.source, e.medium,
396
+ e.campaign, ve.value_minor, ve.currency, 'deterministic', ve.value_source
397
+ FROM analytics_event_value_evaluations ve
398
+ JOIN analytics_events e ON e.id=ve.source_event_id AND e.site_id=ve.site_id
399
+ JOIN subject_sessions s ON s.session_hmac=e.session_hmac
400
+ WHERE ve.site_id=$1
401
+ UNION ALL
402
+ SELECT se.evaluated_at, se.id::text, 'score', sd.name, NULL, NULL, NULL, NULL, NULL,
403
+ se.score::bigint, NULL, 'deterministic', 'lead_score:'||sd.score_kind
404
+ FROM analytics_score_evaluations se JOIN analytics_score_definitions sd ON sd.id=se.definition_id
405
+ WHERE se.site_id=$1 AND se.person_id=$2
406
+ UNION ALL
407
+ SELECT j.created_at, j.id::text, 'receipt', j.status, NULL, NULL, d.platform, NULL, NULL,
408
+ NULL, NULL, 'deterministic', COALESCE(j.response_category,'delivery_receipt')
409
+ FROM analytics_activation_jobs j JOIN analytics_activation_destinations d ON d.id=j.destination_id
410
+ WHERE d.site_id=$1 AND j.person_id=$2
411
+ UNION ALL
412
+ SELECT ca.promoted_at, ca.id::text, 'identity_transition', 'candidate_promoted_to_confirmed', NULL, NULL,
413
+ NULL, NULL, NULL, NULL, NULL, 'deterministic', 'later_deterministic_evidence'
414
+ FROM analytics_candidate_associations ca
415
+ WHERE ca.site_id=$1 AND ca.promoted_person_id=$2 AND ca.status='promoted'
416
+ )
417
+ SELECT * FROM entries
418
+ WHERE occurred_at IS NOT NULL
419
+ AND occurred_at >= $5::timestamptz AND occurred_at < $6::timestamptz
420
+ AND ($7::timestamptz IS NULL OR (occurred_at, stable_id) > ($7::timestamptz, $8::text))
421
+ ORDER BY occurred_at, stable_id LIMIT $9`,[...o,i?.at??null,i?.id??null,n+1]),t.query(`/* analyticsEnhancedJourney:summary */
422
+ WITH subject_sessions AS (${c}), ranged_sessions AS (
423
+ SELECT * FROM subject_sessions WHERE started_at >= $5::timestamptz AND started_at < $6::timestamptz
424
+ ), ranged_events AS (
425
+ SELECT e.* FROM analytics_events e JOIN subject_sessions s ON s.session_hmac=e.session_hmac
426
+ WHERE e.site_id=$1 AND e.occurred_at >= $5::timestamptz AND e.occurred_at < $6::timestamptz
427
+ ), ranged_conversions AS (
428
+ SELECT c.* FROM analytics_conversions c
429
+ WHERE c.site_id=$1 AND c.occurred_at >= $5::timestamptz AND c.occurred_at < $6::timestamptz
430
+ AND (c.person_id=$2 OR c.session_hmac IN (SELECT session_hmac FROM subject_sessions))
431
+ )
432
+ SELECT (SELECT (array_agg(person_id ORDER BY started_at) FILTER (WHERE person_id IS NOT NULL))[1]::text FROM subject_sessions) AS resolved_person_id,
433
+ (SELECT count(*)::int FROM ranged_sessions) AS sessions,
434
+ (SELECT count(*)::int FROM ranged_events WHERE event_name='page_view') AS pageviews,
435
+ (SELECT count(*)::int FROM ranged_events) AS events,
436
+ (SELECT count(*)::int FROM ranged_conversions) AS conversions,
437
+ (SELECT sum(value_minor)::bigint FROM ranged_conversions) AS revenue_minor,
438
+ (SELECT CASE WHEN count(DISTINCT currency)=1 THEN min(currency) END FROM ranged_conversions WHERE value_minor IS NOT NULL) AS currency,
439
+ (SELECT sum(engaged_ms)::bigint FROM ranged_events WHERE event_name='page_engagement' AND engaged_ms IS NOT NULL) AS total_engagement_ms,
440
+ (SELECT round(avg(engaged_ms))::bigint FROM ranged_events WHERE event_name='page_engagement' AND engaged_ms IS NOT NULL) AS average_engagement_ms`,[...o]),t.query(`/* analyticsEnhancedJourney:pages */
441
+ WITH subject_sessions AS (${c}), pageviews AS (
442
+ SELECT e.path, e.session_id, count(*)::int AS visits
443
+ FROM analytics_events e JOIN subject_sessions s ON s.session_hmac=e.session_hmac
444
+ WHERE e.site_id=$1 AND e.event_name='page_view' AND e.path IS NOT NULL
445
+ AND e.occurred_at >= $5::timestamptz AND e.occurred_at < $6::timestamptz
446
+ GROUP BY e.path,e.session_id
447
+ ), engagement AS (
448
+ SELECT e.path, sum(e.engaged_ms)::bigint AS engaged_ms
449
+ FROM analytics_events e JOIN subject_sessions s ON s.session_hmac=e.session_hmac
450
+ WHERE e.site_id=$1 AND e.event_name='page_engagement' AND e.path IS NOT NULL AND e.engaged_ms IS NOT NULL
451
+ AND e.occurred_at >= $5::timestamptz AND e.occurred_at < $6::timestamptz GROUP BY e.path
452
+ ), conversion_pages AS (
453
+ SELECT latest.path, count(*)::int AS conversions FROM analytics_conversions c
454
+ JOIN LATERAL (
455
+ SELECT e.path FROM analytics_events e
456
+ WHERE e.site_id=$1 AND e.session_id=c.session_id AND e.event_name='page_view'
457
+ AND e.occurred_at <= c.occurred_at ORDER BY e.occurred_at DESC,e.id DESC LIMIT 1
458
+ ) latest ON true
459
+ WHERE c.site_id=$1 AND c.occurred_at >= $5::timestamptz AND c.occurred_at < $6::timestamptz
460
+ AND (c.person_id=$2 OR c.session_hmac IN (SELECT session_hmac FROM subject_sessions))
461
+ GROUP BY latest.path
462
+ )
463
+ SELECT p.path,sum(p.visits)::int AS visits,count(*)::int AS unique_sessions,e.engaged_ms,
464
+ COALESCE(c.conversions,0)::int AS conversions
465
+ FROM pageviews p LEFT JOIN engagement e USING(path) LEFT JOIN conversion_pages c USING(path)
466
+ GROUP BY p.path,e.engaged_ms,c.conversions ORDER BY visits DESC,p.path LIMIT 20`,[...o]),t.query(`/* analyticsEnhancedJourney:acquisition */
467
+ WITH subject_sessions AS (${c}), first_touch AS (
468
+ SELECT DISTINCT ON (e.session_hmac) e.session_hmac,e.source,e.medium,e.campaign,e.referrer
469
+ FROM analytics_events e JOIN subject_sessions s ON s.session_hmac=e.session_hmac
470
+ WHERE e.site_id=$1 AND e.occurred_at >= $5::timestamptz AND e.occurred_at < $6::timestamptz
471
+ ORDER BY e.session_hmac,e.occurred_at,e.id
472
+ ), event_counts AS (
473
+ SELECT e.session_hmac,count(*)::int AS events FROM analytics_events e JOIN subject_sessions s ON s.session_hmac=e.session_hmac
474
+ WHERE e.site_id=$1 AND e.occurred_at >= $5::timestamptz AND e.occurred_at < $6::timestamptz GROUP BY e.session_hmac
475
+ ), conversion_counts AS (
476
+ SELECT c.session_hmac,count(*)::int AS conversions FROM analytics_conversions c
477
+ WHERE c.site_id=$1 AND c.occurred_at >= $5::timestamptz AND c.occurred_at < $6::timestamptz
478
+ AND (c.person_id=$2 OR c.session_hmac IN (SELECT session_hmac FROM subject_sessions)) GROUP BY c.session_hmac
479
+ )
480
+ SELECT f.source,f.medium,f.campaign,f.referrer,count(*)::int AS sessions,
481
+ sum(COALESCE(e.events,0))::int AS events,sum(COALESCE(c.conversions,0))::int AS conversions
482
+ FROM first_touch f LEFT JOIN event_counts e USING(session_hmac) LEFT JOIN conversion_counts c USING(session_hmac)
483
+ GROUP BY f.source,f.medium,f.campaign,f.referrer ORDER BY sessions DESC,f.source NULLS LAST LIMIT 20`,[...o]),t.query(`/* analyticsEnhancedJourney:events */
484
+ WITH subject_sessions AS (${c}), event_groups AS (
485
+ SELECT e.event_name AS name,count(*)::int AS count,count(DISTINCT e.session_id)::int AS unique_sessions,
486
+ array_agg(DISTINCT e.session_id) AS session_ids
487
+ FROM analytics_events e JOIN subject_sessions s ON s.session_hmac=e.session_hmac
488
+ WHERE e.site_id=$1 AND e.occurred_at >= $5::timestamptz AND e.occurred_at < $6::timestamptz
489
+ GROUP BY e.event_name
490
+ )
491
+ SELECT g.name,g.count,g.unique_sessions,
492
+ (SELECT count(*)::int FROM analytics_conversions c WHERE c.site_id=$1
493
+ AND c.occurred_at >= $5::timestamptz AND c.occurred_at < $6::timestamptz
494
+ AND c.session_id=ANY(g.session_ids)) AS conversions
495
+ FROM event_groups g ORDER BY g.count DESC,g.name LIMIT 50`,[...o]),t.query(`/* analyticsEnhancedJourney:sessions */
496
+ WITH subject_sessions AS (${c}), ranged_sessions AS (
497
+ SELECT * FROM subject_sessions WHERE started_at >= $5::timestamptz AND started_at < $6::timestamptz
498
+ ), event_stats AS (
499
+ SELECT e.session_hmac,count(*) FILTER (WHERE e.event_name='page_view')::int AS pageviews,count(*)::int AS events
500
+ FROM analytics_events e JOIN ranged_sessions s ON s.session_hmac=e.session_hmac
501
+ WHERE e.site_id=$1 AND e.occurred_at >= $5::timestamptz AND e.occurred_at < $6::timestamptz GROUP BY e.session_hmac
502
+ ), first_touch AS (
503
+ SELECT DISTINCT ON (e.session_hmac) e.session_hmac,e.source,e.medium,e.campaign,e.referrer
504
+ FROM analytics_events e JOIN ranged_sessions s ON s.session_hmac=e.session_hmac
505
+ WHERE e.site_id=$1 ORDER BY e.session_hmac,e.occurred_at,e.id
506
+ ), conversion_stats AS (
507
+ SELECT c.session_hmac,count(*)::int AS conversions,sum(c.value_minor)::bigint AS revenue_minor,
508
+ CASE WHEN count(DISTINCT c.currency)=1 THEN min(c.currency) END AS currency
509
+ FROM analytics_conversions c JOIN ranged_sessions s ON s.session_hmac=c.session_hmac
510
+ WHERE c.site_id=$1 AND c.occurred_at >= $5::timestamptz AND c.occurred_at < $6::timestamptz GROUP BY c.session_hmac
511
+ )
512
+ SELECT s.id AS internal_id,s.started_at,s.ended_at,s.duration_ms,s.landing_path,s.exit_path,
513
+ COALESCE(e.pageviews,0)::int AS pageviews,COALESCE(e.events,0)::int AS events,
514
+ COALESCE(c.conversions,0)::int AS conversions,c.revenue_minor,c.currency,
515
+ f.source,f.medium,f.campaign,f.referrer
516
+ FROM ranged_sessions s LEFT JOIN event_stats e ON e.session_hmac=s.session_hmac
517
+ LEFT JOIN first_touch f ON f.session_hmac=s.session_hmac LEFT JOIN conversion_stats c ON c.session_hmac=s.session_hmac
518
+ ORDER BY s.started_at DESC,s.id DESC LIMIT 50`,[...o])]),V=d.rows.map(m=>({...m,_cursorAt:S(m.occurred_at),_cursorId:m.stable_id})),P=be(e.siteId,V,n,m=>({occurredAt:S(m.occurred_at),type:m.entry_type,name:m.name,path:m.path,referrer:Rt(m.referrer),acquisition:m.source||m.medium||m.campaign?{source:m.source,medium:m.medium,campaign:m.campaign}:null,value:m.value_minor===null?null:{valueMinor:Number(m.value_minor),currency:m.currency},evidenceClass:m.evidence_class,provenance:m.provenance})),h=_.rows[0]??{resolved_person_id:null,sessions:0,pageviews:0,events:0,conversions:0,revenue_minor:null,currency:null,total_engagement_ms:null,average_engagement_ms:null},T=h.resolved_person_id??r.personId;return{subject:{kind:T?"person":r.kind,reference:e.reference,personRef:T?O("person",e.siteId,T):null},range:{...a,fromInclusive:!0,toExclusive:!0},journeyTier:"confirmed",candidateAssisted:null,summary:{sessions:Number(h.sessions),pageviews:Number(h.pageviews),events:Number(h.events),conversions:Number(h.conversions),revenue:h.revenue_minor===null?null:{valueMinor:Number(h.revenue_minor),currency:h.currency},totalEngagementMs:fe(h.total_engagement_ms),averageEngagementMs:fe(h.average_engagement_ms)},commonPages:u.rows.map(m=>({path:m.path,visits:Number(m.visits),uniqueSessions:Number(m.unique_sessions),engagedMs:fe(m.engaged_ms),conversions:Number(m.conversions)})),acquisition:y.rows.map(m=>({source:m.source,medium:m.medium,campaign:m.campaign,referrer:Rt(m.referrer),sessions:Number(m.sessions),events:Number(m.events),conversions:Number(m.conversions)})),eventBreakdown:E.rows.map(m=>({name:m.name,count:Number(m.count),uniqueSessions:Number(m.unique_sessions),conversions:Number(m.conversions)})),sessions:b.rows.map(m=>({sessionRef:O("session",e.siteId,m.internal_id),startedAt:S(m.started_at),endedAt:Jn(m.ended_at),durationMs:fe(m.duration_ms),pageviews:Number(m.pageviews),events:Number(m.events),conversions:Number(m.conversions),revenue:m.revenue_minor===null?null:{valueMinor:Number(m.revenue_minor),currency:m.currency},landingPath:m.landing_path,exitPath:m.exit_path,acquisition:m.source||m.medium||m.campaign||m.referrer?{source:m.source,medium:m.medium,campaign:m.campaign,referrer:Rt(m.referrer)}:null})),...P}}async function vo(e,t=v()){await Ae(e,t);let n=Ee(e.limit),i=ve(e.siteId,e.cursor),s=(await t.query(`/* analyticsConfirmedJourneys:keyset */
519
+ SELECT p.id AS person_id, p.namespace_id, sessions.first_seen_at,
520
+ sessions.last_seen_at, COALESCE(sessions.session_count,0)::int AS session_count,
521
+ COALESCE(events.event_count,0)::int AS event_count,
522
+ COALESCE(conversions.conversion_count,0)::int AS conversion_count,
523
+ conversions.revenue_minor, conversions.currency
524
+ FROM analytics_people p
525
+ JOIN analytics_identity_namespaces n ON n.id=p.namespace_id AND n.site_id=p.site_id
526
+ LEFT JOIN LATERAL (
527
+ SELECT min(s.started_at) AS first_seen_at, max(s.last_active_at) AS last_seen_at,
528
+ count(*)::int AS session_count
529
+ FROM analytics_sessions s WHERE s.site_id=p.site_id AND s.person_id=p.id
530
+ ) sessions ON true
531
+ LEFT JOIN LATERAL (
532
+ SELECT count(*)::int AS event_count
533
+ FROM analytics_events e WHERE e.site_id=p.site_id AND e.person_id=p.id
534
+ ) events ON true
535
+ LEFT JOIN LATERAL (
536
+ SELECT count(*)::int AS conversion_count, sum(c.value_minor)::bigint AS revenue_minor,
537
+ CASE WHEN count(DISTINCT c.currency)=1 THEN min(c.currency) END AS currency
538
+ FROM analytics_conversions c WHERE c.site_id=p.site_id AND c.person_id=p.id
539
+ ) conversions ON true
540
+ WHERE p.site_id=$1 AND n.status='approved'
541
+ AND ($2::timestamptz IS NULL OR (sessions.last_seen_at,p.id) < ($2::timestamptz,$3::uuid))
542
+ ORDER BY last_seen_at DESC, p.id DESC LIMIT $4`,[e.siteId,i?.at??null,i?.id??null,n+1])).rows.map(o=>({...o,_cursorAt:S(o.last_seen_at),_cursorId:o.person_id}));return{journeyTier:"confirmed",...be(e.siteId,s,n,o=>({journeyTier:"confirmed",personRef:O("person",e.siteId,o.person_id),firstSeenAt:S(o.first_seen_at),lastSeenAt:S(o.last_seen_at),sessions:o.session_count,events:o.event_count,conversions:o.conversion_count,revenue:o.revenue_minor===null?null:{valueMinor:Number(o.revenue_minor),currency:o.currency},evidenceClass:"deterministic"}))}}async function Eo(e,t=v()){await Ae(e,t);let n=Ee(e.limit),i=ve(e.siteId,e.cursor),s=(await t.query(`/* analyticsBestGuessJourneys:keyset */
543
+ SELECT ca.id AS association_id, ca.namespace_id, ca.model_version, ca.expires_at, ca.updated_at,
544
+ ca.left_identity_node_id AS left_node_id, ca.right_identity_node_id AS right_node_id,
545
+ COALESCE(stats.confirmed_sessions,0)::int AS confirmed_sessions,
546
+ COALESCE(stats.inferred_sessions,0)::int AS inferred_sessions,
547
+ COALESCE(stats.confirmed_events,0)::int AS confirmed_events,
548
+ COALESCE(stats.inferred_events,0)::int AS inferred_events,
549
+ COALESCE(stats.confirmed_conversions,0)::int AS confirmed_conversions,
550
+ COALESCE(stats.inferred_conversions,0)::int AS inferred_conversions,
551
+ stats.confirmed_revenue_minor, stats.inferred_revenue_minor, stats.currency,
552
+ COALESCE(ev.feature_kinds, ARRAY[]::text[]) AS feature_kinds
553
+ FROM analytics_candidate_associations ca
554
+ JOIN analytics_identity_namespaces n ON n.id=ca.namespace_id AND n.site_id=ca.site_id
555
+ LEFT JOIN LATERAL (
556
+ SELECT array_agg(DISTINCT ce.feature_kind ORDER BY ce.feature_kind) AS feature_kinds
557
+ FROM analytics_candidate_evidence ce
558
+ WHERE ce.association_id=ca.id AND ce.namespace_id=ca.namespace_id AND ce.expires_at > now()
559
+ ) ev ON true
560
+ LEFT JOIN LATERAL (
561
+ SELECT
562
+ sum(node_stats.session_count) FILTER (WHERE node_stats.node_id=ca.left_identity_node_id)::int AS confirmed_sessions,
563
+ sum(node_stats.session_count) FILTER (WHERE node_stats.node_id=ca.right_identity_node_id)::int AS inferred_sessions,
564
+ sum(node_stats.event_count) FILTER (WHERE node_stats.node_id=ca.left_identity_node_id)::int AS confirmed_events,
565
+ sum(node_stats.event_count) FILTER (WHERE node_stats.node_id=ca.right_identity_node_id)::int AS inferred_events,
566
+ sum(node_stats.conversion_count) FILTER (WHERE node_stats.node_id=ca.left_identity_node_id)::int AS confirmed_conversions,
567
+ sum(node_stats.conversion_count) FILTER (WHERE node_stats.node_id=ca.right_identity_node_id)::int AS inferred_conversions,
568
+ sum(node_stats.revenue_minor) FILTER (WHERE node_stats.node_id=ca.left_identity_node_id)::bigint AS confirmed_revenue_minor,
569
+ sum(node_stats.revenue_minor) FILTER (WHERE node_stats.node_id=ca.right_identity_node_id)::bigint AS inferred_revenue_minor,
570
+ CASE WHEN count(DISTINCT node_stats.currency)=1 THEN min(node_stats.currency) END AS currency
571
+ FROM (
572
+ SELECT inode.id AS node_id,
573
+ (SELECT count(*) FROM analytics_sessions s
574
+ WHERE s.site_id=ca.site_id AND s.visitor_hmac=inode.value_hmac)::int AS session_count,
575
+ (SELECT count(*) FROM analytics_events e JOIN analytics_sessions s
576
+ ON s.site_id=e.site_id AND s.session_hmac=e.session_hmac
577
+ WHERE e.site_id=ca.site_id AND s.visitor_hmac=inode.value_hmac)::int AS event_count,
578
+ (SELECT count(*) FROM analytics_conversions c JOIN analytics_sessions s
579
+ ON s.site_id=c.site_id AND s.session_hmac=c.session_hmac
580
+ WHERE c.site_id=ca.site_id AND s.visitor_hmac=inode.value_hmac)::int AS conversion_count,
581
+ (SELECT sum(c.value_minor) FROM analytics_conversions c JOIN analytics_sessions s
582
+ ON s.site_id=c.site_id AND s.session_hmac=c.session_hmac
583
+ WHERE c.site_id=ca.site_id AND s.visitor_hmac=inode.value_hmac)::bigint AS revenue_minor,
584
+ (SELECT CASE WHEN count(DISTINCT c.currency)=1 THEN min(c.currency) END
585
+ FROM analytics_conversions c JOIN analytics_sessions s
586
+ ON s.site_id=c.site_id AND s.session_hmac=c.session_hmac
587
+ WHERE c.site_id=ca.site_id AND s.visitor_hmac=inode.value_hmac) AS currency
588
+ FROM analytics_identity_nodes inode
589
+ WHERE inode.site_id=ca.site_id AND inode.namespace_id=ca.namespace_id
590
+ AND inode.id IN (ca.left_identity_node_id,ca.right_identity_node_id)
591
+ ) node_stats
592
+ ) stats ON true
593
+ WHERE ca.site_id=$1 AND n.status='approved' AND ca.status='active'
594
+ AND ca.confidence_band='high' AND ca.expires_at > now()
595
+ AND ($2::timestamptz IS NULL OR (ca.updated_at,ca.id) < ($2::timestamptz,$3::uuid))
596
+ ORDER BY ca.updated_at DESC, ca.id DESC LIMIT $4`,[e.siteId,i?.at??null,i?.id??null,n+1])).rows.map(o=>({...o,_cursorAt:S(o.updated_at),_cursorId:o.association_id}));return{journeyTier:"candidate_assisted",totalsAreSeparated:!0,...be(e.siteId,s,n,o=>({journeyTier:"candidate_assisted",inferenceRef:O("candidate",e.siteId,o.association_id),confirmedTotals:{sessions:o.confirmed_sessions,events:o.confirmed_events,conversions:o.confirmed_conversions,revenue:o.confirmed_revenue_minor===null?null:{valueMinor:Number(o.confirmed_revenue_minor),currency:o.currency}},inferredIncrementalTotals:{sessions:o.inferred_sessions,events:o.inferred_events,conversions:o.inferred_conversions,revenue:o.inferred_revenue_minor===null?null:{valueMinor:Number(o.inferred_revenue_minor),currency:o.currency}},candidateCount:1,confidenceBand:"high",modelVersion:o.model_version,featureClasses:o.feature_kinds,coverage:o.inferred_sessions>0?"incremental_sessions_available":"no_incremental_sessions",expiresAt:S(o.expires_at),provenance:"consented_high_confidence_candidate_association"}))}}async function bo(e,t=v()){await Ae(e,t);let n=Ee(e.limit),i=ve(e.siteId,e.cursor),s=(await t.query(`/* analyticsInferenceReview:keyset */
597
+ SELECT ca.id AS association_id, ca.updated_at, ca.first_observed_at, ca.last_observed_at,
598
+ ca.expires_at, ca.model_version, ca.confidence_band,
599
+ COALESCE(array_agg(DISTINCT ce.feature_kind ORDER BY ce.feature_kind)
600
+ FILTER (WHERE ce.id IS NOT NULL), ARRAY[]::text[]) AS feature_kinds
601
+ FROM analytics_candidate_associations ca
602
+ JOIN analytics_identity_namespaces n ON n.id=ca.namespace_id AND n.site_id=ca.site_id
603
+ LEFT JOIN analytics_candidate_evidence ce ON ce.association_id=ca.id
604
+ AND ce.namespace_id=ca.namespace_id AND ce.expires_at > now()
605
+ WHERE ca.site_id=$1 AND n.status='approved' AND ca.status='active'
606
+ AND ca.confidence_band='medium' AND ca.expires_at > now()
607
+ AND ($2::timestamptz IS NULL OR (ca.updated_at,ca.id) < ($2::timestamptz,$3::uuid))
608
+ GROUP BY ca.id
609
+ ORDER BY ca.updated_at DESC, ca.id DESC LIMIT $4`,[e.siteId,i?.at??null,i?.id??null,n+1])).rows.map(a=>({...a,_cursorAt:S(a.updated_at),_cursorId:a.association_id}));return be(e.siteId,s,n,a=>({inferenceRef:O("candidate",e.siteId,a.association_id),confidenceBand:a.confidence_band,modelVersion:a.model_version,featureClasses:a.feature_kinds,firstObservedAt:S(a.first_observed_at),lastObservedAt:S(a.last_observed_at),expiresAt:S(a.expires_at),action:"diagnostic_only",eligibleForConfirmedMetrics:!1,eligibleForCrmOrActivation:!1}))}import{createHash as Fr,createVerify as jr,verify as Hr}from"crypto";function zn(e){if(!ge(e.timestamp,e.now))return!1;let t=`${e.method.toUpperCase()}${e.url}${e.rawBody}${e.timestamp}`;return ye(e.signature,pe("sha256",e.clientSecret,t))}var qr=`-----BEGIN PUBLIC KEY-----
610
+ MCowBQYDK2VwAyEAi2HR1srL4o18O8BRa7gVJY7G7bupbN3H9AwJrHCDiOg=
611
+ -----END PUBLIC KEY-----`;function Yn(e){try{if(e.ghlSignature&&e.ghlSignature!=="N/A")return Hr(null,Buffer.from(e.rawBody,"utf8"),e.ed25519PublicKey||qr,Buffer.from(e.ghlSignature,"base64"));if(e.legacySignature&&e.legacySignature!=="N/A"&&e.legacyRsaPublicKey){let t=jr("SHA256");return t.update(e.rawBody),t.verify(e.legacyRsaPublicKey,e.legacySignature,"base64")}return!1}catch{return!1}}function Ot(e){return{schemaVersion:1,eventId:`crm_${Fr("sha256").update(`${e.source}:${e.sourceEventId}`).digest("hex").slice(0,24)}`,sourceEventId:e.sourceEventId,source:e.source,sourceAccountRef:e.sourceAccountRef,eventKind:"crm",eventName:"crm_stage_changed",occurredAt:e.occurredAt,visitorId:e.visitorId,sessionId:e.sessionId,personRef:e.contactId,dealId:e.dealId,orderId:e.orderId,valueMinor:e.valueMinor,currency:e.currency,clickIds:e.clickIds,properties:{pipelineId:e.pipelineId||null,stageId:e.stageId,stageName:e.stageName||null,status:e.status}}}function Xn(e){let t=e.item,n=p(t.objectId,240)||"unknown",i=p(t.propertyValue,240)||"unknown";return{sourceEventId:`deal:${n}:stage:${i}:${p(t.occurredAt,100)||"unknown"}`,source:"hubspot",sourceAccountRef:e.sourceAccountRef,dealId:n,stageId:i,stageName:e.stageName,occurredAt:B(t.occurredAt),valueMinor:e.valueMinor,currency:e.currency,status:/closed.?won/i.test(i)?"won":/closed.?lost/i.test(i)?"lost":"open"}}function Gn(e){let t=e.payload,n=t.opportunity&&typeof t.opportunity=="object"?t.opportunity:t,i=p(n.id??t.id,240)||"unknown",r=p(n.pipelineStageId??t.pipelineStageId,240)||"unknown",s=(p(n.status??t.status,80)||"open").toLowerCase();return{sourceEventId:`opportunity:${i}:stage:${r}:${p(t.timestamp??t.dateAdded,100)||"unknown"}`,source:"highlevel",sourceAccountRef:e.sourceAccountRef,contactId:p(n.contactId??t.contactId,240),dealId:i,pipelineId:p(n.pipelineId??t.pipelineId,240),stageId:r,stageName:p(n.pipelineStageName??t.pipelineStageName,240),occurredAt:B(t.timestamp,t.dateAdded,n.updatedAt),valueMinor:Math.round((W(n.monetaryValue??t.monetaryValue)??0)*100),currency:p(n.currency??t.currency,3)?.toUpperCase(),status:s.includes("won")?"won":s.includes("lost")?"lost":s.includes("spam")?"spam":"open"}}var Kr=90;function Qn(e){let t=e.consent;if(!t)return{necessary:!0,siteAnalytics:!1,enhancedMatching:!1,advertisingMeasurement:!1,receiptId:null,reason:"missing"};if(t.globalPrivacyControl)return{necessary:!0,siteAnalytics:!1,enhancedMatching:!1,advertisingMeasurement:!1,receiptId:t.receiptId??null,reason:"gpc"};let n=t.purposes,i={necessary:!0,siteAnalytics:n.site_analytics==="granted",enhancedMatching:n.enhanced_matching==="granted",advertisingMeasurement:n.advertising_measurement==="granted",receiptId:t.receiptId??null};return{...i,reason:i.siteAnalytics||i.enhancedMatching||i.advertisingMeasurement?"granted":"denied"}}function Jr(e){let t=[];return e.confirmedPeople<e.minimumConfirmedPeople&&t.push(`confirmed_people:${e.confirmedPeople}/${e.minimumConfirmedPeople}`),e.positiveOutcomes<e.minimumPositiveOutcomes&&t.push(`positive_outcomes:${e.positiveOutcomes}/${e.minimumPositiveOutcomes}`),e.validatedManifest||t.push("validated_calibrated_manifest"),{...e,eligible:t.length===0,missingEvidence:t}}var zr={callrail:{expectedEventTypes:["call"],expectedEventPhases:["pre_call","routing_complete","post_call","call_modified"],signatureMode:"callrail_hmac_sha1_exact_body",freshnessSlaSeconds:86400,supportsReconciliation:!0},calltrackingmetrics:{expectedEventTypes:["call"],expectedEventPhases:["call_started","call_answered","call_complete","call_modified"],signatureMode:"ctm_hmac_sha1_time_and_exact_body",freshnessSlaSeconds:86400,supportsReconciliation:!1},twilio:{expectedEventTypes:["call_status"],expectedEventPhases:["initiated","ringing","in-progress","completed","busy","failed","no-answer","canceled"],signatureMode:"twilio_sdk_exact_url_and_form_body",freshnessSlaSeconds:86400,supportsReconciliation:!0},hubspot:{expectedEventTypes:["deal.propertyChange"],expectedEventPhases:["dealstage"],signatureMode:"hubspot_v3_hmac_sha256_exact_url_body_timestamp",freshnessSlaSeconds:86400,supportsReconciliation:!1},highlevel:{expectedEventTypes:["OpportunityStageUpdate"],expectedEventPhases:["open","won","lost","spam"],signatureMode:"highlevel_ed25519_or_legacy_rsa",freshnessSlaSeconds:86400,supportsReconciliation:!1},generic_crm:{expectedEventTypes:["crm_stage_changed","transaction","custom_server"],expectedEventPhases:["open","qualified","won","lost","spam"],signatureMode:"xray_hmac_sha256_timestamp_and_exact_body",freshnessSlaSeconds:86400,supportsReconciliation:!1}},Yr=/(^|_)(api_?key|access_?key|api_?token|auth_?token|token|password|private_?key|secret|signing_?key)($|_)/i;function Xr(e){let t=(n,i)=>{if(!(!n||typeof n!="object"))for(let[r,s]of Object.entries(n)){let a=i?`${i}.${r}`:r,o=r.replace(/([a-z0-9])([A-Z])/g,"$1_$2").toLowerCase();if(Yr.test(o)&&!/public_?key/i.test(o))throw new f("analytics_connection_config_contains_credential",`Connection config cannot contain credentials (${a}). Use the encrypted webhook secret or service connection instead.`,400);t(s,a)}};t(e,"")}function Ho(e){return e instanceof Error&&e.message==="webhook_secret_unavailable"?"webhook_secret_unavailable":e instanceof f?e.code.slice(0,80):"invalid_webhook_payload"}function Tt(e){let t=e.provider,n=zr[t];if(!n)throw new f("analytics_connection_provider_unavailable","This analytics connection is not available in the current public release.",404);return{callbackPath:`/analytics/v1/webhooks/${t}/${e.connectionId}`,expectedEventTypes:[...n.expectedEventTypes],expectedEventPhases:[...n.expectedEventPhases],signatureMode:n.signatureMode,freshnessSlaSeconds:n.freshnessSlaSeconds,reconciliationCapability:n.supportsReconciliation?e.serviceConnectionRef?"available":"connection_required":"webhook_only"}}function qo(e,t){let n=String(e.id),i=String(e.provider),r=Tt({provider:i,connectionId:n,serviceConnectionRef:typeof e.service_connection_ref=="string"?e.service_connection_ref:null}),s=String(e.readiness??"configured_unverified"),a=e.verification_evidence==="provider"||e.verification_evidence==="synthetic"?e.verification_evidence:null,o=!!e.webhook_secret_configured,c=e.last_event_at?String(e.last_event_at):null,d=s==="live"&&c?Date.now()-Date.parse(c)>r.freshnessSlaSeconds*1e3:!1;return{...e,callbackUrl:new URL(r.callbackPath,`${t.replace(/\/$/,"")}/`).toString(),expectedEventTypes:r.expectedEventTypes,expectedEventPhases:r.expectedEventPhases,signatureMode:r.signatureMode,freshnessSlaSeconds:r.freshnessSlaSeconds,freshness:d?"stale":c?"fresh":"awaiting_first_event",reconciliationCapability:r.reconciliationCapability,lastVerifiedReceipt:e.last_verified_receipt_id?{receiptId:String(e.last_verified_receipt_id),evidence:a,verifiedAt:e.last_verified_at?String(e.last_verified_at):null}:a?{receiptId:null,evidence:a,verifiedAt:e.last_verified_at?String(e.last_verified_at):null}:null,safeError:e.last_error_code?{code:String(e.last_error_code)}:null,setupChecklist:{callbackUrlGenerated:!0,callbackConfigured:a==="provider",signatureConfigured:i==="highlevel"||o,syntheticTestPassed:a==="synthetic"||a==="provider",providerReceiptReceived:a==="provider"},evidenceStillMissing:a==="provider"?[]:["Send a real provider-signed event to the callback URL to mark this connection live."]}}async function Wo(e){let t=At.parse(e.definition),n=v();await J(n,e.siteId,e.userId);let i=t.enabled?"active":"disabled";return(await n.query(`INSERT INTO analytics_event_definitions(
612
+ id, site_id, name, event_name, trigger_kind, definition, status, created_by_user_id
613
+ ) VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7,$8)
614
+ ON CONFLICT(site_id, name) DO UPDATE SET event_name=EXCLUDED.event_name,
615
+ trigger_kind=EXCLUDED.trigger_kind, definition=EXCLUDED.definition,
616
+ status=EXCLUDED.status, version=analytics_event_definitions.version+1, updated_at=now()
617
+ RETURNING id, name, event_name, trigger_kind, definition, version, status, created_at, updated_at`,[se(),e.siteId,t.name,t.eventName,t.triggerKind,JSON.stringify(t),i,e.userId])).rows[0]}async function Bo(e,t){let n=v();return await U(n,e,t),(await n.query(`SELECT id, name, event_name, trigger_kind, definition, version, status, created_at, updated_at
618
+ FROM analytics_event_definitions WHERE site_id=$1 AND status <> 'archived'
619
+ ORDER BY created_at DESC`,[e])).rows}async function xt(e){let t=v();e.requireEditor?await J(t,e.siteId,e.userId):await U(t,e.siteId,e.userId);let i=(await t.query(`SELECT id, definition, version, status FROM analytics_event_definitions
620
+ WHERE id=$1 AND site_id=$2`,[e.definitionId,e.siteId])).rows[0];if(!i||i.status==="archived")throw new f("analytics_event_definition_not_found","Event definition not found.",404);return{db:t,row:i}}async function Vo(e){let{db:t,row:n}=await xt({...e,requireEditor:!0}),i=At.parse({...n.definition,...e.changes}),r=i.enabled?"active":"disabled";return(await t.query(`UPDATE analytics_event_definitions SET
621
+ name=$3, event_name=$4, trigger_kind=$5, definition=$6::jsonb,
622
+ status=$7, version=version+1, updated_at=now()
623
+ WHERE id=$1 AND site_id=$2
624
+ RETURNING id, name, event_name, trigger_kind, definition, version, status, created_at, updated_at`,[e.definitionId,e.siteId,i.name,i.eventName,i.triggerKind,JSON.stringify(i),r])).rows[0]}async function Ko(e){let t=v();await J(t,e.siteId,e.userId);let n=await t.query(`UPDATE analytics_event_definitions SET status='archived', updated_at=now()
625
+ WHERE id=$1 AND site_id=$2
626
+ RETURNING id, status`,[e.definitionId,e.siteId]);if(!n.rows[0])throw new f("analytics_event_definition_not_found","Event definition not found.",404);return n.rows[0]}function Gr(e,t,n){let i=t.hostnames.length===0||t.hostnames.includes(n.hostname),r=t.triggerKind===n.triggerKind,s=t.triggerKind==="page"?t.pathMatch==="prefix"?n.path.startsWith(t.path??"/"):n.path===t.path:null,a=t.triggerKind==="semantic"?n.tagEventName===t.eventName:null,o=t.triggerKind==="click"||t.triggerKind==="form"?n.selectorMatched===!0:null;return{definitionId:e,eventName:t.eventName,triggerKind:t.triggerKind,matched:t.enabled&&i&&r&&(s??a??o??!1),checks:{hostname:i,triggerKind:r,path:s,tag:a,selector:o}}}async function Jo(e){let{row:t}=await xt({...e,requireEditor:!1});return Gr(e.definitionId,t.definition,e.preview)}async function zo(e){let{db:t,row:n}=await xt({...e,requireEditor:!1}),i=Math.max(0,Math.min(15e3,Math.trunc(e.timeoutMs??0))),r=Math.max(1,Math.min(86400,Math.trunc(e.maxAgeSeconds??300))),s=Date.now()+i;do{let o=(await t.query(`SELECT event_id, received_at
627
+ FROM analytics_events
628
+ WHERE site_id=$1 AND event_definition_id=$2 AND event_definition_version=$3
629
+ AND received_at >= now() - ($4 * interval '1 second')
630
+ ORDER BY received_at DESC, id DESC
631
+ LIMIT 1`,[e.siteId,e.definitionId,n.version,r])).rows[0];if(o)return{status:"observed_live_match",definitionId:e.definitionId,definitionVersion:n.version,eventId:o.event_id,observedAt:new Date(o.received_at).toISOString(),maxAgeSeconds:r};let c=s-Date.now();if(c<=0)break;await new Promise(d=>setTimeout(d,Math.min(500,c)))}while(Date.now()<=s);return{status:"not_observed",definitionId:e.definitionId,definitionVersion:n.version,eventId:null,observedAt:null,maxAgeSeconds:r}}async function Yo(e){let t=await v().query(`SELECT s.owner_user_id, s.id AS site_id, d.id, d.version, d.definition
632
+ FROM analytics_pixels p JOIN analytics_sites s ON s.id=p.site_id
633
+ LEFT JOIN analytics_event_definitions d ON d.site_id=s.id AND d.status='active'
634
+ WHERE p.public_id=$1 AND p.status='active'`,[e]);return{ownerUserId:t.rows[0]?Number(t.rows[0].owner_user_id):null,siteId:t.rows[0]?.site_id??null,definitions:t.rows.filter(n=>n.id&&n.version&&n.definition).map(n=>({...n.definition,id:n.id,version:n.version}))}}async function Xo(e){Xr(e.config??{});let t=v();await J(t,e.siteId,e.userId);let n=e.webhookSecret?de(e.webhookSecret,`connection-secret:${e.provider}:${e.sourceAccountRef}`):null;return(await t.query(`INSERT INTO analytics_connections(
635
+ id, site_id, provider, name, source_account_ref, service_connection_ref,
636
+ secret_ciphertext, config, created_by_user_id
637
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8::jsonb,$9)
638
+ ON CONFLICT(site_id, provider, source_account_ref) DO UPDATE SET
639
+ name = EXCLUDED.name,
640
+ service_connection_ref = COALESCE(EXCLUDED.service_connection_ref, analytics_connections.service_connection_ref),
641
+ secret_ciphertext = COALESCE(EXCLUDED.secret_ciphertext, analytics_connections.secret_ciphertext),
642
+ config = EXCLUDED.config,
643
+ readiness = CASE WHEN analytics_connections.readiness = 'live' THEN 'live' ELSE 'configured_unverified' END,
644
+ updated_at = now()
645
+ RETURNING id, site_id, provider, name, source_account_ref, service_connection_ref,
646
+ readiness, last_verified_at, last_event_at, last_reconciled_at, last_error_code,
647
+ verification_evidence, last_verified_receipt_id,
648
+ (secret_ciphertext IS NOT NULL) AS webhook_secret_configured,
649
+ created_at, updated_at`,[se(),e.siteId,e.provider.trim().toLowerCase().slice(0,80),e.name.trim().slice(0,120),e.sourceAccountRef.trim().slice(0,240),e.serviceConnectionRef?.trim().slice(0,240)||null,n,JSON.stringify(e.config??{}),e.userId])).rows[0]}async function Go(e,t){let n=v();return await U(n,e,t),(await n.query(`SELECT id, site_id, provider, name, source_account_ref, service_connection_ref,
650
+ readiness, last_verified_at, last_event_at, last_reconciled_at,
651
+ last_error_code, verification_evidence, last_verified_receipt_id,
652
+ created_at, updated_at,
653
+ (secret_ciphertext IS NOT NULL) AS webhook_secret_configured
654
+ FROM analytics_connections WHERE site_id = $1 ORDER BY created_at`,[e])).rows}async function Zo(e){let n=(await v().query(`SELECT id, site_id, provider, source_account_ref, secret_ciphertext,
655
+ readiness, config
656
+ FROM analytics_connections WHERE id = $1 AND readiness <> 'disabled'`,[e])).rows[0];if(!n)return null;let i=`connection-secret:${n.provider}:${n.source_account_ref}`;return{id:n.id,siteId:n.site_id,provider:n.provider,sourceAccountRef:n.source_account_ref,secret:n.secret_ciphertext?he(n.secret_ciphertext,i):null,readiness:n.readiness,config:n.config||{}}}async function ei(e){let t=v();await J(t,e.siteId,e.userId);let i=(await t.query(`SELECT id, site_id, provider, source_account_ref, service_connection_ref,
657
+ secret_ciphertext, readiness, config
658
+ FROM analytics_connections
659
+ WHERE id=$1 AND site_id=$2 AND readiness <> 'disabled'`,[e.connectionId,e.siteId])).rows[0];if(!i)throw new f("analytics_connection_not_found","Analytics connection not found.",404);return i}function Zr(e){return e.secret_ciphertext?he(e.secret_ciphertext,`connection-secret:${e.provider}:${e.source_account_ref}`):null}function Qr(e){let t=e.now??Date.now(),n=new Date(t).toISOString(),i=e.secret||"xray-ephemeral-synthetic-fixture-secret";if(e.provider==="callrail"){let c={resource_id:"xray-test-call",timestamp:n,answered:!0,duration:30},d=JSON.stringify(c);return{signatureVerified:Hn({rawBody:d,signature:e.tamperSignature?"invalid":Dt("sha1",i).update(d).digest("base64"),secret:i,payloadTimestamp:n,now:t}),event:qn({payload:c,sourceAccountRef:e.sourceAccountRef,phase:"post_call"})}}if(e.provider==="calltrackingmetrics"){let c={activity_id:"xray-test-call",timestamp:n,status:"completed"},d=JSON.stringify(c),_=String(Math.floor(t/1e3));return{signatureVerified:Wn({rawBody:d,requestTime:_,signature:e.tamperSignature?"invalid":Dt("sha1",i).update(_+d).digest("base64"),secret:i,now:t}),event:Bn({payload:c,sourceAccountRef:e.sourceAccountRef,phase:"call_complete"})}}if(e.provider==="twilio"){let c={CallSid:"CA_XRAY_TEST",CallStatus:"completed",Timestamp:n},d=e.tamperSignature?"invalid":Vr.getExpectedTwilioSignature(i,e.callbackUrl,c);return{signatureVerified:Vn({authToken:i,signature:d,url:e.callbackUrl,params:c}),event:Kn({params:c,sourceAccountRef:e.sourceAccountRef})}}if(e.provider==="hubspot"){let c={subscriptionType:"deal.propertyChange",propertyName:"dealstage",objectId:"xray-test-deal",propertyValue:"qualified",occurredAt:String(t)},d=JSON.stringify([c]),_=String(t),u=Dt("sha256",i).update(`POST${e.callbackUrl}${d}${_}`).digest("base64"),y=e.tamperSignature?"invalid":u;return{signatureVerified:zn({clientSecret:i,signature:y,timestamp:_,method:"POST",url:e.callbackUrl,rawBody:d,now:t}),event:Ot(Xn({item:c,sourceAccountRef:e.sourceAccountRef}))}}if(e.provider==="highlevel"){let c={timestamp:n,opportunity:{id:"xray-test-opportunity",pipelineStageId:"qualified",status:"open"}},d=JSON.stringify(c),{privateKey:_,publicKey:u}=Wr("ed25519"),y=e.tamperSignature?Buffer.alloc(64).toString("base64"):Br(null,Buffer.from(d),_).toString("base64");return{signatureVerified:Yn({rawBody:d,ghlSignature:y,ed25519PublicKey:u.export({type:"spki",format:"pem"}).toString()}),event:Ot(Gn({payload:c,sourceAccountRef:e.sourceAccountRef}))}}let r=Qe.parse({schemaVersion:1,eventId:"generic_xray_test",sourceEventId:"xray-test-event",source:"generic_crm",sourceAccountRef:e.sourceAccountRef,eventKind:"crm",eventName:"crm_stage_changed",occurredAt:n,dealId:"xray-test-deal",properties:{stageId:"qualified",status:"open"}}),s=JSON.stringify(r),a=String(t),o=Qt(e.provider,a,s,i);return{signatureVerified:en(e.tamperSignature?`${o.startsWith("0")?"1":"0"}${o.slice(1)}`:o,o),event:r}}async function Qo(e){let t=await ei(e),n=Zr(t);if(t.provider!=="highlevel"&&!n)throw new f("analytics_connection_secret_unavailable","Configure the provider webhook validation secret before running a connection test.",409);let i=Tt({provider:t.provider,connectionId:t.id,serviceConnectionRef:t.service_connection_ref}),r=new URL(i.callbackPath,`${e.requestOrigin.replace(/\/$/,"")}/`).toString(),s=Qr({provider:t.provider,sourceAccountRef:t.source_account_ref,callbackUrl:r,secret:n});if(!s.signatureVerified)throw await Zn({connectionId:t.id,readiness:"degraded",errorCode:"synthetic_signature_test_failed"}),new f("analytics_connection_test_failed","Connection signature test failed.",409);let a=await Zn({connectionId:t.id,readiness:"verified",evidence:"synthetic"}),o=a?.verification_evidence??"synthetic";return{status:a?.readiness??"verified",evidence:o,provider:t.provider,eventType:s.event.eventKind,eventPhase:s.event.eventName,connection:a,evidenceStillMissing:o==="provider"?[]:["A real provider-signed receipt is required before this connection becomes live."]}}async function ec(e){let t=await ei(e),n=Tt({provider:t.provider,connectionId:t.id,serviceConnectionRef:t.service_connection_ref});return{capability:n.reconciliationCapability,started:!1,evidence:null,nextAction:n.reconciliationCapability==="available"?"Reconciliation is available; the reconciliation worker must claim and execute this request.":n.reconciliationCapability==="connection_required"?"Connect the provider read API before reconciliation can run.":"This provider is webhook only; use webhook retries or historical backfill."}}async function tc(e){let t=se(),n=e.rawBody?de(e.rawBody,"webhook-payload"):null,i=await v().query(`INSERT INTO analytics_webhook_receipts(
660
+ id, site_id, connection_id, provider, source_account_ref, source_event_id,
661
+ signature_status, payload_ciphertext, payload_expires_at
662
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,now() + interval '7 days')
663
+ ON CONFLICT(site_id, provider, source_account_ref, source_event_id) DO UPDATE SET
664
+ normalization_status = CASE
665
+ WHEN analytics_webhook_receipts.normalization_status = 'accepted' THEN 'replayed'
666
+ ELSE analytics_webhook_receipts.normalization_status
667
+ END
668
+ RETURNING id, normalization_status`,[t,e.siteId,e.connectionId,e.provider,e.sourceAccountRef,e.sourceEventId,e.signatureStatus,n]);return{id:i.rows[0].id,duplicate:i.rows[0].id!==t,status:i.rows[0].normalization_status}}async function nc(e){await v().query(`UPDATE analytics_webhook_receipts SET
669
+ normalization_status = $2,
670
+ error_code = $3,
671
+ normalized_at = now()
672
+ WHERE id = $1`,[e.receiptId,e.accepted?"accepted":"rejected",e.errorCode??null])}async function Zn(e){return(await v().query(`UPDATE analytics_connections SET
673
+ readiness = CASE WHEN readiness='live' AND $2='verified' THEN 'live' ELSE $2 END,
674
+ last_error_code = $3,
675
+ last_verified_at = CASE WHEN $2 IN ('verified','live') THEN now() ELSE last_verified_at END,
676
+ last_event_at = CASE WHEN $4 THEN now() ELSE last_event_at END,
677
+ last_reconciled_at = CASE WHEN $5 THEN now() ELSE last_reconciled_at END,
678
+ verification_evidence = CASE
679
+ WHEN verification_evidence='provider' THEN 'provider'
680
+ ELSE COALESCE($6, verification_evidence)
681
+ END,
682
+ last_verified_receipt_id = COALESCE($7::uuid, last_verified_receipt_id),
683
+ updated_at = now()
684
+ WHERE id = $1
685
+ RETURNING id, readiness, last_verified_at, last_event_at, last_reconciled_at,
686
+ last_error_code, verification_evidence, last_verified_receipt_id`,[e.connectionId,e.readiness,e.errorCode??null,!!e.receipt,!!e.reconciled,e.evidence??null,e.receiptId??null])).rows[0]??null}async function ic(e){let t=Dn.parse(e.rule),n=v();return await J(n,e.siteId,e.userId),(await n.query(`INSERT INTO analytics_conversion_rules(
687
+ id, site_id, name, conversion_kind, condition, default_value_minor,
688
+ default_currency, status, created_by_user_id
689
+ ) VALUES ($1,$2,$3,$4,$5::jsonb,$6,$7,$8,$9)
690
+ ON CONFLICT(site_id, name) DO UPDATE SET
691
+ conversion_kind = EXCLUDED.conversion_kind,
692
+ condition = EXCLUDED.condition,
693
+ default_value_minor = EXCLUDED.default_value_minor,
694
+ default_currency = EXCLUDED.default_currency,
695
+ status = EXCLUDED.status,
696
+ version = analytics_conversion_rules.version + 1,
697
+ updated_at = now()
698
+ RETURNING *`,[se(),e.siteId,t.name,t.conversionKind,JSON.stringify({...t.condition,...t.valueRule?{valueRule:t.valueRule}:{}}),t.defaultValueMinor??null,t.defaultCurrency.toUpperCase(),t.enabled?"active":"disabled",e.userId])).rows[0]}async function rc(e,t){let n=v();return await U(n,e,t),(await n.query(`SELECT id, name, conversion_kind, condition, default_value_minor,
699
+ default_currency, version, status, created_at, updated_at
700
+ FROM analytics_conversion_rules WHERE site_id = $1 AND status <> 'archived'
701
+ ORDER BY created_at`,[e])).rows.map(r=>{let s=r.condition&&typeof r.condition=="object"?r.condition:{},{valueRule:a,...o}=s;return{...r,condition:o,value_rule:a??null}})}function es(e){let t=e.properties.tags;return Array.isArray(t)?t.filter(n=>typeof n=="string"):[]}function ts(e,t){let n=[],i=e.condition;i.eventName&&i.eventName!==t.eventName&&n.push("event_name_mismatch"),i.eventKind&&i.eventKind!==t.eventKind&&n.push("event_kind_mismatch"),i.source&&i.source!==t.source&&n.push("source_mismatch"),i.stageId&&i.stageId!==t.properties.stageId&&n.push("stage_mismatch"),i.status&&i.status!==t.properties.status&&n.push("status_mismatch"),i.answered!==void 0&&i.answered!==!!t.properties.answered&&n.push("answered_mismatch"),i.minimumCallDurationSeconds!==void 0&&Number(t.properties.durationSeconds??0)<i.minimumCallDurationSeconds&&n.push("call_too_short");let r=es(t);return i.excludedTags.some(s=>r.includes(s))&&n.push("excluded_tag"),i.requiredTags.some(s=>!r.includes(s))&&n.push("required_tag_missing"),(t.properties.status==="spam"||r.includes("spam"))&&n.push("spam_excluded"),{matches:n.length===0,reasons:n}}async function ns(e,t,n){if(!Qn(n).enhancedMatching)return{personId:null,namespaceId:null,identityTier:"unlinked"};let s=(await e.query(`SELECT id FROM analytics_identity_namespaces
702
+ WHERE site_id=$1 AND status='approved' ORDER BY version DESC LIMIT 1`,[t])).rows[0]?.id??null,a=[];n.visitorId&&a.push(["visitor_id",n.visitorId]),n.sessionId&&a.push(["session_id",n.sessionId]),n.personRef&&a.push(["customer_id",n.personRef]);for(let[o,c]of Object.entries(G(n.clickIds)))a.push(["click_id",`${o}:${c}`]);for(let[o,c]of a){let d=await e.query(`SELECT e.person_id FROM analytics_identity_nodes n
703
+ JOIN analytics_identity_edges e ON e.identity_node_id = n.id
704
+ WHERE n.site_id = $1 AND n.kind = $2 AND n.value_hmac = $3
705
+ AND ($4::uuid IS NULL OR (n.namespace_id=$4 AND e.namespace_id=$4))
706
+ ORDER BY e.confidence DESC, e.last_seen_at DESC LIMIT 1`,[t,o,Ne(o,c),s]);if(d.rows[0])return{personId:d.rows[0].person_id,namespaceId:s,identityTier:"confirmed"}}return{personId:null,namespaceId:s,identityTier:"unlinked"}}async function is(e,t){let i=(await e.query(`SELECT m.person_id::text, m.namespace_id::text,
707
+ c.provider AS connection_provider,
708
+ s.latest_receipt_id::text AS receipt_id, s.policy_version,
709
+ s.site_analytics, s.enhanced_matching, s.advertising_measurement,
710
+ s.gpc, r.action, s.effective_at, s.expires_at
711
+ FROM analytics_crm_person_mappings m
712
+ JOIN analytics_connections c ON c.id=m.connection_id AND c.site_id=m.site_id
713
+ JOIN analytics_people p ON p.id=m.person_id AND p.site_id=m.site_id AND p.namespace_id=m.namespace_id
714
+ JOIN analytics_identity_namespaces n ON n.id=m.namespace_id AND n.site_id=m.site_id AND n.status='approved'
715
+ LEFT JOIN analytics_consent_current_state s
716
+ ON s.site_id=m.site_id AND s.subject_ref=p.public_ref
717
+ LEFT JOIN analytics_consent_receipts r
718
+ ON r.id=s.latest_receipt_id AND r.site_id=s.site_id
719
+ WHERE m.site_id=$1 AND m.connection_id::text=$2
720
+ AND m.provider_person_ref=$3 AND m.person_id::text=$4
721
+ LIMIT 1`,[t.siteId,t.context.connectionId,t.context.providerContactId,t.context.expectedPersonId])).rows[0];if(!i)return{personId:null,namespaceId:null,consent:void 0,reason:"mapping_unavailable"};let r,s;try{r=ee(t.context.provider),s=ee(i.connection_provider)}catch{return{personId:null,namespaceId:null,consent:void 0,reason:"provider_mismatch"}}if(r!==s)return{personId:null,namespaceId:null,consent:void 0,reason:"provider_mismatch"};if(!i.receipt_id||!i.effective_at)return{personId:null,namespaceId:null,consent:void 0,reason:"consent_missing"};if(i.action==="withdraw")return{personId:null,namespaceId:null,consent:void 0,reason:"consent_withdrawn"};if(i.gpc||i.action==="gpc")return{personId:null,namespaceId:null,consent:void 0,reason:"consent_gpc"};let a=new Date(t.at??Date.now()).getTime();if(i.expires_at&&new Date(i.expires_at).getTime()<=a)return{personId:null,namespaceId:null,consent:void 0,reason:"consent_expired"};if(!i.enhanced_matching)return{personId:null,namespaceId:null,consent:void 0,reason:"enhanced_matching_denied"};if(!i.advertising_measurement)return{personId:null,namespaceId:null,consent:void 0,reason:"advertising_measurement_denied"};let o=new Date(i.effective_at).toISOString();return{personId:i.person_id,namespaceId:i.namespace_id,reason:"eligible",consent:{analytics:i.site_analytics?"granted":"denied",adUserData:"granted",adPersonalization:"granted",globalPrivacyControl:!1,occurredAt:o,receiptId:i.receipt_id,policyVersion:i.policy_version??void 0,purposes:{site_analytics:i.site_analytics?"granted":"denied",enhanced_matching:"granted",advertising_measurement:"granted"}}}}async function rs(e,t){let n=G(t.eventClickIds),i=await e.query(`SELECT click_ids FROM analytics_attribution_touches
722
+ WHERE site_id=$1 AND person_id=$2
723
+ AND occurred_at <= $3::timestamptz
724
+ AND occurred_at >= $3::timestamptz - ($4::text || ' days')::interval
725
+ AND click_ids <> '{}'::jsonb
726
+ ORDER BY occurred_at DESC, id DESC`,[t.siteId,t.personId,t.occurredAt,Kr]),r={};for(let s of i.rows)for(let[a,o]of Object.entries(G(s.click_ids)))a in r||(r[a]=o);return{...r,...n}}function ss(e,t){let n=e.valueMinor??Number(t.default_value_minor??0),i=e.valueMinor!==void 0?"dynamic":(t.default_value_minor!==null,"fixed"),r=e.valueMinor!==void 0?"source_event":null,s={mode:e.valueMinor!==void 0?"dynamic":"fixed",source:r,observedAt:e.valueMinor!==void 0?e.occurredAt:null,fallback:!1,ruleVersion:String(t.version),resolution:i,baseValueMinor:n,resolvedValueMinor:n,currency:(e.currency||t.default_currency||"USD").toUpperCase(),linkedEvent:null};return{valueMinor:n,currency:s.currency,provenance:s}}async function as(e,t){if(!t.rule.value_rule)return ss(t.event,t.rule);let n=t.rule.value_rule.conditionalOverride?.linkedEventName,i=n&&t.personId?(await e.query(`SELECT id, event_name, occurred_at FROM analytics_external_events
727
+ WHERE site_id=$1 AND person_id=$2 AND event_name=$3 AND id<>$4
728
+ ORDER BY occurred_at DESC LIMIT 100`,[t.siteId,t.personId,n,t.insertedEventId])).rows.map(s=>({eventId:s.id,eventName:s.event_name,occurredAt:s.occurred_at.toISOString(),identityTier:"confirmed"})):[],r=t.event.valueMinor===void 0?void 0:{source_event:{valueMinor:t.event.valueMinor,currency:(t.event.currency||t.rule.default_currency||"USD").toUpperCase(),observedAt:t.event.occurredAt},crm_field:t.event.source.includes("crm")?{valueMinor:t.event.valueMinor,currency:(t.event.currency||t.rule.default_currency||"USD").toUpperCase(),observedAt:t.event.occurredAt}:void 0,transaction:t.event.eventKind==="transaction"?{valueMinor:t.event.valueMinor,currency:(t.event.currency||t.rule.default_currency||"USD").toUpperCase(),observedAt:t.event.occurredAt}:void 0};return Rn(t.rule.value_rule,{targetEvent:{eventId:t.insertedEventId,eventName:t.event.eventName,occurredAt:t.event.occurredAt,identityTier:t.personId?"confirmed":"candidate"},linkedEvents:i,observations:r,observedAt:t.event.occurredAt,providerOperation:"new_conversion"})}async function os(e){if(!e.namespaceId||!e.personId)return[];let t=await e.client.query(`SELECT id, name, score_kind, rules, minimum_score, maximum_score, version
729
+ FROM analytics_score_definitions WHERE site_id=$1 AND status='active'`,[e.siteId]);if(!t.rowCount)return[];let n=await e.client.query(`SELECT id, event_name, event_kind, occurred_at, source, properties
730
+ FROM analytics_external_events WHERE site_id=$1 AND person_id=$2
731
+ ORDER BY occurred_at DESC LIMIT 5000`,[e.siteId,e.personId]),i={events:n.rows.map(s=>({ref:s.id,name:s.event_name,occurredAt:s.occurred_at.toISOString()})),pages:n.rows.filter(s=>typeof s.properties.path=="string").map(s=>({ref:s.id,name:String(s.properties.path),occurredAt:s.occurred_at.toISOString()})),sources:n.rows.map(s=>({ref:s.id,name:s.source,occurredAt:s.occurred_at.toISOString()})),calls:n.rows.filter(s=>s.event_kind==="call").map(s=>({ref:s.id,name:String(s.properties.outcome??s.properties.status??s.event_name),occurredAt:s.occurred_at.toISOString()})),crmStages:n.rows.filter(s=>s.event_kind==="crm").map(s=>({ref:s.id,name:String(s.properties.stageId??s.properties.stageName??s.event_name),occurredAt:s.occurred_at.toISOString()}))},r=[];for(let s of t.rows){let a=s.rules&&typeof s.rules=="object"&&!Array.isArray(s.rules)?s.rules:{},o=Array.isArray(s.rules)?s.rules:a.groups;if(!Array.isArray(o)||o.length===0)continue;let c=Array.isArray(a.permittedProperties)?a.permittedProperties.filter(d=>typeof d=="string"):[];try{let d=Un();Fn({tables:d,id:s.id,definition:{namespaceId:e.namespaceId,name:s.name,kind:s.score_kind,targetObject:"person",minimum:s.minimum_score,maximum:s.maximum_score,groups:o,permittedProperties:c,enabled:!0},now:e.occurredAt});let _=jn({tables:d,namespaceId:e.namespaceId,scoreId:s.id,subjectRef:e.personId,identityTier:"confirmed",facts:i,now:e.occurredAt});await e.client.query(`INSERT INTO analytics_score_evaluations(
732
+ id, site_id, namespace_id, definition_id, definition_version,
733
+ person_id, score, contributions, evaluated_at
734
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8::jsonb,$9)`,[_.id,e.siteId,e.namespaceId,s.id,s.version,e.personId,_.value,JSON.stringify(_.components),_.evaluatedAt]),r.push(_.id)}catch{}}return r}async function cs(e){return e.namespaceId?(await e.client.query(`SELECT t.id, d.event_name, t.minimum_confirmed_people, t.minimum_positive_outcomes,
735
+ (SELECT count(DISTINCT p.id)::text FROM analytics_people p
736
+ WHERE p.site_id=t.site_id AND p.namespace_id=t.namespace_id) AS confirmed_people,
737
+ (SELECT count(DISTINCT e.person_id)::text FROM analytics_external_events e
738
+ JOIN analytics_people p ON p.id=e.person_id AND p.site_id=e.site_id
739
+ WHERE e.site_id=t.site_id AND p.namespace_id=t.namespace_id AND e.event_name=d.event_name) AS positive_outcomes,
740
+ EXISTS(SELECT 1 FROM analytics_prediction_manifests m
741
+ WHERE m.target_id=t.id AND m.status IN ('validated','active')) AS validated_manifest
742
+ FROM analytics_prediction_targets t
743
+ JOIN analytics_event_definitions d ON d.id=t.target_event_definition_id
744
+ WHERE t.site_id=$1 AND t.namespace_id=$2 AND t.status<>'disabled'`,[e.siteId,e.namespaceId])).rows.map(n=>Jr({targetId:n.id,confirmedPeople:Number(n.confirmed_people),positiveOutcomes:Number(n.positive_outcomes),minimumConfirmedPeople:n.minimum_confirmed_people,minimumPositiveOutcomes:n.minimum_positive_outcomes,validatedManifest:n.validated_manifest})):[]}async function ti(e){let t=Qe.parse(e.event),n=e.db??v();e.userId!==void 0&&await J(n,e.siteId,e.userId);let i=!e.transactionClient,r=e.transactionClient??await n.connect();try{i&&await r.query("BEGIN");let s=e.trustedCrmContext?await is(r,{siteId:e.siteId,context:e.trustedCrmContext}):null,a=e.trustedCrmContext?Qe.parse({...t,personRef:void 0,consent:s?.consent,properties:{...t.properties,_xrayCrmLinkReason:s?.reason??"mapping_unavailable"}}):t,o=Qn(a),c=s?.personId&&s.namespaceId?{personId:s.personId,namespaceId:s.namespaceId,identityTier:"confirmed"}:e.trustedCrmContext?{personId:null,namespaceId:null,identityTier:"unlinked"}:await ns(r,e.siteId,a),d=c.personId,_=await r.query(`INSERT INTO analytics_external_events(
745
+ id, site_id, receipt_id, person_id, source, source_account_ref,
746
+ source_event_id, event_kind, event_name, occurred_at, visitor_hmac,
747
+ session_hmac, call_id, deal_id, order_id, value_minor, currency,
748
+ click_ids, properties
749
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18::jsonb,$19::jsonb)
750
+ ON CONFLICT(site_id, source, source_account_ref, source_event_id) DO NOTHING
751
+ RETURNING id`,[se(),e.siteId,e.receiptId??null,d,a.source,a.sourceAccountRef,a.sourceEventId,a.eventKind,a.eventName,a.occurredAt,a.visitorId?lt("visitor_id",a.visitorId):null,a.sessionId?lt("session_id",a.sessionId):null,a.callId??null,a.dealId??null,a.orderId??null,a.valueMinor??null,a.currency?.toUpperCase()??null,JSON.stringify(G(a.clickIds)),JSON.stringify({...a.properties,_xrayConsentEligibility:o,_xrayIdentityTier:c.identityTier,...c.namespaceId?{_xrayNamespaceId:c.namespaceId}:{}})]),u=_.rows[0]?.id??(await r.query(`SELECT id FROM analytics_external_events
752
+ WHERE site_id=$1 AND source=$2 AND source_account_ref=$3 AND source_event_id=$4`,[e.siteId,a.source,a.sourceAccountRef,a.sourceEventId])).rows[0]?.id;if(!u)throw new f("analytics_external_event_conflict","The canonical event could not be resolved.",409);let E=(await r.query(`SELECT id, name, conversion_kind, condition, default_value_minor,
753
+ default_currency, version
754
+ FROM analytics_conversion_rules WHERE site_id = $1 AND status = 'active'`,[e.siteId])).rows.filter(A=>ts({name:A.name,conversionKind:A.conversion_kind,condition:(({valueRule:k,...Le})=>Le)(A.condition),defaultValueMinor:A.default_value_minor?Number(A.default_value_minor):void 0,defaultCurrency:A.default_currency,valueRule:A.condition.valueRule,enabled:!0},a).matches),b=[],V={};for(let A of E){let k=await as(r,{siteId:e.siteId,personId:d,insertedEventId:u,event:a,rule:{value_rule:A.condition.valueRule,default_value_minor:A.default_value_minor,default_currency:A.default_currency,version:A.version}}),Le=k.valueMinor??0,Pt=(k.currency||A.default_currency||"USD").toUpperCase(),ci=k.provenance.resolution==="absent"?"absent":k.provenance.resolution==="fallback"?"dynamic":k.provenance.resolution,di=se(),Se=await r.query(`INSERT INTO analytics_conversions(
755
+ id, source_event_id, site_id, session_id, conversion_kind,
756
+ value_minor, currency, occurred_at, person_id, rule_id, rule_version, order_id,
757
+ value_source, value_provenance, session_hmac
758
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14::jsonb,$15)
759
+ ON CONFLICT(site_id, source_event_id) DO NOTHING RETURNING id`,[di,`rule:${A.id}:${a.source}:${a.sourceEventId}`,e.siteId,a.sessionId??null,A.conversion_kind,Le,Pt,a.occurredAt,d,A.id,A.version,a.orderId??null,ci,JSON.stringify(k.provenance),a.sessionId?Ne("session_id",a.sessionId):null]);if(!Se.rowCount||(b.push(Se.rows[0].id),V[Se.rows[0].id]=k.provenance,!d||!o.advertisingMeasurement))continue;let li=await r.query(`SELECT id FROM analytics_activation_destinations
760
+ WHERE site_id = $1 AND status = 'active' AND readiness IN ('verified','live')`,[e.siteId]),ui=await rs(r,{siteId:e.siteId,personId:d,occurredAt:a.occurredAt,eventClickIds:a.clickIds}),_i=de(JSON.stringify({eventId:Se.rows[0].id,eventName:A.conversion_kind,eventTime:a.occurredAt,valueMinor:Le,currency:Pt,valueProvenance:k.provenance,attributionModelProvenance:{model:typeof a.properties.attributionModel=="string"?a.properties.attributionModel:"unassigned",evidenceTier:"confirmed"},orderId:a.orderId,clickIds:ui,source:a.source,personId:d,identityTier:"confirmed",namespaceId:c.namespaceId,consent:{receiptId:o.receiptId,advertisingMeasurement:!0}}),"activation-payload");for(let mi of li.rows)await r.query(`INSERT INTO analytics_activation_jobs(
761
+ id, destination_id, conversion_id, person_id, payload_ciphertext
762
+ ) VALUES ($1,$2,$3,$4,$5)
763
+ ON CONFLICT(destination_id, conversion_id) DO NOTHING`,[se(),mi.id,Se.rows[0].id,d,_i])}let P=await os({client:r,siteId:e.siteId,namespaceId:c.namespaceId,personId:d,occurredAt:a.occurredAt}),h=await cs({client:r,siteId:e.siteId,namespaceId:c.namespaceId});d&&await Sn({siteId:e.siteId,personId:d,canonicalEventId:u,event:a,consent:{...o.receiptId?{receiptId:o.receiptId}:{},siteAnalytics:o.siteAnalytics,advertisingMeasurement:o.advertisingMeasurement},db:r}),i&&await r.query("COMMIT");let T=null,m=typeof a.properties.candidateAssociationId=="string"?a.properties.candidateAssociationId:null,K=typeof a.properties.deterministicEvidenceRef=="string"?a.properties.deterministicEvidenceRef:null,w=a.properties.deterministicEvidenceKind;return d&&c.namespaceId&&m&&K&&["verified_email","verified_phone","crm_contact","signed_linker","authenticated_user"].includes(String(w))&&(T=await Pn({siteId:e.siteId,namespaceId:c.namespaceId,associationId:m,personId:d,evidenceKind:w,evidenceRef:K,occurredAt:a.occurredAt}).catch(()=>null)),{id:_.rows[0]?.id??null,created:!!_.rowCount,personId:d,identityTier:c.identityTier,namespaceId:c.namespaceId,conversionIds:b,consentEligibility:o,valueProvenance:V,scoreEvaluationIds:P,predictionEvidence:h,candidatePromotion:T}}catch(s){throw i&&await r.query("ROLLBACK"),s}finally{i&&r.release()}}async function sc(e,t,n){let i=v();if(await U(i,e,t),!(await i.query("SELECT 1 FROM analytics_people WHERE id = $1 AND site_id = $2",[n,e])).rowCount)throw new f("analytics_person_not_found","Attributed person not found.",404);let[s,a,o,c,d]=await Promise.all([i.query(`SELECT id, started_at, last_active_at, ended_at, duration_ms, landing_path,
764
+ exit_path, pageview_count, event_count
765
+ FROM analytics_sessions WHERE site_id = $1 AND person_id = $2 ORDER BY started_at`,[e,n]),i.query(`SELECT occurred_at, source, medium, campaign, referrer, landing_path
766
+ FROM analytics_attribution_touches WHERE site_id = $1 AND person_id = $2 ORDER BY occurred_at`,[e,n]),i.query(`SELECT source, event_kind, event_name, occurred_at, value_minor, currency
767
+ FROM analytics_external_events WHERE site_id = $1 AND person_id = $2 ORDER BY occurred_at`,[e,n]),i.query(`SELECT conversion_kind, value_minor, currency, occurred_at, rule_version
768
+ FROM analytics_conversions WHERE site_id = $1 AND person_id = $2 ORDER BY occurred_at`,[e,n]),i.query(`SELECT j.status, j.attempts, j.response_category, j.created_at, j.delivered_at,
769
+ d.platform, d.name AS destination_name
770
+ FROM analytics_activation_jobs j
771
+ JOIN analytics_activation_destinations d ON d.id = j.destination_id
772
+ WHERE d.site_id = $1 AND j.person_id = $2 ORDER BY j.created_at`,[e,n])]),_=u=>{if(typeof u!="string"||!u)return null;try{let y=new URL(u);return`${y.origin}${y.pathname}`.slice(0,2e3)}catch{return null}};return{personRef:O("person",e,n),sessions:s.rows.map(u=>({sessionRef:O("session",e,String(u.id)),started_at:u.started_at,last_active_at:u.last_active_at,ended_at:u.ended_at,duration_ms:u.duration_ms,landing_path:u.landing_path,exit_path:u.exit_path,pageview_count:u.pageview_count,event_count:u.event_count})),touches:a.rows.map(u=>({occurred_at:u.occurred_at,source:u.source,medium:u.medium,campaign:u.campaign,referrer:_(u.referrer),landing_path:u.landing_path})),externalEvents:o.rows.map(u=>({source:u.source,event_kind:u.event_kind,event_name:u.event_name,occurred_at:u.occurred_at,value_minor:u.value_minor,currency:u.currency})),conversions:c.rows.map(u=>({conversion_kind:u.conversion_kind,value_minor:u.value_minor,currency:u.currency,occurred_at:u.occurred_at,rule_version:u.rule_version})),deliveries:d.rows.map(u=>({status:u.status,attempts:u.attempts,response_category:u.response_category,created_at:u.created_at,delivered_at:u.delivered_at,platform:u.platform,destination_name:u.destination_name}))}}async function ac(e,t){let n=v();await U(n,e,t);let r=(await n.query(`SELECT
773
+ count(*)::int AS total_events,
774
+ count(*) FILTER (WHERE person_id IS NOT NULL)::int AS identified_events,
775
+ count(*) FILTER (WHERE click_ids <> '{}'::jsonb)::int AS events_with_click_id,
776
+ count(*) FILTER (WHERE event_kind = 'call')::int AS call_events,
777
+ count(*) FILTER (WHERE event_kind = 'call' AND person_id IS NOT NULL)::int AS joined_call_events,
778
+ count(*) FILTER (WHERE event_kind = 'crm')::int AS crm_events,
779
+ count(*) FILTER (WHERE event_kind = 'crm' AND person_id IS NOT NULL)::int AS joined_crm_events,
780
+ count(*) FILTER (WHERE properties->>'status' = 'spam' OR properties->'tags' ? 'spam')::int AS spam_events
781
+ FROM analytics_external_events WHERE site_id = $1`,[e])).rows[0],s=(a,o)=>o?a/o:null;return{...r,identifiedRate:s(r.identified_events,r.total_events),clickIdCaptureRate:s(r.events_with_click_id,r.total_events),phoneJoinRate:s(r.joined_call_events,r.call_events),crmStageJoinRate:s(r.joined_crm_events,r.crm_events)}}var xe="mcp_scraper_subscription_paid";function ds(e){return/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(e)}async function ls(e,t){return(await e.query(`SELECT p.id::text AS person_id, p.namespace_id::text AS namespace_id,
782
+ cs.latest_receipt_id::text AS receipt_id, cs.policy_version,
783
+ cs.site_analytics, cs.enhanced_matching, cs.advertising_measurement,
784
+ cs.effective_at
785
+ FROM analytics_identity_nodes node
786
+ JOIN analytics_identity_edges edge
787
+ ON edge.identity_node_id=node.id AND edge.site_id=node.site_id
788
+ JOIN analytics_people p
789
+ ON p.id=edge.person_id AND p.site_id=node.site_id
790
+ JOIN analytics_identity_namespaces ns
791
+ ON ns.id=p.namespace_id AND ns.site_id=p.site_id AND ns.status='approved'
792
+ JOIN analytics_sites site
793
+ ON site.id=p.site_id AND site.status='active'
794
+ JOIN analytics_restricted_data profile
795
+ ON profile.site_id=p.site_id AND profile.person_id=p.id
796
+ AND profile.purpose='enhanced_matching' AND profile.expires_at>now()
797
+ JOIN analytics_consent_receipts provenance
798
+ ON provenance.id=profile.consent_receipt_id AND provenance.site_id=profile.site_id
799
+ JOIN analytics_consent_current_state cs
800
+ ON cs.site_id=p.site_id AND cs.subject_ref=provenance.subject_ref
801
+ JOIN analytics_consent_receipts receipt
802
+ ON receipt.id=cs.latest_receipt_id AND receipt.site_id=cs.site_id
803
+ WHERE node.site_id=$1 AND node.kind='customer_id' AND node.value_hmac=$2
804
+ AND p.public_ref IS NOT NULL
805
+ AND cs.site_analytics=true AND cs.enhanced_matching=true
806
+ AND cs.gpc=false AND receipt.action NOT IN ('withdraw','gpc')
807
+ AND cs.effective_at <= now()
808
+ AND (cs.expires_at IS NULL OR cs.expires_at > now())
809
+ AND NOT EXISTS (
810
+ SELECT 1 FROM analytics_identity_tombstones tombstone
811
+ WHERE tombstone.site_id=p.site_id AND tombstone.person_id=p.id
812
+ )
813
+ AND NOT EXISTS (
814
+ SELECT 1 FROM analytics_privacy_audits audit
815
+ WHERE audit.site_id=p.site_id AND audit.subject_ref=p.public_ref
816
+ AND audit.action='deletion_requested' AND audit.result_code='running'
817
+ )
818
+ ORDER BY edge.confidence DESC, edge.last_seen_at DESC
819
+ LIMIT 1
820
+ FOR SHARE OF p, ns, cs, receipt`,[t.siteId,Ne("customer_id",t.stableUserId)])).rows[0]??null}async function Lt(e,t){let n=process.env.MCP_SCRAPER_XRAY_SITE_ID?.trim();if(!n)return{status:"skipped",eventName:xe,reason:"site_not_configured"};if(!ds(n))return{status:"skipped",eventName:xe,reason:"site_id_invalid"};let i=String(e.userId).trim();if(!i||!e.invoiceId.trim()||!Number.isSafeInteger(e.amountPaidMinor)||e.amountPaidMinor<0||!/^[a-z]{3}$/i.test(e.currency)||!Number.isFinite(new Date(e.occurredAt).getTime()))throw new Error("analytics_first_party_outcome_invalid");let r=t??{db:v(),record:(a,o)=>ti({...a,transactionClient:o})},s=await r.db.connect();try{await s.query("BEGIN");let a=await ls(s,{siteId:n,stableUserId:i});if(!a)return await s.query("ROLLBACK"),{status:"skipped",eventName:xe,reason:"identity_or_consent_unavailable"};let o={schemaVersion:1,eventId:`mcp_invoice_${e.invoiceId}`.slice(0,200),sourceEventId:`stripe:invoice:${e.invoiceId}`.slice(0,300),source:"mcp_scraper",sourceAccountRef:"first_party_billing",eventKind:"transaction",eventName:xe,occurredAt:e.occurredAt,personRef:i,orderId:e.invoiceId,valueMinor:e.amountPaidMinor,currency:e.currency.toUpperCase(),consent:{analytics:"granted",adUserData:"granted",adPersonalization:a.advertising_measurement?"granted":"denied",globalPrivacyControl:!1,occurredAt:new Date(a.effective_at).toISOString(),receiptId:a.receipt_id,policyVersion:a.policy_version,purposes:{site_analytics:"granted",enhanced_matching:"granted",advertising_measurement:a.advertising_measurement?"granted":"denied"}},properties:{product:"mcp_scraper",billingKind:"base_plan_paid_invoice",revenueProvenance:"stripe_paid_invoice"}},c=await r.record({siteId:n,event:o},s);return await s.query("COMMIT"),{status:"recorded",eventName:xe,reason:"recorded",created:c.created,conversionIds:c.conversionIds}}catch(a){throw await s.query("ROLLBACK").catch(()=>{}),a}finally{s.release()}}function ni(e){let t=e;return t?.price?.id??t?.pricing?.price_details?.price??t?.plan?.id}function Ie(e){if(typeof e=="string")return e;if(e&&typeof e=="object"&&"id"in e)return String(e.id)}function us(e){let t=e.subscription;if(t)return Ie(t);let n=e.parent;return Ie(n?.subscription_details?.subscription)}async function _s(e,t){let n=await M(e);return n||!t||(n=await $e(t),n&&await Pe(n.id,e)),n}async function ms(e,t,n){let i=t.memory_subscription_id;if(!(!i||i===n))try{let r=await e.subscriptions.retrieve(i);r.status!=="canceled"&&!r.cancel_at_period_end&&await e.subscriptions.update(i,{cancel_at_period_end:!0})}catch(r){console.error("[stripe] could not wind down standalone memory subscription",i,r instanceof Error?r.message:r)}}async function $t(e,t,n){if(!t.id||t.status!=="paid")return{status:"not_applicable",invoiceId:t.id??null};let i=t.lines.data.some(y=>{let E=ni(y);return!!E&&E in D});if(!hn(i,t.total))return{status:"not_applicable",invoiceId:t.id};let r=us(t),a=(r?le(await e.subscriptions.retrieve(r))?.price?.id:void 0)??t.lines.data.map(ni).find(y=>!!y&&y in D),o=a?D[a]:void 0;if(!o)return{status:"not_applicable",invoiceId:t.id};let c=Ie(t.customer);if(!c)throw new Error("Paid subscription invoice has no Stripe customer");let d=await _s(c,t.customer_email);if(!d)throw new Error("Paid subscription invoice does not map to an MCP Scraper account");let _=await Ut({invoiceId:t.id,userId:d.id,subscriptionId:r??d.subscription_id,tier:o.tier,concurrency:o.concurrency,creditsMc:o.credits_mc,description:`${o.label} subscription credits`,neverExpires:!!o.credits_never_expire,includedMemoryPlan:o.includes_memory?"pro":null});o.includes_memory&&(await ms(e,d,r??d.subscription_id),await z(d,F({...d,memory_plan:"pro",subscription_tier:o.tier})));let u=await qe(d);u.ok||console.warn("[stripe] scheduled-action credential sync failed:",u.error??"unknown error");try{await Lt({userId:d.id,invoiceId:t.id,amountPaidMinor:t.amount_paid,currency:t.currency,occurredAt:new Date(((t.status_transitions?.paid_at??n)||Math.floor(Date.now()/1e3))*1e3).toISOString()})}catch(y){console.error("[stripe] X-Ray paid outcome failed",{invoiceId:t.id,error:y instanceof Error?y.message:String(y)})}return{status:_.granted?"fulfilled":"already_fulfilled",invoiceId:t.id,userId:d.id,tier:o.tier}}async function ii(e,t,n){let i=await e.checkout.sessions.retrieve(t,{expand:["invoice"]});if(i.mode!=="subscription")throw new Error("Checkout session is not a subscription purchase");if(i.payment_status!=="paid"&&i.payment_status!=="no_payment_required")return{status:"not_applicable",invoiceId:Ie(i.invoice)??null};let r=Ie(i.customer);if(n&&r!==n)throw new Error("Checkout session does not belong to this billing account");let s=typeof i.invoice=="object"&&i.invoice&&!("deleted"in i.invoice)?i.invoice:await e.invoices.retrieve(Ie(i.invoice)??"");if(!s?.id)throw new Error("Paid subscription checkout has no invoice");return $t(e,s,i.created)}import ps from"stripe";import{Hono as ys}from"hono";var $=new ps(process.env.STRIPE_SECRET_KEY,{apiVersion:"2026-02-25.clover"});async function gs(e,t){if(!(!e||e===t))try{let n=await $.subscriptions.retrieve(e),i=n.items.data[0]?.price?.id,r=n.status!=="canceled"&&!n.cancel_at_period_end;i&&i in ce&&r&&await $.subscriptions.update(e,{cancel_at_period_end:!0})}catch(n){console.error("[stripe] could not wind down standalone memory subscription",e,n instanceof Error?n.message:n)}}var fs=new ys;fs.post("/webhooks",async e=>{let t=e.req.header("stripe-signature"),n=await e.req.text(),i;try{i=$.webhooks.constructEvent(n,t,process.env.STRIPE_WEBHOOK_SECRET)}catch{return e.json({error:"Invalid signature"},400)}try{let r=await kt(i.id,i.type,i.created);return e.json({received:!0,disposition:r})}catch(r){let s=r instanceof Error?r.message:String(r);return console.error("[stripe] webhook enqueue failed",i.id,s),e.json({error:"Webhook enqueue failed"},500)}});async function Dc(e){if(e.type==="checkout.session.completed"||e.type==="checkout.session.async_payment_succeeded"){let t=e.data.object;await ii($,t.id);return}if(e.type==="invoice.created"){let t=e.data.object;ri(t)&&t.status==="draft"&&await $.invoices.update(t.id,{description:Bt,footer:Vt,custom_fields:[{name:"Pack benefit",value:"2 browsers per $5 monthly pack"},{name:"Billing cadence",value:"Monthly"}]});return}if(e.type==="invoice.paid"||e.type==="invoice.payment_succeeded"){let t=e.data.object;if(ri(t)){let i=t.customer_email?void 0:await ai(t.customer,void 0),r=t.customer_email??i?.email;if(!r)throw new Error("Paid concurrency invoice has no customer receipt email");await vs(t.id,r);return}if(t.lines.data.map(oi).find(i=>i&&i in ce))return;await $t($,t,e.created)}if(e.type==="customer.subscription.created"||e.type==="customer.subscription.updated"){let t=e.data.object,n=si(t);if(n){let s=await M(t.customer);if(!s)return;let a=Ce(t.status);await nt(s.id,a?Es(n):0),await it(s.id,a?t.id:null);return}let i=le(t)?.price?.id??t.items.data[0]?.price?.id;if(i&&i in ce){let s=ce[i],a=await ai(t.customer,t.customer_email);if(a){let o=Ce(t.status),c=o?s.plan:"free";await oe(a.id,c,o?t.id:null),await z(a,F({...a,memory_plan:c}))}return}let r=await M(t.customer);if(!r)return;if(i&&i in D){let s=D[i],a=Ce(t.status),o=a?s.tier:null;if(await rt(r.id,o,a?s.concurrency:0,a?t.id:null),s.includes_memory){let c=a?"pro":"free";a&&await gs(r.memory_subscription_id,t.id),await oe(r.id,c,null),await z(r,F({...r,memory_plan:c}))}else r.memory_subscription_id===t.id&&(await oe(r.id,"free",null),await z(r,F({...r,memory_plan:"free"})));if(a){let c=await qe(r);c.ok||console.warn("[stripe] scheduled-action credential sync failed:",c.error??"unknown error")}}}if(e.type==="customer.subscription.deleted"){let t=e.data.object;if(si(t)){let i=await M(t.customer);i&&(await nt(i.id,0),await it(i.id,null));return}let n=le(t)?.price?.id??t.items.data[0]?.price?.id;if(n&&n in D){let i=D[n],r=await M(t.customer);r&&(await rt(r.id,null,0,null),(i.includes_memory||r.memory_subscription_id===t.id)&&(await oe(r.id,"free",null),await z(r,F({...r,memory_plan:"free"}))));return}if(n&&n in ce||await st(t.id)){let i=await st(t.id)??await M(t.customer);i&&(await oe(i.id,"free",null),await z(i,F({...i,memory_plan:"free"})));return}}}function ri(e){return e.lines.data.some(t=>oi(t)===at)}async function vs(e,t){let i=(await $.invoicePayments.list({invoice:e,status:"paid",limit:10})).data.find(a=>a.status==="paid");if(!i)throw new Error("Paid concurrency invoice has no paid Stripe payment");let r;if(i.payment.type==="charge")r=i.payment.charge;else if(i.payment.type==="payment_intent"&&i.payment.payment_intent){let a=typeof i.payment.payment_intent=="string"?i.payment.payment_intent:i.payment.payment_intent.id;r=(await $.paymentIntents.retrieve(a,{expand:["latest_charge"]})).latest_charge}if(!r)throw new Error("Paid concurrency invoice has no receipt-capable charge");let s=typeof r=="string"?await $.charges.retrieve(r):r;s.receipt_email!==t&&await $.charges.update(s.id,{receipt_email:t})}function si(e){return e.items.data.find(t=>t.price?.id===at)}function Es(e){let t=e.quantity??1;if(!Number.isSafeInteger(t)||t<1)throw new Error("Stripe concurrency pack quantity must be a positive safe integer");return t}function oi(e){let t=e;return t?.price?.id??t?.pricing?.price_details?.price??t?.plan?.id}async function ai(e,t){let n=await M(e);if(n)return n;if(t)return n=await $e(t),n&&await Pe(n.id,e),n}export{bi as a,Is as b,rn as c,ut as d,Ds as e,Ts as f,an as g,Ti as h,xs as i,Ls as j,xi as k,cn as l,qe as m,pt as n,j as o,le as p,Hs as q,qs as r,Ws as s,Ze as t,Qe as u,Dn as v,gr as w,vr as x,At as y,Ua as z,Fa as A,ja as B,Ha as C,za as D,Pn as E,zs as F,Ys as G,Xs as H,Gs as I,Zs as J,Qs as K,ea as L,ta as M,na as N,ia as O,ra as P,sa as Q,aa as R,_n as S,oa as T,qi as U,ca as V,ee as W,Ke as X,x as Y,da as Z,Vi as _,ma as $,pa as aa,ya as ba,bn as ca,An as da,ga as ea,Ra as fa,Un as ga,Fn as ha,Za as ia,Qa as ja,jn as ka,no as la,Hn as ma,qn as na,Wn as oa,Bn as pa,Vn as qa,Kn as ra,tt as sa,yo as ta,go as ua,fo as va,vo as wa,Eo as xa,bo as ya,zn as za,Yn as Aa,Ot as Ba,Xn as Ca,Gn as Da,Xr as Ea,Ho as Fa,qo as Ga,Wo as Ha,Bo as Ia,Vo as Ja,Ko as Ka,Jo as La,zo as Ma,Yo as Na,Xo as Oa,Go as Pa,Zo as Qa,Qo as Ra,ec as Sa,tc as Ta,nc as Ua,Zn as Va,ic as Wa,rc as Xa,ti as Ya,sc as Za,ac as _a,ii as $a,$ as ab,fs as bb,Dc as cb};