@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,276 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ /**
18
+ * Durable CSP-violation aggregates (AGL-1799).
19
+ *
20
+ * The collectors were delivering reports into a sink that forgets: the Vercel
21
+ * runtime log retains ~60 minutes and there is no log drain, so AGL-1702's
22
+ * "a week of signed-in traffic" and AGL-1726's "a business week of visitor
23
+ * traffic" were gated on evidence that structurally could not exist. This
24
+ * module is the durable half — the log lines stay exactly as they were (they
25
+ * are still the right tool for the last hour), and every accepted violation
26
+ * ALSO compounds into a Firestore counter that holds for the retention window.
27
+ *
28
+ * ## Shape: counters, never report bodies
29
+ *
30
+ * One document per (UTC day x app x directive x disposition x blocked origin),
31
+ * with `FieldValue.increment` — NOT one document per report. AGL-523 made a
32
+ * deliberate choice not to write report bodies to Firestore from an
33
+ * unauthenticated route, and AGL-1799 argued the distinction explicitly: "a
34
+ * counter with a bounded key space is a different proposition from storing
35
+ * report bodies". The bound is what this module is careful about, because
36
+ * every key ingredient except the day is attacker-influenced:
37
+ *
38
+ * - the DIRECTIVE is normalized into a closed set (a real browser can only
39
+ * ever send a spec directive; anything else lands in `other` rather than
40
+ * minting a document per invented directive);
41
+ * - the ORIGIN is reduced to the host of `blocked-uri` (or its bare keyword
42
+ * — `inline`, `eval`, `data`), sanitized to a fixed character set, and
43
+ * capped: at most {@link MAX_DISTINCT_ORIGINS_PER_DAY} distinct origins per
44
+ * (day x app x directive) may mint documents per instance per day. Beyond
45
+ * that, new origins are dropped while known ones keep counting — a flood
46
+ * of unique URLs (the AGL-1769 lesson: never let request content mint
47
+ * unbounded documents) saturates the cap instead of the collection;
48
+ * - WRITES are budgeted per instance per minute, so a POST flood cannot turn
49
+ * an unauthenticated beacon into a Firestore write bill even against
50
+ * already-minted documents.
51
+ *
52
+ * Every cap here is per instance, like the tenant collector's `keyStore`: the
53
+ * fleet-wide worst case is (instances x cap), which is bounded and small,
54
+ * and an instance recycle merely re-counts an origin against a fresh cap —
55
+ * the merge write is idempotent about which document it lands in.
56
+ *
57
+ * ## Ids are attacker-derived, so they go through `isDocumentId`
58
+ *
59
+ * Key parts are sanitized to `[a-z0-9.:_-]` and joined with `|`, then the
60
+ * whole id is checked with {@link isDocumentId} (the AGL-1771 predicate —
61
+ * imported from its leaf, `./document-id`, which imports nothing). Belt and
62
+ * braces: the sanitizer already removes `/` and the joiner makes a reserved
63
+ * `__x__` form impossible, but the predicate is the contract and it is
64
+ * cheaper to check than to argue.
65
+ *
66
+ * ## Retention is TTL, not a sweep
67
+ *
68
+ * `expiresAt` (a Timestamp — TTL refuses numbers) is set
69
+ * {@link CSP_AGGREGATE_RETENTION_DAYS} days past the day bucket. The TTL
70
+ * policy is a gcloud action plus a `fieldOverrides` entry in
71
+ * `cloud/firebase-firestore.indexes.json` — see
72
+ * docs/FIRESTORE_MANUAL_CONFIG.md (AGL-1793): the entry ships with this
73
+ * module and the gcloud enable is owed at deploy time.
74
+ *
75
+ * ## Fail-soft, like everything on this path
76
+ *
77
+ * A collector must never turn an observed problem into a served one. Every
78
+ * failure here — no credential, Firestore down, a hostile id that survives
79
+ * sanitization — drops the aggregate (one `console.error` line, tagged) and
80
+ * returns. The log lines the collectors already emit are unaffected.
81
+ *
82
+ * No client ever reads or writes this collection: there is no rules `match`
83
+ * for it, and Firestore denies what nothing matches, so the Admin SDK is the
84
+ * only path in. The staff read-back is `/api/admin/csp-reports` on the
85
+ * console.
86
+ */ import { FieldValue } from "firebase-admin/firestore";
87
+ import { isDocumentId } from "./document-id.js";
88
+ /** Top-level collection of counter documents. Server-only; no rules match. */ export const CSP_AGGREGATE_COLLECTION = 'cspViolationDaily';
89
+ /**
90
+ * Long enough that "a week of traffic" (AGL-1702 condition 1, AGL-1726
91
+ * condition 3) is readable with room to be read late, short enough that the
92
+ * collection stays a working set rather than an archive.
93
+ */ export const CSP_AGGREGATE_RETENTION_DAYS = 60;
94
+ /**
95
+ * Distinct blocked origins that may mint documents per (day x app x
96
+ * directive), per instance. A legitimate policy violates against a handful of
97
+ * hosts; thirty is generous headroom for a real regression while keeping a
98
+ * flood of attacker-invented origins from minting unbounded documents.
99
+ */ export const MAX_DISTINCT_ORIGINS_PER_DAY = 30;
100
+ /**
101
+ * Aggregate writes allowed per instance per minute. Distinct-origin capping
102
+ * bounds document COUNT; this bounds write OPS, which are billed even when
103
+ * they land on existing documents. Sized far above what real browsers on one
104
+ * instance produce and far below what makes a flood expensive.
105
+ */ const WRITE_BUDGET_PER_MINUTE = 120;
106
+ /**
107
+ * The directives a browser can actually report, per the CSP3 registry. An
108
+ * unrecognized directive is a non-browser caller (or a future spec revision)
109
+ * and shares one bucket rather than minting its own key space.
110
+ */ const KNOWN_DIRECTIVES = new Set([
111
+ 'base-uri',
112
+ 'block-all-mixed-content',
113
+ 'child-src',
114
+ 'connect-src',
115
+ 'default-src',
116
+ 'font-src',
117
+ 'form-action',
118
+ 'frame-ancestors',
119
+ 'frame-src',
120
+ 'img-src',
121
+ 'manifest-src',
122
+ 'media-src',
123
+ 'object-src',
124
+ 'prefetch-src',
125
+ 'script-src',
126
+ 'script-src-attr',
127
+ 'script-src-elem',
128
+ 'style-src',
129
+ 'style-src-attr',
130
+ 'style-src-elem',
131
+ 'upgrade-insecure-requests',
132
+ 'worker-src'
133
+ ]);
134
+ /**
135
+ * Per-instance mint tracker: (day|app|directive) to the origin|disposition
136
+ * pairs that have been allowed to mint documents. Cleared wholesale at the
137
+ * cap, like the tenant collector's `keyStore`: the failure mode of clearing
138
+ * is a brief widening of the mint cap, strictly better than an unbounded map
139
+ * fed by attacker-chosen keys.
140
+ */ const mintTracker = new Map();
141
+ const MAX_TRACKED_GROUPS = 512;
142
+ /** Per-instance write budget window. */ let budgetWindowStartMs = 0;
143
+ let budgetUsed = 0;
144
+ /** Test seam: module-scope state would otherwise leak across cases. */ export function resetCspAggregateStateForTests() {
145
+ mintTracker.clear();
146
+ budgetWindowStartMs = 0;
147
+ budgetUsed = 0;
148
+ }
149
+ /** One id/field component: lowercase, fixed charset, never empty. */ function sanitizePart(value, max) {
150
+ const cleaned = value.toLowerCase().replace(/[^a-z0-9.:_-]+/g, '-').slice(0, max);
151
+ return cleaned || 'unknown';
152
+ }
153
+ /**
154
+ * `blocked-uri` reduced to the coarsest thing worth counting: the HOST for a
155
+ * URL, the bare keyword (`inline`, `eval`, `data`, ...) otherwise. Paths are
156
+ * deliberately dropped — twelve chunk URLs off one CDN are one origin-level
157
+ * fact (the AGL-1779 shape), and a path would both explode the key space and
158
+ * hand the id to whoever controls the URL.
159
+ */ export function cspBlockedOrigin(blockedUri) {
160
+ if (!blockedUri) return 'none';
161
+ try {
162
+ return sanitizePart(new URL(blockedUri).host, 120);
163
+ } catch (unused) {
164
+ return sanitizePart(blockedUri, 40);
165
+ }
166
+ }
167
+ /** UTC day bucket, `YYYY-MM-DD`. */ export function cspAggregateDay(nowMs) {
168
+ return new Date(nowMs).toISOString().slice(0, 10);
169
+ }
170
+ function normalizeDirective(directive) {
171
+ return KNOWN_DIRECTIVES.has(directive) ? directive : 'other';
172
+ }
173
+ /**
174
+ * Compound `violations` into the daily counters. Never throws; returns how
175
+ * many documents were written (0 on any failure, budget exhaustion included).
176
+ */ export async function recordCspViolations(violations, options) {
177
+ try {
178
+ var _options_now, _options_firestore;
179
+ if (!violations.length) return 0;
180
+ const now = (_options_now = options.now) != null ? _options_now : Date.now();
181
+ const day = cspAggregateDay(now);
182
+ const dayStartMs = Date.parse(`${day}T00:00:00.000Z`);
183
+ // Group first: N occurrences of one key in a batch are ONE write carrying
184
+ // increment(N), so a page that repeats itself costs one op, not N.
185
+ const grouped = new Map();
186
+ for (const violation of violations){
187
+ const directive = normalizeDirective(violation.effectiveDirective);
188
+ const origin = cspBlockedOrigin(violation.blockedUri);
189
+ const disposition = sanitizePart(violation.disposition || 'report', 16);
190
+ const id = [
191
+ day,
192
+ options.app,
193
+ sanitizePart(directive, 64),
194
+ disposition,
195
+ origin
196
+ ].join('|');
197
+ // Belt and braces — the AGL-1771 contract for anything handed to
198
+ // `.doc()`. The sanitizer above should make this unreachable.
199
+ if (!isDocumentId(id)) continue;
200
+ const existing = grouped.get(id);
201
+ if (existing) {
202
+ existing.n += 1;
203
+ continue;
204
+ }
205
+ // Distinct-origin cap, per (day x app x directive). Known pairs keep
206
+ // counting; new pairs beyond the cap are dropped — bounded documents
207
+ // is the property, not perfect counts.
208
+ const groupKey = `${day}|${options.app}|${directive}`;
209
+ if (mintTracker.size > MAX_TRACKED_GROUPS) mintTracker.clear();
210
+ let minted = mintTracker.get(groupKey);
211
+ if (!minted) {
212
+ minted = new Set();
213
+ mintTracker.set(groupKey, minted);
214
+ }
215
+ const pair = `${disposition}|${origin}`;
216
+ if (!minted.has(pair)) {
217
+ if (minted.size >= MAX_DISTINCT_ORIGINS_PER_DAY) continue;
218
+ minted.add(pair);
219
+ }
220
+ grouped.set(id, {
221
+ n: 1,
222
+ directive,
223
+ origin,
224
+ violation
225
+ });
226
+ }
227
+ if (!grouped.size) return 0;
228
+ // Write budget: fixed one-minute window per instance.
229
+ if (now - budgetWindowStartMs >= 60000) {
230
+ budgetWindowStartMs = now;
231
+ budgetUsed = 0;
232
+ }
233
+ const room = WRITE_BUDGET_PER_MINUTE - budgetUsed;
234
+ if (room <= 0) return 0;
235
+ const entries = [
236
+ ...grouped.entries()
237
+ ].slice(0, room);
238
+ budgetUsed += entries.length;
239
+ const firestore = (_options_firestore = options.firestore) != null ? _options_firestore : // Lazily resolved so this module stays inert at load: route specs and
240
+ // this module's own spec inject a fake, and nothing initializes
241
+ // firebase-admin just by importing the barrel.
242
+ (await import("./firebase-admin.js")).firebaseAdmin.app().firestore();
243
+ await Promise.all(entries.map(([id, { n, directive, origin, violation }])=>firestore.collection(CSP_AGGREGATE_COLLECTION).doc(id).set(_extends({
244
+ day,
245
+ app: options.app,
246
+ directive,
247
+ origin,
248
+ disposition: sanitizePart(violation.disposition || 'report', 16),
249
+ count: FieldValue.increment(n),
250
+ lastSeenMs: now,
251
+ // Samples, not keys: enough to find the surface, no power to
252
+ // mint documents. Already clamped by the parser; clamped again
253
+ // because this module's contract should not depend on that.
254
+ lastPath: violation.documentPath.slice(0, 200)
255
+ }, options.site ? {
256
+ lastSite: options.site.slice(0, 253)
257
+ } : {}, {
258
+ // Timestamp-typed via Date — Firestore TTL refuses numbers.
259
+ expiresAt: new Date(dayStartMs + CSP_AGGREGATE_RETENTION_DAYS * 86400000)
260
+ }), {
261
+ merge: true
262
+ })));
263
+ return entries.length;
264
+ } catch (error) {
265
+ // Fail-soft: the aggregate is an observer, and `console.error` rather
266
+ // than `console.warn` on purpose — the collectors' own log lines are
267
+ // warns, and their specs count warns.
268
+ console.error(JSON.stringify({
269
+ tag: 'AGL-1799:csp-aggregate',
270
+ error: String(error).slice(0, 200)
271
+ }));
272
+ return 0;
273
+ }
274
+ }
275
+
276
+ //# sourceMappingURL=csp-aggregate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/csp-aggregate.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 * Durable CSP-violation aggregates (AGL-1799).\n *\n * The collectors were delivering reports into a sink that forgets: the Vercel\n * runtime log retains ~60 minutes and there is no log drain, so AGL-1702's\n * \"a week of signed-in traffic\" and AGL-1726's \"a business week of visitor\n * traffic\" were gated on evidence that structurally could not exist. This\n * module is the durable half — the log lines stay exactly as they were (they\n * are still the right tool for the last hour), and every accepted violation\n * ALSO compounds into a Firestore counter that holds for the retention window.\n *\n * ## Shape: counters, never report bodies\n *\n * One document per (UTC day x app x directive x disposition x blocked origin),\n * with `FieldValue.increment` — NOT one document per report. AGL-523 made a\n * deliberate choice not to write report bodies to Firestore from an\n * unauthenticated route, and AGL-1799 argued the distinction explicitly: \"a\n * counter with a bounded key space is a different proposition from storing\n * report bodies\". The bound is what this module is careful about, because\n * every key ingredient except the day is attacker-influenced:\n *\n * - the DIRECTIVE is normalized into a closed set (a real browser can only\n * ever send a spec directive; anything else lands in `other` rather than\n * minting a document per invented directive);\n * - the ORIGIN is reduced to the host of `blocked-uri` (or its bare keyword\n * — `inline`, `eval`, `data`), sanitized to a fixed character set, and\n * capped: at most {@link MAX_DISTINCT_ORIGINS_PER_DAY} distinct origins per\n * (day x app x directive) may mint documents per instance per day. Beyond\n * that, new origins are dropped while known ones keep counting — a flood\n * of unique URLs (the AGL-1769 lesson: never let request content mint\n * unbounded documents) saturates the cap instead of the collection;\n * - WRITES are budgeted per instance per minute, so a POST flood cannot turn\n * an unauthenticated beacon into a Firestore write bill even against\n * already-minted documents.\n *\n * Every cap here is per instance, like the tenant collector's `keyStore`: the\n * fleet-wide worst case is (instances x cap), which is bounded and small,\n * and an instance recycle merely re-counts an origin against a fresh cap —\n * the merge write is idempotent about which document it lands in.\n *\n * ## Ids are attacker-derived, so they go through `isDocumentId`\n *\n * Key parts are sanitized to `[a-z0-9.:_-]` and joined with `|`, then the\n * whole id is checked with {@link isDocumentId} (the AGL-1771 predicate —\n * imported from its leaf, `./document-id`, which imports nothing). Belt and\n * braces: the sanitizer already removes `/` and the joiner makes a reserved\n * `__x__` form impossible, but the predicate is the contract and it is\n * cheaper to check than to argue.\n *\n * ## Retention is TTL, not a sweep\n *\n * `expiresAt` (a Timestamp — TTL refuses numbers) is set\n * {@link CSP_AGGREGATE_RETENTION_DAYS} days past the day bucket. The TTL\n * policy is a gcloud action plus a `fieldOverrides` entry in\n * `cloud/firebase-firestore.indexes.json` — see\n * docs/FIRESTORE_MANUAL_CONFIG.md (AGL-1793): the entry ships with this\n * module and the gcloud enable is owed at deploy time.\n *\n * ## Fail-soft, like everything on this path\n *\n * A collector must never turn an observed problem into a served one. Every\n * failure here — no credential, Firestore down, a hostile id that survives\n * sanitization — drops the aggregate (one `console.error` line, tagged) and\n * returns. The log lines the collectors already emit are unaffected.\n *\n * No client ever reads or writes this collection: there is no rules `match`\n * for it, and Firestore denies what nothing matches, so the Admin SDK is the\n * only path in. The staff read-back is `/api/admin/csp-reports` on the\n * console.\n */\n\nimport { FieldValue } from 'firebase-admin/firestore'\nimport type { CspViolation } from '@aglyn/aglyn/app-utils/csp-report'\nimport { isDocumentId } from './document-id'\n\n/** Top-level collection of counter documents. Server-only; no rules match. */\nexport const CSP_AGGREGATE_COLLECTION = 'cspViolationDaily'\n\n/**\n * Long enough that \"a week of traffic\" (AGL-1702 condition 1, AGL-1726\n * condition 3) is readable with room to be read late, short enough that the\n * collection stays a working set rather than an archive.\n */\nexport const CSP_AGGREGATE_RETENTION_DAYS = 60\n\n/**\n * Distinct blocked origins that may mint documents per (day x app x\n * directive), per instance. A legitimate policy violates against a handful of\n * hosts; thirty is generous headroom for a real regression while keeping a\n * flood of attacker-invented origins from minting unbounded documents.\n */\nexport const MAX_DISTINCT_ORIGINS_PER_DAY = 30\n\n/**\n * Aggregate writes allowed per instance per minute. Distinct-origin capping\n * bounds document COUNT; this bounds write OPS, which are billed even when\n * they land on existing documents. Sized far above what real browsers on one\n * instance produce and far below what makes a flood expensive.\n */\nconst WRITE_BUDGET_PER_MINUTE = 120\n\n/**\n * The directives a browser can actually report, per the CSP3 registry. An\n * unrecognized directive is a non-browser caller (or a future spec revision)\n * and shares one bucket rather than minting its own key space.\n */\nconst KNOWN_DIRECTIVES = new Set([\n 'base-uri',\n 'block-all-mixed-content',\n 'child-src',\n 'connect-src',\n 'default-src',\n 'font-src',\n 'form-action',\n 'frame-ancestors',\n 'frame-src',\n 'img-src',\n 'manifest-src',\n 'media-src',\n 'object-src',\n 'prefetch-src',\n 'script-src',\n 'script-src-attr',\n 'script-src-elem',\n 'style-src',\n 'style-src-attr',\n 'style-src-elem',\n 'upgrade-insecure-requests',\n 'worker-src',\n])\n\n/**\n * Per-instance mint tracker: (day|app|directive) to the origin|disposition\n * pairs that have been allowed to mint documents. Cleared wholesale at the\n * cap, like the tenant collector's `keyStore`: the failure mode of clearing\n * is a brief widening of the mint cap, strictly better than an unbounded map\n * fed by attacker-chosen keys.\n */\nconst mintTracker = new Map<string, Set<string>>()\nconst MAX_TRACKED_GROUPS = 512\n\n/** Per-instance write budget window. */\nlet budgetWindowStartMs = 0\nlet budgetUsed = 0\n\n/** Test seam: module-scope state would otherwise leak across cases. */\nexport function resetCspAggregateStateForTests(): void {\n mintTracker.clear()\n budgetWindowStartMs = 0\n budgetUsed = 0\n}\n\n/** One id/field component: lowercase, fixed charset, never empty. */\nfunction sanitizePart(value: string, max: number): string {\n const cleaned = value\n .toLowerCase()\n .replace(/[^a-z0-9.:_-]+/g, '-')\n .slice(0, max)\n return cleaned || 'unknown'\n}\n\n/**\n * `blocked-uri` reduced to the coarsest thing worth counting: the HOST for a\n * URL, the bare keyword (`inline`, `eval`, `data`, ...) otherwise. Paths are\n * deliberately dropped — twelve chunk URLs off one CDN are one origin-level\n * fact (the AGL-1779 shape), and a path would both explode the key space and\n * hand the id to whoever controls the URL.\n */\nexport function cspBlockedOrigin(blockedUri: string): string {\n if (!blockedUri) return 'none'\n try {\n return sanitizePart(new URL(blockedUri).host, 120)\n } catch {\n return sanitizePart(blockedUri, 40)\n }\n}\n\n/** UTC day bucket, `YYYY-MM-DD`. */\nexport function cspAggregateDay(nowMs: number): string {\n return new Date(nowMs).toISOString().slice(0, 10)\n}\n\nfunction normalizeDirective(directive: string): string {\n return KNOWN_DIRECTIVES.has(directive) ? directive : 'other'\n}\n\nexport interface RecordCspViolationsOptions {\n /** Which collector this came through — part of the counter key. */\n app: 'console' | 'tenant'\n /**\n * The reporting site (tenant only), REQUEST-derived and already clamped by\n * the caller. Stored as a sample field, never in the id: distinct sites\n * must not multiply the key space.\n */\n site?: string\n now?: number\n /** Injectable for tests; defaults to the Admin SDK's Firestore. */\n firestore?: any\n}\n\n/**\n * Compound `violations` into the daily counters. Never throws; returns how\n * many documents were written (0 on any failure, budget exhaustion included).\n */\nexport async function recordCspViolations(\n violations: readonly CspViolation[],\n options: RecordCspViolationsOptions,\n): Promise<number> {\n try {\n if (!violations.length) return 0\n const now = options.now ?? Date.now()\n const day = cspAggregateDay(now)\n const dayStartMs = Date.parse(`${day}T00:00:00.000Z`)\n\n // Group first: N occurrences of one key in a batch are ONE write carrying\n // increment(N), so a page that repeats itself costs one op, not N.\n const grouped = new Map<\n string,\n { n: number; directive: string; origin: string; violation: CspViolation }\n >()\n for (const violation of violations) {\n const directive = normalizeDirective(violation.effectiveDirective)\n const origin = cspBlockedOrigin(violation.blockedUri)\n const disposition = sanitizePart(violation.disposition || 'report', 16)\n const id = [\n day,\n options.app,\n sanitizePart(directive, 64),\n disposition,\n origin,\n ].join('|')\n // Belt and braces — the AGL-1771 contract for anything handed to\n // `.doc()`. The sanitizer above should make this unreachable.\n if (!isDocumentId(id)) continue\n\n const existing = grouped.get(id)\n if (existing) {\n existing.n += 1\n continue\n }\n\n // Distinct-origin cap, per (day x app x directive). Known pairs keep\n // counting; new pairs beyond the cap are dropped — bounded documents\n // is the property, not perfect counts.\n const groupKey = `${day}|${options.app}|${directive}`\n if (mintTracker.size > MAX_TRACKED_GROUPS) mintTracker.clear()\n let minted = mintTracker.get(groupKey)\n if (!minted) {\n minted = new Set()\n mintTracker.set(groupKey, minted)\n }\n const pair = `${disposition}|${origin}`\n if (!minted.has(pair)) {\n if (minted.size >= MAX_DISTINCT_ORIGINS_PER_DAY) continue\n minted.add(pair)\n }\n\n grouped.set(id, { n: 1, directive, origin, violation })\n }\n if (!grouped.size) return 0\n\n // Write budget: fixed one-minute window per instance.\n if (now - budgetWindowStartMs >= 60_000) {\n budgetWindowStartMs = now\n budgetUsed = 0\n }\n const room = WRITE_BUDGET_PER_MINUTE - budgetUsed\n if (room <= 0) return 0\n const entries = [...grouped.entries()].slice(0, room)\n budgetUsed += entries.length\n\n const firestore =\n options.firestore ??\n // Lazily resolved so this module stays inert at load: route specs and\n // this module's own spec inject a fake, and nothing initializes\n // firebase-admin just by importing the barrel.\n (await import('./firebase-admin')).firebaseAdmin.app().firestore()\n\n await Promise.all(\n entries.map(([id, { n, directive, origin, violation }]) =>\n firestore\n .collection(CSP_AGGREGATE_COLLECTION)\n .doc(id)\n .set(\n {\n day,\n app: options.app,\n directive,\n origin,\n disposition: sanitizePart(violation.disposition || 'report', 16),\n count: FieldValue.increment(n),\n lastSeenMs: now,\n // Samples, not keys: enough to find the surface, no power to\n // mint documents. Already clamped by the parser; clamped again\n // because this module's contract should not depend on that.\n lastPath: violation.documentPath.slice(0, 200),\n ...(options.site ? { lastSite: options.site.slice(0, 253) } : {}),\n // Timestamp-typed via Date — Firestore TTL refuses numbers.\n expiresAt: new Date(\n dayStartMs + CSP_AGGREGATE_RETENTION_DAYS * 86_400_000,\n ),\n },\n { merge: true },\n ),\n ),\n )\n return entries.length\n } catch (error) {\n // Fail-soft: the aggregate is an observer, and `console.error` rather\n // than `console.warn` on purpose — the collectors' own log lines are\n // warns, and their specs count warns.\n console.error(\n JSON.stringify({\n tag: 'AGL-1799:csp-aggregate',\n error: String(error).slice(0, 200),\n }),\n )\n return 0\n }\n}\n"],"names":["FieldValue","isDocumentId","CSP_AGGREGATE_COLLECTION","CSP_AGGREGATE_RETENTION_DAYS","MAX_DISTINCT_ORIGINS_PER_DAY","WRITE_BUDGET_PER_MINUTE","KNOWN_DIRECTIVES","Set","mintTracker","Map","MAX_TRACKED_GROUPS","budgetWindowStartMs","budgetUsed","resetCspAggregateStateForTests","clear","sanitizePart","value","max","cleaned","toLowerCase","replace","slice","cspBlockedOrigin","blockedUri","URL","host","cspAggregateDay","nowMs","Date","toISOString","normalizeDirective","directive","has","recordCspViolations","violations","options","length","now","day","dayStartMs","parse","grouped","violation","effectiveDirective","origin","disposition","id","app","join","existing","get","n","groupKey","size","minted","set","pair","add","room","entries","firestore","firebaseAdmin","Promise","all","map","collection","doc","count","increment","lastSeenMs","lastPath","documentPath","site","lastSite","expiresAt","merge","error","console","JSON","stringify","tag","String"],"mappings":"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"}
@@ -0,0 +1,66 @@
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
+ * `dataStorageMbPerOrg`, answered once for every path that writes dataset
19
+ * bytes (AGL-2253).
20
+ *
21
+ * AGL-2163 wired `checkDataStorageQuota().allowed` into `/api/orgs/datasets`
22
+ * and stopped there, so the console route refused what the REST API and the
23
+ * public form path went on accepting. The measurement and the shape logic
24
+ * lived inside that route file, which is exactly why the other two writers
25
+ * never got it — a gate that only one caller can reach is a gate the next
26
+ * caller is guaranteed to miss.
27
+ *
28
+ * The VERDICT lives here; the REFUSAL does not. `/api/orgs/datasets` answers
29
+ * a console `{ error }` 403, `/v1` answers an `ApiErrors.planRequired` body
30
+ * with a machine-readable `code`, and the tenant form path answers nothing at
31
+ * all — it drops the dataset row and still accepts the submission. Three
32
+ * correct, incompatible renderings of one decision.
33
+ *
34
+ * ## What it costs a paying customer: NOTHING
35
+ *
36
+ * Every plan with an `extraDataGbMonthlyUsd` rate resolves to
37
+ * `'never-blocks'` — the overage bills, exactly as `checkDataStorageQuota`'s
38
+ * docblock promises — so the metered case returns `null` without a single
39
+ * read and with no possibility of a refusal. Only the other two shapes cost
40
+ * anything, and one of them costs no read either.
41
+ */
42
+ export interface DataStorageRefusal {
43
+ /** The included band the write would exceed, MB. */
44
+ includedMb: number;
45
+ /**
46
+ * `'always'` — the band is zero and the plan meters nothing, so no
47
+ * measurement could change the answer and none was taken.
48
+ * `'measured'` — a finite non-zero band on a plan with no rate, decided
49
+ * against the monthly rollup.
50
+ */
51
+ basis: 'always' | 'measured';
52
+ }
53
+ /**
54
+ * Null when the write may proceed; a refusal when dataset bytes are not
55
+ * included on this org's plan.
56
+ *
57
+ * The `'measure'` shape reads `orgs/{orgId}/usage/{YYYY-MM}.dataStorageMb`
58
+ * rather than re-summing the org's datasets: `report-usage`'s
59
+ * `orgDatasetBytes` is O(datasets) reads with two aggregate queries EACH,
60
+ * which is not a per-record-write cost. The reading is therefore up to a
61
+ * month stale, and that is stated rather than hidden — it can only ever
62
+ * UNDER-refuse, never refuse a write it should have allowed. The only orgs
63
+ * that shape can reach are ones staff configured by hand
64
+ * (`entitlementOverrides.dataStorageMbPerOrg` on a plan that meters nothing).
65
+ */
66
+ export declare function dataStorageRefusal(org: unknown, orgRef: FirebaseFirestore.DocumentReference, now?: Date): Promise<DataStorageRefusal | null>;
@@ -0,0 +1,48 @@
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
+ */ import { checkDataStorageQuota, dataStorageEnforcementShape } from "@aglyn/aglyn/server";
17
+ /**
18
+ * Null when the write may proceed; a refusal when dataset bytes are not
19
+ * included on this org's plan.
20
+ *
21
+ * The `'measure'` shape reads `orgs/{orgId}/usage/{YYYY-MM}.dataStorageMb`
22
+ * rather than re-summing the org's datasets: `report-usage`'s
23
+ * `orgDatasetBytes` is O(datasets) reads with two aggregate queries EACH,
24
+ * which is not a per-record-write cost. The reading is therefore up to a
25
+ * month stale, and that is stated rather than hidden — it can only ever
26
+ * UNDER-refuse, never refuse a write it should have allowed. The only orgs
27
+ * that shape can reach are ones staff configured by hand
28
+ * (`entitlementOverrides.dataStorageMbPerOrg` on a plan that meters nothing).
29
+ */ export async function dataStorageRefusal(org, orgRef, now = new Date()) {
30
+ var _usage_get;
31
+ const shape = dataStorageEnforcementShape(org);
32
+ if (shape === 'never-blocks') return null;
33
+ if (shape === 'always-blocks') {
34
+ return {
35
+ includedMb: checkDataStorageQuota(org, 0).includedMb,
36
+ basis: 'always'
37
+ };
38
+ }
39
+ const usage = await orgRef.collection('usage').doc(now.toISOString().slice(0, 7)).get();
40
+ const quota = checkDataStorageQuota(org, Number((_usage_get = usage.get('dataStorageMb')) != null ? _usage_get : 0));
41
+ if (quota.allowed) return null;
42
+ return {
43
+ includedMb: quota.includedMb,
44
+ basis: 'measured'
45
+ };
46
+ }
47
+
48
+ //# sourceMappingURL=data-storage-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/data-storage-gate.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\nimport {\n checkDataStorageQuota,\n dataStorageEnforcementShape,\n} from '@aglyn/aglyn/server'\n\n/**\n * `dataStorageMbPerOrg`, answered once for every path that writes dataset\n * bytes (AGL-2253).\n *\n * AGL-2163 wired `checkDataStorageQuota().allowed` into `/api/orgs/datasets`\n * and stopped there, so the console route refused what the REST API and the\n * public form path went on accepting. The measurement and the shape logic\n * lived inside that route file, which is exactly why the other two writers\n * never got it — a gate that only one caller can reach is a gate the next\n * caller is guaranteed to miss.\n *\n * The VERDICT lives here; the REFUSAL does not. `/api/orgs/datasets` answers\n * a console `{ error }` 403, `/v1` answers an `ApiErrors.planRequired` body\n * with a machine-readable `code`, and the tenant form path answers nothing at\n * all — it drops the dataset row and still accepts the submission. Three\n * correct, incompatible renderings of one decision.\n *\n * ## What it costs a paying customer: NOTHING\n *\n * Every plan with an `extraDataGbMonthlyUsd` rate resolves to\n * `'never-blocks'` — the overage bills, exactly as `checkDataStorageQuota`'s\n * docblock promises — so the metered case returns `null` without a single\n * read and with no possibility of a refusal. Only the other two shapes cost\n * anything, and one of them costs no read either.\n */\nexport interface DataStorageRefusal {\n /** The included band the write would exceed, MB. */\n includedMb: number\n /**\n * `'always'` — the band is zero and the plan meters nothing, so no\n * measurement could change the answer and none was taken.\n * `'measured'` — a finite non-zero band on a plan with no rate, decided\n * against the monthly rollup.\n */\n basis: 'always' | 'measured'\n}\n\n/**\n * Null when the write may proceed; a refusal when dataset bytes are not\n * included on this org's plan.\n *\n * The `'measure'` shape reads `orgs/{orgId}/usage/{YYYY-MM}.dataStorageMb`\n * rather than re-summing the org's datasets: `report-usage`'s\n * `orgDatasetBytes` is O(datasets) reads with two aggregate queries EACH,\n * which is not a per-record-write cost. The reading is therefore up to a\n * month stale, and that is stated rather than hidden — it can only ever\n * UNDER-refuse, never refuse a write it should have allowed. The only orgs\n * that shape can reach are ones staff configured by hand\n * (`entitlementOverrides.dataStorageMbPerOrg` on a plan that meters nothing).\n */\nexport async function dataStorageRefusal(\n org: unknown,\n orgRef: FirebaseFirestore.DocumentReference,\n now: Date = new Date(),\n): Promise<DataStorageRefusal | null> {\n const shape = dataStorageEnforcementShape(org as never)\n if (shape === 'never-blocks') return null\n if (shape === 'always-blocks') {\n return {\n includedMb: checkDataStorageQuota(org as never, 0).includedMb,\n basis: 'always',\n }\n }\n const usage = await orgRef\n .collection('usage')\n .doc(now.toISOString().slice(0, 7))\n .get()\n const quota = checkDataStorageQuota(\n org as never,\n Number(usage.get('dataStorageMb') ?? 0),\n )\n if (quota.allowed) return null\n return { includedMb: quota.includedMb, basis: 'measured' }\n}\n"],"names":["checkDataStorageQuota","dataStorageEnforcementShape","dataStorageRefusal","org","orgRef","now","Date","usage","shape","includedMb","basis","collection","doc","toISOString","slice","get","quota","Number","allowed"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,qBAAqB,EACrBC,2BAA2B,QACtB,sBAAqB;AAuC5B;;;;;;;;;;;;CAYC,GACD,OAAO,eAAeC,mBACpBC,GAAY,EACZC,MAA2C,EAC3CC,MAAY,IAAIC,MAAM;QAgBbC;IAdT,MAAMC,QAAQP,4BAA4BE;IAC1C,IAAIK,UAAU,gBAAgB,OAAO;IACrC,IAAIA,UAAU,iBAAiB;QAC7B,OAAO;YACLC,YAAYT,sBAAsBG,KAAc,GAAGM,UAAU;YAC7DC,OAAO;QACT;IACF;IACA,MAAMH,QAAQ,MAAMH,OACjBO,UAAU,CAAC,SACXC,GAAG,CAACP,IAAIQ,WAAW,GAAGC,KAAK,CAAC,GAAG,IAC/BC,GAAG;IACN,MAAMC,QAAQhB,sBACZG,KACAc,QAAOV,aAAAA,MAAMQ,GAAG,CAAC,4BAAVR,aAA8B;IAEvC,IAAIS,MAAME,OAAO,EAAE,OAAO;IAC1B,OAAO;QAAET,YAAYO,MAAMP,UAAU;QAAEC,OAAO;IAAW;AAC3D"}
@@ -0,0 +1,179 @@
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 { Firestore } from 'firebase-admin/firestore';
18
+ import { type UsageSources } from './live-page-usage';
19
+ /**
20
+ * How many documents per collection the per-site scan reads.
21
+ *
22
+ * The console's publish scan uses the same number for the same reason
23
+ * (AGL-1161): this decides which caches get dropped, so a prefix scan would
24
+ * report a refreshed site and leave real pages serving the old rows. Bounded
25
+ * all the same, and when the bound bites it is SAID rather than absorbed.
26
+ */
27
+ export declare const DATASET_SCAN_LIMIT = 2000;
28
+ /**
29
+ * How many sites one record write fans out to.
30
+ *
31
+ * `MAX_SCOPE_HOSTS` is the ceiling on a NAMED selection, so only an org-wide
32
+ * dataset can reach this — an org with more sites than this pays a scan per
33
+ * site on every write otherwise, which is a cost the person writing one row
34
+ * did not ask for. Sites past the cap keep the TTL they had before this
35
+ * existed, and the shortfall is returned and logged rather than swallowed.
36
+ */
37
+ export declare const MAX_DATASET_FANOUT_HOSTS = 30;
38
+ /**
39
+ * How long one site-and-dataset pair waits before announcing again.
40
+ *
41
+ * A 1,000-row import is one announce because the importer announces after its
42
+ * loop, not per row. This covers the other burst — many small writes arriving
43
+ * separately, a form under load or an automation fanning out — where each one
44
+ * would otherwise pay a full scan per site and hand the tenant the same path
45
+ * list again.
46
+ *
47
+ * Leading edge, so the FIRST write of a burst refreshes immediately; what is
48
+ * skipped is a repeat inside the window, and the next write after it announces
49
+ * again. The cost of the skip is bounded by the window: a row written during
50
+ * one is visible no later than the next announce.
51
+ */
52
+ export declare const DATASET_ANNOUNCE_WINDOW_MS = 2000;
53
+ /** Test seam: the window is process-wide state, so a spec has to reset it. */
54
+ export declare function resetDatasetAnnounceThrottle(): void;
55
+ /** One site's share of a dataset change. */
56
+ export interface DatasetLivePageTarget {
57
+ hostId: string;
58
+ /** The tenant keys its cache on this, never on `hostId`. */
59
+ subdomain: string;
60
+ /** The attached custom domain, whose pages live under a second cache key. */
61
+ cname?: string;
62
+ /** Site-absolute addresses (`/`, `/team`) that repeat over the dataset. */
63
+ paths: string[];
64
+ /** The site held more documents than the scan read, so `paths` is partial. */
65
+ truncated: boolean;
66
+ }
67
+ export interface DatasetLivePageScope {
68
+ targets: DatasetLivePageTarget[];
69
+ /**
70
+ * Sites the fan-out cap left out. Zero in the ordinary case, and reported
71
+ * rather than logged because a caller that says "refreshed" while some sites
72
+ * were skipped is the failure this whole arc exists to remove.
73
+ */
74
+ hostsDropped: number;
75
+ /**
76
+ * Why there is nothing to announce, or `'ok'`.
77
+ *
78
+ * `'not-rendered'` is a success: a dataset no page repeats over has no live
79
+ * page to refresh. It is kept apart from `'error'` for the same reason the
80
+ * revalidate route keeps `not-routed` apart from a refusal — one means there
81
+ * was nothing to do, the other means something was left stale.
82
+ */
83
+ reason: 'ok' | 'no-dataset' | 'no-sites' | 'not-rendered' | 'error';
84
+ }
85
+ /**
86
+ * Every live screen whose rendered output repeats over the dataset, however
87
+ * indirectly.
88
+ *
89
+ * The same three-bucket closure a placed form's pages are found by, for the
90
+ * same reason: a binding is found by SEARCHING node trees rather than by
91
+ * matching a pointer, so it can sit on the screen, in the chrome of the layout
92
+ * the screen renders inside, or inside a reusable component a third component
93
+ * nests. After the first level the fan-out is identical, so it is delegated
94
+ * rather than restated.
95
+ *
96
+ * Pure, and separate from the Firestore read, so the closure is testable
97
+ * without a database.
98
+ */
99
+ export declare function screenIdsRepeatingDataset(keys: ReadonlySet<string>, sources: UsageSources): string[];
100
+ /**
101
+ * Every live page a change to this dataset's records makes stale, per site.
102
+ *
103
+ * Reads only; announcing is the caller's, because the two runtimes announce
104
+ * differently — the console posts to the tenant over the shared secret, and
105
+ * the tenant drops its own caches in process. Splitting it here is what lets
106
+ * both use one answer to the question of WHICH pages.
107
+ *
108
+ * Never throws. A scope that could not be computed is a scope of nothing, and
109
+ * the TTL underneath is unaffected.
110
+ */
111
+ export declare function datasetLivePageScope(options: {
112
+ firestore: Firestore;
113
+ orgId: string;
114
+ datasetId: string;
115
+ }): Promise<DatasetLivePageScope>;
116
+ /**
117
+ * How one runtime drops a site's cached pages.
118
+ *
119
+ * The generic seam, registered by the deployment that owns the caches. The
120
+ * tenant registers an in-process drop at boot; the console announces over the
121
+ * shared secret and passes its own dropper at the call site. Core names
122
+ * neither, exactly as it names no repeat source.
123
+ *
124
+ * Returns whether the drop landed. It must never throw: everything above it
125
+ * has already been written.
126
+ */
127
+ export type LivePageDropper = (target: DatasetLivePageTarget) => Promise<boolean>;
128
+ /**
129
+ * Registers the dropper for this process, replacing any previous one. Returns
130
+ * the unregister, which removes it only if it is still the one registered.
131
+ *
132
+ * Called from a function the platform calls BY NAME at boot, never from a
133
+ * module's top level: a bundler deletes a module imported only for its side
134
+ * effect when its package says it has none (AGL-3025).
135
+ */
136
+ export declare function registerLivePageDropper(drop: LivePageDropper): () => void;
137
+ export interface DatasetAnnounceResult {
138
+ /** Sites whose cached pages were dropped. */
139
+ sitesRefreshed: number;
140
+ /** Pages named across those sites. */
141
+ pathsRefreshed: number;
142
+ /** Sites the drop was attempted on and refused. */
143
+ sitesRefused: number;
144
+ /** Sites past the fan-out cap, and sites whose scan was truncated. */
145
+ hostsDropped: number;
146
+ scanTruncated: boolean;
147
+ /** `'throttled'` when a drop for this pair already went out just now. */
148
+ reason: DatasetLivePageScope['reason'] | 'throttled' | 'no-dropper';
149
+ }
150
+ /**
151
+ * Refresh every live page that shows this dataset's records.
152
+ *
153
+ * The one call a write path makes. BEST EFFORT, ALWAYS: it never throws and
154
+ * never rejects, so a caller can `await` it inside the try that already guards
155
+ * the write without any chance of turning a stored record into a failed one.
156
+ *
157
+ * `drop` overrides the registered dropper for callers that hold their own —
158
+ * the console, whose announce is an authenticated request rather than a local
159
+ * cache call.
160
+ */
161
+ export declare function announceDatasetRecordChange(options: {
162
+ firestore: Firestore;
163
+ orgId: string;
164
+ datasetId: string;
165
+ drop?: LivePageDropper;
166
+ }): Promise<DatasetAnnounceResult>;
167
+ /**
168
+ * One sentence for a refresh that did not cover every page, or `null` when it
169
+ * did.
170
+ *
171
+ * Beside the call rather than at each surface, so the console cannot word this
172
+ * differently in two places — the same reason `describeRevalidateShortfall`
173
+ * lives beside `revalidateLivePages`. `lead` names the act that succeeded,
174
+ * because only that word differs between surfaces: a record is SAVED, and
175
+ * telling someone who fixed a typo that they published it is a worse error
176
+ * than the staleness the sentence is about.
177
+ */
178
+ export declare function describeDatasetAnnounceShortfall(result: DatasetAnnounceResult | null, lead?: string): string | null;
179
+ export default announceDatasetRecordChange;