@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,300 @@
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
+ * Which live pages a change reaches, for a site's screens, layouts and
19
+ * components.
20
+ *
21
+ * The corpus read and the two closures over it, held here rather than in the
22
+ * console because the console is no longer the only side that needs them. A
23
+ * dataset record is written from the tenant as well — a form submission, an
24
+ * automation step — and those writes make the same pages stale as a console
25
+ * edit does. Two implementations of "which screens render this" is the shape
26
+ * that put AGL-1223 in the tree: one reader handled a node tree's second
27
+ * storage form and the other did not, so half the corpus answered "used
28
+ * nowhere" and a publish dropped no cache.
29
+ *
30
+ * Nothing here is dataset-specific, and nothing is console-specific. The
31
+ * console's `scan-artifact-usage` re-exports these and keeps the parts that
32
+ * are about the console's own "what would I break" copy.
33
+ */ import { nodesReferenceComponent, decodeStoredNodes } from "@aglyn/aglyn/server";
34
+ /** `displayName`, falling back to a legacy `name`, then the raw id. */ export function usageCandidateLabel(candidate) {
35
+ var _ref, _candidate_displayName;
36
+ return String((_ref = (_candidate_displayName = candidate.displayName) != null ? _candidate_displayName : candidate.name) != null ? _ref : candidate.id);
37
+ }
38
+ /** A soft-deleted document renders nothing, so it depends on nothing. */ export const isLiveUsageCandidate = (candidate)=>!candidate.deletedAt;
39
+ /**
40
+ * Everything that references a reusable component (AGL-703).
41
+ *
42
+ * Three places, because the renderer expands instances in three places:
43
+ * published screen versions, published layout versions, and OTHER component
44
+ * definitions — `composeReusableComponentNodes` grafts nested instances, so
45
+ * a component used only inside another component is genuinely used. Omitting
46
+ * that third scan would report "used nowhere" for it and invite a confident
47
+ * deletion, which is worse than showing nothing at all.
48
+ */ export function scanComponentUsage(componentId, sources) {
49
+ if (!componentId) return [];
50
+ const dependents = [];
51
+ const collect = (candidates, type)=>{
52
+ for (const candidate of candidates){
53
+ if (!isLiveUsageCandidate(candidate)) continue;
54
+ // A component never counts as using itself, however it nests.
55
+ if (type === 'component' && candidate.id === componentId) continue;
56
+ if (!nodesReferenceComponent(candidate.nodes, componentId)) continue;
57
+ dependents.push(_extends({
58
+ type,
59
+ id: candidate.id,
60
+ name: usageCandidateLabel(candidate),
61
+ // Instances reference by id, so a rename can never break them.
62
+ via: [
63
+ 'id'
64
+ ]
65
+ }, candidate.versionId ? {
66
+ versionId: candidate.versionId
67
+ } : {}));
68
+ }
69
+ };
70
+ collect(sources.screens, 'screen');
71
+ collect(sources.layouts, 'layout');
72
+ collect(sources.components, 'component');
73
+ return dependents;
74
+ }
75
+ /**
76
+ * Everything rendering inside a layout (AGL-703).
77
+ *
78
+ * Two kinds of dependent, both expressed by the same `layoutId` pointer:
79
+ *
80
+ * - **screens**, which name the layout they render inside;
81
+ * - **other layouts**, since a layout can itself sit inside one. A nested
82
+ * layout is a real dependent — deleting its parent unwraps every screen
83
+ * underneath it — so leaving layouts out would report a parent layout as
84
+ * used only by the screens that name it directly, and none of the ones
85
+ * that reach it through a child.
86
+ *
87
+ * A layout never counts as its own dependent; `canNestLayout` refuses that,
88
+ * and this refuses to report it even if stored data holds one.
89
+ */ export function scanLayoutUsage(layoutId, screens, layouts = []) {
90
+ if (!layoutId) return [];
91
+ const dependentsOf = (candidates, type)=>candidates.filter((candidate)=>isLiveUsageCandidate(candidate) && candidate.layoutId === layoutId && candidate.id !== layoutId).map((candidate)=>_extends({
92
+ type,
93
+ id: candidate.id,
94
+ name: usageCandidateLabel(candidate),
95
+ via: [
96
+ 'id'
97
+ ]
98
+ }, candidate.versionId ? {
99
+ versionId: candidate.versionId
100
+ } : {}));
101
+ return [
102
+ ...dependentsOf(screens, 'screen'),
103
+ ...dependentsOf(layouts, 'layout')
104
+ ];
105
+ }
106
+ /**
107
+ * Every live screen rendered inside `layoutId`, at ANY nesting depth
108
+ * (AGL-1150).
109
+ *
110
+ * `scanLayoutUsage` answers one level. Layouts nest — a screen points at a
111
+ * layout, which can point at a parent layout, and `compose-screen-nodes` walks
112
+ * that whole chain when composing a page. So publishing a layout changes every
113
+ * screen below it, not just the ones bound to it directly, and a cache drop
114
+ * that only handles the direct level leaves the rest showing stale chrome for
115
+ * the full revalidate window.
116
+ *
117
+ * Pure, and separate from the Firestore read, so the nesting behaviour is
118
+ * testable without a database.
119
+ *
120
+ * Cycle-safe. `canNestLayout` refuses to create a cycle, but a document written
121
+ * straight to Firestore is not bound by that, and a cycle here would hang a
122
+ * publish request rather than surface anything.
123
+ */ export function screenIdsUsingLayoutDeep(layoutId, screens, layouts = []) {
124
+ if (!layoutId) return [];
125
+ const screenIds = new Set();
126
+ const seenLayouts = new Set([
127
+ layoutId
128
+ ]);
129
+ let frontier = [
130
+ layoutId
131
+ ];
132
+ while(frontier.length){
133
+ const next = [];
134
+ for (const id of frontier){
135
+ for (const dependent of scanLayoutUsage(id, screens, layouts)){
136
+ if (dependent.type === 'screen') {
137
+ screenIds.add(dependent.id);
138
+ } else if (!seenLayouts.has(dependent.id)) {
139
+ seenLayouts.add(dependent.id);
140
+ next.push(dependent.id);
141
+ }
142
+ }
143
+ }
144
+ frontier = next;
145
+ }
146
+ return [
147
+ ...screenIds
148
+ ];
149
+ }
150
+ /**
151
+ * Every live screen whose rendered output contains `componentId`, however
152
+ * indirectly (AGL-1161).
153
+ *
154
+ * `scanComponentUsage` answers one level and returns three kinds of dependent.
155
+ * Only one of them is a screen, and the other two both reach screens by routes
156
+ * a single-level scan cannot see:
157
+ *
158
+ * - a **component** dependent nests the target inside itself, and that outer
159
+ * component may itself only be used inside a third — so component→component
160
+ * edges have to be followed to a fixed point;
161
+ * - a **layout** dependent puts the component in page chrome, which every
162
+ * screen under that layout renders. Layouts nest, so that is
163
+ * `screenIdsUsingLayoutDeep`, not a direct `layoutId` match.
164
+ *
165
+ * Miss either and a publish reports success while some pages keep serving the
166
+ * old component for the full revalidate window — the failure this whole arc
167
+ * exists to remove, and the one that is hardest to notice because the pages
168
+ * that ARE dropped update instantly.
169
+ *
170
+ * Pure, and separate from the Firestore read, so the closure is testable
171
+ * without a database — the same split `screenIdsUsingLayoutDeep` uses.
172
+ *
173
+ * Cycle-safe. `composeReusableComponentNodes` would not survive a cycle, but a
174
+ * document written straight to Firestore is not bound by what the editor
175
+ * allows, and a cycle here would hang a publish rather than surface anything.
176
+ */ export function screenIdsUsingComponentDeep(componentId, sources) {
177
+ if (!componentId) return [];
178
+ const screenIds = new Set();
179
+ const seenComponents = new Set([
180
+ componentId
181
+ ]);
182
+ // Layouts are resolved through their own deep walk, so remember which ones
183
+ // have already been expanded: two components in the same layout would
184
+ // otherwise re-walk the whole layout tree once each.
185
+ const seenLayouts = new Set();
186
+ let frontier = [
187
+ componentId
188
+ ];
189
+ while(frontier.length){
190
+ const next = [];
191
+ for (const id of frontier){
192
+ for (const dependent of scanComponentUsage(id, sources)){
193
+ if (dependent.type === 'screen') {
194
+ screenIds.add(dependent.id);
195
+ } else if (dependent.type === 'layout') {
196
+ if (seenLayouts.has(dependent.id)) continue;
197
+ seenLayouts.add(dependent.id);
198
+ // The layout itself renders no URL; the screens beneath it do.
199
+ for (const screenId of screenIdsUsingLayoutDeep(dependent.id, sources.screens, sources.layouts)){
200
+ screenIds.add(screenId);
201
+ }
202
+ } else if (!seenComponents.has(dependent.id)) {
203
+ seenComponents.add(dependent.id);
204
+ next.push(dependent.id);
205
+ }
206
+ }
207
+ }
208
+ frontier = next;
209
+ }
210
+ return [
211
+ ...screenIds
212
+ ];
213
+ }
214
+ /**
215
+ * One collection's documents, with published nodes attached when the scan
216
+ * needs to search them.
217
+ *
218
+ * `limit` is a real bound, not a guess: it is fetched with one extra document
219
+ * so exceeding it is DETECTED rather than assumed away. A caller that ignores
220
+ * `truncated` is choosing to be wrong quietly.
221
+ */ export async function readUsageCandidates(hostRef, collectionName, options) {
222
+ const { withNodes, limit } = options;
223
+ // One over the limit: if the extra document comes back, there was more than
224
+ // we are about to look at. Cheaper than a count() and exact.
225
+ const docs = await hostRef.collection(collectionName).limit(limit + 1).get();
226
+ const truncated = docs.size > limit;
227
+ const inScope = truncated ? docs.docs.slice(0, limit) : docs.docs;
228
+ const candidates = await Promise.all(inScope.map(async (docSnapshot)=>{
229
+ const versionId = docSnapshot.get('versionId');
230
+ // Components keep their tree on the document; screens and layouts keep
231
+ // it on the published version.
232
+ //
233
+ // BOTH reads are decoded (AGL-1223). A component document is msgpack
234
+ // for anything promoted since components were compressed and a plain
235
+ // map for everything older, and `decodeStoredNodes` returns a map
236
+ // unchanged — so the branch below cannot be simplified back into a raw
237
+ // read on either side.
238
+ //
239
+ // What rides on this: every consumer walks the value with
240
+ // `Object.values`, which over a `Buffer` yields byte NUMBERS and
241
+ // matches nothing. The answer that produces is "used nowhere" — for
242
+ // `/api/hosts/where-used` an invitation to delete something a live page
243
+ // renders, and for `/api/screens/revalidate` a publish that drops no
244
+ // cache and leaves the old page served.
245
+ const nodes = collectionName === 'components' ? decodeStoredNodes(docSnapshot.get('nodes')) : withNodes && versionId ? await docSnapshot.ref.collection('versions').doc(String(versionId)).get().then((version)=>decodeStoredNodes(version.get('nodes'))).catch(()=>null) : null;
246
+ return _extends({
247
+ id: docSnapshot.id,
248
+ displayName: docSnapshot.get('displayName'),
249
+ name: docSnapshot.get('name'),
250
+ deletedAt: docSnapshot.get('deletedAt'),
251
+ nodes
252
+ }, versionId ? {
253
+ versionId: String(versionId)
254
+ } : {}, docSnapshot.get('layoutId') ? {
255
+ layoutId: String(docSnapshot.get('layoutId'))
256
+ } : {}, docSnapshot.get('parentId') ? {
257
+ parentId: String(docSnapshot.get('parentId'))
258
+ } : {}, collectionName === 'components' && Array.isArray(docSnapshot.get('props')) ? {
259
+ props: docSnapshot.get('props')
260
+ } : {});
261
+ }));
262
+ return {
263
+ candidates,
264
+ truncated
265
+ };
266
+ }
267
+ /**
268
+ * Every screen, layout and component of a site, with their node trees — the
269
+ * corpus every tree-searching closure above walks.
270
+ *
271
+ * Each collection ONCE, in memory: a query per level would multiply round
272
+ * trips by the nesting depth of the graph. Shared so two scans cannot read
273
+ * different corpora under different bounds, and a change to one cannot quietly
274
+ * narrow the other.
275
+ */ export async function readUsageSources(hostRef, limit) {
276
+ const [screens, layouts, components] = await Promise.all([
277
+ readUsageCandidates(hostRef, 'screens', {
278
+ withNodes: true,
279
+ limit
280
+ }),
281
+ readUsageCandidates(hostRef, 'layouts', {
282
+ withNodes: true,
283
+ limit
284
+ }),
285
+ readUsageCandidates(hostRef, 'components', {
286
+ withNodes: true,
287
+ limit
288
+ })
289
+ ]);
290
+ return {
291
+ candidates: {
292
+ screens: screens.candidates,
293
+ layouts: layouts.candidates,
294
+ components: components.candidates
295
+ },
296
+ truncated: screens.truncated || layouts.truncated || components.truncated
297
+ };
298
+ }
299
+
300
+ //# sourceMappingURL=live-page-usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/live-page-usage.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 * Which live pages a change reaches, for a site's screens, layouts and\n * components.\n *\n * The corpus read and the two closures over it, held here rather than in the\n * console because the console is no longer the only side that needs them. A\n * dataset record is written from the tenant as well — a form submission, an\n * automation step — and those writes make the same pages stale as a console\n * edit does. Two implementations of \"which screens render this\" is the shape\n * that put AGL-1223 in the tree: one reader handled a node tree's second\n * storage form and the other did not, so half the corpus answered \"used\n * nowhere\" and a publish dropped no cache.\n *\n * Nothing here is dataset-specific, and nothing is console-specific. The\n * console's `scan-artifact-usage` re-exports these and keeps the parts that\n * are about the console's own \"what would I break\" copy.\n */\n\nimport {\n nodesReferenceComponent,\n decodeStoredNodes,\n type ReusableComponentProp,\n} from '@aglyn/aglyn/server'\n\n/** A screen/layout/component reduced to what a usage scan needs. */\nexport interface UsageCandidate {\n id: string\n displayName?: string\n /** Legacy field some older documents used instead of `displayName`. */\n name?: string\n deletedAt?: unknown\n /**\n * Node tree to search. For screens and layouts this is the PUBLISHED\n * version's nodes (what visitors see); for components it is the definition\n * tree off the component document, which is what the runtime reads.\n */\n nodes?: Record<string, any> | null\n /** Published version, carried through so the caller can deep-link. */\n versionId?: string\n /** Screens only: the layout they render inside. */\n layoutId?: string\n /** Screens only: the screen they nest under, which is part of their path. */\n parentId?: string\n /**\n * Components only: the properties the definition declares (AGL-1247). A\n * Link property's default renders as a link wherever an instance leaves the\n * property unset, and it is stored here, not in `nodes` (AGL-2846).\n */\n props?: ReadonlyArray<ReusableComponentProp | null | undefined> | null\n}\n\n/** One document that depends on the artifact a scan was asked about. */\nexport interface UsageDependent {\n type: 'screen' | 'layout' | 'component' | 'collection'\n id: string\n name: string\n via: Array<'id' | 'name'>\n versionId?: string\n /**\n * HOW the dependent references the artifact — screens and collection\n * listings (AGL-703, AGL-2806).\n *\n * A component or a layout has exactly one kind of dependent and the noun\n * says everything: an instance, or a binding. A screen has three, and they\n * break in three different ways — a link goes dead, a child moves, a\n * collection loses the page it renders through. Copy that could not tell\n * them apart would have to describe the worst case every time. A collection\n * listing's dependents are all links, and say so.\n */\n relation?: 'link' | 'child' | 'template'\n}\n\n/** The three corpora every closure below walks. */\nexport interface UsageSources {\n screens: UsageCandidate[]\n layouts: UsageCandidate[]\n components: UsageCandidate[]\n}\n\n/** `displayName`, falling back to a legacy `name`, then the raw id. */\nexport function usageCandidateLabel(candidate: UsageCandidate): string {\n return String(candidate.displayName ?? candidate.name ?? candidate.id)\n}\n\n/** A soft-deleted document renders nothing, so it depends on nothing. */\nexport const isLiveUsageCandidate = (candidate: UsageCandidate): boolean =>\n !candidate.deletedAt\n\n/**\n * Everything that references a reusable component (AGL-703).\n *\n * Three places, because the renderer expands instances in three places:\n * published screen versions, published layout versions, and OTHER component\n * definitions — `composeReusableComponentNodes` grafts nested instances, so\n * a component used only inside another component is genuinely used. Omitting\n * that third scan would report \"used nowhere\" for it and invite a confident\n * deletion, which is worse than showing nothing at all.\n */\nexport function scanComponentUsage(\n componentId: string,\n sources: UsageSources,\n): UsageDependent[] {\n if (!componentId) return []\n const dependents: UsageDependent[] = []\n const collect = (\n candidates: UsageCandidate[],\n type: UsageDependent['type'],\n ) => {\n for (const candidate of candidates) {\n if (!isLiveUsageCandidate(candidate)) continue\n // A component never counts as using itself, however it nests.\n if (type === 'component' && candidate.id === componentId) continue\n if (!nodesReferenceComponent(candidate.nodes, componentId)) continue\n dependents.push({\n type,\n id: candidate.id,\n name: usageCandidateLabel(candidate),\n // Instances reference by id, so a rename can never break them.\n via: ['id'],\n ...(candidate.versionId ? { versionId: candidate.versionId } : {}),\n })\n }\n }\n collect(sources.screens, 'screen')\n collect(sources.layouts, 'layout')\n collect(sources.components, 'component')\n return dependents\n}\n\n/**\n * Everything rendering inside a layout (AGL-703).\n *\n * Two kinds of dependent, both expressed by the same `layoutId` pointer:\n *\n * - **screens**, which name the layout they render inside;\n * - **other layouts**, since a layout can itself sit inside one. A nested\n * layout is a real dependent — deleting its parent unwraps every screen\n * underneath it — so leaving layouts out would report a parent layout as\n * used only by the screens that name it directly, and none of the ones\n * that reach it through a child.\n *\n * A layout never counts as its own dependent; `canNestLayout` refuses that,\n * and this refuses to report it even if stored data holds one.\n */\nexport function scanLayoutUsage(\n layoutId: string,\n screens: UsageCandidate[],\n layouts: UsageCandidate[] = [],\n): UsageDependent[] {\n if (!layoutId) return []\n const dependentsOf = (\n candidates: UsageCandidate[],\n type: 'screen' | 'layout',\n ) =>\n candidates\n .filter(\n (candidate) =>\n isLiveUsageCandidate(candidate) &&\n candidate.layoutId === layoutId &&\n candidate.id !== layoutId,\n )\n .map((candidate) => ({\n type,\n id: candidate.id,\n name: usageCandidateLabel(candidate),\n via: ['id' as const],\n ...(candidate.versionId ? { versionId: candidate.versionId } : {}),\n }))\n return [\n ...dependentsOf(screens, 'screen'),\n ...dependentsOf(layouts, 'layout'),\n ]\n}\n\n/**\n * Every live screen rendered inside `layoutId`, at ANY nesting depth\n * (AGL-1150).\n *\n * `scanLayoutUsage` answers one level. Layouts nest — a screen points at a\n * layout, which can point at a parent layout, and `compose-screen-nodes` walks\n * that whole chain when composing a page. So publishing a layout changes every\n * screen below it, not just the ones bound to it directly, and a cache drop\n * that only handles the direct level leaves the rest showing stale chrome for\n * the full revalidate window.\n *\n * Pure, and separate from the Firestore read, so the nesting behaviour is\n * testable without a database.\n *\n * Cycle-safe. `canNestLayout` refuses to create a cycle, but a document written\n * straight to Firestore is not bound by that, and a cycle here would hang a\n * publish request rather than surface anything.\n */\nexport function screenIdsUsingLayoutDeep(\n layoutId: string,\n screens: UsageCandidate[],\n layouts: UsageCandidate[] = [],\n): string[] {\n if (!layoutId) return []\n const screenIds = new Set<string>()\n const seenLayouts = new Set<string>([layoutId])\n let frontier = [layoutId]\n\n while (frontier.length) {\n const next: string[] = []\n for (const id of frontier) {\n for (const dependent of scanLayoutUsage(id, screens, layouts)) {\n if (dependent.type === 'screen') {\n screenIds.add(dependent.id)\n } else if (!seenLayouts.has(dependent.id)) {\n seenLayouts.add(dependent.id)\n next.push(dependent.id)\n }\n }\n }\n frontier = next\n }\n\n return [...screenIds]\n}\n\n/**\n * Every live screen whose rendered output contains `componentId`, however\n * indirectly (AGL-1161).\n *\n * `scanComponentUsage` answers one level and returns three kinds of dependent.\n * Only one of them is a screen, and the other two both reach screens by routes\n * a single-level scan cannot see:\n *\n * - a **component** dependent nests the target inside itself, and that outer\n * component may itself only be used inside a third — so component→component\n * edges have to be followed to a fixed point;\n * - a **layout** dependent puts the component in page chrome, which every\n * screen under that layout renders. Layouts nest, so that is\n * `screenIdsUsingLayoutDeep`, not a direct `layoutId` match.\n *\n * Miss either and a publish reports success while some pages keep serving the\n * old component for the full revalidate window — the failure this whole arc\n * exists to remove, and the one that is hardest to notice because the pages\n * that ARE dropped update instantly.\n *\n * Pure, and separate from the Firestore read, so the closure is testable\n * without a database — the same split `screenIdsUsingLayoutDeep` uses.\n *\n * Cycle-safe. `composeReusableComponentNodes` would not survive a cycle, but a\n * document written straight to Firestore is not bound by what the editor\n * allows, and a cycle here would hang a publish rather than surface anything.\n */\nexport function screenIdsUsingComponentDeep(\n componentId: string,\n sources: UsageSources,\n): string[] {\n if (!componentId) return []\n const screenIds = new Set<string>()\n const seenComponents = new Set<string>([componentId])\n // Layouts are resolved through their own deep walk, so remember which ones\n // have already been expanded: two components in the same layout would\n // otherwise re-walk the whole layout tree once each.\n const seenLayouts = new Set<string>()\n let frontier = [componentId]\n\n while (frontier.length) {\n const next: string[] = []\n for (const id of frontier) {\n for (const dependent of scanComponentUsage(id, sources)) {\n if (dependent.type === 'screen') {\n screenIds.add(dependent.id)\n } else if (dependent.type === 'layout') {\n if (seenLayouts.has(dependent.id)) continue\n seenLayouts.add(dependent.id)\n // The layout itself renders no URL; the screens beneath it do.\n for (const screenId of screenIdsUsingLayoutDeep(\n dependent.id,\n sources.screens,\n sources.layouts,\n )) {\n screenIds.add(screenId)\n }\n } else if (!seenComponents.has(dependent.id)) {\n seenComponents.add(dependent.id)\n next.push(dependent.id)\n }\n }\n }\n frontier = next\n }\n\n return [...screenIds]\n}\n\nexport interface UsageCandidateRead {\n candidates: UsageCandidate[]\n /**\n * The collection held more documents than `limit` allowed, so the scan below\n * it is INCOMPLETE.\n *\n * Returned rather than logged, because the two callers owe the user\n * different things: an advisory \"what would I break\" can show a partial\n * answer and say so, while a cache drop that silently scans a prefix reports\n * a successful publish and leaves real pages stale.\n */\n truncated: boolean\n}\n\n/**\n * One collection's documents, with published nodes attached when the scan\n * needs to search them.\n *\n * `limit` is a real bound, not a guess: it is fetched with one extra document\n * so exceeding it is DETECTED rather than assumed away. A caller that ignores\n * `truncated` is choosing to be wrong quietly.\n */\nexport async function readUsageCandidates(\n hostRef: FirebaseFirestore.DocumentReference,\n collectionName: 'screens' | 'layouts' | 'components',\n options: { withNodes: boolean; limit: number },\n): Promise<UsageCandidateRead> {\n const { withNodes, limit } = options\n // One over the limit: if the extra document comes back, there was more than\n // we are about to look at. Cheaper than a count() and exact.\n const docs = await hostRef.collection(collectionName).limit(limit + 1).get()\n const truncated = docs.size > limit\n const inScope = truncated ? docs.docs.slice(0, limit) : docs.docs\n\n const candidates = await Promise.all(\n inScope.map(async (docSnapshot) => {\n const versionId = docSnapshot.get('versionId')\n // Components keep their tree on the document; screens and layouts keep\n // it on the published version.\n //\n // BOTH reads are decoded (AGL-1223). A component document is msgpack\n // for anything promoted since components were compressed and a plain\n // map for everything older, and `decodeStoredNodes` returns a map\n // unchanged — so the branch below cannot be simplified back into a raw\n // read on either side.\n //\n // What rides on this: every consumer walks the value with\n // `Object.values`, which over a `Buffer` yields byte NUMBERS and\n // matches nothing. The answer that produces is \"used nowhere\" — for\n // `/api/hosts/where-used` an invitation to delete something a live page\n // renders, and for `/api/screens/revalidate` a publish that drops no\n // cache and leaves the old page served.\n const nodes =\n collectionName === 'components'\n ? decodeStoredNodes(docSnapshot.get('nodes'))\n : withNodes && versionId\n ? await docSnapshot.ref\n .collection('versions')\n .doc(String(versionId))\n .get()\n .then((version) => decodeStoredNodes(version.get('nodes')))\n .catch(() => null)\n : null\n return {\n id: docSnapshot.id,\n displayName: docSnapshot.get('displayName'),\n name: docSnapshot.get('name'),\n deletedAt: docSnapshot.get('deletedAt'),\n nodes,\n ...(versionId ? { versionId: String(versionId) } : {}),\n ...(docSnapshot.get('layoutId')\n ? { layoutId: String(docSnapshot.get('layoutId')) }\n : {}),\n // Screens only, and only the screen scan reads it: a child screen is\n // a dependent of its parent, because its PATH is built from it\n // (AGL-703).\n ...(docSnapshot.get('parentId')\n ? { parentId: String(docSnapshot.get('parentId')) }\n : {}),\n // Components only: the declared properties, whose Link defaults render\n // as links wherever an instance leaves one unset (AGL-2846). The same\n // document the tree came from, so it costs no read.\n ...(collectionName === 'components' &&\n Array.isArray(docSnapshot.get('props'))\n ? { props: docSnapshot.get('props') }\n : {}),\n } satisfies UsageCandidate\n }),\n )\n\n return { candidates, truncated }\n}\n\n/**\n * Every screen, layout and component of a site, with their node trees — the\n * corpus every tree-searching closure above walks.\n *\n * Each collection ONCE, in memory: a query per level would multiply round\n * trips by the nesting depth of the graph. Shared so two scans cannot read\n * different corpora under different bounds, and a change to one cannot quietly\n * narrow the other.\n */\nexport async function readUsageSources(\n hostRef: FirebaseFirestore.DocumentReference,\n limit: number,\n): Promise<{ candidates: UsageSources; truncated: boolean }> {\n const [screens, layouts, components] = await Promise.all([\n readUsageCandidates(hostRef, 'screens', { withNodes: true, limit }),\n readUsageCandidates(hostRef, 'layouts', { withNodes: true, limit }),\n readUsageCandidates(hostRef, 'components', { withNodes: true, limit }),\n ])\n return {\n candidates: {\n screens: screens.candidates,\n layouts: layouts.candidates,\n components: components.candidates,\n },\n truncated: screens.truncated || layouts.truncated || components.truncated,\n }\n}\n"],"names":["nodesReferenceComponent","decodeStoredNodes","usageCandidateLabel","candidate","String","displayName","name","id","isLiveUsageCandidate","deletedAt","scanComponentUsage","componentId","sources","dependents","collect","candidates","type","nodes","push","via","versionId","screens","layouts","components","scanLayoutUsage","layoutId","dependentsOf","filter","map","screenIdsUsingLayoutDeep","screenIds","Set","seenLayouts","frontier","length","next","dependent","add","has","screenIdsUsingComponentDeep","seenComponents","screenId","readUsageCandidates","hostRef","collectionName","options","withNodes","limit","docs","collection","get","truncated","size","inScope","slice","Promise","all","docSnapshot","ref","doc","then","version","catch","parentId","Array","isArray","props","readUsageSources"],"mappings":"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"}
@@ -0,0 +1,313 @@
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
+ * Server half of the lockdown system (AGL-1501): the verdict every
19
+ * enforcement chokepoint asks for. The pure shape/precedence logic lives in
20
+ * `@aglyn/aglyn` `app-utils/lockdown.ts`; this module adds the Admin-SDK
21
+ * reads and the two properties only the server can hold:
22
+ *
23
+ * **The un-panic invariant.** A verified `staff` claim bypasses EVERY scope,
24
+ * unconditionally, before any read happens. A platform-wide lockdown must
25
+ * never lock out the staff who can lift it — a panic button that panics its
26
+ * own operator is worse than none. Nothing may be added to
27
+ * `getLockdownVerdict` above the staff check.
28
+ *
29
+ * **Fail-open on infrastructure error.** If the `lockdowns/platform` read
30
+ * throws (Firestore outage, emulator without the collection), the verdict is
31
+ * "not locked": an infrastructure blip must not weld the whole platform
32
+ * shut. The same posture as the sanctions gate on an absent geo signal
33
+ * (AGL-1492) and the release-flag reader on a missing template.
34
+ *
35
+ * **…except for takedowns (AGL-1621).** One class of lock inverts that cost
36
+ * function: a legal or abuse takedown must keep holding when Firestore is
37
+ * unreachable, because "we served it, our database was down" answers no
38
+ * court order. The class is an EXPLICIT stored field (`enforcement`), never
39
+ * inferred from scope or reason, and absent means `standard` — so every
40
+ * lock that exists today, and every lock an operator does not classify,
41
+ * keeps failing open exactly as before. See the takedown ledger below for
42
+ * how a classification survives the read that loses it.
43
+ */
44
+ import { type LockdownFeatureKey, type LockdownIntent, type LockdownNotice, type LockdownState, normalizeHostLockdown, normalizeOrgLockdown } from '@aglyn/aglyn/server';
45
+ /**
46
+ * Bounded like the user and domain caches, and for the same reason: two of
47
+ * the four keyed readers take their key from caller-controlled input.
48
+ * Eviction is least-recently-USED.
49
+ */
50
+ export declare const TAKEDOWN_LEDGER_MAX = 5000;
51
+ /**
52
+ * Forget every remembered takedown. NOT part of the panic path and not
53
+ * called by the admin route — this exists for process-boundary and test
54
+ * setup, where a ledger surviving between cases would make one test's lock
55
+ * another test's fail-closed verdict.
56
+ */
57
+ export declare function resetTakedownLedger(): void;
58
+ /**
59
+ * Record what a SUCCESSFUL site read saw, per scope. Same contract as the
60
+ * four `lockdowns/*` readers: a takedown is remembered, and anything else —
61
+ * no lock, an ordinary lock, a takedown DOWNGRADED to one — retires the
62
+ * entry, which is how a lift takes effect.
63
+ */
64
+ export declare function rememberSiteTakedown(hostId: string, states: {
65
+ org: LockdownState | null;
66
+ host: LockdownState | null;
67
+ }): void;
68
+ /**
69
+ * What a FAILED site read may still serve: the remembered, still-active
70
+ * takedowns at org and host scope, or nulls — which is the shipped fail-open
71
+ * answer and therefore the default for every lock that is not an observed
72
+ * takedown.
73
+ */
74
+ export declare function heldSiteTakedown(hostId: string, nowMs: number): {
75
+ org: LockdownState | null;
76
+ host: LockdownState | null;
77
+ };
78
+ /**
79
+ * Drop the in-process platform cache. Called by /api/admin/lockdown after a
80
+ * platform write so the process that took the action serves fresh verdicts
81
+ * immediately; other processes converge within PLATFORM_TTL_MS.
82
+ */
83
+ export declare function invalidatePlatformLockdownCache(): void;
84
+ /** `lockdowns/platform`, normalized; null = not locked (incl. on error). */
85
+ export declare function getPlatformLockdown(): Promise<LockdownState | null>;
86
+ /** Drop the in-process feature cache (all keys) after an admin write. */
87
+ export declare function invalidateFeatureLockdownCache(): void;
88
+ /** `lockdowns/feature--{key}`, normalized; null = not locked (incl. error). */
89
+ export declare function getFeatureLockdown(feature: LockdownFeatureKey): Promise<LockdownState | null>;
90
+ /**
91
+ * `lockdowns/feature--{key}--org--{orgId}` (AGL-2927): the same capability
92
+ * paused for ONE workspace. Same cache, same TTL, same fail-open posture as
93
+ * the platform-wide document; null = not paused for that org.
94
+ */
95
+ export declare function getOrgFeatureLockdown(feature: LockdownFeatureKey, orgId: string): Promise<LockdownState | null>;
96
+ /**
97
+ * The feature verdict and refusal in one call (AGL-1510) — the one-line
98
+ * wiring a feature chokepoint carries:
99
+ *
100
+ * const locked = await featureLockdownRefusal({ feature: 'checkout', staff })
101
+ * if (locked) return locked
102
+ *
103
+ * Composition, not ranking: a PLATFORM lock implies every feature, so it is
104
+ * checked first (TTL-cached — routes that already ran the scope verdict pay
105
+ * no extra read), and the platform-scope staff bypass there is UNCHANGED
106
+ * and unconditional. The feature doc is checked second, and its staff
107
+ * bypass is per-feature (`lockdownFeatureStaffBypass`): granted where a
108
+ * staff action aids incident response (uploads, installs, ai-assist),
109
+ * withheld where it would BE the incident (checkout — a staff checkout
110
+ * session is still a real charge). A feature lock implies nothing about the
111
+ * org/host/user scopes — those stay with `lockdownRefusal` at the routes
112
+ * that carry it.
113
+ *
114
+ * A door that names the org it serves passes `orgId` (AGL-2927), and the
115
+ * workspace-scoped carrier for the same feature is checked third, under the
116
+ * same per-feature staff bypass: the staff AI pause is a spend stop on one
117
+ * customer, and a staff call made to verify the pause is ours to spend. A
118
+ * door that names no org — the plugin dispatcher, which gates by path
119
+ * before the body is read — sees the platform-wide document only, which is
120
+ * what it saw before.
121
+ */
122
+ export declare function featureLockdownRefusal(options: {
123
+ feature: LockdownFeatureKey;
124
+ /** Verified `staff` custom claim — from a VERIFIED token only. */
125
+ staff?: boolean;
126
+ /** The workspace the request is metered against, where the door knows it. */
127
+ orgId?: string | null;
128
+ nowMs?: number;
129
+ }): Promise<Response | null>;
130
+ /**
131
+ * User-doc cache (AGL-1522): the last per-call read on the verdict path.
132
+ * AGL-1506 wired the verdict into ~36 routes plus the session mint/exchange,
133
+ * and every call carrying a `uid` paid one Firestore get on
134
+ * `lockdowns/user--{uid}` — an active console editor generated one read per
135
+ * wired mutation, against the AGL-1302 read budget. Same TTL, same pending
136
+ * dedupe, same fail-open posture as the platform and feature caches above —
137
+ * ONE cache pattern in this module, not three implementations.
138
+ *
139
+ * The staleness tradeoff, stated: a user locked mid-window keeps passing
140
+ * this check for up to PLATFORM_TTL_MS (15s) plus the caller's polling
141
+ * cadence — on every process EXCEPT the one that took the action, which
142
+ * refuses immediately because /api/admin/lockdown invalidates this uid's
143
+ * entry after the write (the platform/feature invalidation hook, mirrored).
144
+ * That bound is acceptable here for the same reason `media/sign` omits the
145
+ * user scope entirely: the hard kill never rode this read. A user lock also
146
+ * disables the Auth account and revokes refresh tokens, so the ID token
147
+ * stops refreshing and the session cookie dies at its next
148
+ * `verifySessionCookie(…, true)` — the cache can only delay the DISTINCT
149
+ * 423 body, not access itself, and 15s matches the platform scope's stated
150
+ * worst case and the drill's measured flip times.
151
+ *
152
+ * Bounded so a scan of uids cannot balloon a warm process: past
153
+ * USER_LOCKDOWN_CACHE_MAX entries the least-recently-USED is evicted (a
154
+ * cache hit refreshes recency, so active sessions survive a scan).
155
+ */
156
+ export declare const USER_LOCKDOWN_CACHE_MAX = 5000;
157
+ /**
158
+ * Drop one uid's cached verdict (after a user lock/unlock write), or the
159
+ * whole cache when called bare. The acting process serves a fresh verdict
160
+ * immediately; other processes converge within PLATFORM_TTL_MS.
161
+ */
162
+ export declare function invalidateUserLockdownCache(uid?: string): void;
163
+ /** `lockdowns/user--{uid}`, normalized; null = not locked (incl. on error). */
164
+ export declare function getUserLockdown(uid: string): Promise<LockdownState | null>;
165
+ /**
166
+ * DOMAIN-lock cache (AGL-1513), same shape and same TTL as the user cache.
167
+ *
168
+ * Bounded for the same reason and one more: the key is a HOSTNAME taken from
169
+ * the request, so an unbounded map here would be attacker-keyed by anyone who
170
+ * can point a DNS record at us — which, for the hijack incident this scope
171
+ * exists for, is the adversary by definition.
172
+ */
173
+ export declare const DOMAIN_LOCKDOWN_CACHE_MAX = 5000;
174
+ /**
175
+ * Drop one hostname's cached verdict (after a domain lock/unlock write), or
176
+ * the whole cache when called bare.
177
+ */
178
+ export declare function invalidateDomainLockdownCache(hostname?: string): void;
179
+ /**
180
+ * `lockdowns/domain--{hostname}`, normalized; null = not locked (incl. on
181
+ * error), matching every other reader here — an unreachable Firestore is an
182
+ * outage, not a lockdown.
183
+ */
184
+ export declare function getDomainLockdown(hostname: string): Promise<LockdownState | null>;
185
+ export interface LockdownVerdictOptions {
186
+ /**
187
+ * The verified `staff` custom claim of the caller — from a VERIFIED token
188
+ * only, never from a header or body. True bypasses every scope: this is
189
+ * the un-panic invariant.
190
+ */
191
+ staff?: boolean;
192
+ /** Verified caller uid; enables the user scope. */
193
+ uid?: string | null;
194
+ /**
195
+ * Already-loaded org doc (or its suspension fields), when the caller has
196
+ * one in hand — org scope costs no extra read. Absent = scope not
197
+ * evaluated.
198
+ */
199
+ org?: Parameters<typeof normalizeOrgLockdown>[0];
200
+ /** Already-loaded host doc; same contract as `org`. */
201
+ host?: Parameters<typeof normalizeHostLockdown>[0];
202
+ /**
203
+ * READ-ONLY discrimination (AGL-1511). A `read-only` lock refuses writes
204
+ * and passes reads; a `full` lock refuses both, so this changes nothing
205
+ * for every lock written before the mode existed.
206
+ *
207
+ * The request the chokepoint is answering — its METHOD decides. Passing
208
+ * the route's own `request` is the wiring, because a single console
209
+ * handler is usually exported as both GET and POST and the verdict runs
210
+ * before the method branch: derive the intent, never restate it.
211
+ */
212
+ request?: {
213
+ method?: string;
214
+ } | null;
215
+ /**
216
+ * Explicit intent, overriding `request`. For the routes whose method lies
217
+ * about what they do — a POST that is really a query (`where-used`,
218
+ * `plugin-impact`), or a GET that mutates.
219
+ *
220
+ * Neither given = `write`, the fail-safe: a chokepoint that never declared
221
+ * its intent refuses during a migration rather than letting an
222
+ * unconsidered write race the repair.
223
+ */
224
+ intent?: LockdownIntent;
225
+ nowMs?: number;
226
+ }
227
+ /**
228
+ * The one verdict: the lockdown that REFUSES this caller's request, or null.
229
+ * Precedence platform > org > host > user, strictness before width (from
230
+ * `resolveLockdown`).
231
+ *
232
+ * "Refuses this request" rather than "covers this caller" since AGL-1511: a
233
+ * read-only lock is active but does not refuse a read, and returning it
234
+ * anyway would make every wired chokepoint 423 a GET — a read-only mode that
235
+ * behaves exactly like a full one. The active state is still what
236
+ * `resolveLockdown` chose, so the staff probe and the notice copy see the
237
+ * real lock; only the ANSWER is intent-aware.
238
+ */
239
+ export declare function getLockdownVerdict(options: LockdownVerdictOptions): Promise<LockdownState | null>;
240
+ /**
241
+ * The refusal an API route returns for a locked caller: **423 Locked** with
242
+ * a machine-readable body, so an API consumer sees "suspended: billing" and
243
+ * not a mystery 403. Carries only the sanitized, user-facing subset — never
244
+ * the actor uid or staff rationale.
245
+ */
246
+ /**
247
+ * Verdict and refusal in one call — the two-line wiring every org-scoped
248
+ * API route carries (AGL-1506):
249
+ *
250
+ * const locked = await lockdownRefusal({ staff, uid, org, host })
251
+ * if (locked) return locked
252
+ *
253
+ * Null means "not locked, proceed". One mechanism: this delegates to
254
+ * `getLockdownVerdict` (so the un-panic invariant and fail-open posture are
255
+ * inherited, never re-implemented) and to `lockdownJsonResponse` for the
256
+ * distinct 423 body. Routes with a richer flow (the session mint clears
257
+ * cookies on refusal) keep calling the two halves directly.
258
+ */
259
+ export declare function lockdownRefusal(options: LockdownVerdictOptions & {
260
+ /** Visitor-facing copy override; see `lockdownJsonResponse`. */
261
+ notice?: LockdownNotice;
262
+ }): Promise<Response | null>;
263
+ export declare function lockdownJsonResponse(state: LockdownState, options?: {
264
+ /**
265
+ * Substitute copy for a surface whose reader is not the account holder
266
+ * — the tenant runtime's visitor-facing pause (AGL-1511). ONE wire
267
+ * shape and ONE writer either way: only the words change, so
268
+ * `parseLockdownRefusal` reads a paused checkout exactly as it reads a
269
+ * locked console.
270
+ */
271
+ notice?: LockdownNotice;
272
+ }): Response;
273
+ /**
274
+ * IS THE CREATION-LEVEL VALVE ACTUALLY ARMED? (AGL-1531)
275
+ *
276
+ * The signups feature lock refuses the session mint, the acceptance recorder
277
+ * and the signup-page doors from code that ships with every deploy. It also
278
+ * refuses account CREATION — but only through a Firebase Auth
279
+ * `beforeUserCreated` blocking function in `cloud/functions`, which merging
280
+ * does not deploy and deploying does not necessarily register. Identity
281
+ * Platform holds that registration, not this repo.
282
+ *
283
+ * So the staff page had a way to be confidently wrong: the switch reads
284
+ * LOCKED either way, and an operator during a bot wave would have no way to
285
+ * tell "creation is refused" from "creation is still wide open and only
286
+ * sessions are being turned away". This is the read that closes that gap —
287
+ * the panic surface states which of the two it is instead of implying the
288
+ * stronger one.
289
+ *
290
+ * `unknown` is a first-class answer and is NEVER rendered as armed. The
291
+ * probe needs a credential with the identitytoolkit scope and an outbound
292
+ * call; a deployment where either is missing must report that it does not
293
+ * know, because "we could not check" and "it is on" are the two answers an
294
+ * incident cannot afford to have confused.
295
+ */
296
+ /**
297
+ * A STRING discriminant, not a `boolean | null`. `strictNullChecks` is off
298
+ * repo-wide, so `null` is assignable to every member and a `armed: null` arm
299
+ * cannot narrow — the compiler collapses the union and the "unknown" case
300
+ * silently stops being distinguishable from the other two. Which is the one
301
+ * distinction this type exists to hold.
302
+ */
303
+ export type SignupsCreationTriggerStatus = {
304
+ status: 'armed';
305
+ functionUri: string | null;
306
+ updateTime: string | null;
307
+ } | {
308
+ status: 'absent';
309
+ } | {
310
+ status: 'unknown';
311
+ reason: string;
312
+ };
313
+ export declare function readSignupsCreationTriggerStatus(): Promise<SignupsCreationTriggerStatus>;