@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,417 @@
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
+ * THE PLATFORM SEND-RATE GOVERNOR — durable half (AGL-2409).
19
+ *
20
+ * The policy is pure and lives in `@aglyn/shared-util-email`
21
+ * (`send-rate.ts`); this adds the Firestore counter that makes the ceiling
22
+ * global rather than per-instance, the configuration read that makes a ramp a
23
+ * value change instead of a deploy, and the installation that puts the whole
24
+ * thing on `sendEmail`'s path.
25
+ *
26
+ * ## Storage: the `rateLimits` collection, on purpose
27
+ *
28
+ * Both documents live in `rateLimits`, the same collection AGL-794's counters,
29
+ * AGL-1679's degradation markers and AGL-1907's signup-refusal markers already
30
+ * use, for the reason those two state: it inherits the deny-all security rule
31
+ * and the `expiresAt` TTL policy that already exist rather than needing a new
32
+ * collection, a rules deploy and a second TTL policy.
33
+ *
34
+ * - `rateLimits/sendRate_{windowStartMs}` — one document per hour, holding
35
+ * the count. Carries `expiresAt`, so the TTL policy sweeps it.
36
+ * - `rateLimits/sendRateConfig` — the ceiling. **Carries NO `expiresAt`, and
37
+ * must never be given one**, or the TTL policy that serves the counters
38
+ * would quietly delete the configuration and the platform would silently
39
+ * revert to the compiled-in default. `emailSendRateConfigWrite` is the one
40
+ * writer and `email-send-rate.spec.ts` asserts the field is absent.
41
+ *
42
+ * The window document does NOT carry `lastAtMs`. AGL-1693's rate-limiter
43
+ * health probe queries this collection with `where('lastAtMs', '>=', cutoff)`;
44
+ * a per-hour document in that range would compete with the degradation
45
+ * markers the probe exists to find. It uses `sentAtMs`, which keeps the two
46
+ * queries disjoint at the index level — the same reasoning, and the same
47
+ * mistake avoided, as `refusedAtMs` on the signup markers.
48
+ *
49
+ * ## Failure posture: OPEN
50
+ *
51
+ * If the counter or the config cannot be read, every send is granted. That is
52
+ * the opposite of `consumeRateLimit`'s fail-closed-on-contention rule, and the
53
+ * difference is what the two controls protect. A brute-force limiter that
54
+ * fails open lets an attacker in; this one failing open lets a campaign out.
55
+ * The cost of failing CLOSED here is refusing a paying customer's campaign
56
+ * because of an unrelated Firestore blip — and, worse, refusing a bulk sweep
57
+ * that a customer's month depends on. Neither is worth the hour of ramp it
58
+ * would buy back, and the operator can see it: a degraded grant is reported
59
+ * and surfaced on /admin/emails.
60
+ */ import { EMAIL_SEND_RATE_WINDOW_MS, emailSendRateVerdict, emailSendRateWindowStartMs, normalizeEmailSendRateConfig, orgHourlyCampaignCeiling, setEmailSendGovernor } from "@aglyn/shared-util-email";
61
+ import { firebaseAdmin } from "./firebase-admin.js";
62
+ import { RATE_LIMIT_COLLECTION } from "./rate-limit-store.js";
63
+ /** Document id of the live ceiling. NEVER written with `expiresAt`. */ export const EMAIL_SEND_RATE_CONFIG_DOC = 'sendRateConfig';
64
+ /** Id prefix for the per-hour counters. */ export const EMAIL_SEND_RATE_WINDOW_PREFIX = 'sendRate_';
65
+ /** The counter document id for a window. */ export function emailSendRateWindowDocId(windowStartMs) {
66
+ return `${EMAIL_SEND_RATE_WINDOW_PREFIX}${windowStartMs}`;
67
+ }
68
+ /**
69
+ * Config cache TTL.
70
+ *
71
+ * 15s, the same number and the same reasoning as the platform lockdown doc:
72
+ * this sits on a path that every outbound message crosses, so a per-send read
73
+ * would be one Firestore read per email platform-wide, and 15s is the
74
+ * worst-case lag between staff moving the ramp and a warm process honouring
75
+ * it. A ramp is measured in hours; fifteen seconds is free.
76
+ */ const CONFIG_TTL_MS = 15000;
77
+ let configCache;
78
+ let configPending;
79
+ /** Drop the in-process config cache — called by the console after a write. */ export function invalidateEmailSendRateConfigCache() {
80
+ configCache = undefined;
81
+ configPending = undefined;
82
+ }
83
+ /**
84
+ * The live ceiling. Falls back to the compiled-in default on any read error,
85
+ * which is the fail-open posture stated above: an unreachable config must not
86
+ * be read as a ceiling of zero.
87
+ */ export async function readEmailSendRateConfig(options) {
88
+ var _ref;
89
+ const now = (_ref = options == null ? void 0 : options.now) != null ? _ref : Date.now();
90
+ if (!(options == null ? void 0 : options.firestore) && configCache && now - configCache.at < CONFIG_TTL_MS) {
91
+ return configCache.config;
92
+ }
93
+ const load = async ()=>{
94
+ let config = normalizeEmailSendRateConfig(null);
95
+ try {
96
+ var _ref;
97
+ const firestore = (_ref = options == null ? void 0 : options.firestore) != null ? _ref : firebaseAdmin.app().firestore();
98
+ const snapshot = await firestore.collection(RATE_LIMIT_COLLECTION).doc(EMAIL_SEND_RATE_CONFIG_DOC).get();
99
+ config = normalizeEmailSendRateConfig(snapshot.exists ? snapshot.data() : null);
100
+ } catch (unused) {
101
+ // Fail open: an unreachable config is an outage, not a ceiling of zero.
102
+ }
103
+ return config;
104
+ };
105
+ // An injected firestore is a test or a one-off read; never cached, so a
106
+ // spec cannot poison the process cache for the next one.
107
+ if (options == null ? void 0 : options.firestore) return load();
108
+ if (!configPending) {
109
+ configPending = load().then((config)=>{
110
+ configCache = {
111
+ at: now,
112
+ config
113
+ };
114
+ return config;
115
+ }).finally(()=>{
116
+ configPending = undefined;
117
+ });
118
+ }
119
+ return configPending;
120
+ }
121
+ /**
122
+ * Counts `count` messages against the current hour, globally, and answers
123
+ * whether they may go.
124
+ *
125
+ * The transaction reads the counter and writes an ABSOLUTE value derived from
126
+ * that read — not `FieldValue.increment` — because the read is the authority
127
+ * for the decision. Firestore aborts and re-runs the callback when a
128
+ * document it read has moved, so two concurrent sweeps cannot both see the
129
+ * same headroom and both take it. An increment would be atomic on the number
130
+ * and useless for the decision, which is exactly the read-then-write shape
131
+ * AGL-2267 is about one surface over.
132
+ *
133
+ * A refused send writes NOTHING. A caller that will retry next hour must not
134
+ * have spent budget on being told no.
135
+ */ export async function consumeEmailSendBudget(options) {
136
+ var _options_now, _options_config;
137
+ const now = (_options_now = options.now) != null ? _options_now : Date.now();
138
+ const windowStartMs = emailSendRateWindowStartMs(now);
139
+ const resetMs = windowStartMs + EMAIL_SEND_RATE_WINDOW_MS;
140
+ const config = (_options_config = options.config) != null ? _options_config : await readEmailSendRateConfig({
141
+ firestore: options.firestore,
142
+ now
143
+ });
144
+ const fallback = ()=>_extends({}, emailSendRateVerdict({
145
+ priority: options.priority,
146
+ used: 0,
147
+ count: options.count,
148
+ ceiling: config.perHour,
149
+ enabled: false,
150
+ windowStartMs
151
+ }), {
152
+ windowStartMs,
153
+ degraded: true
154
+ });
155
+ try {
156
+ var _options_firestore;
157
+ const firestore = (_options_firestore = options.firestore) != null ? _options_firestore : firebaseAdmin.app().firestore();
158
+ const ref = firestore.collection(RATE_LIMIT_COLLECTION).doc(emailSendRateWindowDocId(windowStartMs));
159
+ const verdict = await firestore.runTransaction(async (tx)=>{
160
+ var _ref;
161
+ const snapshot = await tx.get(ref);
162
+ const used = Number((_ref = snapshot.exists ? snapshot.get('count') : 0) != null ? _ref : 0);
163
+ const decision = emailSendRateVerdict({
164
+ priority: options.priority,
165
+ used,
166
+ count: options.count,
167
+ ceiling: config.perHour,
168
+ enabled: config.enabled,
169
+ windowStartMs
170
+ });
171
+ if (!decision.allowed) return decision;
172
+ tx.set(ref, {
173
+ count: decision.used + Math.max(0, Math.floor(Number(options.count) || 0)),
174
+ windowStartMs,
175
+ // NOT `lastAtMs` — see the storage note at the top of this file.
176
+ sentAtMs: now,
177
+ // The TTL field the counters in this collection already use. Two
178
+ // windows of retention so the console can show the hour just gone.
179
+ expiresAt: new Date(resetMs + EMAIL_SEND_RATE_WINDOW_MS)
180
+ }, {
181
+ merge: true
182
+ });
183
+ return decision;
184
+ });
185
+ return _extends({}, verdict, {
186
+ windowStartMs,
187
+ degraded: false
188
+ });
189
+ } catch (error) {
190
+ console.error('[send-rate] counter unavailable — allowing', error);
191
+ return fallback();
192
+ }
193
+ }
194
+ /**
195
+ * What the current hour looks like. Read-only, for the staff console.
196
+ *
197
+ * Returns `used: 0` rather than throwing when the window document does not
198
+ * exist, which is the ordinary state of a quiet hour.
199
+ */ export async function readEmailSendRateWindow(options) {
200
+ var _ref;
201
+ const now = (_ref = options == null ? void 0 : options.now) != null ? _ref : Date.now();
202
+ const windowStartMs = emailSendRateWindowStartMs(now);
203
+ const resetMs = windowStartMs + EMAIL_SEND_RATE_WINDOW_MS;
204
+ try {
205
+ var _ref1, _ref2;
206
+ const firestore = (_ref1 = options == null ? void 0 : options.firestore) != null ? _ref1 : firebaseAdmin.app().firestore();
207
+ const snapshot = await firestore.collection(RATE_LIMIT_COLLECTION).doc(emailSendRateWindowDocId(windowStartMs)).get();
208
+ const used = Number((_ref2 = snapshot.exists ? snapshot.get('count') : 0) != null ? _ref2 : 0);
209
+ return {
210
+ windowStartMs,
211
+ resetMs,
212
+ used: Number.isFinite(used) && used > 0 ? Math.floor(used) : 0
213
+ };
214
+ } catch (unused) {
215
+ return {
216
+ windowStartMs,
217
+ resetMs,
218
+ used: 0
219
+ };
220
+ }
221
+ }
222
+ /** Id prefix for the per-org, per-hour campaign counters. */ export const EMAIL_ORG_SEND_RATE_WINDOW_PREFIX = 'sendRateOrg_';
223
+ /**
224
+ * The per-org counter document id for a window.
225
+ *
226
+ * A separate document per org rather than a map field on the platform window,
227
+ * so two orgs sending in the same hour contend on their own documents instead
228
+ * of serialising on one. The platform window is already a single hot document
229
+ * and adding N org fields to it would make every campaign in the hour a write
230
+ * conflict with every other.
231
+ */ export function emailOrgSendRateWindowDocId(windowStartMs, orgId) {
232
+ return `${EMAIL_ORG_SEND_RATE_WINDOW_PREFIX}${windowStartMs}_${orgId}`;
233
+ }
234
+ /** The current hour for one org. Read-only, for a usage surface. */ export async function readOrgEmailSendWindow(options) {
235
+ var _options_now;
236
+ const now = (_options_now = options.now) != null ? _options_now : Date.now();
237
+ const windowStartMs = emailSendRateWindowStartMs(now);
238
+ const resetMs = windowStartMs + EMAIL_SEND_RATE_WINDOW_MS;
239
+ if (!options.orgId) return {
240
+ windowStartMs,
241
+ resetMs,
242
+ used: 0
243
+ };
244
+ try {
245
+ var _options_firestore, _ref;
246
+ const firestore = (_options_firestore = options.firestore) != null ? _options_firestore : firebaseAdmin.app().firestore();
247
+ const snapshot = await firestore.collection(RATE_LIMIT_COLLECTION).doc(emailOrgSendRateWindowDocId(windowStartMs, options.orgId)).get();
248
+ const used = Number((_ref = snapshot.exists ? snapshot.get('count') : 0) != null ? _ref : 0);
249
+ return {
250
+ windowStartMs,
251
+ resetMs,
252
+ used: Number.isFinite(used) && used > 0 ? Math.floor(used) : 0
253
+ };
254
+ } catch (unused) {
255
+ return {
256
+ windowStartMs,
257
+ resetMs,
258
+ used: 0
259
+ };
260
+ }
261
+ }
262
+ /**
263
+ * THE PER-ORG SHARE OF THE PLATFORM HOUR.
264
+ *
265
+ * The platform governor bounds total volume; it does not bound how much of
266
+ * that total ONE tenant may take. Without this, a single org with a large
267
+ * audience occupies the whole hour and every other customer's campaigns are
268
+ * refused by a ceiling they did nothing to reach — one tenant denying service
269
+ * to the rest, on a limit they cannot see.
270
+ *
271
+ * The ceiling is derived, not configured: `orgHourlyCampaignCeiling` is a
272
+ * share of whatever the live platform ceiling currently is, so a staff ramp
273
+ * moves both together and the two can never drift into contradiction. See
274
+ * `send-ceilings.ts` for the arithmetic and the relations it maintains.
275
+ *
276
+ * **Campaigns only.** This function is not on `sendEmail`'s path and is called
277
+ * from the campaign sender alone. A transactional message can never reach it,
278
+ * which is the same boundary `emailSendRateVerdict` enforces one layer down
279
+ * and for the same reason: a password reset refused by a throttle converts a
280
+ * reputation risk into an outage on somebody else's business.
281
+ *
282
+ * ## Fails OPEN
283
+ *
284
+ * An unreachable counter grants the send, matching `consumeEmailSendBudget`.
285
+ * A refusal produced by a Firestore blip is a refused campaign for a paying
286
+ * customer, and the hour of pacing it buys back is not worth it.
287
+ *
288
+ * ## Claimed, not reconciled
289
+ *
290
+ * The claim is taken for the whole batch and never refunded, unlike the
291
+ * monthly reservation. The window is one hour and TTL-swept, so an
292
+ * undelivered remainder costs the org the rest of that hour and nothing
293
+ * after it — where an unreconciled MONTHLY claim would cost the rest of the
294
+ * month, which is why that one is reconciled and this one is not. The error
295
+ * is in the direction that can only ever pace mail more, never let more out.
296
+ */ export async function claimOrgEmailSendBudget(options) {
297
+ var _options_now;
298
+ const now = (_options_now = options.now) != null ? _options_now : Date.now();
299
+ const windowStartMs = emailSendRateWindowStartMs(now);
300
+ const retryAtMs = windowStartMs + EMAIL_SEND_RATE_WINDOW_MS;
301
+ const ceiling = orgHourlyCampaignCeiling(options.platformPerHour);
302
+ const count = Math.max(0, Math.floor(Number(options.count) || 0));
303
+ const granted = (used, degraded)=>({
304
+ allowed: true,
305
+ used,
306
+ ceiling,
307
+ remaining: Math.max(0, ceiling - (used + count)),
308
+ retryAtMs,
309
+ degraded
310
+ });
311
+ // A parked control still reports the ceiling, so a surface reading this
312
+ // shows a real number rather than blanking while the control is off.
313
+ if (options.enabled === false) return granted(0, false);
314
+ // No org means the counter has nowhere to live. The monthly reservation
315
+ // refuses in this case because an unattributable campaign must not send
316
+ // unbounded; here the monthly claim has already made that decision, so
317
+ // failing open costs nothing it has not already been charged for.
318
+ if (!options.orgId) return granted(0, true);
319
+ try {
320
+ var _options_firestore;
321
+ const firestore = (_options_firestore = options.firestore) != null ? _options_firestore : firebaseAdmin.app().firestore();
322
+ const ref = firestore.collection(RATE_LIMIT_COLLECTION).doc(emailOrgSendRateWindowDocId(windowStartMs, options.orgId));
323
+ return await firestore.runTransaction(async (tx)=>{
324
+ var _ref;
325
+ const snapshot = await tx.get(ref);
326
+ const rawUsed = Number((_ref = snapshot.exists ? snapshot.get('count') : 0) != null ? _ref : 0);
327
+ // A corrupt or negative counter must not read as headroom.
328
+ const used = Number.isFinite(rawUsed) && rawUsed > 0 ? Math.floor(rawUsed) : 0;
329
+ if (used + count > ceiling) {
330
+ // A refused claim writes NOTHING. A campaign that will be retried
331
+ // next hour must not have spent budget on being told no.
332
+ return {
333
+ allowed: false,
334
+ used,
335
+ ceiling,
336
+ remaining: Math.max(0, ceiling - used),
337
+ retryAtMs,
338
+ degraded: false
339
+ };
340
+ }
341
+ // An absolute write from this transaction's own read, not
342
+ // `FieldValue.increment` — the read is the authority for the decision,
343
+ // and an increment would be atomic on the number while proving nothing
344
+ // about the value the decision was made from.
345
+ tx.set(ref, {
346
+ count: used + count,
347
+ windowStartMs,
348
+ orgId: options.orgId,
349
+ // NOT `lastAtMs` — the rate-limiter health probe queries this
350
+ // collection on that field and a per-hour document in its range
351
+ // would compete with the markers it exists to find.
352
+ sentAtMs: now,
353
+ expiresAt: new Date(retryAtMs + EMAIL_SEND_RATE_WINDOW_MS)
354
+ }, {
355
+ merge: true
356
+ });
357
+ return granted(used, false);
358
+ });
359
+ } catch (error) {
360
+ console.error('[send-rate] org window unavailable — allowing', error);
361
+ return granted(0, true);
362
+ }
363
+ }
364
+ /**
365
+ * The document the console writes when staff move the ramp.
366
+ *
367
+ * Returned rather than written so the route owns the write (and the audit row
368
+ * beside it), and so a spec can assert the SHAPE without a Firestore. The one
369
+ * thing it must never contain is `expiresAt`.
370
+ */ export function emailSendRateConfigWrite(input) {
371
+ var _input_now, _input_actorEmail;
372
+ const normalized = normalizeEmailSendRateConfig({
373
+ perHour: input.perHour,
374
+ enabled: input.enabled,
375
+ note: input.note,
376
+ updatedAtMs: (_input_now = input.now) != null ? _input_now : Date.now(),
377
+ updatedByEmail: (_input_actorEmail = input.actorEmail) != null ? _input_actorEmail : null
378
+ });
379
+ return {
380
+ perHour: normalized.perHour,
381
+ enabled: normalized.enabled,
382
+ note: normalized.note,
383
+ updatedAtMs: normalized.updatedAtMs,
384
+ updatedByEmail: normalized.updatedByEmail
385
+ };
386
+ }
387
+ /**
388
+ * Puts the durable governor on `sendEmail`'s path.
389
+ *
390
+ * **Called at module load**, from the bottom of this file, so that importing
391
+ * `@aglyn/tenant-data-admin` anywhere is enough — every server surface in the
392
+ * product already imports that barrel, and `export *` forces this module to
393
+ * evaluate. The alternative, an `installEmailSendGovernor()` call at each
394
+ * server entrypoint, is the 37-places-to-remember shape this codebase already
395
+ * rejected once for the `context` tag.
396
+ *
397
+ * Idempotent: installing twice replaces the same closure with an equivalent
398
+ * one, and the closure holds no state — the state is the Firestore document.
399
+ */ export function installEmailSendGovernor() {
400
+ setEmailSendGovernor(async (request)=>{
401
+ const result = await consumeEmailSendBudget({
402
+ priority: request.priority,
403
+ count: request.count
404
+ });
405
+ return {
406
+ allowed: result.allowed,
407
+ ceiling: result.ceiling,
408
+ used: result.used,
409
+ remaining: result.remaining,
410
+ retryAtMs: result.retryAtMs,
411
+ degraded: result.degraded
412
+ };
413
+ });
414
+ }
415
+ installEmailSendGovernor();
416
+
417
+ //# sourceMappingURL=email-send-rate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/email-send-rate.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 * THE PLATFORM SEND-RATE GOVERNOR — durable half (AGL-2409).\n *\n * The policy is pure and lives in `@aglyn/shared-util-email`\n * (`send-rate.ts`); this adds the Firestore counter that makes the ceiling\n * global rather than per-instance, the configuration read that makes a ramp a\n * value change instead of a deploy, and the installation that puts the whole\n * thing on `sendEmail`'s path.\n *\n * ## Storage: the `rateLimits` collection, on purpose\n *\n * Both documents live in `rateLimits`, the same collection AGL-794's counters,\n * AGL-1679's degradation markers and AGL-1907's signup-refusal markers already\n * use, for the reason those two state: it inherits the deny-all security rule\n * and the `expiresAt` TTL policy that already exist rather than needing a new\n * collection, a rules deploy and a second TTL policy.\n *\n * - `rateLimits/sendRate_{windowStartMs}` — one document per hour, holding\n * the count. Carries `expiresAt`, so the TTL policy sweeps it.\n * - `rateLimits/sendRateConfig` — the ceiling. **Carries NO `expiresAt`, and\n * must never be given one**, or the TTL policy that serves the counters\n * would quietly delete the configuration and the platform would silently\n * revert to the compiled-in default. `emailSendRateConfigWrite` is the one\n * writer and `email-send-rate.spec.ts` asserts the field is absent.\n *\n * The window document does NOT carry `lastAtMs`. AGL-1693's rate-limiter\n * health probe queries this collection with `where('lastAtMs', '>=', cutoff)`;\n * a per-hour document in that range would compete with the degradation\n * markers the probe exists to find. It uses `sentAtMs`, which keeps the two\n * queries disjoint at the index level — the same reasoning, and the same\n * mistake avoided, as `refusedAtMs` on the signup markers.\n *\n * ## Failure posture: OPEN\n *\n * If the counter or the config cannot be read, every send is granted. That is\n * the opposite of `consumeRateLimit`'s fail-closed-on-contention rule, and the\n * difference is what the two controls protect. A brute-force limiter that\n * fails open lets an attacker in; this one failing open lets a campaign out.\n * The cost of failing CLOSED here is refusing a paying customer's campaign\n * because of an unrelated Firestore blip — and, worse, refusing a bulk sweep\n * that a customer's month depends on. Neither is worth the hour of ramp it\n * would buy back, and the operator can see it: a degraded grant is reported\n * and surfaced on /admin/emails.\n */\n\nimport {\n type EmailSendGovernorRequest,\n type EmailSendGovernorVerdict,\n type EmailSendPriority,\n type EmailSendRateConfig,\n type EmailSendRateVerdict,\n EMAIL_SEND_RATE_WINDOW_MS,\n emailSendRateVerdict,\n emailSendRateWindowStartMs,\n normalizeEmailSendRateConfig,\n orgHourlyCampaignCeiling,\n setEmailSendGovernor,\n} from '@aglyn/shared-util-email'\nimport { firebaseAdmin } from './firebase-admin'\nimport { RATE_LIMIT_COLLECTION } from './rate-limit-store'\n\n/** Document id of the live ceiling. NEVER written with `expiresAt`. */\nexport const EMAIL_SEND_RATE_CONFIG_DOC = 'sendRateConfig'\n\n/** Id prefix for the per-hour counters. */\nexport const EMAIL_SEND_RATE_WINDOW_PREFIX = 'sendRate_'\n\n/** The counter document id for a window. */\nexport function emailSendRateWindowDocId(windowStartMs: number): string {\n return `${EMAIL_SEND_RATE_WINDOW_PREFIX}${windowStartMs}`\n}\n\n/**\n * Config cache TTL.\n *\n * 15s, the same number and the same reasoning as the platform lockdown doc:\n * this sits on a path that every outbound message crosses, so a per-send read\n * would be one Firestore read per email platform-wide, and 15s is the\n * worst-case lag between staff moving the ramp and a warm process honouring\n * it. A ramp is measured in hours; fifteen seconds is free.\n */\nconst CONFIG_TTL_MS = 15_000\n\nlet configCache: { at: number; config: EmailSendRateConfig } | undefined\nlet configPending: Promise<EmailSendRateConfig> | undefined\n\n/** Drop the in-process config cache — called by the console after a write. */\nexport function invalidateEmailSendRateConfigCache(): void {\n configCache = undefined\n configPending = undefined\n}\n\n/**\n * The live ceiling. Falls back to the compiled-in default on any read error,\n * which is the fail-open posture stated above: an unreachable config must not\n * be read as a ceiling of zero.\n */\nexport async function readEmailSendRateConfig(options?: {\n firestore?: any\n now?: number\n}): Promise<EmailSendRateConfig> {\n const now = options?.now ?? Date.now()\n if (!options?.firestore && configCache && now - configCache.at < CONFIG_TTL_MS) {\n return configCache.config\n }\n const load = async (): Promise<EmailSendRateConfig> => {\n let config = normalizeEmailSendRateConfig(null)\n try {\n const firestore = options?.firestore ?? firebaseAdmin.app().firestore()\n const snapshot = await firestore\n .collection(RATE_LIMIT_COLLECTION)\n .doc(EMAIL_SEND_RATE_CONFIG_DOC)\n .get()\n config = normalizeEmailSendRateConfig(\n snapshot.exists ? (snapshot.data() as Partial<EmailSendRateConfig>) : null,\n )\n } catch {\n // Fail open: an unreachable config is an outage, not a ceiling of zero.\n }\n return config\n }\n // An injected firestore is a test or a one-off read; never cached, so a\n // spec cannot poison the process cache for the next one.\n if (options?.firestore) return load()\n if (!configPending) {\n configPending = load()\n .then((config) => {\n configCache = { at: now, config }\n return config\n })\n .finally(() => {\n configPending = undefined\n })\n }\n return configPending\n}\n\nexport interface ConsumeEmailSendBudgetOptions {\n priority: EmailSendPriority\n /** Messages this send would add. */\n count: number\n now?: number\n firestore?: any\n /** Injectable for tests; otherwise read (and cached) from Firestore. */\n config?: EmailSendRateConfig\n}\n\nexport interface ConsumeEmailSendBudgetResult extends EmailSendRateVerdict {\n windowStartMs: number\n /** True when the counter was unreachable and this failed open. */\n degraded: boolean\n}\n\n/**\n * Counts `count` messages against the current hour, globally, and answers\n * whether they may go.\n *\n * The transaction reads the counter and writes an ABSOLUTE value derived from\n * that read — not `FieldValue.increment` — because the read is the authority\n * for the decision. Firestore aborts and re-runs the callback when a\n * document it read has moved, so two concurrent sweeps cannot both see the\n * same headroom and both take it. An increment would be atomic on the number\n * and useless for the decision, which is exactly the read-then-write shape\n * AGL-2267 is about one surface over.\n *\n * A refused send writes NOTHING. A caller that will retry next hour must not\n * have spent budget on being told no.\n */\nexport async function consumeEmailSendBudget(\n options: ConsumeEmailSendBudgetOptions,\n): Promise<ConsumeEmailSendBudgetResult> {\n const now = options.now ?? Date.now()\n const windowStartMs = emailSendRateWindowStartMs(now)\n const resetMs = windowStartMs + EMAIL_SEND_RATE_WINDOW_MS\n const config =\n options.config ??\n (await readEmailSendRateConfig({ firestore: options.firestore, now }))\n\n const fallback = (): ConsumeEmailSendBudgetResult => ({\n ...emailSendRateVerdict({\n priority: options.priority,\n used: 0,\n count: options.count,\n ceiling: config.perHour,\n enabled: false,\n windowStartMs,\n }),\n windowStartMs,\n degraded: true,\n })\n\n try {\n const firestore = options.firestore ?? firebaseAdmin.app().firestore()\n const ref = firestore\n .collection(RATE_LIMIT_COLLECTION)\n .doc(emailSendRateWindowDocId(windowStartMs))\n\n const verdict = await firestore.runTransaction(async (tx: any) => {\n const snapshot = await tx.get(ref)\n const used = Number((snapshot.exists ? snapshot.get('count') : 0) ?? 0)\n const decision = emailSendRateVerdict({\n priority: options.priority,\n used,\n count: options.count,\n ceiling: config.perHour,\n enabled: config.enabled,\n windowStartMs,\n })\n if (!decision.allowed) return decision\n tx.set(\n ref,\n {\n count: decision.used + Math.max(0, Math.floor(Number(options.count) || 0)),\n windowStartMs,\n // NOT `lastAtMs` — see the storage note at the top of this file.\n sentAtMs: now,\n // The TTL field the counters in this collection already use. Two\n // windows of retention so the console can show the hour just gone.\n expiresAt: new Date(resetMs + EMAIL_SEND_RATE_WINDOW_MS),\n },\n { merge: true },\n )\n return decision\n })\n\n return { ...(verdict as EmailSendRateVerdict), windowStartMs, degraded: false }\n } catch (error) {\n console.error('[send-rate] counter unavailable — allowing', error)\n return fallback()\n }\n}\n\n/**\n * What the current hour looks like. Read-only, for the staff console.\n *\n * Returns `used: 0` rather than throwing when the window document does not\n * exist, which is the ordinary state of a quiet hour.\n */\nexport async function readEmailSendRateWindow(options?: {\n firestore?: any\n now?: number\n}): Promise<{ windowStartMs: number; resetMs: number; used: number }> {\n const now = options?.now ?? Date.now()\n const windowStartMs = emailSendRateWindowStartMs(now)\n const resetMs = windowStartMs + EMAIL_SEND_RATE_WINDOW_MS\n try {\n const firestore = options?.firestore ?? firebaseAdmin.app().firestore()\n const snapshot = await firestore\n .collection(RATE_LIMIT_COLLECTION)\n .doc(emailSendRateWindowDocId(windowStartMs))\n .get()\n const used = Number((snapshot.exists ? snapshot.get('count') : 0) ?? 0)\n return {\n windowStartMs,\n resetMs,\n used: Number.isFinite(used) && used > 0 ? Math.floor(used) : 0,\n }\n } catch {\n return { windowStartMs, resetMs, used: 0 }\n }\n}\n\n/** Id prefix for the per-org, per-hour campaign counters. */\nexport const EMAIL_ORG_SEND_RATE_WINDOW_PREFIX = 'sendRateOrg_'\n\n/**\n * The per-org counter document id for a window.\n *\n * A separate document per org rather than a map field on the platform window,\n * so two orgs sending in the same hour contend on their own documents instead\n * of serialising on one. The platform window is already a single hot document\n * and adding N org fields to it would make every campaign in the hour a write\n * conflict with every other.\n */\nexport function emailOrgSendRateWindowDocId(\n windowStartMs: number,\n orgId: string,\n): string {\n return `${EMAIL_ORG_SEND_RATE_WINDOW_PREFIX}${windowStartMs}_${orgId}`\n}\n\n/** The current hour for one org. Read-only, for a usage surface. */\nexport async function readOrgEmailSendWindow(options: {\n orgId: string\n firestore?: any\n now?: number\n}): Promise<{ windowStartMs: number; resetMs: number; used: number }> {\n const now = options.now ?? Date.now()\n const windowStartMs = emailSendRateWindowStartMs(now)\n const resetMs = windowStartMs + EMAIL_SEND_RATE_WINDOW_MS\n if (!options.orgId) return { windowStartMs, resetMs, used: 0 }\n try {\n const firestore = options.firestore ?? firebaseAdmin.app().firestore()\n const snapshot = await firestore\n .collection(RATE_LIMIT_COLLECTION)\n .doc(emailOrgSendRateWindowDocId(windowStartMs, options.orgId))\n .get()\n const used = Number((snapshot.exists ? snapshot.get('count') : 0) ?? 0)\n return {\n windowStartMs,\n resetMs,\n used: Number.isFinite(used) && used > 0 ? Math.floor(used) : 0,\n }\n } catch {\n return { windowStartMs, resetMs, used: 0 }\n }\n}\n\n/** The answer to a per-org hourly claim. Every field is a stated number. */\nexport interface OrgEmailSendClaimResult {\n allowed: boolean\n /** Count in this org's window BEFORE this send. */\n used: number\n /** What this org may send in an hour. */\n ceiling: number\n /** Headroom after this send, floored at 0. */\n remaining: number\n /** When the window rolls and a deferred campaign may go. */\n retryAtMs: number\n /** True when the counter was unreachable and this failed open. */\n degraded: boolean\n}\n\n/**\n * THE PER-ORG SHARE OF THE PLATFORM HOUR.\n *\n * The platform governor bounds total volume; it does not bound how much of\n * that total ONE tenant may take. Without this, a single org with a large\n * audience occupies the whole hour and every other customer's campaigns are\n * refused by a ceiling they did nothing to reach — one tenant denying service\n * to the rest, on a limit they cannot see.\n *\n * The ceiling is derived, not configured: `orgHourlyCampaignCeiling` is a\n * share of whatever the live platform ceiling currently is, so a staff ramp\n * moves both together and the two can never drift into contradiction. See\n * `send-ceilings.ts` for the arithmetic and the relations it maintains.\n *\n * **Campaigns only.** This function is not on `sendEmail`'s path and is called\n * from the campaign sender alone. A transactional message can never reach it,\n * which is the same boundary `emailSendRateVerdict` enforces one layer down\n * and for the same reason: a password reset refused by a throttle converts a\n * reputation risk into an outage on somebody else's business.\n *\n * ## Fails OPEN\n *\n * An unreachable counter grants the send, matching `consumeEmailSendBudget`.\n * A refusal produced by a Firestore blip is a refused campaign for a paying\n * customer, and the hour of pacing it buys back is not worth it.\n *\n * ## Claimed, not reconciled\n *\n * The claim is taken for the whole batch and never refunded, unlike the\n * monthly reservation. The window is one hour and TTL-swept, so an\n * undelivered remainder costs the org the rest of that hour and nothing\n * after it — where an unreconciled MONTHLY claim would cost the rest of the\n * month, which is why that one is reconciled and this one is not. The error\n * is in the direction that can only ever pace mail more, never let more out.\n */\nexport async function claimOrgEmailSendBudget(options: {\n orgId: string\n /** Messages this campaign would send. */\n count: number\n /** The live platform ceiling; the org share is derived from it. */\n platformPerHour: number\n /** False parks the control, exactly as the platform governor's flag does. */\n enabled?: boolean\n now?: number\n firestore?: any\n}): Promise<OrgEmailSendClaimResult> {\n const now = options.now ?? Date.now()\n const windowStartMs = emailSendRateWindowStartMs(now)\n const retryAtMs = windowStartMs + EMAIL_SEND_RATE_WINDOW_MS\n const ceiling = orgHourlyCampaignCeiling(options.platformPerHour)\n const count = Math.max(0, Math.floor(Number(options.count) || 0))\n\n const granted = (used: number, degraded: boolean): OrgEmailSendClaimResult => ({\n allowed: true,\n used,\n ceiling,\n remaining: Math.max(0, ceiling - (used + count)),\n retryAtMs,\n degraded,\n })\n\n // A parked control still reports the ceiling, so a surface reading this\n // shows a real number rather than blanking while the control is off.\n if (options.enabled === false) return granted(0, false)\n // No org means the counter has nowhere to live. The monthly reservation\n // refuses in this case because an unattributable campaign must not send\n // unbounded; here the monthly claim has already made that decision, so\n // failing open costs nothing it has not already been charged for.\n if (!options.orgId) return granted(0, true)\n\n try {\n const firestore = options.firestore ?? firebaseAdmin.app().firestore()\n const ref = firestore\n .collection(RATE_LIMIT_COLLECTION)\n .doc(emailOrgSendRateWindowDocId(windowStartMs, options.orgId))\n\n return await firestore.runTransaction(async (tx: any) => {\n const snapshot = await tx.get(ref)\n const rawUsed = Number((snapshot.exists ? snapshot.get('count') : 0) ?? 0)\n // A corrupt or negative counter must not read as headroom.\n const used =\n Number.isFinite(rawUsed) && rawUsed > 0 ? Math.floor(rawUsed) : 0\n if (used + count > ceiling) {\n // A refused claim writes NOTHING. A campaign that will be retried\n // next hour must not have spent budget on being told no.\n return {\n allowed: false,\n used,\n ceiling,\n remaining: Math.max(0, ceiling - used),\n retryAtMs,\n degraded: false,\n }\n }\n // An absolute write from this transaction's own read, not\n // `FieldValue.increment` — the read is the authority for the decision,\n // and an increment would be atomic on the number while proving nothing\n // about the value the decision was made from.\n tx.set(\n ref,\n {\n count: used + count,\n windowStartMs,\n orgId: options.orgId,\n // NOT `lastAtMs` — the rate-limiter health probe queries this\n // collection on that field and a per-hour document in its range\n // would compete with the markers it exists to find.\n sentAtMs: now,\n expiresAt: new Date(retryAtMs + EMAIL_SEND_RATE_WINDOW_MS),\n },\n { merge: true },\n )\n return granted(used, false)\n })\n } catch (error) {\n console.error('[send-rate] org window unavailable — allowing', error)\n return granted(0, true)\n }\n}\n\n/**\n * The document the console writes when staff move the ramp.\n *\n * Returned rather than written so the route owns the write (and the audit row\n * beside it), and so a spec can assert the SHAPE without a Firestore. The one\n * thing it must never contain is `expiresAt`.\n */\nexport function emailSendRateConfigWrite(input: {\n perHour: number\n enabled: boolean\n actorEmail?: string | null\n note?: string\n now?: number\n}): Partial<EmailSendRateConfig> {\n const normalized = normalizeEmailSendRateConfig({\n perHour: input.perHour,\n enabled: input.enabled,\n note: input.note,\n updatedAtMs: input.now ?? Date.now(),\n updatedByEmail: input.actorEmail ?? null,\n })\n return {\n perHour: normalized.perHour,\n enabled: normalized.enabled,\n note: normalized.note,\n updatedAtMs: normalized.updatedAtMs,\n updatedByEmail: normalized.updatedByEmail,\n }\n}\n\n/**\n * Puts the durable governor on `sendEmail`'s path.\n *\n * **Called at module load**, from the bottom of this file, so that importing\n * `@aglyn/tenant-data-admin` anywhere is enough — every server surface in the\n * product already imports that barrel, and `export *` forces this module to\n * evaluate. The alternative, an `installEmailSendGovernor()` call at each\n * server entrypoint, is the 37-places-to-remember shape this codebase already\n * rejected once for the `context` tag.\n *\n * Idempotent: installing twice replaces the same closure with an equivalent\n * one, and the closure holds no state — the state is the Firestore document.\n */\nexport function installEmailSendGovernor(): void {\n setEmailSendGovernor(\n async (\n request: EmailSendGovernorRequest,\n ): Promise<EmailSendGovernorVerdict> => {\n const result = await consumeEmailSendBudget({\n priority: request.priority,\n count: request.count,\n })\n return {\n allowed: result.allowed,\n ceiling: result.ceiling,\n used: result.used,\n remaining: result.remaining,\n retryAtMs: result.retryAtMs,\n degraded: result.degraded,\n }\n },\n )\n}\n\ninstallEmailSendGovernor()\n"],"names":["EMAIL_SEND_RATE_WINDOW_MS","emailSendRateVerdict","emailSendRateWindowStartMs","normalizeEmailSendRateConfig","orgHourlyCampaignCeiling","setEmailSendGovernor","firebaseAdmin","RATE_LIMIT_COLLECTION","EMAIL_SEND_RATE_CONFIG_DOC","EMAIL_SEND_RATE_WINDOW_PREFIX","emailSendRateWindowDocId","windowStartMs","CONFIG_TTL_MS","configCache","configPending","invalidateEmailSendRateConfigCache","undefined","readEmailSendRateConfig","options","now","Date","firestore","at","config","load","app","snapshot","collection","doc","get","exists","data","then","finally","consumeEmailSendBudget","resetMs","fallback","priority","used","count","ceiling","perHour","enabled","degraded","ref","verdict","runTransaction","tx","Number","decision","allowed","set","Math","max","floor","sentAtMs","expiresAt","merge","error","console","readEmailSendRateWindow","isFinite","EMAIL_ORG_SEND_RATE_WINDOW_PREFIX","emailOrgSendRateWindowDocId","orgId","readOrgEmailSendWindow","claimOrgEmailSendBudget","retryAtMs","platformPerHour","granted","remaining","rawUsed","emailSendRateConfigWrite","input","normalized","note","updatedAtMs","updatedByEmail","actorEmail","installEmailSendGovernor","request","result"],"mappings":"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"}