@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,201 @@
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
+ /** the number, 2026-08-19. The floor the console can move off. */
18
+ export declare const DEFAULT_FREE_WORKSPACE_CAP = 3;
19
+ /** Document id of the live ceiling. NEVER written with `expiresAt`. */
20
+ export declare const FREE_WORKSPACE_CAP_CONFIG_DOC = "freeWorkspaceCapConfig";
21
+ /** Id prefix for the per-owner contention markers. */
22
+ export declare const FREE_WORKSPACE_MARKER_PREFIX = "freeWorkspaces_";
23
+ /** Bounds the console will accept. One is a real answer; zero is not. */
24
+ export declare const FREE_WORKSPACE_CAP_MIN = 1;
25
+ export declare const FREE_WORKSPACE_CAP_MAX = 500;
26
+ /** Longest audited note the console may attach to a change. */
27
+ export declare const FREE_WORKSPACE_CAP_NOTE_MAX = 280;
28
+ /**
29
+ * How many of an owner's org documents the count will read.
30
+ *
31
+ * The count filters by plan in memory (`plan` is not a queryable predicate
32
+ * that survives `resolveEffectivePlan`'s subscription check), so the query
33
+ * cannot be bounded at the ceiling. An account holding more orgs than this is
34
+ * far past any ceiling staff would set and is a support conversation, not a
35
+ * create-time decision.
36
+ */
37
+ export declare const FREE_WORKSPACE_SCAN_LIMIT = 250;
38
+ /** How long a contention marker survives. Disposable — see the notes above. */
39
+ export declare const FREE_WORKSPACE_MARKER_TTL_MS: number;
40
+ export interface FreeWorkspaceCapConfig {
41
+ /** Free workspaces one account may hold. */
42
+ limit: number;
43
+ /** False turns the ceiling OFF entirely (staff kill switch). */
44
+ enabled: boolean;
45
+ /** Why it is where it is — shown on the card, written to the audit row. */
46
+ note: string;
47
+ updatedAtMs: number | null;
48
+ updatedByEmail: string | null;
49
+ /**
50
+ * Whether this is a value that was actually READ, or the compiled-in
51
+ * default standing in for one that could not be.
52
+ *
53
+ * Exposed rather than swallowed because a loading default answering a
54
+ * question as though it were a real value is a bug shape this codebase has
55
+ * shipped before. Callers that must not act on a guess check this; the
56
+ * verdict function below treats it as the difference between "the operator
57
+ * chose unlimited" and "we do not know yet", and the second is never
58
+ * unlimited.
59
+ */
60
+ ready: boolean;
61
+ }
62
+ /** The marker document id for one owner. */
63
+ export declare function freeWorkspaceMarkerDocId(uid: string): string;
64
+ /**
65
+ * Coerces whatever is stored (or nothing) into a usable ceiling.
66
+ *
67
+ * A missing, non-numeric, zero, negative or absurd `limit` becomes the
68
+ * compiled-in default rather than zero. Storing a broken number must not be a
69
+ * way to refuse every signup, and it must not be a way to admit everyone
70
+ * either — both are the same mistake with opposite signs.
71
+ */
72
+ export declare function normalizeFreeWorkspaceCapConfig(data: Partial<FreeWorkspaceCapConfig> | null | undefined, options?: {
73
+ ready?: boolean;
74
+ }): FreeWorkspaceCapConfig;
75
+ export interface FreeWorkspaceCapVerdict {
76
+ allowed: boolean;
77
+ /** The ceiling the decision was actually made against. */
78
+ limit: number;
79
+ held: number;
80
+ remaining: number;
81
+ /** False when the ceiling was a stand-in rather than a value we read. */
82
+ ready: boolean;
83
+ /** True when the ceiling is switched off and this granted for that reason. */
84
+ disabled: boolean;
85
+ }
86
+ /**
87
+ * The whole decision, pure.
88
+ *
89
+ * The `ready` input is the point of the signature. A config that has not
90
+ * loaded arrives here as `{ ready: false }` and is answered against the
91
+ * compiled-in default — never as `limit: 0` (which would refuse every signup
92
+ * on the platform during a blip) and never as "no limit known, therefore no
93
+ * limit" (which would make an outage the way through the control).
94
+ */
95
+ export declare function freeWorkspaceCapVerdict(input: {
96
+ held: number;
97
+ limit?: number | null;
98
+ enabled?: boolean;
99
+ ready?: boolean;
100
+ }): FreeWorkspaceCapVerdict;
101
+ /**
102
+ * Whether an org document is on the free plan for the purposes of this count.
103
+ *
104
+ * `resolveEffectivePlan` and not `data.plan`: a workspace whose subscription
105
+ * is canceled or unpaid resolves back to free, which is right — it is
106
+ * consuming the free allowance again, so it counts again.
107
+ */
108
+ export declare function isFreeOrgDoc(data: unknown): boolean;
109
+ /**
110
+ * The two queries whose UNION is the attributed set. Exported so the spec can
111
+ * assert the second one exists — the union IS the anti-laundering property,
112
+ * and a future edit that drops `createdByUid` for being redundant would put
113
+ * the transfer sequence back without changing any behaviour a happy-path test
114
+ * observes.
115
+ */
116
+ export declare function freeWorkspaceOwnerQueries(firestore: any, uid: string): any[];
117
+ export interface FreeWorkspaceCount {
118
+ held: number;
119
+ orgIds: string[];
120
+ }
121
+ /**
122
+ * Read-only count, for the console and for anything that wants to explain a
123
+ * refusal after the fact. The creation path does NOT use this — it counts
124
+ * inside its own transaction.
125
+ */
126
+ export declare function countFreeWorkspacesForOwner(options: {
127
+ uid: string;
128
+ firestore?: any;
129
+ }): Promise<FreeWorkspaceCount>;
130
+ /** Drop the in-process config cache — called by the console after a write. */
131
+ export declare function invalidateFreeWorkspaceCapConfigCache(): void;
132
+ /**
133
+ * The live ceiling.
134
+ *
135
+ * An unreachable document returns the compiled-in default with
136
+ * `ready: false`, which every caller must carry rather than flatten — see the
137
+ * failure-posture note at the top of the file.
138
+ */
139
+ export declare function readFreeWorkspaceCapConfig(options?: {
140
+ firestore?: any;
141
+ now?: number;
142
+ }): Promise<FreeWorkspaceCapConfig>;
143
+ /**
144
+ * The document the console writes when staff move the ceiling.
145
+ *
146
+ * Returned rather than written so the route owns the write (and the audit row
147
+ * beside it), and so a spec can assert the SHAPE without a Firestore. The one
148
+ * thing it must never contain is `expiresAt`.
149
+ */
150
+ export declare function freeWorkspaceCapConfigWrite(input: {
151
+ limit: number;
152
+ enabled: boolean;
153
+ actorEmail?: string | null;
154
+ note?: string;
155
+ now?: number;
156
+ }): Partial<FreeWorkspaceCapConfig>;
157
+ /**
158
+ * Thrown by `createOrganization` when the ceiling refuses. Carries the numbers
159
+ * so the API route can say which ceiling and how many, rather than "no".
160
+ */
161
+ export declare class FreeWorkspaceCapError extends Error {
162
+ readonly limit: number;
163
+ readonly held: number;
164
+ constructor(verdict: {
165
+ limit: number;
166
+ held: number;
167
+ });
168
+ }
169
+ /**
170
+ * The create-time gate, run INSIDE the creating transaction.
171
+ *
172
+ * Reads the attributed count through `tx` (so it is part of the read set and
173
+ * re-runs against fresh data on a retry) and reads-then-writes the per-owner
174
+ * marker (so two concurrent creates by one account contend, and the loser
175
+ * re-runs and sees the winner's org).
176
+ *
177
+ * Throws `FreeWorkspaceCapError` when it refuses. Firestore does not retry a
178
+ * callback that threw, which is what we want: a refusal is a decision, not
179
+ * contention.
180
+ *
181
+ * Call it AFTER the caller's other reads and BEFORE the caller's writes — it
182
+ * both reads and writes, and Firestore requires all reads first.
183
+ */
184
+ export declare function enforceFreeWorkspaceCapInTransaction(options: {
185
+ tx: any;
186
+ firestore: any;
187
+ uid: string;
188
+ config: FreeWorkspaceCapConfig;
189
+ now?: number;
190
+ }): Promise<FreeWorkspaceCapVerdict>;
191
+ /**
192
+ * Turn a ceiling refusal into the 403 the creating routes return, or null when
193
+ * the error is something else and must keep propagating to the 500.
194
+ *
195
+ * Lives here beside the policy, and mirrors `collaboratorSeatRefusalResponse`,
196
+ * so a route's catch block is one line and cannot accidentally mask a real
197
+ * fault. The copy names the number and the way out, because the person reading
198
+ * it is far more often a consultant with three real workspaces than the script
199
+ * this exists to stop.
200
+ */
201
+ export declare function freeWorkspaceCapRefusalResponse(error: unknown): Response | null;
@@ -0,0 +1,404 @@
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
+ * THE FREE-WORKSPACE CEILING (AGL-2265).
18
+ *
19
+ * Every free cap in the product is enforced PER ORG, and nothing bounded how
20
+ * many orgs one account could hold — so the free allowance multiplied by
21
+ * workspaces: N orgs = N sites, N × 250 MB media, N × 5 GB bandwidth, N × 10
22
+ * Assist messages a day. `/api/orgs/create` is rate limited per uid and per IP
23
+ * (AGL-1534), which bounds the RATE and not the TOTAL.
24
+ *
25
+ * the decision, 2026-08-19: **three, with a control in the staff console.**
26
+ * Three is the number; the number living in a Firestore document rather than a
27
+ * constant is the other half of the decision, because the population this
28
+ * refuses (a script minting workspaces) and the population it must not refuse
29
+ * (an agency, a consultant, a customer support raises) are told apart by a
30
+ * human, and a human must not need a deploy to say yes.
31
+ *
32
+ * ## What counts
33
+ *
34
+ * A FREE workspace ATTRIBUTED to the account. Three properties, each of which
35
+ * is a bug if it is wrong:
36
+ *
37
+ * - **Paid workspaces do not count.** An agency's workspaces are paid, and
38
+ * the abuse this closes is free-allowance multiplication. `plan` is read
39
+ * through `resolveEffectivePlan`, so a workspace whose subscription died
40
+ * counts again — correctly: it is free again.
41
+ * - **Being INVITED to someone else's workspace never counts.** The count
42
+ * comes off `orgs`, keyed by ownership and creation. It never reads
43
+ * `users/{uid}/orgs`, which is a membership index — a contractor added to
44
+ * ten client workspaces owns none of them.
45
+ * - **Handing a workspace away does not free a slot.** See below; this is
46
+ * the whole difficulty.
47
+ *
48
+ * ## Why the count is not launderable
49
+ *
50
+ * A create-time quota computed from a number the applicant can lower is not a
51
+ * quota. The cheap laundering sequence here is not deletion — deleting a
52
+ * workspace really does give up the workspace — it is **ownership transfer**,
53
+ * which is free, instant and reversible:
54
+ *
55
+ * hold 3 → transfer #3 to an alt account → count reads 2 → create a 4th
56
+ * → transfer #3 back → hold 4, having never been refused.
57
+ *
58
+ * So the count is the UNION of two sets: workspaces the account owns NOW
59
+ * (`ownerUid`) and workspaces the account CREATED (`createdByUid`).
60
+ * `createdByUid` is stamped once, inside the creating transaction, and no code
61
+ * path anywhere mutates it — `transferOrgOwnership` moves `ownerUid` and does
62
+ * not touch it. Transferring away therefore changes nothing about the count,
63
+ * and the sequence above is refused at the fourth workspace exactly as it
64
+ * would have been without the detour. Erasing a workspace removes the org
65
+ * document from both sets, which is the one decrement that is honest: the
66
+ * allowance it was multiplying went with it.
67
+ *
68
+ * (Orgs created before this shipped carry no `createdByUid`. They are counted
69
+ * by `ownerUid` like always, so the union is never smaller than the old
70
+ * behaviour; it only stops shrinking under a transfer.)
71
+ *
72
+ * ## Why the decision is atomic
73
+ *
74
+ * The count is read INSIDE the org-creation transaction, and the transaction
75
+ * also reads-and-writes a per-owner marker document. Without that marker, two
76
+ * `/api/orgs/create` requests in flight together would both observe three,
77
+ * both find headroom and both commit a fourth and a fifth: the new org
78
+ * documents are not in either transaction's read set, so nothing would make
79
+ * them contend. The marker is a document they DO both read and both write, so
80
+ * the second commit aborts, the callback re-runs, its queries re-read at the
81
+ * new read time, they now see the org the first request committed, and it is
82
+ * refused. That is the AGL-2267 lesson — a read-then-write quota must contend
83
+ * on something.
84
+ *
85
+ * ## Storage, and why it needs no rules or index deploy
86
+ *
87
+ * The ceiling lives at `rateLimits/freeWorkspaceCapConfig` and the per-owner
88
+ * marker at `rateLimits/freeWorkspaces_{uid}`, the same collection AGL-794's
89
+ * counters and AGL-2409's send-rate ramp already use, and for the same
90
+ * reason: it inherits the deny-all security rule and the `expiresAt` TTL
91
+ * policy that already exist. Deny-all matters here — no client, staff
92
+ * included, may write the ceiling; the audited console route is the only
93
+ * writer.
94
+ *
95
+ * The config document **must never carry `expiresAt`**, or the TTL policy that
96
+ * serves the counters would delete the ceiling and the platform would silently
97
+ * revert to the compiled-in default. The marker document carries one on
98
+ * purpose: it is disposable, its only job is contention, and a swept marker
99
+ * costs nothing because the queries are the authority for the number.
100
+ *
101
+ * ## Failure posture: the DEFAULT, which is neither open nor closed
102
+ *
103
+ * An unreadable configuration resolves to the compiled-in 3 and reports
104
+ * `ready: false`. Not zero — a Firestore blip must not refuse every signup on
105
+ * the platform. Not unlimited — a Firestore blip must not be the way through
106
+ * the control. `checkQuota(undefined)` resolving to the free tier is a bug
107
+ * this codebase has already had; `freeWorkspaceCapVerdict` therefore takes
108
+ * `ready` as an explicit input and a not-yet-loaded limit is never read as a
109
+ * number.
110
+ */ import { resolveEffectivePlan } from "@aglyn/aglyn/server";
111
+ import { firebaseAdmin } from "./firebase-admin.js";
112
+ import { RATE_LIMIT_COLLECTION } from "./rate-limit-store.js";
113
+ /** the number, 2026-08-19. The floor the console can move off. */ export const DEFAULT_FREE_WORKSPACE_CAP = 3;
114
+ /** Document id of the live ceiling. NEVER written with `expiresAt`. */ export const FREE_WORKSPACE_CAP_CONFIG_DOC = 'freeWorkspaceCapConfig';
115
+ /** Id prefix for the per-owner contention markers. */ export const FREE_WORKSPACE_MARKER_PREFIX = 'freeWorkspaces_';
116
+ /** Bounds the console will accept. One is a real answer; zero is not. */ export const FREE_WORKSPACE_CAP_MIN = 1;
117
+ export const FREE_WORKSPACE_CAP_MAX = 500;
118
+ /** Longest audited note the console may attach to a change. */ export const FREE_WORKSPACE_CAP_NOTE_MAX = 280;
119
+ /**
120
+ * How many of an owner's org documents the count will read.
121
+ *
122
+ * The count filters by plan in memory (`plan` is not a queryable predicate
123
+ * that survives `resolveEffectivePlan`'s subscription check), so the query
124
+ * cannot be bounded at the ceiling. An account holding more orgs than this is
125
+ * far past any ceiling staff would set and is a support conversation, not a
126
+ * create-time decision.
127
+ */ export const FREE_WORKSPACE_SCAN_LIMIT = 250;
128
+ /** How long a contention marker survives. Disposable — see the notes above. */ export const FREE_WORKSPACE_MARKER_TTL_MS = 24 * 60 * 60 * 1000;
129
+ /** The marker document id for one owner. */ export function freeWorkspaceMarkerDocId(uid) {
130
+ return `${FREE_WORKSPACE_MARKER_PREFIX}${uid}`;
131
+ }
132
+ /**
133
+ * Coerces whatever is stored (or nothing) into a usable ceiling.
134
+ *
135
+ * A missing, non-numeric, zero, negative or absurd `limit` becomes the
136
+ * compiled-in default rather than zero. Storing a broken number must not be a
137
+ * way to refuse every signup, and it must not be a way to admit everyone
138
+ * either — both are the same mistake with opposite signs.
139
+ */ export function normalizeFreeWorkspaceCapConfig(data, options) {
140
+ const rawLimit = Number(data == null ? void 0 : data.limit);
141
+ const limit = Number.isFinite(rawLimit) && rawLimit >= FREE_WORKSPACE_CAP_MIN ? Math.min(FREE_WORKSPACE_CAP_MAX, Math.floor(rawLimit)) : DEFAULT_FREE_WORKSPACE_CAP;
142
+ const updatedAtMs = Number(data == null ? void 0 : data.updatedAtMs);
143
+ return {
144
+ limit,
145
+ // Only an explicit `false` turns it off — an absent field is ON, so a
146
+ // half-written document cannot silently disable the ceiling.
147
+ enabled: (data == null ? void 0 : data.enabled) !== false,
148
+ note: typeof (data == null ? void 0 : data.note) === 'string' ? data.note.slice(0, FREE_WORKSPACE_CAP_NOTE_MAX) : '',
149
+ updatedAtMs: Number.isFinite(updatedAtMs) && updatedAtMs > 0 ? updatedAtMs : null,
150
+ updatedByEmail: typeof (data == null ? void 0 : data.updatedByEmail) === 'string' && data.updatedByEmail ? data.updatedByEmail : null,
151
+ ready: (options == null ? void 0 : options.ready) !== false
152
+ };
153
+ }
154
+ /**
155
+ * The whole decision, pure.
156
+ *
157
+ * The `ready` input is the point of the signature. A config that has not
158
+ * loaded arrives here as `{ ready: false }` and is answered against the
159
+ * compiled-in default — never as `limit: 0` (which would refuse every signup
160
+ * on the platform during a blip) and never as "no limit known, therefore no
161
+ * limit" (which would make an outage the way through the control).
162
+ */ export function freeWorkspaceCapVerdict(input) {
163
+ const ready = input.ready !== false;
164
+ const rawHeld = Number(input.held);
165
+ const held = Number.isFinite(rawHeld) && rawHeld > 0 ? Math.floor(rawHeld) : 0;
166
+ const rawLimit = Number(input.limit);
167
+ // An unready config does not get to contribute a number at all, even if one
168
+ // happens to be sitting in the field.
169
+ const limit = ready && Number.isFinite(rawLimit) && rawLimit >= FREE_WORKSPACE_CAP_MIN ? Math.min(FREE_WORKSPACE_CAP_MAX, Math.floor(rawLimit)) : DEFAULT_FREE_WORKSPACE_CAP;
170
+ const disabled = input.enabled === false;
171
+ return {
172
+ allowed: disabled || held < limit,
173
+ limit,
174
+ held,
175
+ remaining: Math.max(0, limit - held),
176
+ ready,
177
+ disabled
178
+ };
179
+ }
180
+ /**
181
+ * Whether an org document is on the free plan for the purposes of this count.
182
+ *
183
+ * `resolveEffectivePlan` and not `data.plan`: a workspace whose subscription
184
+ * is canceled or unpaid resolves back to free, which is right — it is
185
+ * consuming the free allowance again, so it counts again.
186
+ */ export function isFreeOrgDoc(data) {
187
+ return resolveEffectivePlan(data != null ? data : {}) === 'free';
188
+ }
189
+ /**
190
+ * The two queries whose UNION is the attributed set. Exported so the spec can
191
+ * assert the second one exists — the union IS the anti-laundering property,
192
+ * and a future edit that drops `createdByUid` for being redundant would put
193
+ * the transfer sequence back without changing any behaviour a happy-path test
194
+ * observes.
195
+ */ export function freeWorkspaceOwnerQueries(firestore, uid) {
196
+ const orgs = firestore.collection('orgs');
197
+ return [
198
+ orgs.where('ownerUid', '==', uid).limit(FREE_WORKSPACE_SCAN_LIMIT),
199
+ orgs.where('createdByUid', '==', uid).limit(FREE_WORKSPACE_SCAN_LIMIT)
200
+ ];
201
+ }
202
+ /**
203
+ * How many free workspaces are attributed to `uid`.
204
+ *
205
+ * `readQuery` is how the caller runs each query, and it is the whole
206
+ * difference between the two callers: outside a transaction a query runs
207
+ * itself (`query.get()`), inside one it must run through the transaction
208
+ * (`tx.get(query)`) or the count is not in the read set and the retry that
209
+ * makes this atomic never happens. Passing a Firestore instance and calling
210
+ * `firestore.get(query)` would silently be neither — that method does not
211
+ * exist, and the count would come back as zero, admitting everyone.
212
+ */ async function countAttributedFreeWorkspaces(readQuery, firestore, uid) {
213
+ const seen = new Map();
214
+ for (const query of freeWorkspaceOwnerQueries(firestore, uid)){
215
+ var _ref;
216
+ const snapshot = await readQuery(query);
217
+ for (const doc of (_ref = snapshot == null ? void 0 : snapshot.docs) != null ? _ref : []){
218
+ // Deduplicated by id: the common case is one org in BOTH sets.
219
+ if (!seen.has(doc.id)) seen.set(doc.id, doc.data());
220
+ }
221
+ }
222
+ const orgIds = [
223
+ ...seen.entries()
224
+ ].filter(([, data])=>isFreeOrgDoc(data)).map(([id])=>id);
225
+ return {
226
+ held: orgIds.length,
227
+ orgIds
228
+ };
229
+ }
230
+ /**
231
+ * Read-only count, for the console and for anything that wants to explain a
232
+ * refusal after the fact. The creation path does NOT use this — it counts
233
+ * inside its own transaction.
234
+ */ export async function countFreeWorkspacesForOwner(options) {
235
+ var _options_firestore;
236
+ const firestore = (_options_firestore = options.firestore) != null ? _options_firestore : firebaseAdmin.app().firestore();
237
+ try {
238
+ return await countAttributedFreeWorkspaces((query)=>query.get(), firestore, options.uid);
239
+ } catch (error) {
240
+ console.error('[free-workspace-cap] count unavailable', error);
241
+ return {
242
+ held: 0,
243
+ orgIds: []
244
+ };
245
+ }
246
+ }
247
+ /**
248
+ * Config cache TTL. 15s, the same number and reasoning as the lockdown doc
249
+ * and the send-rate ramp: staff move this rarely and a warm process converges
250
+ * within a quarter of a minute.
251
+ */ const CONFIG_TTL_MS = 15000;
252
+ let configCache;
253
+ let configPending;
254
+ /** Drop the in-process config cache — called by the console after a write. */ export function invalidateFreeWorkspaceCapConfigCache() {
255
+ configCache = undefined;
256
+ configPending = undefined;
257
+ }
258
+ /**
259
+ * The live ceiling.
260
+ *
261
+ * An unreachable document returns the compiled-in default with
262
+ * `ready: false`, which every caller must carry rather than flatten — see the
263
+ * failure-posture note at the top of the file.
264
+ */ export async function readFreeWorkspaceCapConfig(options) {
265
+ var _ref;
266
+ const now = (_ref = options == null ? void 0 : options.now) != null ? _ref : Date.now();
267
+ if (!(options == null ? void 0 : options.firestore) && configCache && now - configCache.at < CONFIG_TTL_MS) {
268
+ return configCache.config;
269
+ }
270
+ const load = async ()=>{
271
+ try {
272
+ var _ref;
273
+ const firestore = (_ref = options == null ? void 0 : options.firestore) != null ? _ref : firebaseAdmin.app().firestore();
274
+ const snapshot = await firestore.collection(RATE_LIMIT_COLLECTION).doc(FREE_WORKSPACE_CAP_CONFIG_DOC).get();
275
+ // A document that does not exist is READY: nobody has changed the
276
+ // number, and the compiled-in 3 is the answer, not a guess.
277
+ return normalizeFreeWorkspaceCapConfig((snapshot == null ? void 0 : snapshot.exists) ? snapshot.data() : null, {
278
+ ready: true
279
+ });
280
+ } catch (unused) {
281
+ return normalizeFreeWorkspaceCapConfig(null, {
282
+ ready: false
283
+ });
284
+ }
285
+ };
286
+ // An injected firestore is a test or a one-off read; never cached, so a
287
+ // spec cannot poison the process cache for the next one.
288
+ if (options == null ? void 0 : options.firestore) return load();
289
+ if (!configPending) {
290
+ configPending = load().then((config)=>{
291
+ // A degraded read is never cached — the next create should try again
292
+ // rather than serve a stand-in for fifteen seconds.
293
+ if (config.ready) configCache = {
294
+ at: now,
295
+ config
296
+ };
297
+ return config;
298
+ }).finally(()=>{
299
+ configPending = undefined;
300
+ });
301
+ }
302
+ return configPending;
303
+ }
304
+ /**
305
+ * The document the console writes when staff move the ceiling.
306
+ *
307
+ * Returned rather than written so the route owns the write (and the audit row
308
+ * beside it), and so a spec can assert the SHAPE without a Firestore. The one
309
+ * thing it must never contain is `expiresAt`.
310
+ */ export function freeWorkspaceCapConfigWrite(input) {
311
+ var _input_now, _input_actorEmail;
312
+ const normalized = normalizeFreeWorkspaceCapConfig({
313
+ limit: input.limit,
314
+ enabled: input.enabled,
315
+ note: input.note,
316
+ updatedAtMs: (_input_now = input.now) != null ? _input_now : Date.now(),
317
+ updatedByEmail: (_input_actorEmail = input.actorEmail) != null ? _input_actorEmail : null
318
+ });
319
+ return {
320
+ limit: normalized.limit,
321
+ enabled: normalized.enabled,
322
+ note: normalized.note,
323
+ updatedAtMs: normalized.updatedAtMs,
324
+ updatedByEmail: normalized.updatedByEmail
325
+ };
326
+ }
327
+ /**
328
+ * Thrown by `createOrganization` when the ceiling refuses. Carries the numbers
329
+ * so the API route can say which ceiling and how many, rather than "no".
330
+ */ export class FreeWorkspaceCapError extends Error {
331
+ constructor(verdict){
332
+ super(`Free workspace ceiling reached: ${verdict.held} of ${verdict.limit}`);
333
+ this.name = 'FreeWorkspaceCapError';
334
+ this.limit = verdict.limit;
335
+ this.held = verdict.held;
336
+ }
337
+ }
338
+ /**
339
+ * The create-time gate, run INSIDE the creating transaction.
340
+ *
341
+ * Reads the attributed count through `tx` (so it is part of the read set and
342
+ * re-runs against fresh data on a retry) and reads-then-writes the per-owner
343
+ * marker (so two concurrent creates by one account contend, and the loser
344
+ * re-runs and sees the winner's org).
345
+ *
346
+ * Throws `FreeWorkspaceCapError` when it refuses. Firestore does not retry a
347
+ * callback that threw, which is what we want: a refusal is a decision, not
348
+ * contention.
349
+ *
350
+ * Call it AFTER the caller's other reads and BEFORE the caller's writes — it
351
+ * both reads and writes, and Firestore requires all reads first.
352
+ */ export async function enforceFreeWorkspaceCapInTransaction(options) {
353
+ var _options_now, _ref;
354
+ const { tx, firestore, uid, config } = options;
355
+ const now = (_options_now = options.now) != null ? _options_now : Date.now();
356
+ const markerRef = firestore.collection(RATE_LIMIT_COLLECTION).doc(freeWorkspaceMarkerDocId(uid));
357
+ const [count, marker] = await Promise.all([
358
+ countAttributedFreeWorkspaces((query)=>tx.get(query), firestore, uid),
359
+ tx.get(markerRef)
360
+ ]);
361
+ const verdict = freeWorkspaceCapVerdict({
362
+ held: count.held,
363
+ limit: config.limit,
364
+ enabled: config.enabled,
365
+ ready: config.ready
366
+ });
367
+ if (!verdict.allowed) throw new FreeWorkspaceCapError(verdict);
368
+ const priorCreates = Number((_ref = (marker == null ? void 0 : marker.exists) ? marker.get('creates') : 0) != null ? _ref : 0);
369
+ // An ABSOLUTE value derived from the read, not `FieldValue.increment`: the
370
+ // read is what has to contend, and an increment is atomic on the number and
371
+ // useless for the decision (AGL-2267).
372
+ tx.set(markerRef, {
373
+ creates: (Number.isFinite(priorCreates) ? priorCreates : 0) + 1,
374
+ heldAtLastCreate: count.held,
375
+ lastCreateAtMs: now,
376
+ // Disposable. NOT on the config document — see the storage note.
377
+ expiresAt: new Date(now + FREE_WORKSPACE_MARKER_TTL_MS)
378
+ }, {
379
+ merge: true
380
+ });
381
+ return verdict;
382
+ }
383
+ /**
384
+ * Turn a ceiling refusal into the 403 the creating routes return, or null when
385
+ * the error is something else and must keep propagating to the 500.
386
+ *
387
+ * Lives here beside the policy, and mirrors `collaboratorSeatRefusalResponse`,
388
+ * so a route's catch block is one line and cannot accidentally mask a real
389
+ * fault. The copy names the number and the way out, because the person reading
390
+ * it is far more often a consultant with three real workspaces than the script
391
+ * this exists to stop.
392
+ */ export function freeWorkspaceCapRefusalResponse(error) {
393
+ if (!(error instanceof FreeWorkspaceCapError)) return null;
394
+ return Response.json({
395
+ error: `You already have ${error.held} free workspaces, which is the limit ` + `of ${error.limit}. Upgrade one of them, delete one you no longer ` + 'need, or contact support and we can raise the limit for your account.',
396
+ code: 'free_workspace_limit',
397
+ limit: error.limit,
398
+ held: error.held
399
+ }, {
400
+ status: 403
401
+ });
402
+ }
403
+
404
+ //# sourceMappingURL=free-workspace-cap.js.map