@aglyn/tenant-data-admin 1.0.0-beta.143

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 (374) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/jest.integration.config.d.ts +39 -0
  4. package/jest.integration.setup.d.ts +17 -0
  5. package/package.json +51 -0
  6. package/src/index.d.ts +17 -0
  7. package/src/index.js +18 -0
  8. package/src/index.js.map +1 -0
  9. package/src/lib/render-cache.d.ts +184 -0
  10. package/src/lib/render-cache.js +198 -0
  11. package/src/lib/render-cache.js.map +1 -0
  12. package/src/lib/server/account-addresses.d.ts +160 -0
  13. package/src/lib/server/account-addresses.js +311 -0
  14. package/src/lib/server/account-addresses.js.map +1 -0
  15. package/src/lib/server/account-emails.d.ts +219 -0
  16. package/src/lib/server/account-emails.js +640 -0
  17. package/src/lib/server/account-emails.js.map +1 -0
  18. package/src/lib/server/admin-audit.d.ts +180 -0
  19. package/src/lib/server/admin-audit.js +233 -0
  20. package/src/lib/server/admin-audit.js.map +1 -0
  21. package/src/lib/server/analytics-retention.d.ts +63 -0
  22. package/src/lib/server/analytics-retention.js +66 -0
  23. package/src/lib/server/analytics-retention.js.map +1 -0
  24. package/src/lib/server/api-http.d.ts +159 -0
  25. package/src/lib/server/api-http.js +230 -0
  26. package/src/lib/server/api-http.js.map +1 -0
  27. package/src/lib/server/api-keys.d.ts +117 -0
  28. package/src/lib/server/api-keys.js +298 -0
  29. package/src/lib/server/api-keys.js.map +1 -0
  30. package/src/lib/server/auth-handoff.d.ts +165 -0
  31. package/src/lib/server/auth-handoff.js +482 -0
  32. package/src/lib/server/auth-handoff.js.map +1 -0
  33. package/src/lib/server/auth-pools.d.ts +246 -0
  34. package/src/lib/server/auth-pools.js +551 -0
  35. package/src/lib/server/auth-pools.js.map +1 -0
  36. package/src/lib/server/billing-addons.d.ts +171 -0
  37. package/src/lib/server/billing-addons.js +277 -0
  38. package/src/lib/server/billing-addons.js.map +1 -0
  39. package/src/lib/server/campaign-attribution-store.d.ts +55 -0
  40. package/src/lib/server/campaign-attribution-store.js +95 -0
  41. package/src/lib/server/campaign-attribution-store.js.map +1 -0
  42. package/src/lib/server/campaign-conversion-attribution.d.ts +205 -0
  43. package/src/lib/server/campaign-conversion-attribution.js +279 -0
  44. package/src/lib/server/campaign-conversion-attribution.js.map +1 -0
  45. package/src/lib/server/client-error-report.d.ts +301 -0
  46. package/src/lib/server/client-error-report.js +755 -0
  47. package/src/lib/server/client-error-report.js.map +1 -0
  48. package/src/lib/server/connect-account-status.d.ts +74 -0
  49. package/src/lib/server/connect-account-status.js +94 -0
  50. package/src/lib/server/connect-account-status.js.map +1 -0
  51. package/src/lib/server/connect-payout-failure.d.ts +103 -0
  52. package/src/lib/server/connect-payout-failure.js +127 -0
  53. package/src/lib/server/connect-payout-failure.js.map +1 -0
  54. package/src/lib/server/console-api-rate-limit.d.ts +81 -0
  55. package/src/lib/server/console-api-rate-limit.js +111 -0
  56. package/src/lib/server/console-api-rate-limit.js.map +1 -0
  57. package/src/lib/server/console-domains.d.ts +326 -0
  58. package/src/lib/server/console-domains.js +779 -0
  59. package/src/lib/server/console-domains.js.map +1 -0
  60. package/src/lib/server/consume-once.d.ts +91 -0
  61. package/src/lib/server/consume-once.js +96 -0
  62. package/src/lib/server/consume-once.js.map +1 -0
  63. package/src/lib/server/contact-company-link.d.ts +85 -0
  64. package/src/lib/server/contact-company-link.js +105 -0
  65. package/src/lib/server/contact-company-link.js.map +1 -0
  66. package/src/lib/server/contact-email-engagement.d.ts +32 -0
  67. package/src/lib/server/contact-email-engagement.js +145 -0
  68. package/src/lib/server/contact-email-engagement.js.map +1 -0
  69. package/src/lib/server/contact-email-index.d.ts +56 -0
  70. package/src/lib/server/contact-email-index.js +158 -0
  71. package/src/lib/server/contact-email-index.js.map +1 -0
  72. package/src/lib/server/contact-lifecycle-floor.d.ts +89 -0
  73. package/src/lib/server/contact-lifecycle-floor.js +88 -0
  74. package/src/lib/server/contact-lifecycle-floor.js.map +1 -0
  75. package/src/lib/server/contact-merge.d.ts +55 -0
  76. package/src/lib/server/contact-merge.js +279 -0
  77. package/src/lib/server/contact-merge.js.map +1 -0
  78. package/src/lib/server/contact-suppression.d.ts +153 -0
  79. package/src/lib/server/contact-suppression.js +259 -0
  80. package/src/lib/server/contact-suppression.js.map +1 -0
  81. package/src/lib/server/crm-booking-activity.d.ts +86 -0
  82. package/src/lib/server/crm-booking-activity.js +251 -0
  83. package/src/lib/server/crm-booking-activity.js.map +1 -0
  84. package/src/lib/server/crm-email-activity.d.ts +112 -0
  85. package/src/lib/server/crm-email-activity.js +143 -0
  86. package/src/lib/server/crm-email-activity.js.map +1 -0
  87. package/src/lib/server/crm-inbound-email.d.ts +145 -0
  88. package/src/lib/server/crm-inbound-email.js +287 -0
  89. package/src/lib/server/crm-inbound-email.js.map +1 -0
  90. package/src/lib/server/crm-next-activity.d.ts +74 -0
  91. package/src/lib/server/crm-next-activity.js +194 -0
  92. package/src/lib/server/crm-next-activity.js.map +1 -0
  93. package/src/lib/server/crm-records.d.ts +144 -0
  94. package/src/lib/server/crm-records.js +218 -0
  95. package/src/lib/server/crm-records.js.map +1 -0
  96. package/src/lib/server/csp-aggregate.d.ts +62 -0
  97. package/src/lib/server/csp-aggregate.js +276 -0
  98. package/src/lib/server/csp-aggregate.js.map +1 -0
  99. package/src/lib/server/data-storage-gate.d.ts +66 -0
  100. package/src/lib/server/data-storage-gate.js +48 -0
  101. package/src/lib/server/data-storage-gate.js.map +1 -0
  102. package/src/lib/server/dataset-live-pages.d.ts +179 -0
  103. package/src/lib/server/dataset-live-pages.js +421 -0
  104. package/src/lib/server/dataset-live-pages.js.map +1 -0
  105. package/src/lib/server/dns-probe.d.ts +53 -0
  106. package/src/lib/server/dns-probe.js +184 -0
  107. package/src/lib/server/dns-probe.js.map +1 -0
  108. package/src/lib/server/document-id.d.ts +29 -0
  109. package/src/lib/server/document-id.js +100 -0
  110. package/src/lib/server/document-id.js.map +1 -0
  111. package/src/lib/server/domain-provider-vercel.d.ts +33 -0
  112. package/src/lib/server/domain-provider-vercel.js +298 -0
  113. package/src/lib/server/domain-provider-vercel.js.map +1 -0
  114. package/src/lib/server/domain-provider-webhook.d.ts +73 -0
  115. package/src/lib/server/domain-provider-webhook.js +223 -0
  116. package/src/lib/server/domain-provider-webhook.js.map +1 -0
  117. package/src/lib/server/domain-provider-wildcard.d.ts +54 -0
  118. package/src/lib/server/domain-provider-wildcard.js +163 -0
  119. package/src/lib/server/domain-provider-wildcard.js.map +1 -0
  120. package/src/lib/server/domain-provider.d.ts +197 -0
  121. package/src/lib/server/domain-provider.js +221 -0
  122. package/src/lib/server/domain-provider.js.map +1 -0
  123. package/src/lib/server/duplicate-activity.d.ts +54 -0
  124. package/src/lib/server/duplicate-activity.js +67 -0
  125. package/src/lib/server/duplicate-activity.js.map +1 -0
  126. package/src/lib/server/duplicate-resource.d.ts +55 -0
  127. package/src/lib/server/duplicate-resource.js +474 -0
  128. package/src/lib/server/duplicate-resource.js.map +1 -0
  129. package/src/lib/server/dynamic-list-materialize.d.ts +153 -0
  130. package/src/lib/server/dynamic-list-materialize.js +581 -0
  131. package/src/lib/server/dynamic-list-materialize.js.map +1 -0
  132. package/src/lib/server/edit-access-authz.d.ts +56 -0
  133. package/src/lib/server/edit-access-authz.js +107 -0
  134. package/src/lib/server/edit-access-authz.js.map +1 -0
  135. package/src/lib/server/edit-access-token.d.ts +72 -0
  136. package/src/lib/server/edit-access-token.js +116 -0
  137. package/src/lib/server/edit-access-token.js.map +1 -0
  138. package/src/lib/server/edit-hint-token.d.ts +83 -0
  139. package/src/lib/server/edit-hint-token.js +119 -0
  140. package/src/lib/server/edit-hint-token.js.map +1 -0
  141. package/src/lib/server/email-delivery-log.d.ts +509 -0
  142. package/src/lib/server/email-delivery-log.js +1043 -0
  143. package/src/lib/server/email-delivery-log.js.map +1 -0
  144. package/src/lib/server/email-flow-gate.d.ts +80 -0
  145. package/src/lib/server/email-flow-gate.js +153 -0
  146. package/src/lib/server/email-flow-gate.js.map +1 -0
  147. package/src/lib/server/email-marketing-gate.d.ts +333 -0
  148. package/src/lib/server/email-marketing-gate.js +639 -0
  149. package/src/lib/server/email-marketing-gate.js.map +1 -0
  150. package/src/lib/server/email-metering.d.ts +293 -0
  151. package/src/lib/server/email-metering.js +359 -0
  152. package/src/lib/server/email-metering.js.map +1 -0
  153. package/src/lib/server/email-revenue-attribution.d.ts +185 -0
  154. package/src/lib/server/email-revenue-attribution.js +300 -0
  155. package/src/lib/server/email-revenue-attribution.js.map +1 -0
  156. package/src/lib/server/email-send-rate.d.ts +232 -0
  157. package/src/lib/server/email-send-rate.js +417 -0
  158. package/src/lib/server/email-send-rate.js.map +1 -0
  159. package/src/lib/server/email-sender-reputation.d.ts +317 -0
  160. package/src/lib/server/email-sender-reputation.js +464 -0
  161. package/src/lib/server/email-sender-reputation.js.map +1 -0
  162. package/src/lib/server/email-suppression.d.ts +490 -0
  163. package/src/lib/server/email-suppression.js +648 -0
  164. package/src/lib/server/email-suppression.js.map +1 -0
  165. package/src/lib/server/email-topic-confirmation.d.ts +150 -0
  166. package/src/lib/server/email-topic-confirmation.js +272 -0
  167. package/src/lib/server/email-topic-confirmation.js.map +1 -0
  168. package/src/lib/server/email-unsubscribe-link.d.ts +174 -0
  169. package/src/lib/server/email-unsubscribe-link.js +236 -0
  170. package/src/lib/server/email-unsubscribe-link.js.map +1 -0
  171. package/src/lib/server/erase-person.d.ts +116 -0
  172. package/src/lib/server/erase-person.js +272 -0
  173. package/src/lib/server/erase-person.js.map +1 -0
  174. package/src/lib/server/erase.d.ts +458 -0
  175. package/src/lib/server/erase.js +1506 -0
  176. package/src/lib/server/erase.js.map +1 -0
  177. package/src/lib/server/firebase-admin.d.ts +71 -0
  178. package/src/lib/server/firebase-admin.js +343 -0
  179. package/src/lib/server/firebase-admin.js.map +1 -0
  180. package/src/lib/server/form-dataset-binding-token.d.ts +35 -0
  181. package/src/lib/server/form-dataset-binding-token.js +107 -0
  182. package/src/lib/server/form-dataset-binding-token.js.map +1 -0
  183. package/src/lib/server/free-workspace-cap.d.ts +201 -0
  184. package/src/lib/server/free-workspace-cap.js +404 -0
  185. package/src/lib/server/free-workspace-cap.js.map +1 -0
  186. package/src/lib/server/ga4-measurement-protocol.d.ts +256 -0
  187. package/src/lib/server/ga4-measurement-protocol.js +558 -0
  188. package/src/lib/server/ga4-measurement-protocol.js.map +1 -0
  189. package/src/lib/server/host-email-tokens.d.ts +47 -0
  190. package/src/lib/server/host-email-tokens.js +72 -0
  191. package/src/lib/server/host-email-tokens.js.map +1 -0
  192. package/src/lib/server/host-memberships.d.ts +54 -0
  193. package/src/lib/server/host-memberships.js +194 -0
  194. package/src/lib/server/host-memberships.js.map +1 -0
  195. package/src/lib/server/host-sending-domain.d.ts +292 -0
  196. package/src/lib/server/host-sending-domain.js +451 -0
  197. package/src/lib/server/host-sending-domain.js.map +1 -0
  198. package/src/lib/server/host-visitor-records.d.ts +150 -0
  199. package/src/lib/server/host-visitor-records.js +262 -0
  200. package/src/lib/server/host-visitor-records.js.map +1 -0
  201. package/src/lib/server/id-token-refusal.d.ts +28 -0
  202. package/src/lib/server/id-token-refusal.js +111 -0
  203. package/src/lib/server/id-token-refusal.js.map +1 -0
  204. package/src/lib/server/legal-acceptance.d.ts +214 -0
  205. package/src/lib/server/legal-acceptance.js +305 -0
  206. package/src/lib/server/legal-acceptance.js.map +1 -0
  207. package/src/lib/server/list-members.d.ts +211 -0
  208. package/src/lib/server/list-members.js +176 -0
  209. package/src/lib/server/list-members.js.map +1 -0
  210. package/src/lib/server/live-page-usage.d.ts +202 -0
  211. package/src/lib/server/live-page-usage.js +300 -0
  212. package/src/lib/server/live-page-usage.js.map +1 -0
  213. package/src/lib/server/lockdown.d.ts +313 -0
  214. package/src/lib/server/lockdown.js +643 -0
  215. package/src/lib/server/lockdown.js.map +1 -0
  216. package/src/lib/server/media-cdn-rate-limit.d.ts +188 -0
  217. package/src/lib/server/media-cdn-rate-limit.js +204 -0
  218. package/src/lib/server/media-cdn-rate-limit.js.map +1 -0
  219. package/src/lib/server/media-delivery.d.ts +267 -0
  220. package/src/lib/server/media-delivery.js +485 -0
  221. package/src/lib/server/media-delivery.js.map +1 -0
  222. package/src/lib/server/media-download-tokens.d.ts +161 -0
  223. package/src/lib/server/media-download-tokens.js +386 -0
  224. package/src/lib/server/media-download-tokens.js.map +1 -0
  225. package/src/lib/server/media-quarantine.d.ts +127 -0
  226. package/src/lib/server/media-quarantine.js +170 -0
  227. package/src/lib/server/media-quarantine.js.map +1 -0
  228. package/src/lib/server/media-signing.d.ts +126 -0
  229. package/src/lib/server/media-signing.js +159 -0
  230. package/src/lib/server/media-signing.js.map +1 -0
  231. package/src/lib/server/media-storage-path.d.ts +96 -0
  232. package/src/lib/server/media-storage-path.js +111 -0
  233. package/src/lib/server/media-storage-path.js.map +1 -0
  234. package/src/lib/server/media-strong-digest.d.ts +85 -0
  235. package/src/lib/server/media-strong-digest.js +190 -0
  236. package/src/lib/server/media-strong-digest.js.map +1 -0
  237. package/src/lib/server/media-tombstone.d.ts +276 -0
  238. package/src/lib/server/media-tombstone.js +421 -0
  239. package/src/lib/server/media-tombstone.js.map +1 -0
  240. package/src/lib/server/media-variants.d.ts +309 -0
  241. package/src/lib/server/media-variants.js +428 -0
  242. package/src/lib/server/media-variants.js.map +1 -0
  243. package/src/lib/server/member-email-aliases.d.ts +180 -0
  244. package/src/lib/server/member-email-aliases.js +420 -0
  245. package/src/lib/server/member-email-aliases.js.map +1 -0
  246. package/src/lib/server/member-photo.d.ts +88 -0
  247. package/src/lib/server/member-photo.js +185 -0
  248. package/src/lib/server/member-photo.js.map +1 -0
  249. package/src/lib/server/membership-recover-throttle.d.ts +211 -0
  250. package/src/lib/server/membership-recover-throttle.js +231 -0
  251. package/src/lib/server/membership-recover-throttle.js.map +1 -0
  252. package/src/lib/server/notifications.d.ts +55 -0
  253. package/src/lib/server/notifications.js +134 -0
  254. package/src/lib/server/notifications.js.map +1 -0
  255. package/src/lib/server/org-billing.d.ts +97 -0
  256. package/src/lib/server/org-billing.js +236 -0
  257. package/src/lib/server/org-billing.js.map +1 -0
  258. package/src/lib/server/organizations.d.ts +837 -0
  259. package/src/lib/server/organizations.js +1814 -0
  260. package/src/lib/server/organizations.js.map +1 -0
  261. package/src/lib/server/password-reset-throttle.d.ts +84 -0
  262. package/src/lib/server/password-reset-throttle.js +102 -0
  263. package/src/lib/server/password-reset-throttle.js.map +1 -0
  264. package/src/lib/server/personal-data-export.d.ts +241 -0
  265. package/src/lib/server/personal-data-export.js +858 -0
  266. package/src/lib/server/personal-data-export.js.map +1 -0
  267. package/src/lib/server/platform-domain-names.d.ts +62 -0
  268. package/src/lib/server/platform-domain-names.js +197 -0
  269. package/src/lib/server/platform-domain-names.js.map +1 -0
  270. package/src/lib/server/platform-marketing-consent.d.ts +133 -0
  271. package/src/lib/server/platform-marketing-consent.js +209 -0
  272. package/src/lib/server/platform-marketing-consent.js.map +1 -0
  273. package/src/lib/server/rate-limit-store.d.ts +702 -0
  274. package/src/lib/server/rate-limit-store.js +1220 -0
  275. package/src/lib/server/rate-limit-store.js.map +1 -0
  276. package/src/lib/server/realm-plugins.d.ts +67 -0
  277. package/src/lib/server/realm-plugins.js +250 -0
  278. package/src/lib/server/realm-plugins.js.map +1 -0
  279. package/src/lib/server/recaptcha-allowlist.d.ts +90 -0
  280. package/src/lib/server/recaptcha-allowlist.js +409 -0
  281. package/src/lib/server/recaptcha-allowlist.js.map +1 -0
  282. package/src/lib/server/release-flags.d.ts +105 -0
  283. package/src/lib/server/release-flags.js +183 -0
  284. package/src/lib/server/release-flags.js.map +1 -0
  285. package/src/lib/server/replay-erasures.d.ts +126 -0
  286. package/src/lib/server/replay-erasures.js +217 -0
  287. package/src/lib/server/replay-erasures.js.map +1 -0
  288. package/src/lib/server/resolve-people.d.ts +66 -0
  289. package/src/lib/server/resolve-people.js +112 -0
  290. package/src/lib/server/resolve-people.js.map +1 -0
  291. package/src/lib/server/safe-equal.d.ts +45 -0
  292. package/src/lib/server/safe-equal.js +58 -0
  293. package/src/lib/server/safe-equal.js.map +1 -0
  294. package/src/lib/server/sending-domain-debt.d.ts +127 -0
  295. package/src/lib/server/sending-domain-debt.js +268 -0
  296. package/src/lib/server/sending-domain-debt.js.map +1 -0
  297. package/src/lib/server/sending-domain-recheck.d.ts +151 -0
  298. package/src/lib/server/sending-domain-recheck.js +253 -0
  299. package/src/lib/server/sending-domain-recheck.js.map +1 -0
  300. package/src/lib/server/sending-domains.d.ts +364 -0
  301. package/src/lib/server/sending-domains.js +683 -0
  302. package/src/lib/server/sending-domains.js.map +1 -0
  303. package/src/lib/server/serve-media-cdn.d.ts +433 -0
  304. package/src/lib/server/serve-media-cdn.js +1370 -0
  305. package/src/lib/server/serve-media-cdn.js.map +1 -0
  306. package/src/lib/server/serve-plugin-fetch.d.ts +61 -0
  307. package/src/lib/server/serve-plugin-fetch.js +294 -0
  308. package/src/lib/server/serve-plugin-fetch.js.map +1 -0
  309. package/src/lib/server/sms-keywords.d.ts +63 -0
  310. package/src/lib/server/sms-keywords.js +146 -0
  311. package/src/lib/server/sms-keywords.js.map +1 -0
  312. package/src/lib/server/sso-break-glass-owners.d.ts +91 -0
  313. package/src/lib/server/sso-break-glass-owners.js +227 -0
  314. package/src/lib/server/sso-break-glass-owners.js.map +1 -0
  315. package/src/lib/server/sso-domain-policy.d.ts +140 -0
  316. package/src/lib/server/sso-domain-policy.js +239 -0
  317. package/src/lib/server/sso-domain-policy.js.map +1 -0
  318. package/src/lib/server/sso-drift-logic.d.ts +106 -0
  319. package/src/lib/server/sso-drift-logic.js +95 -0
  320. package/src/lib/server/sso-drift-logic.js.map +1 -0
  321. package/src/lib/server/sso-enforcement.d.ts +204 -0
  322. package/src/lib/server/sso-enforcement.js +309 -0
  323. package/src/lib/server/sso-enforcement.js.map +1 -0
  324. package/src/lib/server/sso-provisioning.d.ts +320 -0
  325. package/src/lib/server/sso-provisioning.js +715 -0
  326. package/src/lib/server/sso-provisioning.js.map +1 -0
  327. package/src/lib/server/staff-alert-email.d.ts +41 -0
  328. package/src/lib/server/staff-alert-email.js +63 -0
  329. package/src/lib/server/staff-alert-email.js.map +1 -0
  330. package/src/lib/server/stripe-account-mode.d.ts +156 -0
  331. package/src/lib/server/stripe-account-mode.js +162 -0
  332. package/src/lib/server/stripe-account-mode.js.map +1 -0
  333. package/src/lib/server/tenant-write-lockdown.d.ts +131 -0
  334. package/src/lib/server/tenant-write-lockdown.js +176 -0
  335. package/src/lib/server/tenant-write-lockdown.js.map +1 -0
  336. package/src/lib/server/test-firestore.d.ts +45 -0
  337. package/src/lib/server/test-firestore.js +124 -0
  338. package/src/lib/server/test-firestore.js.map +1 -0
  339. package/src/lib/server/token-revocation.d.ts +140 -0
  340. package/src/lib/server/token-revocation.js +255 -0
  341. package/src/lib/server/token-revocation.js.map +1 -0
  342. package/src/lib/server/update-existing.d.ts +32 -0
  343. package/src/lib/server/update-existing.js +73 -0
  344. package/src/lib/server/update-existing.js.map +1 -0
  345. package/src/lib/server/upload-cors-reconcile.d.ts +118 -0
  346. package/src/lib/server/upload-cors-reconcile.js +234 -0
  347. package/src/lib/server/upload-cors-reconcile.js.map +1 -0
  348. package/src/lib/server/upsert-contact.d.ts +262 -0
  349. package/src/lib/server/upsert-contact.js +546 -0
  350. package/src/lib/server/upsert-contact.js.map +1 -0
  351. package/src/lib/server/usage-invoice.d.ts +203 -0
  352. package/src/lib/server/usage-invoice.js +397 -0
  353. package/src/lib/server/usage-invoice.js.map +1 -0
  354. package/src/lib/server/user-profiles.d.ts +151 -0
  355. package/src/lib/server/user-profiles.js +364 -0
  356. package/src/lib/server/user-profiles.js.map +1 -0
  357. package/src/lib/server/vercel-drain-signature.d.ts +30 -0
  358. package/src/lib/server/vercel-drain-signature.js +61 -0
  359. package/src/lib/server/vercel-drain-signature.js.map +1 -0
  360. package/src/lib/server/vercel-log-drain.d.ts +259 -0
  361. package/src/lib/server/vercel-log-drain.js +479 -0
  362. package/src/lib/server/vercel-log-drain.js.map +1 -0
  363. package/src/lib/server/verify-email-cooldown.d.ts +102 -0
  364. package/src/lib/server/verify-email-cooldown.js +114 -0
  365. package/src/lib/server/verify-email-cooldown.js.map +1 -0
  366. package/src/lib/server/visitor-write-rate-limit.d.ts +87 -0
  367. package/src/lib/server/visitor-write-rate-limit.js +117 -0
  368. package/src/lib/server/visitor-write-rate-limit.js.map +1 -0
  369. package/src/lib/server/workspace-domains.d.ts +156 -0
  370. package/src/lib/server/workspace-domains.js +271 -0
  371. package/src/lib/server/workspace-domains.js.map +1 -0
  372. package/src/tenancy.d.ts +115 -0
  373. package/src/tenancy.js +142 -0
  374. package/src/tenancy.js.map +1 -0
@@ -0,0 +1,464 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ /**
18
+ * PER-TENANT SENDING REPUTATION — the durable half.
19
+ *
20
+ * The policy is pure and lives in `@aglyn/shared-util-email`
21
+ * (`sender-reputation.ts`); this adds the counters that make a rate a fact
22
+ * rather than an intention, and the daily claim that paces a workspace's
23
+ * first week.
24
+ *
25
+ * ## One document per workspace per day
26
+ *
27
+ * `rateLimits/emailRep_{YYYY-MM-DD}_{orgId}` holds four numbers:
28
+ *
29
+ * - `claimed` — campaign messages the day's ramp has granted. The PACING
30
+ * counter, taken before a send and reconciled to what actually left.
31
+ * - `accepted` — campaign messages that actually left. The DENOMINATOR every
32
+ * rate divides by.
33
+ * - `bounced`, `complained` — what the provider reported back.
34
+ *
35
+ * A day per document rather than a map of days on one, because the day is
36
+ * what the TTL policy can sweep: `rateLimits` already carries an `expiresAt`
37
+ * policy, and a document that ages out is a window that prunes itself. A map
38
+ * of every day a workspace has ever sent would grow forever and would have to
39
+ * be pruned by a job nobody would write.
40
+ *
41
+ * A per-ORG document rather than per-site, because the org is the tenant: the
42
+ * plan, the monthly allowance and the hourly share are all keyed on it, and a
43
+ * merchant with three sites on one domain is one sender to a mailbox
44
+ * provider.
45
+ *
46
+ * The window document deliberately carries **no `lastAtMs`** — the
47
+ * rate-limiter health probe queries this collection on that field, and a
48
+ * per-day document in its range would compete with the degradation markers it
49
+ * exists to find. The same reasoning as `sendRate_*` and `sendRateOrg_*`
50
+ * beside it.
51
+ *
52
+ * ## Which day a bounce is filed under
53
+ *
54
+ * The day the EVENT arrived, not the day the message was sent. The webhook
55
+ * knows the first and would need a per-message lookup for the second, and
56
+ * over a seven-day window the two differ only at the edges — a bounce lands
57
+ * within minutes and a complaint within hours or a day. Stated because the
58
+ * rate is a ratio of two things counted on slightly different clocks, and a
59
+ * reader comparing it against a provider's dashboard should know why they
60
+ * will not agree to the last decimal.
61
+ *
62
+ * ## Failure posture: OPEN, and that is not the usual answer here
63
+ *
64
+ * An unreadable counter grades `ok` and grants the send, matching
65
+ * `consumeEmailSendBudget` and `claimOrgEmailSendBudget`. The breaker is the
66
+ * one control in this file that could refuse a paying customer's campaign,
67
+ * and a refusal produced by a Firestore blip is an outage dressed as a policy
68
+ * decision. The reputation risk it fails to catch is bounded by an hour of
69
+ * sending; the refusal it would produce is not bounded by anything.
70
+ */ import { EMAIL_REPUTATION_WINDOW_DAYS, emailRampVerdict, emailReputationVerdict, normalizeEmailReputationPolicy, orgDailyCampaignCeiling, reputationDayKey, reputationWindowDayKeys, daysBetween } from "@aglyn/shared-util-email";
71
+ import { readEmailSendRateConfig } from "./email-send-rate.js";
72
+ import { firebaseAdmin } from "./firebase-admin.js";
73
+ import { RATE_LIMIT_COLLECTION } from "./rate-limit-store.js";
74
+ /** Id prefix for the per-org, per-day reputation documents. */ export const EMAIL_REPUTATION_DOC_PREFIX = 'emailRep_';
75
+ /** One day in ms, named so the arithmetic below reads as arithmetic. */ const DAY_MS = 86400000;
76
+ /** The document id for one workspace's day. */ export function emailReputationDocId(dayKey, orgId) {
77
+ return `${EMAIL_REPUTATION_DOC_PREFIX}${dayKey}_${orgId}`;
78
+ }
79
+ function reputationRef(firestore, dayKey, orgId) {
80
+ return firestore.collection(RATE_LIMIT_COLLECTION).doc(emailReputationDocId(dayKey, orgId));
81
+ }
82
+ /** A stored count, clamped. A corrupt negative reads as 0, never as headroom. */ function storedCount(snapshot, field) {
83
+ var _ref;
84
+ const value = Number((_ref = (snapshot == null ? void 0 : snapshot.exists) ? snapshot.get(field) : 0) != null ? _ref : 0);
85
+ return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
86
+ }
87
+ /**
88
+ * When a day's document may be swept.
89
+ *
90
+ * One window plus a day of slack past the end of the day it counts, so the
91
+ * oldest day a rate still divides by is never deleted underneath the read
92
+ * that needs it.
93
+ */ function reputationExpiresAt(dayStartMs) {
94
+ return new Date(dayStartMs + (EMAIL_REPUTATION_WINDOW_DAYS + 2) * DAY_MS);
95
+ }
96
+ /** Midnight UTC of the day a timestamp falls in. */ function dayStartMs(atMs) {
97
+ return Math.floor(atMs / DAY_MS) * DAY_MS;
98
+ }
99
+ /**
100
+ * Adds to one of a workspace's daily counters.
101
+ *
102
+ * **Never throws.** Reputation accounting runs after mail has gone out, or
103
+ * inside a webhook the provider will retry — a counter write that failed must
104
+ * not turn a delivered campaign into a 500 or a webhook into a retry storm.
105
+ * `FieldValue.increment` rather than a transaction because nothing decides
106
+ * anything from this write: the decision is made by a separate read, and an
107
+ * increment is the contention-free way to record a fact.
108
+ */ export async function recordEmailReputationEvent(options) {
109
+ var _options_orgId, _options_count, _options_atMs;
110
+ const orgId = String((_options_orgId = options.orgId) != null ? _options_orgId : '');
111
+ const count = Math.floor(Number((_options_count = options.count) != null ? _options_count : 1));
112
+ if (!orgId || !Number.isFinite(count) || count <= 0) return;
113
+ const atMs = Number((_options_atMs = options.atMs) != null ? _options_atMs : Date.now());
114
+ const at = Number.isFinite(atMs) && atMs > 0 ? atMs : Date.now();
115
+ try {
116
+ var _options_firestore;
117
+ const firestore = (_options_firestore = options.firestore) != null ? _options_firestore : firebaseAdmin.app().firestore();
118
+ await reputationRef(firestore, reputationDayKey(at), orgId).set({
119
+ [options.counter]: firebaseAdmin.firestore.FieldValue.increment(count),
120
+ orgId,
121
+ dayKey: reputationDayKey(at),
122
+ // NOT `lastAtMs` — see the storage note at the top of this file.
123
+ expiresAt: reputationExpiresAt(dayStartMs(at))
124
+ }, {
125
+ merge: true
126
+ });
127
+ } catch (error) {
128
+ console.error('[sender-reputation] counter write failed', error);
129
+ }
130
+ }
131
+ /** Campaign messages that actually left, for a workspace. The denominator. */ export function recordCampaignAccepted(orgId, count, options) {
132
+ return recordEmailReputationEvent(_extends({
133
+ orgId,
134
+ counter: 'accepted',
135
+ count
136
+ }, options));
137
+ }
138
+ /**
139
+ * A permanent bounce or a spam complaint, attributed to the workspace that
140
+ * sent it.
141
+ *
142
+ * The suppression the same event produces is a separate act and is not
143
+ * conditional on this: an address comes off the list whether or not the
144
+ * counter write lands.
145
+ */ export function recordEmailReputationFailure(orgId, kind, options) {
146
+ return recordEmailReputationEvent(_extends({
147
+ orgId,
148
+ counter: kind === 'complaint' ? 'complained' : 'bounced',
149
+ count: 1
150
+ }, options));
151
+ }
152
+ /**
153
+ * Sums a workspace's last {@link EMAIL_REPUTATION_WINDOW_DAYS} days.
154
+ *
155
+ * One `getAll` over the window's day documents — seven reads per campaign,
156
+ * not per message, and none at all for a workspace that has never sent. An
157
+ * absent day is a quiet day and reads as zero.
158
+ */ export async function readSenderReputationWindow(options) {
159
+ var _options_windowDays, _options_orgId, _options_now;
160
+ const windowDays = Math.max(1, Math.floor(Number((_options_windowDays = options.windowDays) != null ? _options_windowDays : EMAIL_REPUTATION_WINDOW_DAYS)) || EMAIL_REPUTATION_WINDOW_DAYS);
161
+ const empty = {
162
+ accepted: 0,
163
+ bounced: 0,
164
+ complained: 0,
165
+ claimedToday: 0,
166
+ windowDays,
167
+ degraded: false
168
+ };
169
+ const orgId = String((_options_orgId = options.orgId) != null ? _options_orgId : '');
170
+ if (!orgId) return empty;
171
+ const now = Number((_options_now = options.now) != null ? _options_now : Date.now());
172
+ try {
173
+ var _options_firestore;
174
+ const firestore = (_options_firestore = options.firestore) != null ? _options_firestore : firebaseAdmin.app().firestore();
175
+ const dayKeys = reputationWindowDayKeys(now, windowDays);
176
+ const today = reputationDayKey(now);
177
+ const snapshots = await firestore.getAll(...dayKeys.map((dayKey)=>reputationRef(firestore, dayKey, orgId)));
178
+ return snapshots.reduce((totals, snapshot, index)=>_extends({}, totals, {
179
+ accepted: totals.accepted + storedCount(snapshot, 'accepted'),
180
+ bounced: totals.bounced + storedCount(snapshot, 'bounced'),
181
+ complained: totals.complained + storedCount(snapshot, 'complained'),
182
+ claimedToday: dayKeys[index] === today ? totals.claimedToday + storedCount(snapshot, 'claimed') : totals.claimedToday
183
+ }), empty);
184
+ } catch (error) {
185
+ console.error('[sender-reputation] window unavailable — grading ok', error);
186
+ return _extends({}, empty, {
187
+ degraded: true
188
+ });
189
+ }
190
+ }
191
+ /**
192
+ * Grades one workspace.
193
+ *
194
+ * The policy and the grace period come from the ORG DOCUMENT, which the
195
+ * campaign sender has already read — passed in rather than re-fetched, so
196
+ * this costs the window read and nothing else.
197
+ */ export async function readSenderReputation(options) {
198
+ var _options_now, _options_reinstatedUntilMs;
199
+ const now = Number((_options_now = options.now) != null ? _options_now : Date.now());
200
+ const window = await readSenderReputationWindow({
201
+ orgId: options.orgId,
202
+ now,
203
+ firestore: options.firestore
204
+ });
205
+ const verdict = emailReputationVerdict({
206
+ counts: window,
207
+ // A degraded read grades against zeroes, which is `ok` by construction.
208
+ // Stated rather than special-cased: the fail-open posture at the top of
209
+ // this file is the reason, and a branch here would be a second place for
210
+ // it to be got wrong.
211
+ policy: normalizeEmailReputationPolicy(options.policy),
212
+ windowDays: window.windowDays,
213
+ reinstatedUntilMs: Number((_options_reinstatedUntilMs = options.reinstatedUntilMs) != null ? _options_reinstatedUntilMs : 0) || null,
214
+ now
215
+ });
216
+ return _extends({}, verdict, {
217
+ degraded: window.degraded,
218
+ window
219
+ });
220
+ }
221
+ /**
222
+ * The ramp step a workspace has EARNED, from a window it has already read.
223
+ *
224
+ * A thin wrapper over the pure policy that supplies the one number the policy
225
+ * cannot know: what a graduated workspace's day is, which is a share of the
226
+ * live platform ceiling and therefore moves with a staff ramp.
227
+ */ export function resolveOrgEmailRamp(options) {
228
+ return emailRampVerdict({
229
+ ageDays: options.ageDays,
230
+ deliveredLifetime: options.deliveredLifetime,
231
+ graduatedPerDay: orgDailyCampaignCeiling(options.platformPerHour)
232
+ });
233
+ }
234
+ /**
235
+ * A workspace's ramp step, resolved from its record and its history.
236
+ *
237
+ * The reads {@link resolveOrgEmailRamp} needs, in the order that pays for
238
+ * the fewest of them. The platform ceiling comes first because it is process-
239
+ * cached for fifteen seconds and therefore free; the seven-day window is a
240
+ * `getAll` and is only paid when the ramp can still bind.
241
+ *
242
+ * ## A graduated workspace pays nothing
243
+ *
244
+ * Age alone decides graduation — delivery volume can only RAISE a step, never
245
+ * reach past the last one — so a workspace past its first week is answered
246
+ * from the org record with no window read at all. That is every existing
247
+ * customer and every org whose record predates the creation timestamp, which
248
+ * is to say almost every call.
249
+ *
250
+ * ## An unreadable window is `degraded`, and it is the caller's to answer
251
+ *
252
+ * The window read grades itself rather than throwing, and an unreadable one
253
+ * reads as zero delivered — the LOWEST step, which is the conservative
254
+ * direction for a ceiling. What it is not is a fact, so it is flagged: a
255
+ * surface admitting one message at a time cannot defer the remainder to
256
+ * tomorrow the way a campaign can, and a limiter with no answer must refuse
257
+ * rather than admit.
258
+ *
259
+ * ## The platform flag parks this control
260
+ *
261
+ * `enabled: false` on the send-rate config graduates every workspace here,
262
+ * exactly as it parks the hourly share and {@link claimOrgEmailSendDay} — one
263
+ * switch, and it turns the ramp off everywhere it is consulted.
264
+ */ export async function readOrgEmailRamp(options) {
265
+ var _options_now;
266
+ const now = Number((_options_now = options.now) != null ? _options_now : Date.now());
267
+ const at = Number.isFinite(now) && now > 0 ? now : Date.now();
268
+ const config = await readEmailSendRateConfig(options.firestore ? {
269
+ firestore: options.firestore,
270
+ now: at
271
+ } : {
272
+ now: at
273
+ });
274
+ const platformPerHour = config.perHour;
275
+ if (!config.enabled) {
276
+ // A parked control answers with the verdict an established workspace
277
+ // gets, so a surface reading the step does not blank while it is off.
278
+ const parked = resolveOrgEmailRamp({
279
+ ageDays: null,
280
+ deliveredLifetime: 0,
281
+ platformPerHour
282
+ });
283
+ return {
284
+ ramp: parked,
285
+ degraded: false
286
+ };
287
+ }
288
+ const ageDays = orgAgeDays(options.createdAt, at);
289
+ /*
290
+ * The step this workspace would be on having delivered nothing. It is the
291
+ * graduation test and the floor in one value: delivered volume only ever
292
+ * raises a step, so a verdict that graduates here graduates at any volume,
293
+ * and one that does not is the lowest step the workspace can be on.
294
+ */ const floor = resolveOrgEmailRamp({
295
+ ageDays,
296
+ deliveredLifetime: 0,
297
+ platformPerHour
298
+ });
299
+ if (floor.graduated) return {
300
+ ramp: floor,
301
+ degraded: false
302
+ };
303
+ const window = await readSenderReputationWindow({
304
+ orgId: options.orgId,
305
+ now: at,
306
+ firestore: options.firestore
307
+ });
308
+ if (window.degraded) return {
309
+ ramp: floor,
310
+ degraded: true
311
+ };
312
+ return {
313
+ ramp: resolveOrgEmailRamp({
314
+ ageDays,
315
+ deliveredLifetime: window.accepted,
316
+ platformPerHour
317
+ }),
318
+ degraded: false
319
+ };
320
+ }
321
+ /**
322
+ * THE NEW-SENDER RAMP, ENFORCED.
323
+ *
324
+ * Claims `count` campaign messages against what this workspace may send
325
+ * today, atomically. The transaction reads the counter and writes an ABSOLUTE
326
+ * value derived from that read rather than an increment, for the reason
327
+ * `reserveCampaignEmailSends` and `claimOrgEmailSendBudget` both do: the read
328
+ * is the authority for the decision, and an increment proves nothing about
329
+ * the value the decision was made from.
330
+ *
331
+ * The ceiling arrives as a resolved {@link EmailRampVerdict} rather than
332
+ * being derived here, because the caller has already paid the window read
333
+ * that decides which step the workspace is on — see
334
+ * {@link resolveOrgEmailRamp}. A graduated workspace claims nothing at all:
335
+ * the ramp does not bind it, and the hourly share that does has already been
336
+ * claimed by the time this runs.
337
+ *
338
+ * **Campaigns only**, like the hourly claim beside it. This function is not
339
+ * on `sendEmail`'s path and cannot be reached by a transactional message.
340
+ *
341
+ * A refused claim writes NOTHING — a campaign that resumes tomorrow must not
342
+ * have spent tomorrow's budget on being told no.
343
+ */ export async function claimOrgEmailSendDay(options) {
344
+ var _options_now, _options_orgId;
345
+ const now = Number((_options_now = options.now) != null ? _options_now : Date.now());
346
+ const at = Number.isFinite(now) && now > 0 ? now : Date.now();
347
+ const dayKey = reputationDayKey(at);
348
+ const retryAtMs = dayStartMs(at) + DAY_MS;
349
+ const count = Math.max(0, Math.floor(Number(options.count) || 0));
350
+ const orgId = String((_options_orgId = options.orgId) != null ? _options_orgId : '');
351
+ const ramp = options.ramp;
352
+ const granted = (used, degraded, reservation)=>({
353
+ allowed: true,
354
+ used,
355
+ ceiling: ramp.perDay,
356
+ remaining: Math.max(0, ramp.perDay - (used + count)),
357
+ retryAtMs,
358
+ ramp,
359
+ degraded,
360
+ reservation
361
+ });
362
+ // A parked control still reports a real ceiling, so a surface reading this
363
+ // does not blank while the control is off.
364
+ if (options.enabled === false) return granted(0, false, null);
365
+ // A graduated workspace is not paced by the ramp at all. No read, no write,
366
+ // and no claim to give back.
367
+ if (ramp.graduated) return granted(0, false, null);
368
+ if (!orgId) return granted(0, true, null);
369
+ try {
370
+ var _options_firestore;
371
+ const firestore = (_options_firestore = options.firestore) != null ? _options_firestore : firebaseAdmin.app().firestore();
372
+ const ref = reputationRef(firestore, dayKey, orgId);
373
+ return await firestore.runTransaction(async (tx)=>{
374
+ const snapshot = await tx.get(ref);
375
+ const used = storedCount(snapshot, 'claimed');
376
+ if (used + count > ramp.perDay) {
377
+ // A refused claim writes NOTHING.
378
+ return {
379
+ allowed: false,
380
+ used,
381
+ ceiling: ramp.perDay,
382
+ remaining: Math.max(0, ramp.perDay - used),
383
+ retryAtMs,
384
+ ramp,
385
+ degraded: false,
386
+ reservation: null
387
+ };
388
+ }
389
+ tx.set(ref, {
390
+ claimed: used + count,
391
+ orgId,
392
+ dayKey,
393
+ expiresAt: reputationExpiresAt(dayStartMs(at))
394
+ }, {
395
+ merge: true
396
+ });
397
+ return granted(used, false, {
398
+ orgId,
399
+ dayKey,
400
+ claimed: count
401
+ });
402
+ });
403
+ } catch (error) {
404
+ console.error('[sender-reputation] day window unavailable — allowing', error);
405
+ return granted(0, true, null);
406
+ }
407
+ }
408
+ /**
409
+ * Returns the undelivered part of a day's ramp claim.
410
+ *
411
+ * The hourly claim is deliberately never reconciled — its window is an hour
412
+ * and an unrefunded remainder costs the org the rest of it. A DAY is long
413
+ * enough that the same choice would cost a new workspace its whole first day
414
+ * over one failed batch, which is the opposite of what a ramp is for.
415
+ *
416
+ * **Never throws** and never drives the counter below zero, exactly as
417
+ * `reconcileCampaignSendReservation` does one surface over.
418
+ */ export async function reconcileOrgEmailSendDay(reservation, delivered, firestore) {
419
+ if (!(reservation == null ? void 0 : reservation.orgId)) return;
420
+ const sent = Math.max(0, Math.floor(Number(delivered) || 0));
421
+ const refund = Math.max(0, reservation.claimed - sent);
422
+ if (refund <= 0) return;
423
+ try {
424
+ const db = firestore != null ? firestore : firebaseAdmin.app().firestore();
425
+ const ref = reputationRef(db, reservation.dayKey, reservation.orgId);
426
+ await db.runTransaction(async (tx)=>{
427
+ const snapshot = await tx.get(ref);
428
+ const used = storedCount(snapshot, 'claimed');
429
+ tx.set(ref, {
430
+ claimed: Math.max(0, used - refund)
431
+ }, {
432
+ merge: true
433
+ });
434
+ });
435
+ } catch (error) {
436
+ console.error('[sender-reputation] day reconcile failed', error);
437
+ }
438
+ }
439
+ /**
440
+ * How old a workspace is, in days, from whatever its `createdAt` turns out to
441
+ * be.
442
+ *
443
+ * Firestore hands back a `Timestamp`, a restore or a fixture can leave a
444
+ * number, and an org written before the field existed has nothing. All three
445
+ * are handled and the last one answers `null`, which
446
+ * {@link claimOrgEmailSendDay} reads as graduated — the only safe direction,
447
+ * for the reason stated there.
448
+ */ export function orgAgeDays(createdAt, now = Date.now()) {
449
+ const raw = createdAt;
450
+ let createdMs = 0;
451
+ if (typeof raw === 'number') createdMs = raw;
452
+ else if (typeof raw === 'string') createdMs = Date.parse(raw);
453
+ else if (typeof (raw == null ? void 0 : raw.toMillis) === 'function') createdMs = raw.toMillis();
454
+ else if (typeof (raw == null ? void 0 : raw.seconds) === 'number') createdMs = raw.seconds * 1000;
455
+ /*
456
+ * The one check, and it has to answer `null` rather than 0. A creation date
457
+ * that could not be read is an org whose record predates the field, which
458
+ * is an EXISTING customer; `0` would say "created today" and ramp every
459
+ * paying tenant on the platform down to the first step.
460
+ */ if (!Number.isFinite(createdMs) || createdMs <= 0) return null;
461
+ return daysBetween(createdMs, now);
462
+ }
463
+
464
+ //# sourceMappingURL=email-sender-reputation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/email-sender-reputation.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * PER-TENANT SENDING REPUTATION — the durable half.\n *\n * The policy is pure and lives in `@aglyn/shared-util-email`\n * (`sender-reputation.ts`); this adds the counters that make a rate a fact\n * rather than an intention, and the daily claim that paces a workspace's\n * first week.\n *\n * ## One document per workspace per day\n *\n * `rateLimits/emailRep_{YYYY-MM-DD}_{orgId}` holds four numbers:\n *\n * - `claimed` — campaign messages the day's ramp has granted. The PACING\n * counter, taken before a send and reconciled to what actually left.\n * - `accepted` — campaign messages that actually left. The DENOMINATOR every\n * rate divides by.\n * - `bounced`, `complained` — what the provider reported back.\n *\n * A day per document rather than a map of days on one, because the day is\n * what the TTL policy can sweep: `rateLimits` already carries an `expiresAt`\n * policy, and a document that ages out is a window that prunes itself. A map\n * of every day a workspace has ever sent would grow forever and would have to\n * be pruned by a job nobody would write.\n *\n * A per-ORG document rather than per-site, because the org is the tenant: the\n * plan, the monthly allowance and the hourly share are all keyed on it, and a\n * merchant with three sites on one domain is one sender to a mailbox\n * provider.\n *\n * The window document deliberately carries **no `lastAtMs`** — the\n * rate-limiter health probe queries this collection on that field, and a\n * per-day document in its range would compete with the degradation markers it\n * exists to find. The same reasoning as `sendRate_*` and `sendRateOrg_*`\n * beside it.\n *\n * ## Which day a bounce is filed under\n *\n * The day the EVENT arrived, not the day the message was sent. The webhook\n * knows the first and would need a per-message lookup for the second, and\n * over a seven-day window the two differ only at the edges — a bounce lands\n * within minutes and a complaint within hours or a day. Stated because the\n * rate is a ratio of two things counted on slightly different clocks, and a\n * reader comparing it against a provider's dashboard should know why they\n * will not agree to the last decimal.\n *\n * ## Failure posture: OPEN, and that is not the usual answer here\n *\n * An unreadable counter grades `ok` and grants the send, matching\n * `consumeEmailSendBudget` and `claimOrgEmailSendBudget`. The breaker is the\n * one control in this file that could refuse a paying customer's campaign,\n * and a refusal produced by a Firestore blip is an outage dressed as a policy\n * decision. The reputation risk it fails to catch is bounded by an hour of\n * sending; the refusal it would produce is not bounded by anything.\n */\n\nimport {\n EMAIL_REPUTATION_WINDOW_DAYS,\n emailRampVerdict,\n emailReputationVerdict,\n normalizeEmailReputationPolicy,\n orgDailyCampaignCeiling,\n reputationDayKey,\n reputationWindowDayKeys,\n daysBetween,\n type EmailRampVerdict,\n type EmailReputationCounts,\n type EmailReputationPolicy,\n type EmailReputationVerdict,\n} from '@aglyn/shared-util-email'\nimport { readEmailSendRateConfig } from './email-send-rate'\nimport { firebaseAdmin } from './firebase-admin'\nimport { RATE_LIMIT_COLLECTION } from './rate-limit-store'\n\n/** Id prefix for the per-org, per-day reputation documents. */\nexport const EMAIL_REPUTATION_DOC_PREFIX = 'emailRep_'\n\n/** One day in ms, named so the arithmetic below reads as arithmetic. */\nconst DAY_MS = 86_400_000\n\n/** The document id for one workspace's day. */\nexport function emailReputationDocId(dayKey: string, orgId: string): string {\n return `${EMAIL_REPUTATION_DOC_PREFIX}${dayKey}_${orgId}`\n}\n\n/** The counted fields, so a writer cannot invent a fifth by typo. */\nexport type EmailReputationCounter =\n | 'claimed'\n | 'accepted'\n | 'bounced'\n | 'complained'\n\nfunction reputationRef(firestore: any, dayKey: string, orgId: string): any {\n return firestore\n .collection(RATE_LIMIT_COLLECTION)\n .doc(emailReputationDocId(dayKey, orgId))\n}\n\n/** A stored count, clamped. A corrupt negative reads as 0, never as headroom. */\nfunction storedCount(snapshot: any, field: EmailReputationCounter): number {\n const value = Number((snapshot?.exists ? snapshot.get(field) : 0) ?? 0)\n return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0\n}\n\n/**\n * When a day's document may be swept.\n *\n * One window plus a day of slack past the end of the day it counts, so the\n * oldest day a rate still divides by is never deleted underneath the read\n * that needs it.\n */\nfunction reputationExpiresAt(dayStartMs: number): Date {\n return new Date(dayStartMs + (EMAIL_REPUTATION_WINDOW_DAYS + 2) * DAY_MS)\n}\n\n/** Midnight UTC of the day a timestamp falls in. */\nfunction dayStartMs(atMs: number): number {\n return Math.floor(atMs / DAY_MS) * DAY_MS\n}\n\n/**\n * Adds to one of a workspace's daily counters.\n *\n * **Never throws.** Reputation accounting runs after mail has gone out, or\n * inside a webhook the provider will retry — a counter write that failed must\n * not turn a delivered campaign into a 500 or a webhook into a retry storm.\n * `FieldValue.increment` rather than a transaction because nothing decides\n * anything from this write: the decision is made by a separate read, and an\n * increment is the contention-free way to record a fact.\n */\nexport async function recordEmailReputationEvent(options: {\n orgId: string\n counter: EmailReputationCounter\n count?: number\n atMs?: number\n firestore?: any\n}): Promise<void> {\n const orgId = String(options.orgId ?? '')\n const count = Math.floor(Number(options.count ?? 1))\n if (!orgId || !Number.isFinite(count) || count <= 0) return\n const atMs = Number(options.atMs ?? Date.now())\n const at = Number.isFinite(atMs) && atMs > 0 ? atMs : Date.now()\n try {\n const firestore = options.firestore ?? firebaseAdmin.app().firestore()\n await reputationRef(firestore, reputationDayKey(at), orgId).set(\n {\n [options.counter]: firebaseAdmin.firestore.FieldValue.increment(count),\n orgId,\n dayKey: reputationDayKey(at),\n // NOT `lastAtMs` — see the storage note at the top of this file.\n expiresAt: reputationExpiresAt(dayStartMs(at)),\n },\n { merge: true },\n )\n } catch (error) {\n console.error('[sender-reputation] counter write failed', error)\n }\n}\n\n/** Campaign messages that actually left, for a workspace. The denominator. */\nexport function recordCampaignAccepted(\n orgId: string,\n count: number,\n options?: { atMs?: number; firestore?: any },\n): Promise<void> {\n return recordEmailReputationEvent({\n orgId,\n counter: 'accepted',\n count,\n ...options,\n })\n}\n\n/**\n * A permanent bounce or a spam complaint, attributed to the workspace that\n * sent it.\n *\n * The suppression the same event produces is a separate act and is not\n * conditional on this: an address comes off the list whether or not the\n * counter write lands.\n */\nexport function recordEmailReputationFailure(\n orgId: string,\n kind: 'bounce' | 'complaint',\n options?: { atMs?: number; firestore?: any },\n): Promise<void> {\n return recordEmailReputationEvent({\n orgId,\n counter: kind === 'complaint' ? 'complained' : 'bounced',\n count: 1,\n ...options,\n })\n}\n\n/** The counts a workspace's window holds, plus what the ramp needs. */\nexport interface SenderReputationWindow extends EmailReputationCounts {\n /**\n * Campaign messages the ramp has granted TODAY.\n *\n * Read here rather than by a second query because today's document is\n * already in this window: the ramp needs one number off it and the rates\n * need three off all of them, and two reads of one document to answer two\n * questions about the same workspace is a round trip nobody has to pay.\n */\n claimedToday: number\n /** Days the window covers. */\n windowDays: number\n /** True when the window could not be read and everything below is zero. */\n degraded: boolean\n}\n\n/**\n * Sums a workspace's last {@link EMAIL_REPUTATION_WINDOW_DAYS} days.\n *\n * One `getAll` over the window's day documents — seven reads per campaign,\n * not per message, and none at all for a workspace that has never sent. An\n * absent day is a quiet day and reads as zero.\n */\nexport async function readSenderReputationWindow(options: {\n orgId: string\n now?: number\n windowDays?: number\n firestore?: any\n}): Promise<SenderReputationWindow> {\n const windowDays = Math.max(\n 1,\n Math.floor(Number(options.windowDays ?? EMAIL_REPUTATION_WINDOW_DAYS)) ||\n EMAIL_REPUTATION_WINDOW_DAYS,\n )\n const empty: SenderReputationWindow = {\n accepted: 0,\n bounced: 0,\n complained: 0,\n claimedToday: 0,\n windowDays,\n degraded: false,\n }\n const orgId = String(options.orgId ?? '')\n if (!orgId) return empty\n const now = Number(options.now ?? Date.now())\n try {\n const firestore = options.firestore ?? firebaseAdmin.app().firestore()\n const dayKeys = reputationWindowDayKeys(now, windowDays)\n const today = reputationDayKey(now)\n const snapshots = await firestore.getAll(\n ...dayKeys.map((dayKey) => reputationRef(firestore, dayKey, orgId)),\n )\n return snapshots.reduce(\n (totals: SenderReputationWindow, snapshot: any, index: number) => ({\n ...totals,\n accepted: totals.accepted + storedCount(snapshot, 'accepted'),\n bounced: totals.bounced + storedCount(snapshot, 'bounced'),\n complained: totals.complained + storedCount(snapshot, 'complained'),\n claimedToday:\n dayKeys[index] === today\n ? totals.claimedToday + storedCount(snapshot, 'claimed')\n : totals.claimedToday,\n }),\n empty,\n )\n } catch (error) {\n console.error('[sender-reputation] window unavailable — grading ok', error)\n return { ...empty, degraded: true }\n }\n}\n\n/** A workspace's reputation, and whether its campaigns may go out. */\nexport interface SenderReputationRead extends EmailReputationVerdict {\n /** True when the window could not be read, so nothing was graded. */\n degraded: boolean\n /** The counts the grade was made from, so a caller can size a ramp too. */\n window: SenderReputationWindow\n}\n\n/**\n * Grades one workspace.\n *\n * The policy and the grace period come from the ORG DOCUMENT, which the\n * campaign sender has already read — passed in rather than re-fetched, so\n * this costs the window read and nothing else.\n */\nexport async function readSenderReputation(options: {\n orgId: string\n policy?: unknown\n reinstatedUntilMs?: unknown\n now?: number\n firestore?: any\n}): Promise<SenderReputationRead> {\n const now = Number(options.now ?? Date.now())\n const window = await readSenderReputationWindow({\n orgId: options.orgId,\n now,\n firestore: options.firestore,\n })\n const verdict = emailReputationVerdict({\n counts: window,\n // A degraded read grades against zeroes, which is `ok` by construction.\n // Stated rather than special-cased: the fail-open posture at the top of\n // this file is the reason, and a branch here would be a second place for\n // it to be got wrong.\n policy: normalizeEmailReputationPolicy(options.policy),\n windowDays: window.windowDays,\n reinstatedUntilMs: Number(options.reinstatedUntilMs ?? 0) || null,\n now,\n })\n return { ...verdict, degraded: window.degraded, window }\n}\n\n/**\n * The ramp step a workspace has EARNED, from a window it has already read.\n *\n * A thin wrapper over the pure policy that supplies the one number the policy\n * cannot know: what a graduated workspace's day is, which is a share of the\n * live platform ceiling and therefore moves with a staff ramp.\n */\nexport function resolveOrgEmailRamp(options: {\n /** Days since the workspace was created; null or absent graduates it. */\n ageDays: number | null | undefined\n /** Campaign messages it has delivered inside the window. */\n deliveredLifetime: number\n /** The live platform ceiling. */\n platformPerHour: number\n}): EmailRampVerdict {\n return emailRampVerdict({\n ageDays: options.ageDays,\n deliveredLifetime: options.deliveredLifetime,\n graduatedPerDay: orgDailyCampaignCeiling(options.platformPerHour),\n })\n}\n\n/** A workspace's ramp step, and whether the history behind it was readable. */\nexport interface OrgEmailRampRead {\n /** The step, from the age and the delivery history that resolved. */\n ramp: EmailRampVerdict\n /**\n * True when the ramp BINDS and the window it is sized from was unreadable,\n * so {@link ramp} is a floor rather than an answer.\n *\n * A caller that pages a batch — the campaign path — may treat this as the\n * step and carry on, because the floor is the lowest step and erring low\n * defers mail rather than sending it. A caller that admits or refuses ONE\n * message has no such third answer and must refuse: see\n * {@link readOrgEmailRamp}.\n */\n degraded: boolean\n}\n\n/**\n * A workspace's ramp step, resolved from its record and its history.\n *\n * The reads {@link resolveOrgEmailRamp} needs, in the order that pays for\n * the fewest of them. The platform ceiling comes first because it is process-\n * cached for fifteen seconds and therefore free; the seven-day window is a\n * `getAll` and is only paid when the ramp can still bind.\n *\n * ## A graduated workspace pays nothing\n *\n * Age alone decides graduation — delivery volume can only RAISE a step, never\n * reach past the last one — so a workspace past its first week is answered\n * from the org record with no window read at all. That is every existing\n * customer and every org whose record predates the creation timestamp, which\n * is to say almost every call.\n *\n * ## An unreadable window is `degraded`, and it is the caller's to answer\n *\n * The window read grades itself rather than throwing, and an unreadable one\n * reads as zero delivered — the LOWEST step, which is the conservative\n * direction for a ceiling. What it is not is a fact, so it is flagged: a\n * surface admitting one message at a time cannot defer the remainder to\n * tomorrow the way a campaign can, and a limiter with no answer must refuse\n * rather than admit.\n *\n * ## The platform flag parks this control\n *\n * `enabled: false` on the send-rate config graduates every workspace here,\n * exactly as it parks the hourly share and {@link claimOrgEmailSendDay} — one\n * switch, and it turns the ramp off everywhere it is consulted.\n */\nexport async function readOrgEmailRamp(options: {\n orgId: string\n /** The org record's `createdAt`, in whatever shape it was stored. */\n createdAt: unknown\n now?: number\n firestore?: any\n}): Promise<OrgEmailRampRead> {\n const now = Number(options.now ?? Date.now())\n const at = Number.isFinite(now) && now > 0 ? now : Date.now()\n const config = await readEmailSendRateConfig(\n options.firestore ? { firestore: options.firestore, now: at } : { now: at },\n )\n const platformPerHour = config.perHour\n if (!config.enabled) {\n // A parked control answers with the verdict an established workspace\n // gets, so a surface reading the step does not blank while it is off.\n const parked = resolveOrgEmailRamp({\n ageDays: null,\n deliveredLifetime: 0,\n platformPerHour,\n })\n return { ramp: parked, degraded: false }\n }\n const ageDays = orgAgeDays(options.createdAt, at)\n /*\n * The step this workspace would be on having delivered nothing. It is the\n * graduation test and the floor in one value: delivered volume only ever\n * raises a step, so a verdict that graduates here graduates at any volume,\n * and one that does not is the lowest step the workspace can be on.\n */\n const floor = resolveOrgEmailRamp({\n ageDays,\n deliveredLifetime: 0,\n platformPerHour,\n })\n if (floor.graduated) return { ramp: floor, degraded: false }\n const window = await readSenderReputationWindow({\n orgId: options.orgId,\n now: at,\n firestore: options.firestore,\n })\n if (window.degraded) return { ramp: floor, degraded: true }\n return {\n ramp: resolveOrgEmailRamp({\n ageDays,\n deliveredLifetime: window.accepted,\n platformPerHour,\n }),\n degraded: false,\n }\n}\n\n/** The answer to a per-day ramp claim. Every field is a stated number. */\nexport interface OrgEmailSendDayClaim {\n allowed: boolean\n /** Messages claimed today BEFORE this send. */\n used: number\n /** What this workspace may send today. */\n ceiling: number\n /** Headroom after this send, floored at 0. */\n remaining: number\n /** When the day rolls and a deferred campaign may go, ms. */\n retryAtMs: number\n /** The ramp decision the ceiling came from. */\n ramp: EmailRampVerdict\n /** True when the counter was unreachable and this failed open. */\n degraded: boolean\n /**\n * What to hand {@link reconcileOrgEmailSendDay} once the batch is done.\n * Null when nothing was claimed — a graduated workspace, a parked control,\n * or a refusal.\n */\n reservation: OrgEmailSendDayReservation | null\n}\n\n/** A claim on a day's ramp, so an undelivered remainder can be given back. */\nexport interface OrgEmailSendDayReservation {\n orgId: string\n dayKey: string\n claimed: number\n}\n\n/**\n * THE NEW-SENDER RAMP, ENFORCED.\n *\n * Claims `count` campaign messages against what this workspace may send\n * today, atomically. The transaction reads the counter and writes an ABSOLUTE\n * value derived from that read rather than an increment, for the reason\n * `reserveCampaignEmailSends` and `claimOrgEmailSendBudget` both do: the read\n * is the authority for the decision, and an increment proves nothing about\n * the value the decision was made from.\n *\n * The ceiling arrives as a resolved {@link EmailRampVerdict} rather than\n * being derived here, because the caller has already paid the window read\n * that decides which step the workspace is on — see\n * {@link resolveOrgEmailRamp}. A graduated workspace claims nothing at all:\n * the ramp does not bind it, and the hourly share that does has already been\n * claimed by the time this runs.\n *\n * **Campaigns only**, like the hourly claim beside it. This function is not\n * on `sendEmail`'s path and cannot be reached by a transactional message.\n *\n * A refused claim writes NOTHING — a campaign that resumes tomorrow must not\n * have spent tomorrow's budget on being told no.\n */\nexport async function claimOrgEmailSendDay(options: {\n orgId: string\n /** Messages this batch would send. */\n count: number\n /** What this workspace may send today, from {@link resolveOrgEmailRamp}. */\n ramp: EmailRampVerdict\n /** False parks the control, exactly as the platform governor's flag does. */\n enabled?: boolean\n now?: number\n firestore?: any\n}): Promise<OrgEmailSendDayClaim> {\n const now = Number(options.now ?? Date.now())\n const at = Number.isFinite(now) && now > 0 ? now : Date.now()\n const dayKey = reputationDayKey(at)\n const retryAtMs = dayStartMs(at) + DAY_MS\n const count = Math.max(0, Math.floor(Number(options.count) || 0))\n const orgId = String(options.orgId ?? '')\n const ramp = options.ramp\n\n const granted = (\n used: number,\n degraded: boolean,\n reservation: OrgEmailSendDayReservation | null,\n ): OrgEmailSendDayClaim => ({\n allowed: true,\n used,\n ceiling: ramp.perDay,\n remaining: Math.max(0, ramp.perDay - (used + count)),\n retryAtMs,\n ramp,\n degraded,\n reservation,\n })\n\n // A parked control still reports a real ceiling, so a surface reading this\n // does not blank while the control is off.\n if (options.enabled === false) return granted(0, false, null)\n // A graduated workspace is not paced by the ramp at all. No read, no write,\n // and no claim to give back.\n if (ramp.graduated) return granted(0, false, null)\n if (!orgId) return granted(0, true, null)\n\n try {\n const firestore = options.firestore ?? firebaseAdmin.app().firestore()\n const ref = reputationRef(firestore, dayKey, orgId)\n return await firestore.runTransaction(async (tx: any) => {\n const snapshot = await tx.get(ref)\n const used = storedCount(snapshot, 'claimed')\n if (used + count > ramp.perDay) {\n // A refused claim writes NOTHING.\n return {\n allowed: false,\n used,\n ceiling: ramp.perDay,\n remaining: Math.max(0, ramp.perDay - used),\n retryAtMs,\n ramp,\n degraded: false,\n reservation: null,\n }\n }\n tx.set(\n ref,\n {\n claimed: used + count,\n orgId,\n dayKey,\n expiresAt: reputationExpiresAt(dayStartMs(at)),\n },\n { merge: true },\n )\n return granted(used, false, { orgId, dayKey, claimed: count })\n })\n } catch (error) {\n console.error('[sender-reputation] day window unavailable — allowing', error)\n return granted(0, true, null)\n }\n}\n\n/**\n * Returns the undelivered part of a day's ramp claim.\n *\n * The hourly claim is deliberately never reconciled — its window is an hour\n * and an unrefunded remainder costs the org the rest of it. A DAY is long\n * enough that the same choice would cost a new workspace its whole first day\n * over one failed batch, which is the opposite of what a ramp is for.\n *\n * **Never throws** and never drives the counter below zero, exactly as\n * `reconcileCampaignSendReservation` does one surface over.\n */\nexport async function reconcileOrgEmailSendDay(\n reservation: OrgEmailSendDayReservation | null | undefined,\n delivered: number,\n firestore?: any,\n): Promise<void> {\n if (!reservation?.orgId) return\n const sent = Math.max(0, Math.floor(Number(delivered) || 0))\n const refund = Math.max(0, reservation.claimed - sent)\n if (refund <= 0) return\n try {\n const db = firestore ?? firebaseAdmin.app().firestore()\n const ref = reputationRef(db, reservation.dayKey, reservation.orgId)\n await db.runTransaction(async (tx: any) => {\n const snapshot = await tx.get(ref)\n const used = storedCount(snapshot, 'claimed')\n tx.set(ref, { claimed: Math.max(0, used - refund) }, { merge: true })\n })\n } catch (error) {\n console.error('[sender-reputation] day reconcile failed', error)\n }\n}\n\n/**\n * How old a workspace is, in days, from whatever its `createdAt` turns out to\n * be.\n *\n * Firestore hands back a `Timestamp`, a restore or a fixture can leave a\n * number, and an org written before the field existed has nothing. All three\n * are handled and the last one answers `null`, which\n * {@link claimOrgEmailSendDay} reads as graduated — the only safe direction,\n * for the reason stated there.\n */\nexport function orgAgeDays(\n createdAt: unknown,\n now: number = Date.now(),\n): number | null {\n const raw = createdAt as\n | { toMillis?: () => number; seconds?: number }\n | number\n | string\n | null\n | undefined\n let createdMs = 0\n if (typeof raw === 'number') createdMs = raw\n else if (typeof raw === 'string') createdMs = Date.parse(raw)\n else if (typeof raw?.toMillis === 'function') createdMs = raw.toMillis()\n else if (typeof raw?.seconds === 'number') createdMs = raw.seconds * 1000\n /*\n * The one check, and it has to answer `null` rather than 0. A creation date\n * that could not be read is an org whose record predates the field, which\n * is an EXISTING customer; `0` would say \"created today\" and ramp every\n * paying tenant on the platform down to the first step.\n */\n if (!Number.isFinite(createdMs) || createdMs <= 0) return null\n return daysBetween(createdMs, now)\n}\n\n/** Re-exported so a caller reads one module for the whole control. */\nexport type { EmailReputationPolicy, EmailRampVerdict }\n"],"names":["EMAIL_REPUTATION_WINDOW_DAYS","emailRampVerdict","emailReputationVerdict","normalizeEmailReputationPolicy","orgDailyCampaignCeiling","reputationDayKey","reputationWindowDayKeys","daysBetween","readEmailSendRateConfig","firebaseAdmin","RATE_LIMIT_COLLECTION","EMAIL_REPUTATION_DOC_PREFIX","DAY_MS","emailReputationDocId","dayKey","orgId","reputationRef","firestore","collection","doc","storedCount","snapshot","field","value","Number","exists","get","isFinite","Math","floor","reputationExpiresAt","dayStartMs","Date","atMs","recordEmailReputationEvent","options","String","count","now","at","app","set","counter","FieldValue","increment","expiresAt","merge","error","console","recordCampaignAccepted","recordEmailReputationFailure","kind","readSenderReputationWindow","windowDays","max","empty","accepted","bounced","complained","claimedToday","degraded","dayKeys","today","snapshots","getAll","map","reduce","totals","index","readSenderReputation","window","verdict","counts","policy","reinstatedUntilMs","resolveOrgEmailRamp","ageDays","deliveredLifetime","graduatedPerDay","platformPerHour","readOrgEmailRamp","config","perHour","enabled","parked","ramp","orgAgeDays","createdAt","graduated","claimOrgEmailSendDay","retryAtMs","granted","used","reservation","allowed","ceiling","perDay","remaining","ref","runTransaction","tx","claimed","reconcileOrgEmailSendDay","delivered","sent","refund","db","raw","createdMs","parse","toMillis","seconds"],"mappings":"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"}