@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,118 @@
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
+ * Keep the media bucket's upload CORS in step with the names the console is
19
+ * SERVED on (AGL-1452).
20
+ *
21
+ * ## Why this is automation rather than a checklist line
22
+ *
23
+ * GCS matches the CORS `origin` list as an exact string — no subtree form —
24
+ * so every serving console name needs its own entry, and a missing one fails
25
+ * only on files large enough to take the signed direct-to-GCS path, behind a
26
+ * generic "try again" snackbar. Nothing about the symptom points at bucket
27
+ * configuration, and nothing at attach time pointed at it either.
28
+ *
29
+ * That was already costing us, not merely threatening to. Driving real
30
+ * preflights against `gs://aglyn-main.appspot.com` on 2026-08-20: of the
31
+ * console project's seventeen attached names, exactly one —
32
+ * `https://app.aglyn.com` — was permitted, while five names that serve the
33
+ * console at 200 (`zgover.aglyn.com`, `test-org.aglyn.com`,
34
+ * `aglyn-org.aglyn.com`, `sale-test.aglyn.com`, `zachary1748.aglyn.com`) were
35
+ * refused. AGL-1452 recorded workspace subdomains as latent because none were
36
+ * attached at the time; they are attached now.
37
+ *
38
+ * ## Serving names only
39
+ *
40
+ * A name attached as a REDIRECT never becomes a browser origin — the browser
41
+ * is already at the redirect target before it uploads — so it needs no entry.
42
+ * Measured: `console.aglyn.com` 308s and `app.aglyn.io` 307s to
43
+ * `app.aglyn.com`, and neither is in the CORS list, and neither is broken.
44
+ * `attachProjectDomain` therefore calls this only for a name it attached
45
+ * WITHOUT a redirect.
46
+ *
47
+ * ## When it cannot
48
+ *
49
+ * The runtime service account may not hold `storage.buckets.update`. That is
50
+ * a real and likely outcome, and the one thing this must not do about it is
51
+ * be quiet: the verdict carries `permitted: false` and the exact ordered
52
+ * read-modify-write command, so the failure is legible at attach time instead
53
+ * of arriving days later as a broken upload. It never throws and never blocks
54
+ * an attach — a domain must not fail to attach over a storage API.
55
+ */
56
+ import { type CorsRule } from '@aglyn/aglyn/server';
57
+ export interface UploadCorsVerdict {
58
+ /** The origin a browser at this name will send. */
59
+ origin: string;
60
+ /** Can a signed `PUT` from that origin complete, now that we are done? */
61
+ permitted: boolean;
62
+ /** Origins this run added to the bucket. Empty when it was already covered. */
63
+ added: string[];
64
+ /** What a human must run, when we could not. Null when there is nothing owed. */
65
+ remedy: string | null;
66
+ /** Which step failed, for a caller that wants to branch. */
67
+ detail: string | null;
68
+ }
69
+ /**
70
+ * The bucket, behind a seam.
71
+ *
72
+ * Injectable so the merge and the failure paths are exercised without a
73
+ * network — the reason AGL-1408 shipped broken is that the only leg nobody
74
+ * could test cheaply was the only leg that mattered.
75
+ */
76
+ export interface BucketCorsIO {
77
+ bucket: string;
78
+ /** Null when the configuration could not be read at all. */
79
+ read(): Promise<{
80
+ rules: CorsRule[];
81
+ metageneration: string;
82
+ } | null>;
83
+ /**
84
+ * Write the merged configuration back, conditional on the metageneration
85
+ * that was read — two attaches racing would otherwise clobber each other.
86
+ */
87
+ write(rules: CorsRule[], metageneration: string): Promise<void>;
88
+ }
89
+ /** The live bucket, or null when this deployment has no media bucket configured. */
90
+ export declare function liveBucketCorsIO(): BucketCorsIO | null;
91
+ export declare function reconcileUploadCors(host: string, io?: BucketCorsIO | null): Promise<UploadCorsVerdict | null>;
92
+ /** What a detach did, or could not do, about the origin it left behind. */
93
+ export interface UploadCorsRelease {
94
+ /** The origin the detached name used to send. */
95
+ origin: string;
96
+ /** Is that origin gone from the bucket now? */
97
+ revoked: boolean;
98
+ /** Why not, for a caller that wants to branch. Null when it is gone. */
99
+ detail: string | null;
100
+ }
101
+ /**
102
+ * Take an origin back off the bucket when its name stops serving the console
103
+ * (AGL-1452).
104
+ *
105
+ * The mirror of `reconcileUploadCors`, and the half that was missing: attaching
106
+ * added an origin and detaching left it. That is a standing permission for a
107
+ * host we no longer serve — and for a white-label customer domain, a host that
108
+ * somebody else still controls, on a bucket where the signed URL IS the
109
+ * authorization.
110
+ *
111
+ * Same three properties as the attach side. It never throws, never blocks a
112
+ * detach, and reports what it could not do rather than going quiet: a domain
113
+ * must not fail to detach because a storage API was slow, and the operator who
114
+ * detached a customer must be able to see that the permission outlived them.
115
+ *
116
+ * Refuses the platform origin structurally, not by convention.
117
+ */
118
+ export declare function releaseUploadCors(host: string, io?: BucketCorsIO | null): Promise<UploadCorsRelease | null>;
@@ -0,0 +1,234 @@
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
+ * Keep the media bucket's upload CORS in step with the names the console is
18
+ * SERVED on (AGL-1452).
19
+ *
20
+ * ## Why this is automation rather than a checklist line
21
+ *
22
+ * GCS matches the CORS `origin` list as an exact string — no subtree form —
23
+ * so every serving console name needs its own entry, and a missing one fails
24
+ * only on files large enough to take the signed direct-to-GCS path, behind a
25
+ * generic "try again" snackbar. Nothing about the symptom points at bucket
26
+ * configuration, and nothing at attach time pointed at it either.
27
+ *
28
+ * That was already costing us, not merely threatening to. Driving real
29
+ * preflights against `gs://aglyn-main.appspot.com` on 2026-08-20: of the
30
+ * console project's seventeen attached names, exactly one —
31
+ * `https://app.aglyn.com` — was permitted, while five names that serve the
32
+ * console at 200 (`zgover.aglyn.com`, `test-org.aglyn.com`,
33
+ * `aglyn-org.aglyn.com`, `sale-test.aglyn.com`, `zachary1748.aglyn.com`) were
34
+ * refused. AGL-1452 recorded workspace subdomains as latent because none were
35
+ * attached at the time; they are attached now.
36
+ *
37
+ * ## Serving names only
38
+ *
39
+ * A name attached as a REDIRECT never becomes a browser origin — the browser
40
+ * is already at the redirect target before it uploads — so it needs no entry.
41
+ * Measured: `console.aglyn.com` 308s and `app.aglyn.io` 307s to
42
+ * `app.aglyn.com`, and neither is in the CORS list, and neither is broken.
43
+ * `attachProjectDomain` therefore calls this only for a name it attached
44
+ * WITHOUT a redirect.
45
+ *
46
+ * ## When it cannot
47
+ *
48
+ * The runtime service account may not hold `storage.buckets.update`. That is
49
+ * a real and likely outcome, and the one thing this must not do about it is
50
+ * be quiet: the verdict carries `permitted: false` and the exact ordered
51
+ * read-modify-write command, so the failure is legible at attach time instead
52
+ * of arriving days later as a broken upload. It never throws and never blocks
53
+ * an attach — a domain must not fail to attach over a storage API.
54
+ */ import { mergeUploadOrigins, permitsUploadOrigin, revokeUploadOrigins, uploadCorsRemedy, uploadOriginFor } from "@aglyn/aglyn/server";
55
+ /**
56
+ * The one origin a release must never take away.
57
+ *
58
+ * Configured rather than provisioned, so it will never appear in a detach —
59
+ * but the guard is here rather than at the call site because the cost of
60
+ * getting it wrong is every customer's large uploads at once, and a guard that
61
+ * lives at the call site is a guard the next call site does not have.
62
+ */ function platformOrigin() {
63
+ var _process_env_NEXT_PUBLIC_CONSOLE_URL;
64
+ return uploadOriginFor((_process_env_NEXT_PUBLIC_CONSOLE_URL = process.env['NEXT_PUBLIC_CONSOLE_URL']) != null ? _process_env_NEXT_PUBLIC_CONSOLE_URL : 'app.aglyn.com');
65
+ }
66
+ /** The live bucket, or null when this deployment has no media bucket configured. */ export function liveBucketCorsIO() {
67
+ var _process_env_NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET;
68
+ const bucket = String((_process_env_NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET = process.env['NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET']) != null ? _process_env_NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET : '').trim();
69
+ if (!bucket) return null;
70
+ // Imported lazily, not at module scope. `firebase-admin.ts` builds a
71
+ // Firebase app on load, and this module is imported by `workspace-domains`,
72
+ // which had no Firebase dependency at all and must keep none — a domain
73
+ // attach is an HTTP call to Vercel and nothing more.
74
+ const handle = async ()=>{
75
+ const { firebaseAdmin } = await import("./firebase-admin.js");
76
+ return firebaseAdmin.app().storage().bucket(bucket);
77
+ };
78
+ return {
79
+ bucket,
80
+ read: async ()=>{
81
+ var _ref, _ref1;
82
+ const [metadata] = await (await handle()).getMetadata();
83
+ return {
84
+ rules: (_ref = metadata == null ? void 0 : metadata.cors) != null ? _ref : [],
85
+ metageneration: String((_ref1 = metadata == null ? void 0 : metadata.metageneration) != null ? _ref1 : '')
86
+ };
87
+ },
88
+ write: async (rules, metageneration)=>{
89
+ await (await handle()).setMetadata({
90
+ cors: rules
91
+ }, metageneration ? {
92
+ ifMetagenerationMatch: metageneration
93
+ } : {});
94
+ }
95
+ };
96
+ }
97
+ export async function reconcileUploadCors(host, io = liveBucketCorsIO()) {
98
+ const origin = uploadOriginFor(host);
99
+ if (!origin || !io) return null;
100
+ const refused = (detail)=>({
101
+ origin,
102
+ permitted: false,
103
+ added: [],
104
+ remedy: uploadCorsRemedy(io.bucket, [
105
+ origin
106
+ ]),
107
+ detail
108
+ });
109
+ let current;
110
+ try {
111
+ current = await io.read();
112
+ } catch (error) {
113
+ console.error('[upload-cors] could not read bucket CORS', io.bucket, error);
114
+ return refused('read-failed');
115
+ }
116
+ if (!current) return refused('read-failed');
117
+ if (permitsUploadOrigin(current.rules, origin)) {
118
+ return {
119
+ origin,
120
+ permitted: true,
121
+ added: [],
122
+ remedy: null,
123
+ detail: null
124
+ };
125
+ }
126
+ let merged;
127
+ try {
128
+ merged = mergeUploadOrigins(current.rules, [
129
+ origin
130
+ ]);
131
+ } catch (error) {
132
+ // `mergeUploadOrigins` throws only on a wildcard, which `uploadOriginFor`
133
+ // has already ruled out — so reaching here means the invariant moved.
134
+ console.error('[upload-cors] refused to build a policy', origin, error);
135
+ return refused('merge-refused');
136
+ }
137
+ try {
138
+ await io.write(merged.rules, current.metageneration);
139
+ } catch (error) {
140
+ console.error('[upload-cors] could not write bucket CORS — large uploads from', origin, 'will fail until a human runs the command in the verdict', error);
141
+ return refused('write-failed');
142
+ }
143
+ return {
144
+ origin,
145
+ permitted: true,
146
+ added: merged.added,
147
+ remedy: null,
148
+ detail: null
149
+ };
150
+ }
151
+ /**
152
+ * Take an origin back off the bucket when its name stops serving the console
153
+ * (AGL-1452).
154
+ *
155
+ * The mirror of `reconcileUploadCors`, and the half that was missing: attaching
156
+ * added an origin and detaching left it. That is a standing permission for a
157
+ * host we no longer serve — and for a white-label customer domain, a host that
158
+ * somebody else still controls, on a bucket where the signed URL IS the
159
+ * authorization.
160
+ *
161
+ * Same three properties as the attach side. It never throws, never blocks a
162
+ * detach, and reports what it could not do rather than going quiet: a domain
163
+ * must not fail to detach because a storage API was slow, and the operator who
164
+ * detached a customer must be able to see that the permission outlived them.
165
+ *
166
+ * Refuses the platform origin structurally, not by convention.
167
+ */ export async function releaseUploadCors(host, io = liveBucketCorsIO()) {
168
+ var _platformOrigin;
169
+ const origin = uploadOriginFor(host);
170
+ if (!origin || !io) return null;
171
+ if (origin === platformOrigin()) {
172
+ return {
173
+ origin,
174
+ revoked: false,
175
+ detail: 'platform-origin'
176
+ };
177
+ }
178
+ let current;
179
+ try {
180
+ current = await io.read();
181
+ } catch (error) {
182
+ console.error('[upload-cors] could not read bucket CORS', io.bucket, error);
183
+ return {
184
+ origin,
185
+ revoked: false,
186
+ detail: 'read-failed'
187
+ };
188
+ }
189
+ if (!current) return {
190
+ origin,
191
+ revoked: false,
192
+ detail: 'read-failed'
193
+ };
194
+ // Already gone is the success case, not a no-op to report as a failure: a
195
+ // detach that runs twice must not read as a permission that would not go.
196
+ if (!permitsUploadOrigin(current.rules, origin)) {
197
+ return {
198
+ origin,
199
+ revoked: true,
200
+ detail: null
201
+ };
202
+ }
203
+ const { rules, removed } = revokeUploadOrigins(current.rules, [
204
+ origin
205
+ ], {
206
+ keep: [
207
+ (_platformOrigin = platformOrigin()) != null ? _platformOrigin : ''
208
+ ].filter(Boolean)
209
+ });
210
+ if (removed.length === 0) {
211
+ return {
212
+ origin,
213
+ revoked: false,
214
+ detail: 'refused'
215
+ };
216
+ }
217
+ try {
218
+ await io.write(rules, current.metageneration);
219
+ } catch (error) {
220
+ console.error('[upload-cors] could not revoke bucket CORS —', origin, 'keeps a standing permission to complete signed uploads; run', 'npm run check:upload-cors -- --prune', error);
221
+ return {
222
+ origin,
223
+ revoked: false,
224
+ detail: 'write-failed'
225
+ };
226
+ }
227
+ return {
228
+ origin,
229
+ revoked: true,
230
+ detail: null
231
+ };
232
+ }
233
+
234
+ //# sourceMappingURL=upload-cors-reconcile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/upload-cors-reconcile.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 * Keep the media bucket's upload CORS in step with the names the console is\n * SERVED on (AGL-1452).\n *\n * ## Why this is automation rather than a checklist line\n *\n * GCS matches the CORS `origin` list as an exact string — no subtree form —\n * so every serving console name needs its own entry, and a missing one fails\n * only on files large enough to take the signed direct-to-GCS path, behind a\n * generic \"try again\" snackbar. Nothing about the symptom points at bucket\n * configuration, and nothing at attach time pointed at it either.\n *\n * That was already costing us, not merely threatening to. Driving real\n * preflights against `gs://aglyn-main.appspot.com` on 2026-08-20: of the\n * console project's seventeen attached names, exactly one —\n * `https://app.aglyn.com` — was permitted, while five names that serve the\n * console at 200 (`zgover.aglyn.com`, `test-org.aglyn.com`,\n * `aglyn-org.aglyn.com`, `sale-test.aglyn.com`, `zachary1748.aglyn.com`) were\n * refused. AGL-1452 recorded workspace subdomains as latent because none were\n * attached at the time; they are attached now.\n *\n * ## Serving names only\n *\n * A name attached as a REDIRECT never becomes a browser origin — the browser\n * is already at the redirect target before it uploads — so it needs no entry.\n * Measured: `console.aglyn.com` 308s and `app.aglyn.io` 307s to\n * `app.aglyn.com`, and neither is in the CORS list, and neither is broken.\n * `attachProjectDomain` therefore calls this only for a name it attached\n * WITHOUT a redirect.\n *\n * ## When it cannot\n *\n * The runtime service account may not hold `storage.buckets.update`. That is\n * a real and likely outcome, and the one thing this must not do about it is\n * be quiet: the verdict carries `permitted: false` and the exact ordered\n * read-modify-write command, so the failure is legible at attach time instead\n * of arriving days later as a broken upload. It never throws and never blocks\n * an attach — a domain must not fail to attach over a storage API.\n */\n\nimport {\n mergeUploadOrigins,\n permitsUploadOrigin,\n revokeUploadOrigins,\n uploadCorsRemedy,\n uploadOriginFor,\n type CorsRule,\n} from '@aglyn/aglyn/server'\n\n/**\n * The one origin a release must never take away.\n *\n * Configured rather than provisioned, so it will never appear in a detach —\n * but the guard is here rather than at the call site because the cost of\n * getting it wrong is every customer's large uploads at once, and a guard that\n * lives at the call site is a guard the next call site does not have.\n */\nfunction platformOrigin(): string | null {\n return uploadOriginFor(\n process.env['NEXT_PUBLIC_CONSOLE_URL'] ?? 'app.aglyn.com',\n )\n}\n\nexport interface UploadCorsVerdict {\n /** The origin a browser at this name will send. */\n origin: string\n /** Can a signed `PUT` from that origin complete, now that we are done? */\n permitted: boolean\n /** Origins this run added to the bucket. Empty when it was already covered. */\n added: string[]\n /** What a human must run, when we could not. Null when there is nothing owed. */\n remedy: string | null\n /** Which step failed, for a caller that wants to branch. */\n detail: string | null\n}\n\n/**\n * The bucket, behind a seam.\n *\n * Injectable so the merge and the failure paths are exercised without a\n * network — the reason AGL-1408 shipped broken is that the only leg nobody\n * could test cheaply was the only leg that mattered.\n */\nexport interface BucketCorsIO {\n bucket: string\n /** Null when the configuration could not be read at all. */\n read(): Promise<{ rules: CorsRule[]; metageneration: string } | null>\n /**\n * Write the merged configuration back, conditional on the metageneration\n * that was read — two attaches racing would otherwise clobber each other.\n */\n write(rules: CorsRule[], metageneration: string): Promise<void>\n}\n\n/** The live bucket, or null when this deployment has no media bucket configured. */\nexport function liveBucketCorsIO(): BucketCorsIO | null {\n const bucket = String(\n process.env['NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET'] ?? '',\n ).trim()\n if (!bucket) return null\n // Imported lazily, not at module scope. `firebase-admin.ts` builds a\n // Firebase app on load, and this module is imported by `workspace-domains`,\n // which had no Firebase dependency at all and must keep none — a domain\n // attach is an HTTP call to Vercel and nothing more.\n const handle = async () => {\n const { firebaseAdmin } = await import('./firebase-admin')\n return firebaseAdmin.app().storage().bucket(bucket)\n }\n return {\n bucket,\n read: async () => {\n const [metadata] = await (await handle()).getMetadata()\n return {\n rules: (metadata?.cors ?? []) as CorsRule[],\n metageneration: String(metadata?.metageneration ?? ''),\n }\n },\n write: async (rules, metageneration) => {\n await (await handle()).setMetadata(\n { cors: rules },\n metageneration ? { ifMetagenerationMatch: metageneration } : {},\n )\n },\n }\n}\n\nexport async function reconcileUploadCors(\n host: string,\n io: BucketCorsIO | null = liveBucketCorsIO(),\n): Promise<UploadCorsVerdict | null> {\n const origin = uploadOriginFor(host)\n if (!origin || !io) return null\n\n const refused = (detail: string): UploadCorsVerdict => ({\n origin,\n permitted: false,\n added: [],\n remedy: uploadCorsRemedy(io.bucket, [origin]),\n detail,\n })\n\n let current: { rules: CorsRule[]; metageneration: string } | null\n try {\n current = await io.read()\n } catch (error) {\n console.error('[upload-cors] could not read bucket CORS', io.bucket, error)\n return refused('read-failed')\n }\n if (!current) return refused('read-failed')\n\n if (permitsUploadOrigin(current.rules, origin)) {\n return { origin, permitted: true, added: [], remedy: null, detail: null }\n }\n\n let merged: { rules: CorsRule[]; added: string[] }\n try {\n merged = mergeUploadOrigins(current.rules, [origin])\n } catch (error) {\n // `mergeUploadOrigins` throws only on a wildcard, which `uploadOriginFor`\n // has already ruled out — so reaching here means the invariant moved.\n console.error('[upload-cors] refused to build a policy', origin, error)\n return refused('merge-refused')\n }\n\n try {\n await io.write(merged.rules, current.metageneration)\n } catch (error) {\n console.error(\n '[upload-cors] could not write bucket CORS — large uploads from',\n origin,\n 'will fail until a human runs the command in the verdict',\n error,\n )\n return refused('write-failed')\n }\n\n return {\n origin,\n permitted: true,\n added: merged.added,\n remedy: null,\n detail: null,\n }\n}\n\n/** What a detach did, or could not do, about the origin it left behind. */\nexport interface UploadCorsRelease {\n /** The origin the detached name used to send. */\n origin: string\n /** Is that origin gone from the bucket now? */\n revoked: boolean\n /** Why not, for a caller that wants to branch. Null when it is gone. */\n detail: string | null\n}\n\n/**\n * Take an origin back off the bucket when its name stops serving the console\n * (AGL-1452).\n *\n * The mirror of `reconcileUploadCors`, and the half that was missing: attaching\n * added an origin and detaching left it. That is a standing permission for a\n * host we no longer serve — and for a white-label customer domain, a host that\n * somebody else still controls, on a bucket where the signed URL IS the\n * authorization.\n *\n * Same three properties as the attach side. It never throws, never blocks a\n * detach, and reports what it could not do rather than going quiet: a domain\n * must not fail to detach because a storage API was slow, and the operator who\n * detached a customer must be able to see that the permission outlived them.\n *\n * Refuses the platform origin structurally, not by convention.\n */\nexport async function releaseUploadCors(\n host: string,\n io: BucketCorsIO | null = liveBucketCorsIO(),\n): Promise<UploadCorsRelease | null> {\n const origin = uploadOriginFor(host)\n if (!origin || !io) return null\n if (origin === platformOrigin()) {\n return { origin, revoked: false, detail: 'platform-origin' }\n }\n\n let current: { rules: CorsRule[]; metageneration: string } | null\n try {\n current = await io.read()\n } catch (error) {\n console.error('[upload-cors] could not read bucket CORS', io.bucket, error)\n return { origin, revoked: false, detail: 'read-failed' }\n }\n if (!current) return { origin, revoked: false, detail: 'read-failed' }\n\n // Already gone is the success case, not a no-op to report as a failure: a\n // detach that runs twice must not read as a permission that would not go.\n if (!permitsUploadOrigin(current.rules, origin)) {\n return { origin, revoked: true, detail: null }\n }\n\n const { rules, removed } = revokeUploadOrigins(current.rules, [origin], {\n keep: [platformOrigin() ?? ''].filter(Boolean),\n })\n if (removed.length === 0) {\n return { origin, revoked: false, detail: 'refused' }\n }\n\n try {\n await io.write(rules, current.metageneration)\n } catch (error) {\n console.error(\n '[upload-cors] could not revoke bucket CORS —',\n origin,\n 'keeps a standing permission to complete signed uploads; run',\n 'npm run check:upload-cors -- --prune',\n error,\n )\n return { origin, revoked: false, detail: 'write-failed' }\n }\n return { origin, revoked: true, detail: null }\n}\n"],"names":["mergeUploadOrigins","permitsUploadOrigin","revokeUploadOrigins","uploadCorsRemedy","uploadOriginFor","platformOrigin","process","env","liveBucketCorsIO","bucket","String","trim","handle","firebaseAdmin","app","storage","read","metadata","getMetadata","rules","cors","metageneration","write","setMetadata","ifMetagenerationMatch","reconcileUploadCors","host","io","origin","refused","detail","permitted","added","remedy","current","error","console","merged","releaseUploadCors","revoked","removed","keep","filter","Boolean","length"],"mappings":"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"}
@@ -0,0 +1,262 @@
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
+ import { type ContactFacet, type ContactInteraction, type ContactLifecycleStage, type ContactSource } from '@aglyn/aglyn/server';
18
+ import { type ResolvedCampaignTouch } from './campaign-conversion-attribution';
19
+ import { type MarketingConsentSource } from '@aglyn/aglyn/app-utils/marketing-consent';
20
+ /**
21
+ * What an upsert did, for the callers that need to know (AGL-2602).
22
+ *
23
+ * The capture doors never look: a form submission or an order must succeed
24
+ * whatever happened to the CRM record, which is why this function swallows
25
+ * its own errors and why every existing caller `await`s it for its side
26
+ * effect alone. An IMPORT is the caller that has to know, row by row —
27
+ * "created" and "merged" are its two headline numbers, and a row the
28
+ * audience band refused has to be handed back to the operator as a row
29
+ * rather than becoming one more tick on a counter nobody reconciles against
30
+ * a file. A verdict is returned rather than thrown so the swallowing stays:
31
+ * `refused: 'error'` is the same silence the doors have always had, now with
32
+ * a name.
33
+ */
34
+ export type UpsertHostContactVerdict = {
35
+ contactId: string;
36
+ /** True when this call created the row; false when it merged into one. */
37
+ created: boolean;
38
+ } | {
39
+ /**
40
+ * `erased`: the site holds an erasure row for the address (AGL-2623),
41
+ * so no record is created — a capture must not quietly rebuild a
42
+ * person the workspace erased. Only a CREATE is refused; a merge
43
+ * cannot arise, because the erasure removed the row it would merge
44
+ * into.
45
+ */
46
+ refused: 'invalid-email' | 'band' | 'erased' | 'error';
47
+ };
48
+ /**
49
+ * The per-holder profile fields a door may write alongside the identity.
50
+ *
51
+ * `Pick`ed from the facet rather than typed afresh so the two cannot drift:
52
+ * a field the facet grows is a field this option may carry the moment the
53
+ * pick names it, and one it does not name is refused at compile time.
54
+ */
55
+ export type UpsertHostContactFacet = Partial<Pick<ContactFacet, 'phone' | 'jobTitle' | 'companyId' | 'address' | 'ownerUid' | 'lifecycleStage' | 'custom'>>;
56
+ /**
57
+ * The profile fields a door may hand this function (AGL-2596): the parts of
58
+ * a person's record that no capture surface collects — the console's create
59
+ * drawer and the import do, and the order door adds the stage. `custom` is
60
+ * the holder's own field values, keyed by `ContactFieldDefinition.key`; the
61
+ * import maps spreadsheet columns onto them, and the definitions live under
62
+ * the same group the values are written to.
63
+ */
64
+ export type ContactProfileInput = UpsertHostContactFacet;
65
+ /**
66
+ * Contacts ingestion (AGL-197): upserts an org-scoped contact doc (AGL-237)
67
+ * keyed by normalized email from any capture point (forms, membership,
68
+ * orders, bookings). Fire-and-forget by design — callers should never
69
+ * fail their primary write because contact capture had a problem.
70
+ *
71
+ * Quota (AGL-890): contacts are audience BANDS, not hard caps. Paid plans
72
+ * always create — contacts past the included band meter onto the monthly
73
+ * invoice (report-usage cron). Free hard-bands at the included count:
74
+ * only there do dropped creations increment `counters/contactsDropped`,
75
+ * surfaced as a console alert (AGL-891). Interactions on existing
76
+ * contacts always append regardless of plan.
77
+ */
78
+ /**
79
+ * What a capture door learns when its capture made a NEW person.
80
+ *
81
+ * Handed to {@link UpsertHostContactOptions.onCreated} once, on the create
82
+ * branch only. The merge branch is a visit by somebody the org already held,
83
+ * which is another interaction and not a new contact — the same line
84
+ * `campaignTouch` draws for attribution. Scalars and one string array, so
85
+ * the runtime can flatten it into an event payload without inventing keys.
86
+ */
87
+ export interface HostContactCreated {
88
+ contactId: string;
89
+ hostId: string;
90
+ email: string;
91
+ name?: string;
92
+ source: ContactSource;
93
+ /** The capture surface's campaigns, normalized — `[]` when it had none. */
94
+ campaignIds: string[];
95
+ /**
96
+ * The stage the create wrote onto the capturing facet — the door's
97
+ * {@link UpsertHostContactOptions.initialLifecycleStage}, or the profile's
98
+ * own — and absent when the capture named none, so an automation can
99
+ * filter `lifecycleStage == "lead"` on the day the person appears.
100
+ */
101
+ lifecycleStage?: ContactLifecycleStage;
102
+ }
103
+ export interface UpsertHostContactOptions {
104
+ hostId: string;
105
+ email: unknown;
106
+ name?: string;
107
+ source: ContactSource;
108
+ interaction: Omit<ContactInteraction, 'type' | 'atMs'> & {
109
+ atMs?: number;
110
+ };
111
+ /**
112
+ * Explicit marketing opt-in (AGL-301) with a consent timestamp, recorded
113
+ * against {@link hostId} — the brand whose form carried the checkbox.
114
+ */
115
+ marketingConsent?: boolean;
116
+ /**
117
+ * An explicit REFUSAL, recorded against {@link hostId} (AGL-3185).
118
+ *
119
+ * Its own flag rather than `marketingConsent: false`, because every door
120
+ * that passes a checkbox's value passes `false` for a box left alone, and
121
+ * a box left alone records nothing — absence is the third state, and
122
+ * turning it into a refusal would silently make every unticked visitor
123
+ * unmailable. A door sets this only for an act of refusal: a switch turned
124
+ * off, a prompt answered no. Ignored when {@link marketingConsent} is true.
125
+ */
126
+ declineMarketingConsent?: boolean;
127
+ /**
128
+ * The provenance stored on the consent entry {@link marketingConsent} or
129
+ * {@link declineMarketingConsent} writes (AGL-3185): who recorded it, which
130
+ * door, which wording version. The console doors pass the person's own
131
+ * (`actor: 'person'`); a site's capture surface passes none, which the
132
+ * reader takes as the person's own act anyway.
133
+ */
134
+ marketingConsentSource?: MarketingConsentSource;
135
+ /**
136
+ * Order value in cents — rolls into RFM fields (AGL-328).
137
+ *
138
+ * WHAT IT COUNTS (AGL-1748). GROSS of the platform fee and GROSS of
139
+ * refunds — the money the customer handed over, not the money the merchant
140
+ * kept. Every writer passes the same thing: whatever was actually charged
141
+ * (`amount_total` for a Stripe path, `totals.totalCents` for POS), never a
142
+ * figure re-derived from product docs, which is the AGL-1698/AGL-1711
143
+ * lesson. Gross of the fee because this is a CUSTOMER attribute answering
144
+ * "what is this person worth to me?", and the fee is a cost of the channel,
145
+ * not something the buyer failed to spend.
146
+ *
147
+ * Refunds are still NOT netted here, and now they are recorded elsewhere
148
+ * (AGL-1754). `refund.ts` writes `refundedCents`, `refundedOrdersCount` and
149
+ * `lastRefundAtMs` BESIDE these fields — the shape AGL-1747 chose for the
150
+ * same question on the orders CSV — rather than decrementing a stored number
151
+ * whose meaning would then differ between rows written before and after that
152
+ * commit. So `ltvCents` and `ordersCount` remain gross by definition, and a
153
+ * READER that wants the net computes `ltvCents - refundedCents`, clamping
154
+ * only what it ranks on: the difference can be negative for a customer whose
155
+ * pre-AGL-1748 purchase was never counted and whose refund was, which is a
156
+ * missing purchase showing itself rather than a corrupt contact. AGL-1753 is
157
+ * the backfill that reconciles it. See `contact-refund.ts` in the commerce
158
+ * plugin for the full reasoning and for why a refund never CREATES a contact.
159
+ *
160
+ * Passing 0 or omitting it means "no purchase": `ltvCents`, `ordersCount`,
161
+ * `lastPurchaseAtMs` and `firstPurchaseAtMs` are all left untouched, which
162
+ * is why a caller that formats the amount into the interaction summary and
163
+ * forgets this field records a customer who has apparently never bought
164
+ * anything.
165
+ */
166
+ purchaseCents?: number;
167
+ /**
168
+ * The currency {@link purchaseCents} is in, lowercase, when the door knows.
169
+ *
170
+ * Absent everywhere today, because no order document carries a currency and
171
+ * every checkout door writes `currency: 'usd'` onto the Stripe line items.
172
+ * `attributeOrderToEmail` defaults it on that basis and says so. The field
173
+ * exists so a door that ever charges in something else can pass it, and the
174
+ * campaign revenue report keeps it in its own bucket rather than adding it
175
+ * to the dollars.
176
+ */
177
+ purchaseCurrency?: string;
178
+ /**
179
+ * The campaign this person came from, already resolved by the door.
180
+ *
181
+ * ⛔ The ORDER path passes none, and must not start. An order already has
182
+ * its own join one branch below — `attributeOrderToEmail`, keyed on the
183
+ * order id — and a second record for the same sale would be the same money
184
+ * counted twice under two rules. This is the door for the moments an order
185
+ * does NOT cover: a form submission, a membership sign-up, a booking, a
186
+ * newsletter capture.
187
+ *
188
+ * Resolved rather than raw, for the reason `addHostLead` states: one
189
+ * visitor action reaches several writers and the touch lookup is paid once.
190
+ */
191
+ campaignTouch?: ResolvedCampaignTouch | null;
192
+ /**
193
+ * The campaigns the CAPTURE SURFACE is filed under.
194
+ *
195
+ * ⚠️ A different fact from {@link campaignTouch} beside it, and the two must
196
+ * never be folded together. A touch is where the visitor came FROM — an ad,
197
+ * a link, a browser-supplied label resolved through an allowlist. This is
198
+ * which campaigns the merchant put the form itself in, which is the
199
+ * merchant's own act and is true of everybody who fills that form in,
200
+ * including the visitor who arrived by typing the address.
201
+ *
202
+ * ⛔ And it is not consent. Filing a form under a campaign says nothing
203
+ * about what the person agreed to; `marketingConsent` above is the only
204
+ * input that records a basis.
205
+ */
206
+ campaignIds?: readonly string[];
207
+ /**
208
+ * Tags to put on THIS holder's facet (AGL-2602).
209
+ *
210
+ * Added to, never replaced, on a merge: a person the merchant tagged by
211
+ * hand and later imported keeps the hand-written tag beside the file's.
212
+ */
213
+ tags?: readonly string[];
214
+ /**
215
+ * The profile a door knows about the person — phone, title, company,
216
+ * address, owner, stage, custom values — written into THIS holder's facet
217
+ * (AGL-2602). Only the keys present are written, so a door that knows the
218
+ * phone and nothing else does not blank the title somebody typed.
219
+ */
220
+ facet?: UpsertHostContactFacet;
221
+ /**
222
+ * The EARLIEST stage that describes what this capture was (AGL-2612).
223
+ *
224
+ * Every door names one: a form submission is a `lead`, a newsletter opt-in
225
+ * or a member sign-up is a `subscriber`, a purchase — an order, a paid
226
+ * booking — is a `customer`. The rule applied to it is
227
+ * `advanceContactLifecycleStage`: it fills an empty stage and advances an
228
+ * earlier one, and never moves anybody back, so a customer who fills in
229
+ * the contact form is still a customer and a subscriber who then submits
230
+ * a form becomes a lead. Applied on top of `facet.lifecycleStage` when a
231
+ * door carries both — the caller's stage is the base and this is its
232
+ * floor.
233
+ *
234
+ * A FLOOR and not a value, which is why it is not the facet field: the
235
+ * facet's `lifecycleStage` is a SET, the shape the console's create
236
+ * drawer and the import need — the merchant typed a stage and that is the
237
+ * stage — and a capture door writing a set would put every returning
238
+ * customer back to `lead` on their next enquiry. Omitted by the doors
239
+ * that carry the caller's own stage or none (manual, import, API, a lead
240
+ * conversion), whose contacts read as "no stage", which is true.
241
+ */
242
+ initialLifecycleStage?: ContactLifecycleStage;
243
+ /**
244
+ * Told when this capture created a contact (AGL-2605).
245
+ *
246
+ * A HOOK rather than an event emitted from here, and the reason is the
247
+ * dependency direction: the event fan-out lives in `libs/tenant/runtime`,
248
+ * which imports THIS library for its Firestore handle and its org helpers.
249
+ * An import back up from here would be a cycle, and the module boundaries
250
+ * (`scope:data` may depend on data and util only) refuse it besides. So
251
+ * this module reports the fact and the runtime's `captureHostContact`
252
+ * turns it into `contactCreated` — every server door goes through that
253
+ * wrapper, and a door that calls this function directly has chosen to
254
+ * create contacts nothing can react to.
255
+ *
256
+ * Awaited with its own catch, like the order join above: the least
257
+ * important write on the path, and a failure in it must not cost the
258
+ * capture that already happened.
259
+ */
260
+ onCreated?: (created: HostContactCreated) => void | Promise<void>;
261
+ }
262
+ export declare function upsertHostContact(options: UpsertHostContactOptions): Promise<UpsertHostContactVerdict>;