@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,253 @@
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
+ * RE-CHECKING A VERIFIED SENDING DOMAIN.
19
+ *
20
+ * `verifySendingDomain` runs when an admin presses Verify and never again, so
21
+ * a domain that proved itself once was trusted for as long as the record
22
+ * existed. DNS does not hold still: a customer migrates their zone, prunes an
23
+ * unrecognized TXT record, or lets the domain lapse, and the platform keeps
24
+ * signing mail as a name whose owner has withdrawn permission to.
25
+ *
26
+ * ## The failure this must not become
27
+ *
28
+ * The obvious sweep — re-run the check nightly, write what it says — is worse
29
+ * than no sweep. A DNS lookup cannot tell "the record is gone" from "nobody
30
+ * answered", so the obvious sweep un-verifies every customer at once during a
31
+ * resolver outage and every one of them silently stops being able to mail.
32
+ * That is a platform-wide, self-inflicted outage in exchange for closing a
33
+ * slow leak.
34
+ *
35
+ * Two independent things stop it:
36
+ *
37
+ * 1. **The probe's third outcome.** `assessSendingRecords` answers
38
+ * `inconclusive` unless all three lookups ANSWERED, and `dns-probe.ts` asks
39
+ * public resolvers before the runtime's own precisely so a stale local
40
+ * resolver cannot manufacture a conclusive miss. An inconclusive pass here
41
+ * writes nothing but the check time.
42
+ * 2. **The drift discipline.** Even a conclusive miss is counted rather than
43
+ * acted on, through the same {@link assessDomainDrift} the SSO domain sweep
44
+ * uses. An unattended sweep is not an admin watching a button: nobody sees
45
+ * its verdict, so it needs more evidence than one answer before it costs a
46
+ * customer their sending.
47
+ *
48
+ * ## Where this diverges from the SSO sweep, and why
49
+ *
50
+ * `assessDomainDrift` has no `revoke` — its strongest verdict is `report`,
51
+ * because revoking an SSO domain locks people out of their own account and
52
+ * that is a human's call. Here the same verdict DOES change the status, and
53
+ * the asymmetry is in what the change costs: an un-verified sending domain
54
+ * makes the site's sends REFUSE, and a refusal is recoverable by publishing
55
+ * the record and pressing Verify. It never quietly moves the tenant's mail
56
+ * back onto the shared platform domain, which is the outcome the whole feature
57
+ * exists to prevent and which no amount of drift would justify.
58
+ *
59
+ * ## What it does not do
60
+ *
61
+ * It re-checks `verified` domains only. Walking `records-issued` domains to
62
+ * see whether a customer has published yet is a different job with a different
63
+ * cadence and a different failure mode (an onboarding poller, not a trust
64
+ * expiry), and folding the two together would mean one interval serving
65
+ * neither.
66
+ */ import { assessDomainDrift } from "./sso-drift-logic.js";
67
+ import firebaseAdmin from "./firebase-admin.js";
68
+ import { probeSendingRecords, readSendingDomainRecord, SENDING_DOMAINS_COLLECTION } from "./sending-domains.js";
69
+ /**
70
+ * How stale a verified domain's last check must be before it is re-read.
71
+ *
72
+ * Daily. The thing being detected — a customer editing their zone — happens on
73
+ * a human timescale, and a tighter cadence buys hours of detection for a
74
+ * multiple of the DNS traffic and the write volume. It also sets the floor on
75
+ * how long a genuinely removed record keeps sending: three conclusive misses
76
+ * at a day apart, which the age floor below holds to at least three days.
77
+ */ export const SENDING_DOMAIN_RECHECK_AFTER_MS = 24 * 60 * 60000;
78
+ /**
79
+ * Conclusive failures in a row before a verified domain is un-verified.
80
+ *
81
+ * Three. The evidence for each one is already strong — all three lookups
82
+ * answered, across pinned public resolvers — so this is not compensating for a
83
+ * weak probe. It is compensating for the one failure the probe cannot see: the
84
+ * resolvers agreeing on a wrong answer, which `dns-probe.ts` documents having
85
+ * happened once already with a stale zone.
86
+ */ export const SENDING_DOMAIN_FAILURES_BEFORE_REVOKE = 3;
87
+ /**
88
+ * And the wall-clock floor the same run must clear, independent of the count.
89
+ *
90
+ * Both, for the reason the SSO threshold gives: a count alone can be run up in
91
+ * minutes by a beat firing more often than anyone intended, or by somebody
92
+ * re-running the sweep by hand, and "we checked three times" then reads as
93
+ * diligence while meaning nothing.
94
+ *
95
+ * Three days is what three daily checks ought to take. It is not a long time
96
+ * to keep signing for a domain whose records are gone: once the DKIM record is
97
+ * removed our signature stops validating anyway, so the mail is already
98
+ * failing — un-verifying is how the product stops PRETENDING otherwise, not
99
+ * what stops the bad mail.
100
+ */ export const SENDING_DOMAIN_DRIFT_MIN_AGE_MS = 3 * 24 * 60 * 60000;
101
+ /** Domains one beat will re-check. The rest are picked up on the next one. */ export const SENDING_DOMAIN_RECHECK_BATCH = 25;
102
+ const EMPTY = {
103
+ checked: 0,
104
+ held: 0,
105
+ cleared: 0,
106
+ counted: 0,
107
+ revoked: 0
108
+ };
109
+ /**
110
+ * A probe verdict as the drift assessor's vocabulary.
111
+ *
112
+ * The mapping is the whole safety property of this module written in three
113
+ * lines, which is why it is a named function rather than a ternary inside the
114
+ * loop: `inconclusive` becomes `unreachable`, which `assessDomainDrift` holds
115
+ * on — it neither counts the failure nor clears a run already gathered. An
116
+ * outage must not manufacture evidence, and must not launder away evidence
117
+ * either.
118
+ */ export function driftProbeStatus(verdict) {
119
+ if (verdict === 'inconclusive') return 'unreachable';
120
+ return verdict === 'verified' ? 'proven' : 'missing';
121
+ }
122
+ /**
123
+ * Re-check the verified sending domains whose last check has gone stale.
124
+ *
125
+ * Bounded and idempotent, as every handler on the job beat must be: each pass
126
+ * stamps `lastCheckedAtMs`, which moves the domain to the back of the queue,
127
+ * so the sweep resumes without carrying a cursor and an overlapping beat
128
+ * re-checks at worst a domain that was just checked.
129
+ *
130
+ * Never throws for one bad zone. A single unresolvable domain must not stop
131
+ * the platform's other customers from being checked, and the runner's error
132
+ * isolation is a coarser tool than that — it would lose the rest of the batch.
133
+ */ export async function recheckSendingDomains(options = {}) {
134
+ var _options_nowMs, _options_firestore, _options_recheckAfterMs;
135
+ const nowMs = (_options_nowMs = options.nowMs) != null ? _options_nowMs : Date.now();
136
+ const store = (_options_firestore = options.firestore) != null ? _options_firestore : firebaseAdmin.app().firestore();
137
+ const cutoff = nowMs - ((_options_recheckAfterMs = options.recheckAfterMs) != null ? _options_recheckAfterMs : SENDING_DOMAIN_RECHECK_AFTER_MS);
138
+ /*
139
+ * The staleness bound is IN the query, not applied after it. A beat with
140
+ * nothing due then bills one empty read instead of reading a batch of
141
+ * freshly-checked documents to discard them — the standing rule about reads
142
+ * that nobody asked for, on a path that fires unattended forever.
143
+ *
144
+ * Ordered by the same field the inequality is on, so the least recently
145
+ * checked come first and no domain can be starved by a busier neighbour.
146
+ * Needs the (status, lastCheckedAtMs) collection-group index; without it
147
+ * this throws and the runner isolates the failure.
148
+ *
149
+ * The order also decides which documents are VISIBLE: Firestore drops a
150
+ * document that has no `lastCheckedAtMs` at all. Nothing writes
151
+ * `status: 'verified'` except `verifySendingDomain`, which stamps
152
+ * `lastCheckedAtMs` in the same `set`, so no verified document can lack it —
153
+ * an invariant `sending-domain-recheck.spec.ts` pins, because a future
154
+ * second writer of that status would silently make its domains invisible
155
+ * here rather than failing.
156
+ */ let due;
157
+ try {
158
+ var _options_batch, _ref;
159
+ const snapshot = await store.collectionGroup(SENDING_DOMAINS_COLLECTION).where('status', '==', 'verified').where('lastCheckedAtMs', '<', cutoff).orderBy('lastCheckedAtMs', 'asc').limit((_options_batch = options.batch) != null ? _options_batch : SENDING_DOMAIN_RECHECK_BATCH).get();
160
+ due = (_ref = snapshot == null ? void 0 : snapshot.docs) != null ? _ref : [];
161
+ } catch (error) {
162
+ // A missing index reads exactly like this. Reported and returned empty
163
+ // rather than thrown: the runner's isolation would be the same outcome
164
+ // with a less specific log line.
165
+ console.error('[sending-domains] re-check query failed', error);
166
+ return _extends({}, EMPTY);
167
+ }
168
+ const summary = _extends({}, EMPTY);
169
+ for (const doc of due){
170
+ try {
171
+ summary.checked += 1;
172
+ await recheckOneDomain(doc, nowMs, options, summary);
173
+ } catch (error) {
174
+ var _ref1;
175
+ // Counted as checked and not as anything else: the domain keeps its
176
+ // status and its failure run, and the next beat tries again.
177
+ console.error(`[sending-domains] re-check failed for ${(_ref1 = doc == null ? void 0 : doc.id) != null ? _ref1 : 'unknown'}`, error);
178
+ }
179
+ }
180
+ return summary;
181
+ }
182
+ async function recheckOneDomain(doc, nowMs, options, summary) {
183
+ var _ref, _options_failuresBeforeRevoke, _options_minAgeMs;
184
+ const record = readSendingDomainRecord(doc);
185
+ if (!record) return;
186
+ const verdict = await probeSendingRecords(record);
187
+ const data = (_ref = doc.data == null ? void 0 : doc.data.call(doc)) != null ? _ref : {};
188
+ const drift = assessDomainDrift({
189
+ status: driftProbeStatus(verdict.status),
190
+ records: []
191
+ }, {
192
+ consecutiveFailures: Number(data.recheckFailures) || 0,
193
+ firstFailureAtMs: Number(data.recheckFirstFailureAtMs) || null
194
+ }, nowMs, (_options_failuresBeforeRevoke = options.failuresBeforeRevoke) != null ? _options_failuresBeforeRevoke : SENDING_DOMAIN_FAILURES_BEFORE_REVOKE, (_options_minAgeMs = options.minAgeMs) != null ? _options_minAgeMs : SENDING_DOMAIN_DRIFT_MIN_AGE_MS);
195
+ const del = firebaseAdmin.firestore.FieldValue.delete();
196
+ if (drift.action === 'hold') {
197
+ // The check happened, so the timestamp moves; nothing else does. Moving it
198
+ // is what keeps an unreachable domain from monopolizing every batch while
199
+ // a resolver is down, and it costs at most one delayed re-check.
200
+ summary.held += 1;
201
+ await doc.ref.set({
202
+ lastCheckedAtMs: nowMs
203
+ }, {
204
+ merge: true
205
+ });
206
+ return;
207
+ }
208
+ if (drift.action === 'clear') {
209
+ summary.cleared += 1;
210
+ await doc.ref.set({
211
+ lastCheckedAtMs: nowMs,
212
+ // A run that ended leaves no trace. Keeping a stale count would make
213
+ // the NEXT unrelated failure the third one.
214
+ recheckFailures: del,
215
+ recheckFirstFailureAtMs: del,
216
+ lastMissing: del
217
+ }, {
218
+ merge: true
219
+ });
220
+ return;
221
+ }
222
+ if (drift.action === 'count') {
223
+ summary.counted += 1;
224
+ await doc.ref.set({
225
+ lastCheckedAtMs: nowMs,
226
+ recheckFailures: drift.consecutiveFailures,
227
+ recheckFirstFailureAtMs: drift.firstFailureAtMs,
228
+ // Recorded while the domain is still verified and still sending, so
229
+ // the console can say WHICH record went missing before the deadline
230
+ // rather than only after it.
231
+ lastMissing: verdict.missing
232
+ }, {
233
+ merge: true
234
+ });
235
+ return;
236
+ }
237
+ summary.revoked += 1;
238
+ await doc.ref.set({
239
+ status: 'failed',
240
+ lastCheckedAtMs: nowMs,
241
+ lastMissing: verdict.missing,
242
+ // The run is spent. A domain that is re-verified and drifts again starts
243
+ // its own count rather than inheriting this one.
244
+ recheckFailures: del,
245
+ recheckFirstFailureAtMs: del
246
+ }, {
247
+ merge: true
248
+ });
249
+ console.warn(`[sending-domains] un-verified ${record.domain}: ${verdict.missing.join(', ')}`);
250
+ }
251
+ export default recheckSendingDomains;
252
+
253
+ //# sourceMappingURL=sending-domain-recheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/sending-domain-recheck.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 * RE-CHECKING A VERIFIED SENDING DOMAIN.\n *\n * `verifySendingDomain` runs when an admin presses Verify and never again, so\n * a domain that proved itself once was trusted for as long as the record\n * existed. DNS does not hold still: a customer migrates their zone, prunes an\n * unrecognized TXT record, or lets the domain lapse, and the platform keeps\n * signing mail as a name whose owner has withdrawn permission to.\n *\n * ## The failure this must not become\n *\n * The obvious sweep — re-run the check nightly, write what it says — is worse\n * than no sweep. A DNS lookup cannot tell \"the record is gone\" from \"nobody\n * answered\", so the obvious sweep un-verifies every customer at once during a\n * resolver outage and every one of them silently stops being able to mail.\n * That is a platform-wide, self-inflicted outage in exchange for closing a\n * slow leak.\n *\n * Two independent things stop it:\n *\n * 1. **The probe's third outcome.** `assessSendingRecords` answers\n * `inconclusive` unless all three lookups ANSWERED, and `dns-probe.ts` asks\n * public resolvers before the runtime's own precisely so a stale local\n * resolver cannot manufacture a conclusive miss. An inconclusive pass here\n * writes nothing but the check time.\n * 2. **The drift discipline.** Even a conclusive miss is counted rather than\n * acted on, through the same {@link assessDomainDrift} the SSO domain sweep\n * uses. An unattended sweep is not an admin watching a button: nobody sees\n * its verdict, so it needs more evidence than one answer before it costs a\n * customer their sending.\n *\n * ## Where this diverges from the SSO sweep, and why\n *\n * `assessDomainDrift` has no `revoke` — its strongest verdict is `report`,\n * because revoking an SSO domain locks people out of their own account and\n * that is a human's call. Here the same verdict DOES change the status, and\n * the asymmetry is in what the change costs: an un-verified sending domain\n * makes the site's sends REFUSE, and a refusal is recoverable by publishing\n * the record and pressing Verify. It never quietly moves the tenant's mail\n * back onto the shared platform domain, which is the outcome the whole feature\n * exists to prevent and which no amount of drift would justify.\n *\n * ## What it does not do\n *\n * It re-checks `verified` domains only. Walking `records-issued` domains to\n * see whether a customer has published yet is a different job with a different\n * cadence and a different failure mode (an onboarding poller, not a trust\n * expiry), and folding the two together would mean one interval serving\n * neither.\n */\n\nimport { assessDomainDrift, type DomainProbeStatus } from './sso-drift-logic'\nimport firebaseAdmin from './firebase-admin'\nimport {\n probeSendingRecords,\n readSendingDomainRecord,\n SENDING_DOMAINS_COLLECTION,\n} from './sending-domains'\n\n/**\n * How stale a verified domain's last check must be before it is re-read.\n *\n * Daily. The thing being detected — a customer editing their zone — happens on\n * a human timescale, and a tighter cadence buys hours of detection for a\n * multiple of the DNS traffic and the write volume. It also sets the floor on\n * how long a genuinely removed record keeps sending: three conclusive misses\n * at a day apart, which the age floor below holds to at least three days.\n */\nexport const SENDING_DOMAIN_RECHECK_AFTER_MS = 24 * 60 * 60_000\n\n/**\n * Conclusive failures in a row before a verified domain is un-verified.\n *\n * Three. The evidence for each one is already strong — all three lookups\n * answered, across pinned public resolvers — so this is not compensating for a\n * weak probe. It is compensating for the one failure the probe cannot see: the\n * resolvers agreeing on a wrong answer, which `dns-probe.ts` documents having\n * happened once already with a stale zone.\n */\nexport const SENDING_DOMAIN_FAILURES_BEFORE_REVOKE = 3\n\n/**\n * And the wall-clock floor the same run must clear, independent of the count.\n *\n * Both, for the reason the SSO threshold gives: a count alone can be run up in\n * minutes by a beat firing more often than anyone intended, or by somebody\n * re-running the sweep by hand, and \"we checked three times\" then reads as\n * diligence while meaning nothing.\n *\n * Three days is what three daily checks ought to take. It is not a long time\n * to keep signing for a domain whose records are gone: once the DKIM record is\n * removed our signature stops validating anyway, so the mail is already\n * failing — un-verifying is how the product stops PRETENDING otherwise, not\n * what stops the bad mail.\n */\nexport const SENDING_DOMAIN_DRIFT_MIN_AGE_MS = 3 * 24 * 60 * 60_000\n\n/** Domains one beat will re-check. The rest are picked up on the next one. */\nexport const SENDING_DOMAIN_RECHECK_BATCH = 25\n\n/** What one pass settled. */\nexport interface SendingDomainRecheckSummary {\n /** Domains probed. */\n checked: number\n /** Probes that established nothing, and changed nothing. */\n held: number\n /** Domains still publishing their records; any failure run ended. */\n cleared: number\n /** Conclusive misses recorded, without acting on them yet. */\n counted: number\n /** Domains moved out of `verified`. */\n revoked: number\n}\n\nconst EMPTY: SendingDomainRecheckSummary = {\n checked: 0,\n held: 0,\n cleared: 0,\n counted: 0,\n revoked: 0,\n}\n\n/**\n * A probe verdict as the drift assessor's vocabulary.\n *\n * The mapping is the whole safety property of this module written in three\n * lines, which is why it is a named function rather than a ternary inside the\n * loop: `inconclusive` becomes `unreachable`, which `assessDomainDrift` holds\n * on — it neither counts the failure nor clears a run already gathered. An\n * outage must not manufacture evidence, and must not launder away evidence\n * either.\n */\nexport function driftProbeStatus(\n verdict: 'verified' | 'failed' | 'inconclusive',\n): DomainProbeStatus {\n if (verdict === 'inconclusive') return 'unreachable'\n return verdict === 'verified' ? 'proven' : 'missing'\n}\n\nexport interface SendingDomainRecheckOptions {\n nowMs?: number\n /** Injectable for tests; defaults to the Admin SDK's Firestore. */\n firestore?: any\n batch?: number\n recheckAfterMs?: number\n failuresBeforeRevoke?: number\n minAgeMs?: number\n}\n\n/**\n * Re-check the verified sending domains whose last check has gone stale.\n *\n * Bounded and idempotent, as every handler on the job beat must be: each pass\n * stamps `lastCheckedAtMs`, which moves the domain to the back of the queue,\n * so the sweep resumes without carrying a cursor and an overlapping beat\n * re-checks at worst a domain that was just checked.\n *\n * Never throws for one bad zone. A single unresolvable domain must not stop\n * the platform's other customers from being checked, and the runner's error\n * isolation is a coarser tool than that — it would lose the rest of the batch.\n */\nexport async function recheckSendingDomains(\n options: SendingDomainRecheckOptions = {},\n): Promise<SendingDomainRecheckSummary> {\n const nowMs = options.nowMs ?? Date.now()\n const store = options.firestore ?? firebaseAdmin.app().firestore()\n const cutoff =\n nowMs - (options.recheckAfterMs ?? SENDING_DOMAIN_RECHECK_AFTER_MS)\n\n /*\n * The staleness bound is IN the query, not applied after it. A beat with\n * nothing due then bills one empty read instead of reading a batch of\n * freshly-checked documents to discard them — the standing rule about reads\n * that nobody asked for, on a path that fires unattended forever.\n *\n * Ordered by the same field the inequality is on, so the least recently\n * checked come first and no domain can be starved by a busier neighbour.\n * Needs the (status, lastCheckedAtMs) collection-group index; without it\n * this throws and the runner isolates the failure.\n *\n * The order also decides which documents are VISIBLE: Firestore drops a\n * document that has no `lastCheckedAtMs` at all. Nothing writes\n * `status: 'verified'` except `verifySendingDomain`, which stamps\n * `lastCheckedAtMs` in the same `set`, so no verified document can lack it —\n * an invariant `sending-domain-recheck.spec.ts` pins, because a future\n * second writer of that status would silently make its domains invisible\n * here rather than failing.\n */\n let due: any[]\n try {\n const snapshot = await store\n .collectionGroup(SENDING_DOMAINS_COLLECTION)\n .where('status', '==', 'verified')\n .where('lastCheckedAtMs', '<', cutoff)\n .orderBy('lastCheckedAtMs', 'asc')\n .limit(options.batch ?? SENDING_DOMAIN_RECHECK_BATCH)\n .get()\n due = snapshot?.docs ?? []\n } catch (error) {\n // A missing index reads exactly like this. Reported and returned empty\n // rather than thrown: the runner's isolation would be the same outcome\n // with a less specific log line.\n console.error('[sending-domains] re-check query failed', error)\n return { ...EMPTY }\n }\n\n const summary: SendingDomainRecheckSummary = { ...EMPTY }\n for (const doc of due) {\n try {\n summary.checked += 1\n await recheckOneDomain(doc, nowMs, options, summary)\n } catch (error) {\n // Counted as checked and not as anything else: the domain keeps its\n // status and its failure run, and the next beat tries again.\n console.error(\n `[sending-domains] re-check failed for ${doc?.id ?? 'unknown'}`,\n error,\n )\n }\n }\n return summary\n}\n\nasync function recheckOneDomain(\n doc: any,\n nowMs: number,\n options: SendingDomainRecheckOptions,\n summary: SendingDomainRecheckSummary,\n): Promise<void> {\n const record = readSendingDomainRecord(doc)\n if (!record) return\n\n const verdict = await probeSendingRecords(record)\n const data = doc.data?.() ?? {}\n const drift = assessDomainDrift(\n { status: driftProbeStatus(verdict.status), records: [] },\n {\n consecutiveFailures: Number(data.recheckFailures) || 0,\n firstFailureAtMs: Number(data.recheckFirstFailureAtMs) || null,\n },\n nowMs,\n options.failuresBeforeRevoke ?? SENDING_DOMAIN_FAILURES_BEFORE_REVOKE,\n options.minAgeMs ?? SENDING_DOMAIN_DRIFT_MIN_AGE_MS,\n )\n\n const del = firebaseAdmin.firestore.FieldValue.delete()\n\n if (drift.action === 'hold') {\n // The check happened, so the timestamp moves; nothing else does. Moving it\n // is what keeps an unreachable domain from monopolizing every batch while\n // a resolver is down, and it costs at most one delayed re-check.\n summary.held += 1\n await doc.ref.set({ lastCheckedAtMs: nowMs }, { merge: true })\n return\n }\n\n if (drift.action === 'clear') {\n summary.cleared += 1\n await doc.ref.set(\n {\n lastCheckedAtMs: nowMs,\n // A run that ended leaves no trace. Keeping a stale count would make\n // the NEXT unrelated failure the third one.\n recheckFailures: del,\n recheckFirstFailureAtMs: del,\n lastMissing: del,\n },\n { merge: true },\n )\n return\n }\n\n if (drift.action === 'count') {\n summary.counted += 1\n await doc.ref.set(\n {\n lastCheckedAtMs: nowMs,\n recheckFailures: drift.consecutiveFailures,\n recheckFirstFailureAtMs: drift.firstFailureAtMs,\n // Recorded while the domain is still verified and still sending, so\n // the console can say WHICH record went missing before the deadline\n // rather than only after it.\n lastMissing: verdict.missing,\n },\n { merge: true },\n )\n return\n }\n\n summary.revoked += 1\n await doc.ref.set(\n {\n status: 'failed',\n lastCheckedAtMs: nowMs,\n lastMissing: verdict.missing,\n // The run is spent. A domain that is re-verified and drifts again starts\n // its own count rather than inheriting this one.\n recheckFailures: del,\n recheckFirstFailureAtMs: del,\n },\n { merge: true },\n )\n console.warn(\n `[sending-domains] un-verified ${record.domain}: ${verdict.missing.join(', ')}`,\n )\n}\n\nexport default recheckSendingDomains\n"],"names":["assessDomainDrift","firebaseAdmin","probeSendingRecords","readSendingDomainRecord","SENDING_DOMAINS_COLLECTION","SENDING_DOMAIN_RECHECK_AFTER_MS","SENDING_DOMAIN_FAILURES_BEFORE_REVOKE","SENDING_DOMAIN_DRIFT_MIN_AGE_MS","SENDING_DOMAIN_RECHECK_BATCH","EMPTY","checked","held","cleared","counted","revoked","driftProbeStatus","verdict","recheckSendingDomains","options","nowMs","Date","now","store","firestore","app","cutoff","recheckAfterMs","due","snapshot","collectionGroup","where","orderBy","limit","batch","get","docs","error","console","summary","doc","recheckOneDomain","id","record","data","drift","status","records","consecutiveFailures","Number","recheckFailures","firstFailureAtMs","recheckFirstFailureAtMs","failuresBeforeRevoke","minAgeMs","del","FieldValue","delete","action","ref","set","lastCheckedAtMs","merge","lastMissing","missing","warn","domain","join"],"mappings":"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"}
@@ -0,0 +1,364 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * CUSTOM SENDING DOMAINS — durable half.
19
+ *
20
+ * The record, the DNS lookups and the status transitions. The decisions all
21
+ * live in `@aglyn/shared-util-email`'s `sending-domain.ts`, which is pure;
22
+ * this module is the I/O around them, and it holds no policy of its own.
23
+ *
24
+ * ## Per-org record, per-host selection
25
+ *
26
+ * The record is at `orgs/{orgId}/sendingDomains/{domain}`, alongside
27
+ * `orgs/{orgId}/ssoDomains/{domain}` and for the same reason: proving control
28
+ * of a zone is a property of the ORG that proved it, and an agency running
29
+ * four sites on `client.com` should publish the DKIM record once rather than
30
+ * four times.
31
+ *
32
+ * Which identity a given site SENDS on is a separate per-host field,
33
+ * `hosts/{hostId}.sendingDomain`. That split is what answers the two halves of
34
+ * the question at once: per-org verification keeps the DNS chore down, and a
35
+ * per-host selection is what an agency's client actually wants, because the
36
+ * `From:` their recipients see belongs to the site, not to the agency.
37
+ *
38
+ * ## Reads are cheap on purpose
39
+ *
40
+ * `resolveHostSendingIdentity` is on the path of a campaign send, so it reads
41
+ * the host document the caller already has and at most ONE org subcollection
42
+ * document. It never scans, never lists, and never touches DNS — the standing
43
+ * rule against unrequested reads on a hot path applies here more than most,
44
+ * because a campaign resolves an identity once for thousands of messages.
45
+ *
46
+ * ## Why the provider call is not in here
47
+ *
48
+ * Issuing a DKIM key means creating a domain at the mail provider, which needs
49
+ * a credential that can create things — a different one from the send-only
50
+ * `RESEND_API_KEY`, which is exactly why `email-health.ts` can use the domains
51
+ * endpoint as a read-only credential probe.
52
+ *
53
+ * That credential belongs to the console alone, so the driver that reads it
54
+ * lives in the console app — `apps/console/utils/server/` — and not here.
55
+ * **This library is imported by the tenant runtime**, which
56
+ * serves untrusted published sites; a module it can import is a module whose
57
+ * environment read is one bug away from being reachable from a site request.
58
+ * A file in `apps/console` is not importable from the tenant app at all —
59
+ * there is no path mapping to it and nx's module boundaries forbid app→app —
60
+ * so the isolation is structural rather than a convention.
61
+ *
62
+ * What stays here is the seam: {@link recordIssuedSendingDomain} takes what a
63
+ * provider returned, and {@link recordSendingDomainIssueFailure} takes what it
64
+ * refused. `requestSendingDomain` still stops at `requested`, and a domain
65
+ * stopped there refuses sends, which is the correct behavior for a domain that
66
+ * has no signing key.
67
+ */
68
+ import { type DmarcAssessment, type SendingDnsRecord, type SendingDomainRecord, type SendingIdentityPurpose, type SendingIdentityVerdict, type SendingVerification } from '@aglyn/shared-util-email';
69
+ /** Subcollection under the owning org. */
70
+ export declare const SENDING_DOMAINS_COLLECTION = "sendingDomains";
71
+ /**
72
+ * The DKIM selector issued to one org for one domain.
73
+ *
74
+ * Includes the org id so two orgs verifying the same name occupy different
75
+ * record names. Without that they would share `resend._domainkey.<domain>`,
76
+ * and whichever verified second would overwrite the first — or, worse, inherit
77
+ * the first's verification without ever publishing anything.
78
+ */
79
+ export declare function sendingDkimSelector(orgId: string): string;
80
+ /** Both keys always present, one always null — `strictNullChecks` is off. */
81
+ export interface SendingDomainResult {
82
+ record: SendingDomainRecord | null;
83
+ error: string | null;
84
+ /** The HTTP status a route should answer with. */
85
+ status: number;
86
+ }
87
+ /**
88
+ * One stored document as a record.
89
+ *
90
+ * Exported so the re-check sweep, which reaches these documents through a
91
+ * collection-group query rather than by org and name, shapes them the same way
92
+ * every other reader here does — a second reader with its own defaults is how
93
+ * two callers come to disagree about what an absent field means.
94
+ */
95
+ export declare function readSendingDomainRecord(snapshot: FirebaseFirestore.DocumentSnapshot): SendingDomainRecord | null;
96
+ /**
97
+ * Start (or re-read) a claim on a sending domain.
98
+ *
99
+ * Idempotent, and deliberately non-destructive on a re-request: an existing
100
+ * record keeps its selector and its key, because reissuing either would
101
+ * invalidate a record the customer may already have published and turn a
102
+ * working setup into a mysterious failure. This mirrors `issueDomainClaim`.
103
+ */
104
+ export declare function requestSendingDomain(options: {
105
+ orgId: string;
106
+ domain: string;
107
+ }): Promise<SendingDomainResult>;
108
+ /**
109
+ * Record the key the mail provider issued, moving the domain to
110
+ * `records-issued` so the customer has something to publish.
111
+ *
112
+ * Separated from {@link requestSendingDomain} because it needs a credential
113
+ * that may not exist. Nothing here calls the provider: the caller supplies
114
+ * what it was given, so a deployment whose key cannot create domains still has
115
+ * a working path — an operator can complete this step by hand — and this
116
+ * module never grows a dependency on a credential it cannot assume. The
117
+ * console holds the credential and the driver that uses it; this library,
118
+ * which the tenant runtime also imports, holds neither.
119
+ *
120
+ * ## `records-issued` is a promise that records exist
121
+ *
122
+ * The write is refused unless the resulting record actually yields a DKIM
123
+ * record with a value. The alternative is the failure this whole feature is
124
+ * arranged against: a status saying the customer has records to publish, next
125
+ * to a records table with an empty DKIM row, which cannot ever verify and
126
+ * reads to the customer as our bug.
127
+ *
128
+ * ## An issued key is never overwritten
129
+ *
130
+ * A second call carrying a DIFFERENT key is refused rather than applied. The
131
+ * customer may already have published the first one, and replacing it turns a
132
+ * finished setup into a domain that silently stops signing. Re-recording the
133
+ * SAME key is a no-op and succeeds, so a retried request is safe.
134
+ *
135
+ * The SELECTOR, by contrast, comes from the provider when it supplies one:
136
+ * the record a customer publishes has to be the name the provider will sign
137
+ * under, and `sendingDkimSelector` only proposes it.
138
+ */
139
+ export declare function recordIssuedSendingDomain(options: {
140
+ orgId: string;
141
+ domain: string;
142
+ dkimPublicKey: string;
143
+ /** The selector the provider issued, when it chose its own. */
144
+ dkimSelector?: string;
145
+ returnPathHost?: string;
146
+ providerDomainId?: string;
147
+ /**
148
+ * The provider's tracking host, when it issued one — see
149
+ * `SendingDomainRecord.trackingTarget`. Absent leaves the domain sending
150
+ * and unmeasured rather than unsent.
151
+ */
152
+ trackingTarget?: string | null;
153
+ }): Promise<SendingDomainResult>;
154
+ /**
155
+ * Record that the provider did not issue anything, WITHOUT moving the status.
156
+ *
157
+ * The point of a separate function is that there is no path from a provider
158
+ * failure to `records-issued`. A `4xx` or `5xx` means no key exists, so the
159
+ * domain stays `requested` — where it refuses sends, which is correct for a
160
+ * domain that cannot sign — and carries a reason an admin can act on instead
161
+ * of appearing to have finished.
162
+ *
163
+ * `detail` is a short code the caller built from a fixed vocabulary, never a
164
+ * provider's response body. {@link safeProviderDetail} is the second line:
165
+ * an `Authorization` header echoed back by a provider must not become a
166
+ * Firestore document.
167
+ */
168
+ export declare function recordSendingDomainIssueFailure(options: {
169
+ orgId: string;
170
+ domain: string;
171
+ detail: string;
172
+ }): Promise<void>;
173
+ export interface SendingDomainView {
174
+ record: SendingDomainRecord;
175
+ /** Exactly what the customer must publish. */
176
+ records: SendingDnsRecord[];
177
+ /** Their DMARC policy, read and never written. */
178
+ dmarc: DmarcAssessment | null;
179
+ }
180
+ /** One domain plus the records it needs, for a surface that shows both. */
181
+ export declare function getSendingDomain(orgId: string, rawDomain: string): Promise<SendingDomainView | null>;
182
+ export declare function listSendingDomains(orgId: string): Promise<SendingDomainRecord[]>;
183
+ /**
184
+ * Read the customer's DMARC policy.
185
+ *
186
+ * Advisory in both directions: it never blocks verification and it is never
187
+ * written. A domain under `p=reject` with our DKIM unpublished hard-fails
188
+ * every message, and a customer deserves to be told that before they wonder
189
+ * why their campaign vanished.
190
+ */
191
+ export declare function readDmarcPolicy(rawDomain: string): Promise<DmarcAssessment | null>;
192
+ /**
193
+ * Start (or read) the hold that keeps a tracked domain's links alive.
194
+ *
195
+ * Stamped on FIRST sighting rather than computed from an orphan timestamp,
196
+ * because there is no reliable one: a label reassignment leaves no record of
197
+ * when it happened, and the reaper re-infers the orphan on every pass. The
198
+ * stamp is the record of when the hold began, so a domain cannot be held
199
+ * forever by a sweep that keeps starting the clock over.
200
+ *
201
+ * @returns the instant the domain may be released, or `null` when there is
202
+ * nothing to stamp it on — a domain with no org record cannot be
203
+ * held, and refusing to release it on the strength of a hold nobody
204
+ * can see would strand a provider slot permanently.
205
+ */
206
+ export declare function holdTrackedSendingDomain(options: {
207
+ orgId: string | null;
208
+ domain: string;
209
+ nowMs: number;
210
+ windowMs: number;
211
+ }): Promise<number | null>;
212
+ /**
213
+ * Whether this domain publishes a CAA policy that would REFUSE the tracking
214
+ * host's certificate.
215
+ *
216
+ * The question decides whether a customer is shown a CAA record at all, and
217
+ * asking it is the difference between guidance and a footgun. CAA restricts
218
+ * which authorities may issue, and the lookup stops at the first name in the
219
+ * tree publishing any record — so:
220
+ *
221
+ * - A domain publishing NOTHING needs nothing. Any authority may already
222
+ * issue, and handing over a CAA record to paste would be the change that
223
+ * STARTS restricting them, breaking whatever else renews on that name.
224
+ * - A domain that publishes some, none of which name our authority, has to
225
+ * add one ALONGSIDE what it has.
226
+ * - A domain that already names our authority is done.
227
+ *
228
+ * Walks up from the tracking host exactly as a certificate authority does,
229
+ * and stops where a real lookup stops: at the first name with any record.
230
+ *
231
+ * @returns `null` when DNS could not be reached, which is not "they publish
232
+ * none" — telling a customer to add a CAA on the strength of an
233
+ * unanswered lookup is how a working zone gets narrowed by mistake.
234
+ */
235
+ export declare function readTrackingCaaNeed(rawDomain: string, authority: string): Promise<'not-needed' | 'must-add' | 'satisfied' | null>;
236
+ /**
237
+ * Read the live DNS for one record and say what it establishes. Writes
238
+ * nothing.
239
+ *
240
+ * Split out so the two callers that need this answer ask the SAME question of
241
+ * the SAME resolvers: {@link verifySendingDomain}, which acts on it
242
+ * immediately, and the unattended re-check sweep, which counts conclusive
243
+ * failures before it acts. A sweep that assessed the records its own way would
244
+ * be a second opinion on "are these published", and the customer would meet
245
+ * whichever one happened to run last.
246
+ *
247
+ * The three lookups run together because they are independent and the caller
248
+ * is waiting on the slowest either way.
249
+ */
250
+ export declare function probeSendingRecords(record: SendingDomainRecord): Promise<SendingVerification>;
251
+ /**
252
+ * Check the live DNS and move the record to `verified` or `failed`.
253
+ *
254
+ * Never throws: a sweep across every org must not stop at the first bad zone.
255
+ *
256
+ * An INCONCLUSIVE result writes nothing but the check time. A resolver outage
257
+ * must not un-verify a working domain — which would silently stop that
258
+ * tenant's mail — and must not fail a customer who is midway through
259
+ * publishing either. This is the `hold` arm the SSO drift sweep uses, and the
260
+ * reason `assessSendingRecords` has three outcomes rather than two.
261
+ *
262
+ * Acts on ONE conclusive answer, and that is right for this caller: an admin
263
+ * pressed Verify and is watching the result, so the check is attended and its
264
+ * outcome is immediately visible and immediately retryable. The unattended
265
+ * sweep over already-verified domains is the caller that cannot say that, and
266
+ * `sending-domain-recheck.ts` is where the extra evidence it needs is
267
+ * gathered.
268
+ */
269
+ export declare function verifySendingDomain(orgId: string, rawDomain: string): Promise<{
270
+ record: SendingDomainRecord | null;
271
+ missing: string[];
272
+ inconclusive: boolean;
273
+ error: string | null;
274
+ }>;
275
+ /**
276
+ * Drop a claim.
277
+ *
278
+ * The host selections pointing at it are NOT rewritten to the platform
279
+ * identity. A site that was sending as its own domain and now has no verified
280
+ * one must refuse, not quietly revert — silently moving a tenant's mail back
281
+ * onto the shared domain is the exact fallback this feature exists to prevent,
282
+ * and doing it during a delete would be no better than doing it during a send.
283
+ */
284
+ export declare function releaseSendingDomain(orgId: string, rawDomain: string): Promise<void>;
285
+ /**
286
+ * The identity one host sends on, ready to hand to `sendEmail`.
287
+ *
288
+ * Two document reads at most, and none at all for a host that has no
289
+ * selection. The host document is passed in rather than re-fetched because
290
+ * every caller already holds it.
291
+ *
292
+ * ## This function cannot return an `aglyn.com` address, and that is the point
293
+ *
294
+ * Every caller here is resolving mail FOR A SITE — that is what "host" in the
295
+ * name means — so every verdict it produces is tenant mail by construction.
296
+ * `audience: 'tenant'` is therefore passed unconditionally and is not a
297
+ * parameter: a caller that could choose would eventually choose wrong, and the
298
+ * wrong choice puts a merchant's list quality onto the domain the platform's
299
+ * own invoices and password resets leave on.
300
+ *
301
+ * A site that has selected NOTHING does not reach `USAGE_EMAIL_FROM` either.
302
+ * All of its mail goes on the shared pool member `hostSharedFrom` assigns it,
303
+ * marketing included. What keeps the pool usable is measurement rather than
304
+ * prohibition: a pooled campaign is graded against the stricter reputation
305
+ * thresholds, so the site earning complaints is the one that stops.
306
+ */
307
+ export declare function resolveHostSendingIdentity(options: {
308
+ orgId: string | null | undefined;
309
+ /** `hosts/{hostId}.sendingDomain`, the site's selection. */
310
+ selectedDomain: string | null | undefined;
311
+ /** `hosts/{hostId}.sendingLocalPart`, defaulted when unset. */
312
+ selectedLocalPart?: string | null;
313
+ platformFrom?: string | null;
314
+ /**
315
+ * The site, so an unselected host can be assigned a pool member.
316
+ *
317
+ * Optional because two of the three callers resolve an identity for a host
318
+ * they are already holding and one — the console's identity view — is asking
319
+ * about a specific site by id. A caller that omits it gets no shared
320
+ * identity, which is the honest answer: the pool assignment is per site, and
321
+ * "some pool member" is not a thing to send a receipt from.
322
+ */
323
+ hostId?: string | null;
324
+ /**
325
+ * A pool member this host is pinned to, overriding the hash.
326
+ *
327
+ * `hosts/{hostId}.sendingPoolMember`. The QUARANTINE lever: a site whose
328
+ * transactional mail is generating complaints can be moved onto a member set
329
+ * aside for it, without disturbing any of the other sites that hash to the
330
+ * member it was on. Ignored unless it names a real member of the current
331
+ * pool, so a stale or hand-edited value degrades to the ordinary assignment
332
+ * rather than to an address nothing signs for.
333
+ */
334
+ poolMember?: string | null;
335
+ /** See `SendingIdentityPurpose`. Defaults to transactional. */
336
+ purpose?: SendingIdentityPurpose;
337
+ }): Promise<SendingIdentityVerdict>;
338
+ /**
339
+ * The identity one site sends on, from a `hostId` alone.
340
+ *
341
+ * The door every tenant sender uses. {@link resolveHostSendingIdentity} needs
342
+ * the org id and the host's two selection fields, and a survey of the tenant
343
+ * send sites found that all of them hold a `hostId` while only three hold an
344
+ * org id and half never read the host document at all. Asking nineteen call
345
+ * sites to each assemble the same three values is the shape that produces a
346
+ * twentieth which does not — and the cost of forgetting here is a message
347
+ * leaving on the platform's own domain.
348
+ *
349
+ * ## The cache is per-call-graph, not per-process
350
+ *
351
+ * Passed a `cache`, repeated resolutions for one host cost one pair of reads.
352
+ * A sweep that mails two hundred abandoned carts across a dozen sites, or a
353
+ * webhook that sends a receipt and a seller notice for the same order, holds
354
+ * one map for the run and pays for each site once.
355
+ *
356
+ * Deliberately NOT a module-level cache. Verification status is exactly the
357
+ * thing that changes underneath a long-lived process — the re-check sweep
358
+ * un-verifies a domain whose records have gone — and a stale entry would keep
359
+ * a site sending on a domain that no longer authenticates. A caller that
360
+ * declares a cache has also declared how long it may be trusted.
361
+ */
362
+ export declare function hostSendingIdentity(hostId: string | null | undefined, cache?: Map<string, SendingIdentityVerdict>): Promise<SendingIdentityVerdict>;
363
+ /** The record keys a surface highlights as outstanding. */
364
+ export declare function outstandingSendingRecords(record: SendingDomainRecord): SendingDnsRecord[];