@aglyn/aglyn 1.0.0-beta.221 → 1.0.0-beta.222

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 (346) hide show
  1. package/package.json +15 -11
  2. package/src/lib/app-utils/agent-openapi.js +1 -1
  3. package/src/lib/app-utils/agent-openapi.js.map +1 -1
  4. package/src/lib/app-utils/analytics-events.d.ts +3 -2
  5. package/src/lib/app-utils/analytics-events.js +3 -2
  6. package/src/lib/app-utils/analytics-events.js.map +1 -1
  7. package/src/lib/app-utils/api-plugins.d.ts +30 -0
  8. package/src/lib/app-utils/api-plugins.js +24 -1
  9. package/src/lib/app-utils/api-plugins.js.map +1 -1
  10. package/src/lib/app-utils/author-html.d.ts +1 -1
  11. package/src/lib/app-utils/author-html.js.map +1 -1
  12. package/src/lib/app-utils/bandwidth-cap.d.ts +12 -1
  13. package/src/lib/app-utils/bandwidth-cap.js +17 -1
  14. package/src/lib/app-utils/bandwidth-cap.js.map +1 -1
  15. package/src/lib/app-utils/compose-reusable-components.d.ts +19 -0
  16. package/src/lib/app-utils/compose-reusable-components.js +116 -26
  17. package/src/lib/app-utils/compose-reusable-components.js.map +1 -1
  18. package/src/lib/app-utils/console-routes.d.ts +4 -0
  19. package/src/lib/app-utils/console-routes.js +3 -0
  20. package/src/lib/app-utils/console-routes.js.map +1 -1
  21. package/src/lib/app-utils/contacts.d.ts +71 -0
  22. package/src/lib/app-utils/contacts.js +55 -0
  23. package/src/lib/app-utils/contacts.js.map +1 -1
  24. package/src/lib/app-utils/contexts.d.ts +1 -0
  25. package/src/lib/app-utils/contexts.js +1 -0
  26. package/src/lib/app-utils/contexts.js.map +1 -1
  27. package/src/lib/app-utils/crm-email-templates.js +21 -4
  28. package/src/lib/app-utils/crm-email-templates.js.map +1 -1
  29. package/src/lib/app-utils/crm-kinds.d.ts +2 -0
  30. package/src/lib/app-utils/crm-kinds.js +1 -0
  31. package/src/lib/app-utils/crm-kinds.js.map +1 -1
  32. package/src/lib/app-utils/crm.d.ts +1199 -67
  33. package/src/lib/app-utils/crm.js +1951 -310
  34. package/src/lib/app-utils/crm.js.map +1 -1
  35. package/src/lib/app-utils/csv-import.d.ts +10 -38
  36. package/src/lib/app-utils/csv-import.js +17 -42
  37. package/src/lib/app-utils/csv-import.js.map +1 -1
  38. package/src/lib/app-utils/csv.d.ts +32 -11
  39. package/src/lib/app-utils/csv.js +43 -5
  40. package/src/lib/app-utils/csv.js.map +1 -1
  41. package/src/lib/app-utils/default-site.d.ts +116 -0
  42. package/src/lib/app-utils/default-site.js +1024 -0
  43. package/src/lib/app-utils/default-site.js.map +1 -0
  44. package/src/lib/app-utils/docs-help.generated.d.ts +22 -22
  45. package/src/lib/app-utils/docs-help.generated.js +44 -10
  46. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  47. package/src/lib/app-utils/docs-index.generated.js +738 -150
  48. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  49. package/src/lib/app-utils/draft-placeholders.d.ts +76 -0
  50. package/src/lib/app-utils/draft-placeholders.js +115 -0
  51. package/src/lib/app-utils/draft-placeholders.js.map +1 -0
  52. package/src/lib/app-utils/entity-picker-context.d.ts +19 -15
  53. package/src/lib/app-utils/entity-picker-context.js +4 -4
  54. package/src/lib/app-utils/entity-picker-context.js.map +1 -1
  55. package/src/lib/app-utils/expand-repeatables.d.ts +66 -10
  56. package/src/lib/app-utils/expand-repeatables.js +88 -14
  57. package/src/lib/app-utils/expand-repeatables.js.map +1 -1
  58. package/src/lib/app-utils/forms.d.ts +33 -22
  59. package/src/lib/app-utils/forms.js +47 -28
  60. package/src/lib/app-utils/forms.js.map +1 -1
  61. package/src/lib/app-utils/full-use-cost.d.ts +277 -0
  62. package/src/lib/app-utils/full-use-cost.js +421 -0
  63. package/src/lib/app-utils/full-use-cost.js.map +1 -0
  64. package/src/lib/app-utils/health-report.js +11 -0
  65. package/src/lib/app-utils/health-report.js.map +1 -1
  66. package/src/lib/app-utils/llms-txt.d.ts +20 -0
  67. package/src/lib/app-utils/llms-txt.js +7 -1
  68. package/src/lib/app-utils/llms-txt.js.map +1 -1
  69. package/src/lib/app-utils/media-asset-facts.d.ts +16 -1
  70. package/src/lib/app-utils/media-asset-facts.js +40 -13
  71. package/src/lib/app-utils/media-asset-facts.js.map +1 -1
  72. package/src/lib/app-utils/media-bandwidth.d.ts +52 -0
  73. package/src/lib/app-utils/media-bandwidth.js +94 -0
  74. package/src/lib/app-utils/media-bandwidth.js.map +1 -0
  75. package/src/lib/app-utils/media-ref.d.ts +241 -33
  76. package/src/lib/app-utils/media-ref.js +324 -43
  77. package/src/lib/app-utils/media-ref.js.map +1 -1
  78. package/src/lib/app-utils/media-src.d.ts +8 -8
  79. package/src/lib/app-utils/media-src.js +27 -11
  80. package/src/lib/app-utils/media-src.js.map +1 -1
  81. package/src/lib/app-utils/media-takedown-reach.d.ts +4 -1
  82. package/src/lib/app-utils/media-takedown-reach.js +6 -3
  83. package/src/lib/app-utils/media-takedown-reach.js.map +1 -1
  84. package/src/lib/app-utils/notifications.d.ts +1 -1
  85. package/src/lib/app-utils/notifications.js +2 -0
  86. package/src/lib/app-utils/notifications.js.map +1 -1
  87. package/src/lib/app-utils/organizations.d.ts +51 -2
  88. package/src/lib/app-utils/organizations.js +56 -3
  89. package/src/lib/app-utils/organizations.js.map +1 -1
  90. package/src/lib/app-utils/page-record-sources.d.ts +87 -0
  91. package/src/lib/app-utils/page-record-sources.js +53 -0
  92. package/src/lib/app-utils/page-record-sources.js.map +1 -0
  93. package/src/lib/app-utils/paid-media-source.d.ts +3 -7
  94. package/src/lib/app-utils/paid-media-source.js +5 -19
  95. package/src/lib/app-utils/paid-media-source.js.map +1 -1
  96. package/src/lib/app-utils/person-erasure.d.ts +5 -2
  97. package/src/lib/app-utils/person-erasure.js.map +1 -1
  98. package/src/lib/app-utils/picklists.d.ts +187 -0
  99. package/src/lib/app-utils/picklists.js +320 -0
  100. package/src/lib/app-utils/picklists.js.map +1 -0
  101. package/src/lib/app-utils/plan-entitlements.d.ts +220 -50
  102. package/src/lib/app-utils/plan-entitlements.js +309 -115
  103. package/src/lib/app-utils/plan-entitlements.js.map +1 -1
  104. package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
  105. package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
  106. package/src/lib/app-utils/release-flags.d.ts +1 -1
  107. package/src/lib/app-utils/release-flags.js +11 -0
  108. package/src/lib/app-utils/release-flags.js.map +1 -1
  109. package/src/lib/app-utils/rendered-widths.d.ts +121 -0
  110. package/src/lib/app-utils/rendered-widths.js +211 -0
  111. package/src/lib/app-utils/rendered-widths.js.map +1 -0
  112. package/src/lib/app-utils/repeat-sources.d.ts +2 -1
  113. package/src/lib/app-utils/repeat-sources.js.map +1 -1
  114. package/src/lib/app-utils/screen-route.d.ts +154 -36
  115. package/src/lib/app-utils/screen-route.js +185 -40
  116. package/src/lib/app-utils/screen-route.js.map +1 -1
  117. package/src/lib/app-utils/seo-audit.d.ts +11 -1
  118. package/src/lib/app-utils/seo-audit.js +33 -13
  119. package/src/lib/app-utils/seo-audit.js.map +1 -1
  120. package/src/lib/app-utils/seo-keywords.d.ts +11 -0
  121. package/src/lib/app-utils/seo-keywords.js +19 -5
  122. package/src/lib/app-utils/seo-keywords.js.map +1 -1
  123. package/src/lib/app-utils/seo-page-facts.d.ts +47 -4
  124. package/src/lib/app-utils/seo-page-facts.js +136 -31
  125. package/src/lib/app-utils/seo-page-facts.js.map +1 -1
  126. package/src/lib/app-utils/seo-site-scan.d.ts +42 -4
  127. package/src/lib/app-utils/seo-site-scan.js +113 -12
  128. package/src/lib/app-utils/seo-site-scan.js.map +1 -1
  129. package/src/lib/app-utils/server.d.ts +5 -4
  130. package/src/lib/app-utils/server.js +14 -5
  131. package/src/lib/app-utils/server.js.map +1 -1
  132. package/src/lib/app-utils/site-icon-set.d.ts +195 -0
  133. package/src/lib/app-utils/site-icon-set.js +351 -0
  134. package/src/lib/app-utils/site-icon-set.js.map +1 -0
  135. package/src/lib/app-utils/site-interactions.d.ts +101 -4
  136. package/src/lib/app-utils/site-interactions.js +120 -4
  137. package/src/lib/app-utils/site-interactions.js.map +1 -1
  138. package/src/lib/app-utils/starter-template-nodes.d.ts +7 -0
  139. package/src/lib/app-utils/starter-template-nodes.js +3 -0
  140. package/src/lib/app-utils/starter-template-nodes.js.map +1 -1
  141. package/src/lib/app-utils/starter-templates.d.ts +5 -19
  142. package/src/lib/app-utils/starter-templates.js +5 -49
  143. package/src/lib/app-utils/starter-templates.js.map +1 -1
  144. package/src/lib/app-utils/transfer-launcher-context.d.ts +136 -0
  145. package/src/lib/app-utils/transfer-launcher-context.js +23 -0
  146. package/src/lib/app-utils/transfer-launcher-context.js.map +1 -0
  147. package/src/lib/app-utils/transfer-resume-import.d.ts +39 -0
  148. package/src/lib/app-utils/transfer-resume-import.js +128 -0
  149. package/src/lib/app-utils/transfer-resume-import.js.map +1 -0
  150. package/src/lib/app-utils/upgrade-proposal.d.ts +66 -0
  151. package/src/lib/app-utils/upgrade-proposal.js +95 -0
  152. package/src/lib/app-utils/upgrade-proposal.js.map +1 -0
  153. package/src/lib/app-utils/utc-month.d.ts +33 -0
  154. package/src/lib/app-utils/utc-month.js +35 -0
  155. package/src/lib/app-utils/utc-month.js.map +1 -0
  156. package/src/lib/app-utils/visitor-record-ceiling.d.ts +4 -4
  157. package/src/lib/app-utils/visitor-record-ceiling.js +4 -4
  158. package/src/lib/app-utils/visitor-record-ceiling.js.map +1 -1
  159. package/src/lib/app-utils/wire-nodes.d.ts +39 -19
  160. package/src/lib/app-utils/wire-nodes.js +120 -34
  161. package/src/lib/app-utils/wire-nodes.js.map +1 -1
  162. package/src/lib/data-transfer/access.d.ts +74 -0
  163. package/src/lib/data-transfer/access.js +93 -0
  164. package/src/lib/data-transfer/access.js.map +1 -0
  165. package/src/lib/data-transfer/derive.d.ts +246 -0
  166. package/src/lib/data-transfer/derive.js +1007 -0
  167. package/src/lib/data-transfer/derive.js.map +1 -0
  168. package/src/lib/data-transfer/export-file.d.ts +46 -0
  169. package/src/lib/data-transfer/export-file.js +146 -0
  170. package/src/lib/data-transfer/export-file.js.map +1 -0
  171. package/src/lib/data-transfer/field-catalog.d.ts +134 -0
  172. package/src/lib/data-transfer/field-catalog.js +278 -0
  173. package/src/lib/data-transfer/field-catalog.js.map +1 -0
  174. package/src/lib/data-transfer/header-match.d.ts +129 -0
  175. package/src/lib/data-transfer/header-match.js +367 -0
  176. package/src/lib/data-transfer/header-match.js.map +1 -0
  177. package/src/lib/data-transfer/index.d.ts +34 -0
  178. package/src/lib/data-transfer/index.js +40 -0
  179. package/src/lib/data-transfer/index.js.map +1 -0
  180. package/src/lib/data-transfer/job.d.ts +190 -0
  181. package/src/lib/data-transfer/job.js +229 -0
  182. package/src/lib/data-transfer/job.js.map +1 -0
  183. package/src/lib/data-transfer/lookup.d.ts +45 -0
  184. package/src/lib/data-transfer/lookup.js +71 -0
  185. package/src/lib/data-transfer/lookup.js.map +1 -0
  186. package/src/lib/data-transfer/match.d.ts +99 -0
  187. package/src/lib/data-transfer/match.js +244 -0
  188. package/src/lib/data-transfer/match.js.map +1 -0
  189. package/src/lib/data-transfer/package-plan.d.ts +205 -0
  190. package/src/lib/data-transfer/package-plan.js +475 -0
  191. package/src/lib/data-transfer/package-plan.js.map +1 -0
  192. package/src/lib/data-transfer/package.d.ts +181 -0
  193. package/src/lib/data-transfer/package.js +426 -0
  194. package/src/lib/data-transfer/package.js.map +1 -0
  195. package/src/lib/data-transfer/picklist-map.d.ts +139 -0
  196. package/src/lib/data-transfer/picklist-map.js +271 -0
  197. package/src/lib/data-transfer/picklist-map.js.map +1 -0
  198. package/src/lib/data-transfer/plan.d.ts +176 -0
  199. package/src/lib/data-transfer/plan.js +451 -0
  200. package/src/lib/data-transfer/plan.js.map +1 -0
  201. package/src/lib/data-transfer/policy.d.ts +170 -0
  202. package/src/lib/data-transfer/policy.js +378 -0
  203. package/src/lib/data-transfer/policy.js.map +1 -0
  204. package/src/lib/data-transfer/resource.d.ts +211 -0
  205. package/src/lib/data-transfer/resource.js +241 -0
  206. package/src/lib/data-transfer/resource.js.map +1 -0
  207. package/src/lib/data-transfer/review.d.ts +70 -0
  208. package/src/lib/data-transfer/review.js +233 -0
  209. package/src/lib/data-transfer/review.js.map +1 -0
  210. package/src/lib/data-transfer/similarity.d.ts +40 -0
  211. package/src/lib/data-transfer/similarity.js +106 -0
  212. package/src/lib/data-transfer/similarity.js.map +1 -0
  213. package/src/lib/data-transfer/site-package.d.ts +365 -0
  214. package/src/lib/data-transfer/site-package.js +1077 -0
  215. package/src/lib/data-transfer/site-package.js.map +1 -0
  216. package/src/lib/data-transfer/source.d.ts +74 -0
  217. package/src/lib/data-transfer/source.js +293 -0
  218. package/src/lib/data-transfer/source.js.map +1 -0
  219. package/src/lib/data-transfer/transfer-api.d.ts +898 -0
  220. package/src/lib/data-transfer/transfer-api.js +107 -0
  221. package/src/lib/data-transfer/transfer-api.js.map +1 -0
  222. package/src/lib/foundation/definitions/org-billing.types.d.ts +31 -0
  223. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
  224. package/src/lib/foundation/definitions/organization.types.d.ts +42 -1
  225. package/src/lib/foundation/definitions/organization.types.js.map +1 -1
  226. package/src/lib/foundation/definitions/platform.types.d.ts +6 -4
  227. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  228. package/src/lib/foundation/index.d.ts +1 -1
  229. package/src/lib/foundation/index.js.map +1 -1
  230. package/src/lib/plugin-manager/feature-plugins.d.ts +91 -2
  231. package/src/lib/plugin-manager/feature-plugins.js +48 -3
  232. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  233. package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +50 -0
  234. package/src/lib/plugin-manager/first-party-plugins.generated.js +1129 -28
  235. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  236. package/src/lib/plugin-manager/index.d.ts +0 -1
  237. package/src/lib/plugin-manager/index.js +0 -1
  238. package/src/lib/plugin-manager/index.js.map +1 -1
  239. package/src/lib/plugin-manager/interaction-recipes.d.ts +142 -0
  240. package/src/lib/plugin-manager/interaction-recipes.js +104 -0
  241. package/src/lib/plugin-manager/interaction-recipes.js.map +1 -0
  242. package/src/lib/plugin-manager/interaction-step-checks.d.ts +66 -0
  243. package/src/lib/plugin-manager/interaction-step-checks.js +86 -0
  244. package/src/lib/plugin-manager/interaction-step-checks.js.map +1 -0
  245. package/src/lib/plugin-manager/plugin-artifact-types.d.ts +235 -0
  246. package/src/lib/plugin-manager/plugin-artifact-types.js +99 -0
  247. package/src/lib/plugin-manager/plugin-artifact-types.js.map +1 -0
  248. package/src/lib/plugin-manager/plugin-contact-capture.d.ts +8 -0
  249. package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
  250. package/src/lib/plugin-manager/plugin-conversion-credit.d.ts +216 -0
  251. package/src/lib/plugin-manager/plugin-conversion-credit.js +91 -0
  252. package/src/lib/plugin-manager/plugin-conversion-credit.js.map +1 -0
  253. package/src/lib/plugin-manager/plugin-entity-pickers.d.ts +83 -0
  254. package/src/lib/plugin-manager/plugin-entity-pickers.js +87 -0
  255. package/src/lib/plugin-manager/plugin-entity-pickers.js.map +1 -0
  256. package/src/lib/plugin-manager/plugin-host-resources.d.ts +6 -0
  257. package/src/lib/plugin-manager/plugin-host-resources.js +6 -1
  258. package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -1
  259. package/src/lib/plugin-manager/plugin-intake-gates.d.ts +75 -0
  260. package/src/lib/plugin-manager/plugin-intake-gates.js +49 -0
  261. package/src/lib/plugin-manager/plugin-intake-gates.js.map +1 -0
  262. package/src/lib/plugin-manager/plugin-jobs.d.ts +5 -5
  263. package/src/lib/plugin-manager/plugin-jobs.js +7 -7
  264. package/src/lib/plugin-manager/plugin-jobs.js.map +1 -1
  265. package/src/lib/plugin-manager/plugin-live-paths.d.ts +70 -0
  266. package/src/lib/plugin-manager/plugin-live-paths.js +66 -0
  267. package/src/lib/plugin-manager/plugin-live-paths.js.map +1 -0
  268. package/src/lib/plugin-manager/plugin-org-erasure.d.ts +3 -2
  269. package/src/lib/plugin-manager/plugin-org-erasure.js +30 -5
  270. package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
  271. package/src/lib/plugin-manager/plugin-person-erasure.d.ts +68 -9
  272. package/src/lib/plugin-manager/plugin-person-erasure.js +196 -51
  273. package/src/lib/plugin-manager/plugin-person-erasure.js.map +1 -1
  274. package/src/lib/plugin-manager/plugin-person-records.d.ts +275 -0
  275. package/src/lib/plugin-manager/plugin-person-records.js +107 -0
  276. package/src/lib/plugin-manager/plugin-person-records.js.map +1 -0
  277. package/src/lib/plugin-manager/plugin-record-counts.d.ts +76 -0
  278. package/src/lib/plugin-manager/plugin-record-counts.js +49 -0
  279. package/src/lib/plugin-manager/plugin-record-counts.js.map +1 -0
  280. package/src/lib/plugin-manager/plugin-record-email-state.d.ts +26 -0
  281. package/src/lib/plugin-manager/plugin-record-email-state.js +16 -0
  282. package/src/lib/plugin-manager/plugin-record-email-state.js.map +1 -1
  283. package/src/lib/plugin-manager/plugin-record-index.d.ts +10 -0
  284. package/src/lib/plugin-manager/plugin-record-index.js.map +1 -1
  285. package/src/lib/plugin-manager/plugin-record-lists.d.ts +86 -6
  286. package/src/lib/plugin-manager/plugin-record-lists.js +42 -5
  287. package/src/lib/plugin-manager/plugin-record-lists.js.map +1 -1
  288. package/src/lib/plugin-manager/plugin-record-origin.d.ts +79 -0
  289. package/src/lib/plugin-manager/plugin-record-origin.js +46 -0
  290. package/src/lib/plugin-manager/plugin-record-origin.js.map +1 -0
  291. package/src/lib/plugin-manager/plugin-record-pages.d.ts +37 -0
  292. package/src/lib/plugin-manager/plugin-record-pages.js +52 -0
  293. package/src/lib/plugin-manager/plugin-record-pages.js.map +1 -0
  294. package/src/lib/plugin-manager/plugin-record-timeline.d.ts +60 -0
  295. package/src/lib/plugin-manager/plugin-record-timeline.js +16 -0
  296. package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
  297. package/src/lib/plugin-manager/plugin-resource-drafts.d.ts +5 -5
  298. package/src/lib/plugin-manager/plugin-resource-drafts.js.map +1 -1
  299. package/src/lib/plugin-manager/plugin-revocations.d.ts +64 -0
  300. package/src/lib/plugin-manager/plugin-revocations.js +47 -0
  301. package/src/lib/plugin-manager/plugin-revocations.js.map +1 -0
  302. package/src/lib/plugin-manager/plugin-send-tallies.d.ts +69 -0
  303. package/src/lib/plugin-manager/plugin-send-tallies.js +45 -0
  304. package/src/lib/plugin-manager/plugin-send-tallies.js.map +1 -0
  305. package/src/lib/plugin-manager/plugin-site-bundle.d.ts +40 -0
  306. package/src/lib/plugin-manager/plugin-site-bundle.js +4 -0
  307. package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -1
  308. package/src/lib/plugin-manager/plugin-site-export.d.ts +57 -1
  309. package/src/lib/plugin-manager/plugin-site-export.js +23 -10
  310. package/src/lib/plugin-manager/plugin-site-export.js.map +1 -1
  311. package/src/lib/plugin-manager/plugin-sitemap-readers.d.ts +146 -0
  312. package/src/lib/plugin-manager/plugin-sitemap-readers.js +110 -0
  313. package/src/lib/plugin-manager/plugin-sitemap-readers.js.map +1 -0
  314. package/src/lib/plugin-manager/plugin-transfer-resources.d.ts +714 -0
  315. package/src/lib/plugin-manager/plugin-transfer-resources.js +443 -0
  316. package/src/lib/plugin-manager/plugin-transfer-resources.js.map +1 -0
  317. package/src/lib/plugin-manager/plugin-usage-axes.d.ts +54 -5
  318. package/src/lib/plugin-manager/plugin-usage-axes.js +11 -3
  319. package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -1
  320. package/src/lib/plugin-manager/plugin-user-erasure.js +9 -1
  321. package/src/lib/plugin-manager/plugin-user-erasure.js.map +1 -1
  322. package/src/lib/plugin-manager/plugin-visitor-doors.d.ts +104 -0
  323. package/src/lib/plugin-manager/plugin-visitor-doors.js +108 -0
  324. package/src/lib/plugin-manager/plugin-visitor-doors.js.map +1 -0
  325. package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
  326. package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
  327. package/src/lib/plugin-manager/repeat-rows.d.ts +5 -6
  328. package/src/lib/plugin-manager/repeat-rows.js.map +1 -1
  329. package/src/lib/app-utils/actions.d.ts +0 -310
  330. package/src/lib/app-utils/actions.js +0 -506
  331. package/src/lib/app-utils/actions.js.map +0 -1
  332. package/src/lib/app-utils/automation-placeholders.d.ts +0 -72
  333. package/src/lib/app-utils/automation-placeholders.js +0 -207
  334. package/src/lib/app-utils/automation-placeholders.js.map +0 -1
  335. package/src/lib/app-utils/dataset-models.d.ts +0 -339
  336. package/src/lib/app-utils/dataset-models.js +0 -722
  337. package/src/lib/app-utils/dataset-models.js.map +0 -1
  338. package/src/lib/app-utils/datasets.d.ts +0 -171
  339. package/src/lib/app-utils/datasets.js +0 -227
  340. package/src/lib/app-utils/datasets.js.map +0 -1
  341. package/src/lib/app-utils/form-abuse-ceiling.d.ts +0 -146
  342. package/src/lib/app-utils/form-abuse-ceiling.js +0 -158
  343. package/src/lib/app-utils/form-abuse-ceiling.js.map +0 -1
  344. package/src/lib/plugin-manager/order-fulfilment.d.ts +0 -142
  345. package/src/lib/plugin-manager/order-fulfilment.js +0 -104
  346. package/src/lib/plugin-manager/order-fulfilment.js.map +0 -1
@@ -0,0 +1,128 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ /**
4
+ * @license
5
+ * Copyright 2026 Aglyn LLC
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */ import { Button, Menu, MenuItem, Stack, Typography } from "@mui/material";
19
+ import { useState } from "react";
20
+ import { useTransferLauncher } from "./transfer-launcher-context.js";
21
+ /** "today", or the day an import was last touched. */ function touched(at) {
22
+ const day = new Date(at);
23
+ return day.toDateString() === new Date().toDateString() ? `today ${day.toLocaleTimeString(undefined, {
24
+ hour: 'numeric',
25
+ minute: '2-digit'
26
+ })}` : day.toLocaleDateString(undefined, {
27
+ month: 'short',
28
+ day: 'numeric'
29
+ });
30
+ }
31
+ /** What one unfinished import is called in the list. */ function nameOf(job) {
32
+ return job.fileName || job.label;
33
+ }
34
+ /**
35
+ * "Resume import" beside a surface's Import (AGL-3549): the imports the
36
+ * person started on this resource and left before anything was written —
37
+ * a tab closed mid-wizard — each reopened where it stopped
38
+ * (`openImport({ jobId })`) or discarded. Draws nothing outside the
39
+ * console shell, for a person who may not import here, or when there is
40
+ * nothing to resume. Every choice made in the wizard is kept with the job,
41
+ * so a resumed import opens on the step it was left at.
42
+ */ export function TransferResumeImport(props) {
43
+ const { target, title, onFinished } = props;
44
+ const launcher = useTransferLauncher();
45
+ const [anchor, setAnchor] = useState(null);
46
+ const [discarding, setDiscarding] = useState(null);
47
+ if (!(launcher == null ? void 0 : launcher.unfinished) || !launcher.can('import', target)) return null;
48
+ const jobs = launcher.unfinished(target);
49
+ if (!jobs.length) return null;
50
+ const resume = (job)=>{
51
+ setAnchor(null);
52
+ launcher.openImport(_extends({
53
+ resource: job.resource,
54
+ scope: target.scope
55
+ }, job.hostId ? {
56
+ hostId: job.hostId
57
+ } : {}, {
58
+ jobId: job.jobId
59
+ }, title ? {
60
+ title
61
+ } : {}, onFinished ? {
62
+ onFinished
63
+ } : {}));
64
+ };
65
+ const discard = async (event, job)=>{
66
+ event.stopPropagation();
67
+ setDiscarding(job.jobId);
68
+ try {
69
+ await (launcher.discard == null ? void 0 : launcher.discard.call(launcher, job.jobId));
70
+ } finally{
71
+ setDiscarding(null);
72
+ }
73
+ };
74
+ return /*#__PURE__*/ _jsxs(_Fragment, {
75
+ children: [
76
+ /*#__PURE__*/ _jsx(Button, {
77
+ size: "small",
78
+ "aria-haspopup": "menu",
79
+ onClick: (event)=>setAnchor(event.currentTarget),
80
+ children: jobs.length === 1 ? 'Resume import' : `Resume import (${jobs.length})`
81
+ }),
82
+ /*#__PURE__*/ _jsx(Menu, {
83
+ anchorEl: anchor,
84
+ open: Boolean(anchor),
85
+ onClose: ()=>setAnchor(null),
86
+ children: jobs.map((job)=>/*#__PURE__*/ _jsx(MenuItem, {
87
+ onClick: ()=>resume(job),
88
+ disabled: discarding === job.jobId,
89
+ children: /*#__PURE__*/ _jsxs(Stack, {
90
+ direction: "row",
91
+ spacing: 2,
92
+ sx: {
93
+ alignItems: 'center',
94
+ justifyContent: 'space-between',
95
+ width: '100%'
96
+ },
97
+ children: [
98
+ /*#__PURE__*/ _jsxs(Stack, {
99
+ children: [
100
+ /*#__PURE__*/ _jsx(Typography, {
101
+ variant: "body2",
102
+ children: nameOf(job)
103
+ }),
104
+ /*#__PURE__*/ _jsx(Typography, {
105
+ variant: "caption",
106
+ color: "text.secondary",
107
+ children: `Left ${touched(job.updatedAt)}`
108
+ })
109
+ ]
110
+ }),
111
+ /*#__PURE__*/ _jsx(Button, {
112
+ size: "small",
113
+ color: "error",
114
+ onClick: (event)=>void discard(event, job),
115
+ disabled: discarding === job.jobId,
116
+ children: 'Discard'
117
+ })
118
+ ]
119
+ })
120
+ }, job.jobId))
121
+ })
122
+ ]
123
+ });
124
+ }
125
+ TransferResumeImport.displayName = 'TransferResumeImport';
126
+ export default TransferResumeImport;
127
+
128
+ //# sourceMappingURL=transfer-resume-import.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/transfer-resume-import.tsx"],"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 { Button, Menu, MenuItem, Stack, Typography } from '@mui/material'\nimport { useState, type MouseEvent } from 'react'\nimport {\n useTransferLauncher,\n type TransferAccessTarget,\n type TransferUnfinishedImport,\n} from './transfer-launcher-context'\n\n/** \"today\", or the day an import was last touched. */\nfunction touched(at: number): string {\n const day = new Date(at)\n return day.toDateString() === new Date().toDateString()\n ? `today ${day.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' })}`\n : day.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })\n}\n\n/** What one unfinished import is called in the list. */\nfunction nameOf(job: TransferUnfinishedImport): string {\n return job.fileName || job.label\n}\n\nexport interface TransferResumeImportProps {\n /** The resource the surface imports into, and the site — as its Import button opens it. */\n target: TransferAccessTarget\n /** The wizard's title, as the surface's Import button sets it. */\n title?: string\n /** Called when the person leaves the resumed wizard from its results. */\n onFinished?(): void\n}\n\n/**\n * \"Resume import\" beside a surface's Import (AGL-3549): the imports the\n * person started on this resource and left before anything was written —\n * a tab closed mid-wizard — each reopened where it stopped\n * (`openImport({ jobId })`) or discarded. Draws nothing outside the\n * console shell, for a person who may not import here, or when there is\n * nothing to resume. Every choice made in the wizard is kept with the job,\n * so a resumed import opens on the step it was left at.\n */\nexport function TransferResumeImport(props: TransferResumeImportProps) {\n const { target, title, onFinished } = props\n const launcher = useTransferLauncher()\n const [anchor, setAnchor] = useState<HTMLElement | null>(null)\n const [discarding, setDiscarding] = useState<string | null>(null)\n if (!launcher?.unfinished || !launcher.can('import', target)) return null\n const jobs = launcher.unfinished(target)\n if (!jobs.length) return null\n\n const resume = (job: TransferUnfinishedImport) => {\n setAnchor(null)\n launcher.openImport({\n resource: job.resource,\n scope: target.scope,\n ...(job.hostId ? { hostId: job.hostId } : {}),\n jobId: job.jobId,\n ...(title ? { title } : {}),\n ...(onFinished ? { onFinished } : {}),\n })\n }\n const discard = async (event: MouseEvent, job: TransferUnfinishedImport) => {\n event.stopPropagation()\n setDiscarding(job.jobId)\n try {\n await launcher.discard?.(job.jobId)\n } finally {\n setDiscarding(null)\n }\n }\n\n return (\n <>\n <Button\n size=\"small\"\n aria-haspopup=\"menu\"\n onClick={(event) => setAnchor(event.currentTarget)}\n >\n {jobs.length === 1 ? 'Resume import' : `Resume import (${jobs.length})`}\n </Button>\n <Menu anchorEl={anchor} open={Boolean(anchor)} onClose={() => setAnchor(null)}>\n {jobs.map((job) => (\n <MenuItem key={job.jobId} onClick={() => resume(job)} disabled={discarding === job.jobId}>\n <Stack direction=\"row\" spacing={2} sx={{ alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>\n <Stack>\n <Typography variant=\"body2\">{nameOf(job)}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Left ${touched(job.updatedAt)}`}\n </Typography>\n </Stack>\n <Button size=\"small\" color=\"error\" onClick={(event) => void discard(event, job)} disabled={discarding === job.jobId}>\n {'Discard'}\n </Button>\n </Stack>\n </MenuItem>\n ))}\n </Menu>\n </>\n )\n}\nTransferResumeImport.displayName = 'TransferResumeImport'\n\nexport default TransferResumeImport\n"],"names":["Button","Menu","MenuItem","Stack","Typography","useState","useTransferLauncher","touched","at","day","Date","toDateString","toLocaleTimeString","undefined","hour","minute","toLocaleDateString","month","nameOf","job","fileName","label","TransferResumeImport","props","target","title","onFinished","launcher","anchor","setAnchor","discarding","setDiscarding","unfinished","can","jobs","length","resume","openImport","resource","scope","hostId","jobId","discard","event","stopPropagation","size","aria-haspopup","onClick","currentTarget","anchorEl","open","Boolean","onClose","map","disabled","direction","spacing","sx","alignItems","justifyContent","width","variant","color","updatedAt","displayName"],"mappings":";;AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,MAAM,EAAEC,IAAI,EAAEC,QAAQ,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AACzE,SAASC,QAAQ,QAAyB,QAAO;AACjD,SACEC,mBAAmB,QAGd,iCAA6B;AAEpC,oDAAoD,GACpD,SAASC,QAAQC,EAAU;IACzB,MAAMC,MAAM,IAAIC,KAAKF;IACrB,OAAOC,IAAIE,YAAY,OAAO,IAAID,OAAOC,YAAY,KACjD,CAAC,MAAM,EAAEF,IAAIG,kBAAkB,CAACC,WAAW;QAAEC,MAAM;QAAWC,QAAQ;IAAU,IAAI,GACpFN,IAAIO,kBAAkB,CAACH,WAAW;QAAEI,OAAO;QAASR,KAAK;IAAU;AACzE;AAEA,sDAAsD,GACtD,SAASS,OAAOC,GAA6B;IAC3C,OAAOA,IAAIC,QAAQ,IAAID,IAAIE,KAAK;AAClC;AAWA;;;;;;;;CAQC,GACD,OAAO,SAASC,qBAAqBC,KAAgC;IACnE,MAAM,EAAEC,MAAM,EAAEC,KAAK,EAAEC,UAAU,EAAE,GAAGH;IACtC,MAAMI,WAAWrB;IACjB,MAAM,CAACsB,QAAQC,UAAU,GAAGxB,SAA6B;IACzD,MAAM,CAACyB,YAAYC,cAAc,GAAG1B,SAAwB;IAC5D,IAAI,EAACsB,4BAAAA,SAAUK,UAAU,KAAI,CAACL,SAASM,GAAG,CAAC,UAAUT,SAAS,OAAO;IACrE,MAAMU,OAAOP,SAASK,UAAU,CAACR;IACjC,IAAI,CAACU,KAAKC,MAAM,EAAE,OAAO;IAEzB,MAAMC,SAAS,CAACjB;QACdU,UAAU;QACVF,SAASU,UAAU,CAAC;YAClBC,UAAUnB,IAAImB,QAAQ;YACtBC,OAAOf,OAAOe,KAAK;WACfpB,IAAIqB,MAAM,GAAG;YAAEA,QAAQrB,IAAIqB,MAAM;QAAC,IAAI,CAAC;YAC3CC,OAAOtB,IAAIsB,KAAK;WACZhB,QAAQ;YAAEA;QAAM,IAAI,CAAC,GACrBC,aAAa;YAAEA;QAAW,IAAI,CAAC;IAEvC;IACA,MAAMgB,UAAU,OAAOC,OAAmBxB;QACxCwB,MAAMC,eAAe;QACrBb,cAAcZ,IAAIsB,KAAK;QACvB,IAAI;YACF,OAAMd,SAASe,OAAO,oBAAhBf,SAASe,OAAO,MAAhBf,UAAmBR,IAAIsB,KAAK;QACpC,SAAU;YACRV,cAAc;QAChB;IACF;IAEA,qBACE;;0BACE,KAAC/B;gBACC6C,MAAK;gBACLC,iBAAc;gBACdC,SAAS,CAACJ,QAAUd,UAAUc,MAAMK,aAAa;0BAEhDd,KAAKC,MAAM,KAAK,IAAI,kBAAkB,CAAC,eAAe,EAAED,KAAKC,MAAM,CAAC,CAAC,CAAC;;0BAEzE,KAAClC;gBAAKgD,UAAUrB;gBAAQsB,MAAMC,QAAQvB;gBAASwB,SAAS,IAAMvB,UAAU;0BACrEK,KAAKmB,GAAG,CAAC,CAAClC,oBACT,KAACjB;wBAAyB6C,SAAS,IAAMX,OAAOjB;wBAAMmC,UAAUxB,eAAeX,IAAIsB,KAAK;kCACtF,cAAA,MAACtC;4BAAMoD,WAAU;4BAAMC,SAAS;4BAAGC,IAAI;gCAAEC,YAAY;gCAAUC,gBAAgB;gCAAiBC,OAAO;4BAAO;;8CAC5G,MAACzD;;sDACC,KAACC;4CAAWyD,SAAQ;sDAAS3C,OAAOC;;sDACpC,KAACf;4CAAWyD,SAAQ;4CAAUC,OAAM;sDACjC,CAAC,KAAK,EAAEvD,QAAQY,IAAI4C,SAAS,GAAG;;;;8CAGrC,KAAC/D;oCAAO6C,MAAK;oCAAQiB,OAAM;oCAAQf,SAAS,CAACJ,QAAU,KAAKD,QAAQC,OAAOxB;oCAAMmC,UAAUxB,eAAeX,IAAIsB,KAAK;8CAChH;;;;uBATQtB,IAAIsB,KAAK;;;;AAiBlC;AACAnB,qBAAqB0C,WAAW,GAAG;AAEnC,eAAe1C,qBAAoB"}
@@ -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
+ * The platform team's request that a workspace move to a paid plan
19
+ * (AGL-3466), read in one place.
20
+ *
21
+ * A workspace staff build for a client is handed over first and sold second:
22
+ * the client joins, looks around on whatever the workspace already has, and
23
+ * only then is asked to upgrade. Staff record that ask as
24
+ * `orgs/{orgId}.upgradeProposal`; this module decides whether it still
25
+ * stands, what it may name, and where the button under it goes.
26
+ */
27
+ import type { AglynOrgBilling, OrgPlan, OrgUpgradeProposal } from '../foundation/definitions/org-billing.types';
28
+ import { type OnboardingPlanIntent } from './onboarding-deep-link';
29
+ /** The longest staff note a proposal carries. */
30
+ export declare const UPGRADE_PROPOSAL_NOTE_MAX = 500;
31
+ /**
32
+ * The plans staff may propose: every tier a customer can buy themselves,
33
+ * less Free, which is not a purchase. Enterprise is quoted per deal, so it
34
+ * is never proposed through a checkout link.
35
+ */
36
+ export declare const UPGRADE_PROPOSAL_PLANS: readonly OrgPlan[];
37
+ export declare function isUpgradeProposalPlan(plan: unknown): plan is OrgPlan;
38
+ /** A staff note as it is stored: trimmed, bounded, and absent when blank. */
39
+ export declare function normalizeUpgradeProposalNote(note: unknown): string | undefined;
40
+ /**
41
+ * The stored proposal, narrowed, or null when there is none worth showing.
42
+ *
43
+ * A proposal naming a plan this build cannot sell reads as none: a card whose
44
+ * button opens a checkout for an unknown tier is worse than no card.
45
+ */
46
+ export declare function readUpgradeProposal(org: Partial<AglynOrgBilling> | null | undefined): OrgUpgradeProposal | null;
47
+ /**
48
+ * The proposal the workspace should be shown: the stored one, while no
49
+ * subscription is live.
50
+ *
51
+ * `billingStatus` is the org document's mirror of the subscription status,
52
+ * readable by every member, so this answers from the document the console
53
+ * already holds. A live subscription means the workspace has bought a plan,
54
+ * which settles the question the proposal asked; `writeOrgBilling`'s caller
55
+ * clears the field then, and this hides it in the meantime.
56
+ */
57
+ export declare function standingUpgradeProposal(org: Partial<AglynOrgBilling> | null | undefined): OrgUpgradeProposal | null;
58
+ /** The plan intent a proposal carries into Billing: the plan, no interval. */
59
+ export declare function upgradeProposalIntent(proposal: Pick<OrgUpgradeProposal, 'plan'>): OnboardingPlanIntent;
60
+ /** Where the proposal's button goes: Billing, with the plan preselected. */
61
+ export declare function upgradeProposalDestination(orgSlug: string, proposal: Pick<OrgUpgradeProposal, 'plan'>): string;
62
+ /** "Acme's Aglyn team proposed the Starter plan". */
63
+ export declare function upgradeProposalHeadline(proposal: Pick<OrgUpgradeProposal, 'plan'>, options: {
64
+ orgName?: string | null;
65
+ productName: string;
66
+ }): string;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * The platform team's request that a workspace move to a paid plan
18
+ * (AGL-3466), read in one place.
19
+ *
20
+ * A workspace staff build for a client is handed over first and sold second:
21
+ * the client joins, looks around on whatever the workspace already has, and
22
+ * only then is asked to upgrade. Staff record that ask as
23
+ * `orgs/{orgId}.upgradeProposal`; this module decides whether it still
24
+ * stands, what it may name, and where the button under it goes.
25
+ */ import { _ as _extends } from "@swc/helpers/_/_extends";
26
+ import { onboardingDestination } from "./onboarding-deep-link.js";
27
+ import { isLiveSubscriptionStatus } from "./org-billing-doc.js";
28
+ import { PLAN_LABELS, SELF_SERVE_PLANS } from "./plan-entitlements.js";
29
+ /** The longest staff note a proposal carries. */ export const UPGRADE_PROPOSAL_NOTE_MAX = 500;
30
+ /**
31
+ * The plans staff may propose: every tier a customer can buy themselves,
32
+ * less Free, which is not a purchase. Enterprise is quoted per deal, so it
33
+ * is never proposed through a checkout link.
34
+ */ export const UPGRADE_PROPOSAL_PLANS = SELF_SERVE_PLANS.filter((plan)=>plan !== 'free');
35
+ export function isUpgradeProposalPlan(plan) {
36
+ return typeof plan === 'string' && UPGRADE_PROPOSAL_PLANS.includes(plan);
37
+ }
38
+ /** A staff note as it is stored: trimmed, bounded, and absent when blank. */ export function normalizeUpgradeProposalNote(note) {
39
+ if (typeof note !== 'string') return undefined;
40
+ const trimmed = note.trim().slice(0, UPGRADE_PROPOSAL_NOTE_MAX);
41
+ return trimmed || undefined;
42
+ }
43
+ /**
44
+ * The stored proposal, narrowed, or null when there is none worth showing.
45
+ *
46
+ * A proposal naming a plan this build cannot sell reads as none: a card whose
47
+ * button opens a checkout for an unknown tier is worse than no card.
48
+ */ export function readUpgradeProposal(org) {
49
+ const raw = org == null ? void 0 : org.upgradeProposal;
50
+ if (!raw || typeof raw !== 'object' || !isUpgradeProposalPlan(raw.plan)) {
51
+ return null;
52
+ }
53
+ const note = normalizeUpgradeProposalNote(raw.note);
54
+ return _extends({
55
+ plan: raw.plan,
56
+ proposedBy: typeof raw.proposedBy === 'string' ? raw.proposedBy : '',
57
+ proposedAt: Number.isFinite(raw.proposedAt) ? Number(raw.proposedAt) : 0
58
+ }, note ? {
59
+ note
60
+ } : {});
61
+ }
62
+ /**
63
+ * The proposal the workspace should be shown: the stored one, while no
64
+ * subscription is live.
65
+ *
66
+ * `billingStatus` is the org document's mirror of the subscription status,
67
+ * readable by every member, so this answers from the document the console
68
+ * already holds. A live subscription means the workspace has bought a plan,
69
+ * which settles the question the proposal asked; `writeOrgBilling`'s caller
70
+ * clears the field then, and this hides it in the meantime.
71
+ */ export function standingUpgradeProposal(org) {
72
+ if (isLiveSubscriptionStatus(org == null ? void 0 : org.billingStatus)) return null;
73
+ return readUpgradeProposal(org);
74
+ }
75
+ /** The plan intent a proposal carries into Billing: the plan, no interval. */ export function upgradeProposalIntent(proposal) {
76
+ return {
77
+ plan: proposal.plan,
78
+ interval: 'month',
79
+ // The proposal names a plan, not a cadence, so the page keeps whatever
80
+ // its toggle says rather than being told "monthly".
81
+ intervalStated: false,
82
+ contactSales: false
83
+ };
84
+ }
85
+ /** Where the proposal's button goes: Billing, with the plan preselected. */ export function upgradeProposalDestination(orgSlug, proposal) {
86
+ return onboardingDestination(orgSlug, upgradeProposalIntent(proposal));
87
+ }
88
+ /** "Acme's Aglyn team proposed the Starter plan". */ export function upgradeProposalHeadline(proposal, options) {
89
+ var _options_orgName;
90
+ const workspace = (_options_orgName = options.orgName) == null ? void 0 : _options_orgName.trim();
91
+ const team = workspace ? `${workspace}'s ${options.productName} team` : `Your ${options.productName} team`;
92
+ return `${team} proposed the ${PLAN_LABELS[proposal.plan]} plan`;
93
+ }
94
+
95
+ //# sourceMappingURL=upgrade-proposal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/upgrade-proposal.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 * The platform team's request that a workspace move to a paid plan\n * (AGL-3466), read in one place.\n *\n * A workspace staff build for a client is handed over first and sold second:\n * the client joins, looks around on whatever the workspace already has, and\n * only then is asked to upgrade. Staff record that ask as\n * `orgs/{orgId}.upgradeProposal`; this module decides whether it still\n * stands, what it may name, and where the button under it goes.\n */\n\nimport type {\n AglynOrgBilling,\n OrgPlan,\n OrgUpgradeProposal,\n} from '../foundation/definitions/org-billing.types'\nimport {\n onboardingDestination,\n type OnboardingPlanIntent,\n} from './onboarding-deep-link'\nimport { isLiveSubscriptionStatus } from './org-billing-doc'\nimport { PLAN_LABELS, SELF_SERVE_PLANS } from './plan-entitlements'\n\n/** The longest staff note a proposal carries. */\nexport const UPGRADE_PROPOSAL_NOTE_MAX = 500\n\n/**\n * The plans staff may propose: every tier a customer can buy themselves,\n * less Free, which is not a purchase. Enterprise is quoted per deal, so it\n * is never proposed through a checkout link.\n */\nexport const UPGRADE_PROPOSAL_PLANS: readonly OrgPlan[] = SELF_SERVE_PLANS.filter(\n (plan) => plan !== 'free',\n)\n\nexport function isUpgradeProposalPlan(plan: unknown): plan is OrgPlan {\n return (\n typeof plan === 'string' &&\n (UPGRADE_PROPOSAL_PLANS as readonly string[]).includes(plan)\n )\n}\n\n/** A staff note as it is stored: trimmed, bounded, and absent when blank. */\nexport function normalizeUpgradeProposalNote(note: unknown): string | undefined {\n if (typeof note !== 'string') return undefined\n const trimmed = note.trim().slice(0, UPGRADE_PROPOSAL_NOTE_MAX)\n return trimmed || undefined\n}\n\n/**\n * The stored proposal, narrowed, or null when there is none worth showing.\n *\n * A proposal naming a plan this build cannot sell reads as none: a card whose\n * button opens a checkout for an unknown tier is worse than no card.\n */\nexport function readUpgradeProposal(\n org: Partial<AglynOrgBilling> | null | undefined,\n): OrgUpgradeProposal | null {\n const raw = org?.upgradeProposal as Partial<OrgUpgradeProposal> | null | undefined\n if (!raw || typeof raw !== 'object' || !isUpgradeProposalPlan(raw.plan)) {\n return null\n }\n const note = normalizeUpgradeProposalNote(raw.note)\n return {\n plan: raw.plan,\n proposedBy: typeof raw.proposedBy === 'string' ? raw.proposedBy : '',\n proposedAt: Number.isFinite(raw.proposedAt) ? Number(raw.proposedAt) : 0,\n ...(note ? { note } : {}),\n }\n}\n\n/**\n * The proposal the workspace should be shown: the stored one, while no\n * subscription is live.\n *\n * `billingStatus` is the org document's mirror of the subscription status,\n * readable by every member, so this answers from the document the console\n * already holds. A live subscription means the workspace has bought a plan,\n * which settles the question the proposal asked; `writeOrgBilling`'s caller\n * clears the field then, and this hides it in the meantime.\n */\nexport function standingUpgradeProposal(\n org: Partial<AglynOrgBilling> | null | undefined,\n): OrgUpgradeProposal | null {\n if (isLiveSubscriptionStatus(org?.billingStatus)) return null\n return readUpgradeProposal(org)\n}\n\n/** The plan intent a proposal carries into Billing: the plan, no interval. */\nexport function upgradeProposalIntent(\n proposal: Pick<OrgUpgradeProposal, 'plan'>,\n): OnboardingPlanIntent {\n return {\n plan: proposal.plan,\n interval: 'month',\n // The proposal names a plan, not a cadence, so the page keeps whatever\n // its toggle says rather than being told \"monthly\".\n intervalStated: false,\n contactSales: false,\n }\n}\n\n/** Where the proposal's button goes: Billing, with the plan preselected. */\nexport function upgradeProposalDestination(\n orgSlug: string,\n proposal: Pick<OrgUpgradeProposal, 'plan'>,\n): string {\n return onboardingDestination(orgSlug, upgradeProposalIntent(proposal))\n}\n\n/** \"Acme's Aglyn team proposed the Starter plan\". */\nexport function upgradeProposalHeadline(\n proposal: Pick<OrgUpgradeProposal, 'plan'>,\n options: { orgName?: string | null; productName: string },\n): string {\n const workspace = options.orgName?.trim()\n const team = workspace\n ? `${workspace}'s ${options.productName} team`\n : `Your ${options.productName} team`\n return `${team} proposed the ${PLAN_LABELS[proposal.plan]} plan`\n}\n"],"names":["onboardingDestination","isLiveSubscriptionStatus","PLAN_LABELS","SELF_SERVE_PLANS","UPGRADE_PROPOSAL_NOTE_MAX","UPGRADE_PROPOSAL_PLANS","filter","plan","isUpgradeProposalPlan","includes","normalizeUpgradeProposalNote","note","undefined","trimmed","trim","slice","readUpgradeProposal","org","raw","upgradeProposal","proposedBy","proposedAt","Number","isFinite","standingUpgradeProposal","billingStatus","upgradeProposalIntent","proposal","interval","intervalStated","contactSales","upgradeProposalDestination","orgSlug","upgradeProposalHeadline","options","workspace","orgName","team","productName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;CASC;AAOD,SACEA,qBAAqB,QAEhB,4BAAwB;AAC/B,SAASC,wBAAwB,QAAQ,uBAAmB;AAC5D,SAASC,WAAW,EAAEC,gBAAgB,QAAQ,yBAAqB;AAEnE,+CAA+C,GAC/C,OAAO,MAAMC,4BAA4B,IAAG;AAE5C;;;;CAIC,GACD,OAAO,MAAMC,yBAA6CF,iBAAiBG,MAAM,CAC/E,CAACC,OAASA,SAAS,QACpB;AAED,OAAO,SAASC,sBAAsBD,IAAa;IACjD,OACE,OAAOA,SAAS,YAChB,AAACF,uBAA6CI,QAAQ,CAACF;AAE3D;AAEA,2EAA2E,GAC3E,OAAO,SAASG,6BAA6BC,IAAa;IACxD,IAAI,OAAOA,SAAS,UAAU,OAAOC;IACrC,MAAMC,UAAUF,KAAKG,IAAI,GAAGC,KAAK,CAAC,GAAGX;IACrC,OAAOS,WAAWD;AACpB;AAEA;;;;;CAKC,GACD,OAAO,SAASI,oBACdC,GAAgD;IAEhD,MAAMC,MAAMD,uBAAAA,IAAKE,eAAe;IAChC,IAAI,CAACD,OAAO,OAAOA,QAAQ,YAAY,CAACV,sBAAsBU,IAAIX,IAAI,GAAG;QACvE,OAAO;IACT;IACA,MAAMI,OAAOD,6BAA6BQ,IAAIP,IAAI;IAClD,OAAO;QACLJ,MAAMW,IAAIX,IAAI;QACda,YAAY,OAAOF,IAAIE,UAAU,KAAK,WAAWF,IAAIE,UAAU,GAAG;QAClEC,YAAYC,OAAOC,QAAQ,CAACL,IAAIG,UAAU,IAAIC,OAAOJ,IAAIG,UAAU,IAAI;OACnEV,OAAO;QAAEA;IAAK,IAAI,CAAC;AAE3B;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASa,wBACdP,GAAgD;IAEhD,IAAIhB,yBAAyBgB,uBAAAA,IAAKQ,aAAa,GAAG,OAAO;IACzD,OAAOT,oBAAoBC;AAC7B;AAEA,4EAA4E,GAC5E,OAAO,SAASS,sBACdC,QAA0C;IAE1C,OAAO;QACLpB,MAAMoB,SAASpB,IAAI;QACnBqB,UAAU;QACV,uEAAuE;QACvE,oDAAoD;QACpDC,gBAAgB;QAChBC,cAAc;IAChB;AACF;AAEA,0EAA0E,GAC1E,OAAO,SAASC,2BACdC,OAAe,EACfL,QAA0C;IAE1C,OAAO3B,sBAAsBgC,SAASN,sBAAsBC;AAC9D;AAEA,mDAAmD,GACnD,OAAO,SAASM,wBACdN,QAA0C,EAC1CO,OAAyD;QAEvCA;IAAlB,MAAMC,aAAYD,mBAAAA,QAAQE,OAAO,qBAAfF,iBAAiBpB,IAAI;IACvC,MAAMuB,OAAOF,YACT,GAAGA,UAAU,GAAG,EAAED,QAAQI,WAAW,CAAC,KAAK,CAAC,GAC5C,CAAC,KAAK,EAAEJ,QAAQI,WAAW,CAAC,KAAK,CAAC;IACtC,OAAO,GAAGD,KAAK,cAAc,EAAEnC,WAAW,CAACyB,SAASpB,IAAI,CAAC,CAAC,KAAK,CAAC;AAClE"}
@@ -0,0 +1,33 @@
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
+ * THE MONTH A MONTHLY COUNTER IS KEYED BY.
19
+ *
20
+ * Every counter the platform keeps per month — a door's refusals and catches,
21
+ * a site's visitor records, an organization's usage — is keyed by the UTC
22
+ * `YYYY-MM` of the moment it counts, and a reader that derived the key any
23
+ * other way would read zero on exactly the sites the counter is about. A
24
+ * monthly ceiling lifts at the boundary that key rolls over on, which is the
25
+ * one moment a notice may promise.
26
+ */
27
+ /** The UTC `YYYY-MM` a monthly counter is keyed by. */
28
+ export declare function utcMonthKey(now?: Date): string;
29
+ /**
30
+ * 00:00 UTC on the first of next month: when a monthly counter's key rolls
31
+ * over, and with it when a monthly ceiling lifts.
32
+ */
33
+ export declare function nextUtcMonthStart(now?: Date): Date;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * THE MONTH A MONTHLY COUNTER IS KEYED BY.
18
+ *
19
+ * Every counter the platform keeps per month — a door's refusals and catches,
20
+ * a site's visitor records, an organization's usage — is keyed by the UTC
21
+ * `YYYY-MM` of the moment it counts, and a reader that derived the key any
22
+ * other way would read zero on exactly the sites the counter is about. A
23
+ * monthly ceiling lifts at the boundary that key rolls over on, which is the
24
+ * one moment a notice may promise.
25
+ */ /** The UTC `YYYY-MM` a monthly counter is keyed by. */ export function utcMonthKey(now = new Date()) {
26
+ return now.toISOString().slice(0, 7);
27
+ }
28
+ /**
29
+ * 00:00 UTC on the first of next month: when a monthly counter's key rolls
30
+ * over, and with it when a monthly ceiling lifts.
31
+ */ export function nextUtcMonthStart(now = new Date()) {
32
+ return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1));
33
+ }
34
+
35
+ //# sourceMappingURL=utc-month.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/utc-month.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 * THE MONTH A MONTHLY COUNTER IS KEYED BY.\n *\n * Every counter the platform keeps per month — a door's refusals and catches,\n * a site's visitor records, an organization's usage — is keyed by the UTC\n * `YYYY-MM` of the moment it counts, and a reader that derived the key any\n * other way would read zero on exactly the sites the counter is about. A\n * monthly ceiling lifts at the boundary that key rolls over on, which is the\n * one moment a notice may promise.\n */\n\n/** The UTC `YYYY-MM` a monthly counter is keyed by. */\nexport function utcMonthKey(now: Date = new Date()): string {\n return now.toISOString().slice(0, 7)\n}\n\n/**\n * 00:00 UTC on the first of next month: when a monthly counter's key rolls\n * over, and with it when a monthly ceiling lifts.\n */\nexport function nextUtcMonthStart(now: Date = new Date()): Date {\n return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1))\n}\n"],"names":["utcMonthKey","now","Date","toISOString","slice","nextUtcMonthStart","UTC","getUTCFullYear","getUTCMonth"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;CASC,GAED,qDAAqD,GACrD,OAAO,SAASA,YAAYC,MAAY,IAAIC,MAAM;IAChD,OAAOD,IAAIE,WAAW,GAAGC,KAAK,CAAC,GAAG;AACpC;AAEA;;;CAGC,GACD,OAAO,SAASC,kBAAkBJ,MAAY,IAAIC,MAAM;IACtD,OAAO,IAAIA,KAAKA,KAAKI,GAAG,CAACL,IAAIM,cAAc,IAAIN,IAAIO,WAAW,KAAK,GAAG;AACxE"}
@@ -128,11 +128,11 @@ export declare const LEADS_MAX_PER_HOST = 200000;
128
128
  * discriminates nothing — this code is the whole discriminator, and one means
129
129
  * "slow down" while the other means "this site is not taking new accounts".
130
130
  * Shared rather than restated so producer and consumer cannot drift, exactly
131
- * as `FORM_ABUSE_CEILING_CODE` is.
131
+ * as the form door's `form-abuse-ceiling` is.
132
132
  */
133
133
  export declare const SITE_MEMBER_CEILING_CODE = "site-member-ceiling";
134
134
  /**
135
- * The visitor's sentence, under the three rules `FORM_UNAVAILABLE_MESSAGE`
135
+ * The visitor's sentence, under the three rules the form door's refusal
136
136
  * established (AGL-1666) and for the identical reason: the person reading it
137
137
  * is a stranger to the site and NOT our customer.
138
138
  *
@@ -225,7 +225,7 @@ export declare function visitorRecordCeilingAnnounced(counter: Record<string, un
225
225
  * pays a read for it.
226
226
  */
227
227
  export declare function visitorRecordAcceptFillsCeiling(used: number, ceiling: number): boolean;
228
- /** What the site's owner is shown. Shaped like `FormsPausedOwnerNotice`. */
228
+ /** What the site's owner is shown. Shaped like `VisitorDoorPausedNotice`. */
229
229
  export interface VisitorRecordsPausedNotice {
230
230
  title: string;
231
231
  /** What happened, how many, and what actually clears it. */
@@ -242,7 +242,7 @@ export interface VisitorRecordsPausedNotice {
242
242
  * line for symmetry would print a date that means nothing.
243
243
  *
244
244
  * Returns `null` below one refusal, the same rule as
245
- * `formSubmissionsPausedNotice` and for the same reason: the counter document
245
+ * `visitorDoorPausedNotice` and for the same reason: the counter document
246
246
  * persists from its first trip forever, and "0 refused" on every later month
247
247
  * trains the owner to ignore the row that will one day be real.
248
248
  */
@@ -124,10 +124,10 @@
124
124
  * discriminates nothing — this code is the whole discriminator, and one means
125
125
  * "slow down" while the other means "this site is not taking new accounts".
126
126
  * Shared rather than restated so producer and consumer cannot drift, exactly
127
- * as `FORM_ABUSE_CEILING_CODE` is.
127
+ * as the form door's `form-abuse-ceiling` is.
128
128
  */ export const SITE_MEMBER_CEILING_CODE = 'site-member-ceiling';
129
129
  /**
130
- * The visitor's sentence, under the three rules `FORM_UNAVAILABLE_MESSAGE`
130
+ * The visitor's sentence, under the three rules the form door's refusal
131
131
  * established (AGL-1666) and for the identical reason: the person reading it
132
132
  * is a stranger to the site and NOT our customer.
133
133
  *
@@ -191,7 +191,7 @@
191
191
  * been told about the crossing the site is in: when the notice went out, or
192
192
  * `null` once the site has been seen below the ceiling again (AGL-3442).
193
193
  */ export const VISITOR_RECORD_NOTICE_SENT_FIELD = 'noticeSentAtMs';
194
- /** A month key on the refusal counter — `submissionMonthKey`'s `YYYY-MM`. */ const REFUSAL_MONTH_KEY = /^\d{4}-\d{2}$/;
194
+ /** A month key on the refusal counter — `utcMonthKey`'s `YYYY-MM`. */ const REFUSAL_MONTH_KEY = /^\d{4}-\d{2}$/;
195
195
  /**
196
196
  * Whether the managers have already been told this site is at the ceiling.
197
197
  *
@@ -236,7 +236,7 @@
236
236
  * line for symmetry would print a date that means nothing.
237
237
  *
238
238
  * Returns `null` below one refusal, the same rule as
239
- * `formSubmissionsPausedNotice` and for the same reason: the counter document
239
+ * `visitorDoorPausedNotice` and for the same reason: the counter document
240
240
  * persists from its first trip forever, and "0 refused" on every later month
241
241
  * trains the owner to ignore the row that will one day be real.
242
242
  */ export function visitorRecordsPausedNotice(input) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/visitor-record-ceiling.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 * Flat platform ceilings on the two host subcollections an ANONYMOUS VISITOR\n * can create documents in (AGL-1529).\n *\n * ## What this is\n *\n * A platform-wide ceiling, and deliberately NOT a plan dimension — so\n * \"unlimited member accounts on every plan\" stays true, because an abuse\n * control is not something we sell. The same instrument is already in place\n * twice: AGL-1655 for forms and AGL-2155 for bandwidth.\n *\n * ## Why it is not a plan dimension, and must never become one\n *\n * AGL-889 decided member accounts are UNLIMITED on every plan as a PRICING\n * matter, and `/pricing` says so. A cap that varied by plan would make that\n * sentence false the moment it shipped. So there is no `OrgEntitlements` key\n * here, no `PLAN_ENTITLEMENTS` row, no band and nothing to re-price: every\n * plan, free through enterprise, gets the same number, and the number exists\n * only to answer the question `checkFormSubmissionAbuseCeiling`'s docblock\n * poses — *\"is this still a customer's traffic at all?\"* A plan gate asks\n * whether the customer bought this; this asks whether a customer is involved.\n *\n * ## The hole this closes\n *\n * `hosts/{hostId}/siteMembers` and `hosts/{hostId}/leads` were bounded by\n * NOTHING. Both are written on behalf of anonymous visitors on a public site\n * (`membershipRegisterHandler`, the two bookings lead paths), and the only\n * bound was `visitorWriteRateLimitRefusal` — keyed on (host, IP), fails soft,\n * and bounds the RATE rather than the TOTAL. That is verbatim the sentence\n * AGL-2265 used as its reason for adding the free-workspace ceiling, and the\n * shape AGL-2266 closed for `actions` and `entries`: unbounded Firestore\n * documents behind a $0 subscription.\n *\n * ## Why the shape here is the FLAT family, not the MULTIPLE family\n *\n * The two ceilings named as precedent — `FORM_ABUSE_CEILING_*` and\n * `BANDWIDTH_ABUSE_CEILING_*` — are both `max(floor, included × 10)`. They\n * can be, because forms and bandwidth each have an *included band* on the\n * price list to multiply. Members and leads have no included band by\n * construction (that is the whole point of AGL-889), so there is nothing to\n * multiply and a multiple would have to invent a plan dimension to have an\n * operand — the exact thing the decision forbids.\n *\n * The repo already has the family for that case, and those same docblocks\n * point at it: `WEBHOOK_MAX_PER_HOST` (AGL-1360), `NON_PAGE_SCREEN_MAX_PER_HOST`\n * (AGL-1399), `ACTIONS_MAX_PER_HOST` / `ENTRIES_MAX_PER_COLLECTION` (AGL-2266),\n * `AUTHORS_MAX_PER_HOST` (AGL-2486) — *\"a flat PLATFORM cap with no\n * `OrgEntitlements` key, no variation by plan, and nothing on the price list\n * to explain.\"* So the two named precedents supply the POSTURE (containment,\n * not capacity; a trip is an incident, not a quota; the refusal must be\n * visible to the host and opaque to the visitor) and the flat family supplies\n * the SHAPE. They do not disagree; they answer different halves.\n *\n * ## A TOTAL, not a monthly count — the one place the posture differs\n *\n * `FORM_ABUSE_CEILING` and `BANDWIDTH_ABUSE_CEILING` both count a month and\n * therefore self-clear at the month boundary with nobody doing anything. These\n * count LIVE DOCUMENTS, which do not. The refusal counters below are still\n * month-keyed — that is a rate of refusals, and it is what the owner's notice\n * reports — but the ceiling itself is cleared by deleting members or leads (or\n * by support raising it), and the owner's notice must not promise a date. A\n * notice that said \"accepting again on 1 September\" would be a promise the\n * platform does not keep.\n */\n\n/**\n * How many member accounts one site may hold (AGL-1529). the figure.\n *\n * Sized to the busiest plausible membership site rather than to today's data,\n * which is the only property a flat ceiling needs. A site member is a human\n * who chose a password on ONE site; 50,000 of them is a large membership\n * roster — larger than anything on the platform and larger than anything the\n * beta will produce — while a script wants millions and reaches this in an\n * afternoon. Every account past it is refused, none is billed, and none of\n * it changes what any plan includes.\n *\n * Counted as LIVE documents, so removing a member in the inbox frees the\n * slot. Counting tombstones would be AGL-1173's bug one collection over.\n */\nexport const SITE_MEMBERS_MAX_PER_HOST = 50_000\n\n/**\n * How many lead records one site may hold (AGL-1529).\n *\n * **Deliberately NOT the same number as {@link SITE_MEMBERS_MAX_PER_HOST}**,\n * and the reason changed when leads were deduped.\n *\n * A lead used to be APPEND-ONLY, keyed by nothing: every sign-up wrote one\n * and so did every booking, so one returning customer legitimately produced\n * many rows. 4× was sized to keep the MEMBER ceiling the binding one on the\n * path they share, rather than refusing a record on a site doing nothing\n * wrong. Since `docs/specs/reusable-forms.md` §4 a lead is one document per\n * person per host, keyed on `personKey`, so leads and members now count the\n * same population and that arithmetic no longer applies.\n *\n * **The number stays where it is.** Lowering it because the collection stopped\n * growing per event would convert a safety margin into a plan limit, which\n * `docs/DECISION_LOG.md` forbids — the 2026-08-23 entry made this ceiling a\n * flat platform abuse control precisely so \"unlimited leads on every plan\"\n * stays literally true. What the headroom now buys is a ceiling that is very\n * unlikely to trip at all, which is the right shape for a control that\n * refuses a person: 200,000 lead rows at ~200 bytes is ~40 MB, which is a\n * number, and unbounded is not.\n *\n * ⚠️ It must stay ABOVE {@link SITE_MEMBERS_MAX_PER_HOST}. A site member is\n * also a lead, so an equal or lower lead ceiling would refuse the side effect\n * of a sign-up the member ceiling had just allowed.\n */\nexport const LEADS_MAX_PER_HOST = 200_000\n\n/**\n * Refusal code carried by the sign-up 429 (AGL-1529).\n *\n * The dispatcher's per-(host, IP) rate limiter answers 429 too, so the STATUS\n * discriminates nothing — this code is the whole discriminator, and one means\n * \"slow down\" while the other means \"this site is not taking new accounts\".\n * Shared rather than restated so producer and consumer cannot drift, exactly\n * as `FORM_ABUSE_CEILING_CODE` is.\n */\nexport const SITE_MEMBER_CEILING_CODE = 'site-member-ceiling'\n\n/**\n * The visitor's sentence, under the three rules `FORM_UNAVAILABLE_MESSAGE`\n * established (AGL-1666) and for the identical reason: the person reading it\n * is a stranger to the site and NOT our customer.\n *\n * 1. **It does not blame them.** They filled the form in correctly. Nothing\n * about their address, their password or their retry is the reason.\n * 2. **It does not explain.** \"Unusual volume\", \"this site is at its member\n * limit\", or anything naming Aglyn, tells a stranger something about the\n * site owner's account that the owner never chose to publish — and names\n * a platform the visitor did not come here to hear about.\n * 3. **It does not sound successful.** It says, in as many words, that the\n * account was not created, so nobody walks away believing they have one\n * and waiting for an email that is never coming.\n */\nexport const SITE_MEMBER_UNAVAILABLE_MESSAGE =\n 'This site isn’t accepting new accounts right now, so your account was ' +\n 'not created. Please try again later.'\n\n/** The verdict every enforcement point below asks for. */\nexport interface VisitorRecordCeilingResult {\n /** True once the live document count has REACHED the ceiling. */\n exceeded: boolean\n /** The ceiling that was compared against. Always a finite integer. */\n ceiling: number\n /** The count that was compared, floored at 0. */\n used: number\n}\n\n/**\n * Is this site at a flat visitor-record ceiling?\n *\n * The ceiling is an explicit argument rather than read from a table, so the\n * suite can re-drive the SAME usage against a ceiling one higher and require\n * it to succeed — the causation leg of `free-tier-caps-refuse.spec.ts`, which\n * is the only thing that distinguishes \"refused by THIS cap\" from \"refuses\n * everything\". No production caller passes anything but the two constants\n * above; {@link checkSiteMemberCeiling} and {@link checkLeadCeiling} bind them.\n *\n * `strictNullChecks` is OFF repo-wide, so both inputs are normalised rather\n * than trusted, and the two directions are deliberately NOT symmetric:\n *\n * - `used` folds a missing/NaN count to 0, matching `checkBandwidthAbuseCeiling`.\n * An unreadable count must not refuse a site; the count is read inside a\n * transaction that throws rather than returning nothing, so this is a\n * belt, not a path.\n * - `ceiling` folds a missing/NaN ceiling to 0, and a ceiling of 0 REFUSES\n * EVERYTHING. `0` is a legitimate ceiling meaning \"none allowed\" and must\n * never read as unlimited — the `if (!count)` trap this repo keeps being\n * bitten by, in the argument that decides the refusal.\n */\nexport function checkVisitorRecordCeiling(\n used: number,\n ceiling: number,\n): VisitorRecordCeilingResult {\n const resolvedCeiling = Math.max(0, Math.floor(Number(ceiling) || 0))\n const resolvedUsed = Math.max(0, Math.floor(Number(used) || 0))\n return {\n exceeded: resolvedUsed >= resolvedCeiling,\n ceiling: resolvedCeiling,\n used: resolvedUsed,\n }\n}\n\n/** {@link checkVisitorRecordCeiling} bound to {@link SITE_MEMBERS_MAX_PER_HOST}. */\nexport function checkSiteMemberCeiling(\n used: number,\n): VisitorRecordCeilingResult {\n return checkVisitorRecordCeiling(used, SITE_MEMBERS_MAX_PER_HOST)\n}\n\n/** {@link checkVisitorRecordCeiling} bound to {@link LEADS_MAX_PER_HOST}. */\nexport function checkLeadCeiling(used: number): VisitorRecordCeilingResult {\n return checkVisitorRecordCeiling(used, LEADS_MAX_PER_HOST)\n}\n\n/** Which collection a trip belongs to — the counter id and the notice differ. */\nexport type VisitorRecordKind = 'siteMembers' | 'leads'\n\n/**\n * `hosts/{hostId}/counters/{…}` — the durable, client-unwritable record of a\n * trip, in the same counters-document shape `formSubmissionsRefused` uses\n * (AGL-1367/1655). Named through a function so the writer in\n * `@aglyn/tenant-data-admin` and the reader in the inbox console page cannot\n * spell it two different ways.\n */\nexport function visitorRecordRefusedCounterId(kind: VisitorRecordKind): string {\n return kind === 'leads' ? 'leadsRefused' : 'siteMembersRefused'\n}\n\n/**\n * The field on the refusal counter that says whether the site's managers have\n * been told about the crossing the site is in: when the notice went out, or\n * `null` once the site has been seen below the ceiling again (AGL-3442).\n */\nexport const VISITOR_RECORD_NOTICE_SENT_FIELD = 'noticeSentAtMs'\n\n/** A month key on the refusal counter — `submissionMonthKey`'s `YYYY-MM`. */\nconst REFUSAL_MONTH_KEY = /^\\d{4}-\\d{2}$/\n\n/**\n * Whether the managers have already been told this site is at the ceiling.\n *\n * The notice goes out once per CROSSING (AGL-3442): on the first refusal after\n * the site reaches the ceiling, never again while it stays there, and again\n * only after it has been seen below the ceiling and reached it once more. A\n * site can sit at this ceiling for months, and a notice every month would be\n * an alarm about a state nobody needs telling about twice.\n *\n * A counter written before {@link VISITOR_RECORD_NOTICE_SENT_FIELD} existed\n * does not carry it. Its writer sent the notice on the first refusal of each\n * month, so any month with a refusal is a month the notice went out, and such\n * a counter reads as announced. Otherwise every site already at its ceiling\n * would be notified again on its next refusal.\n */\nexport function visitorRecordCeilingAnnounced(\n counter: Record<string, unknown> | null | undefined,\n): boolean {\n if (!counter) return false\n const sent = counter[VISITOR_RECORD_NOTICE_SENT_FIELD]\n if (sent === null) return false\n if (sent !== undefined) return true\n return Object.entries(counter).some(\n ([key, value]) => REFUSAL_MONTH_KEY.test(key) && Number(value) > 0,\n )\n}\n\n/**\n * Whether accepting one more record, with `used` already held, takes the\n * site's last free slot under `ceiling`.\n *\n * Every new record is judged against the ceiling one at a time, so a site that\n * dropped below the ceiling can only reach it again through this accept. That\n * makes it the one accept where the notice is re-armed, and the only one that\n * pays a read for it.\n */\nexport function visitorRecordAcceptFillsCeiling(\n used: number,\n ceiling: number,\n): boolean {\n const resolvedUsed = Math.max(0, Math.floor(Number(used) || 0))\n return checkVisitorRecordCeiling(resolvedUsed + 1, ceiling).exceeded\n}\n\n/** What the site's owner is shown. Shaped like `FormsPausedOwnerNotice`. */\nexport interface VisitorRecordsPausedNotice {\n title: string\n /** What happened, how many, and what actually clears it. */\n message: string\n}\n\n/**\n * The owner's notice — the half deliberately withheld from the visitor.\n *\n * Two `{title, message}` lines rather than `LockdownRefusalNotice`'s three:\n * there is **no `until`**, and its absence is the point. The form and\n * bandwidth ceilings count a month and genuinely do lift on the 1st, so their\n * notices name a date. This ceiling counts live documents, so no date lifts\n * it — deleting records does, or support raising it does. Borrowing the third\n * line for symmetry would print a date that means nothing.\n *\n * Returns `null` below one refusal, the same rule as\n * `formSubmissionsPausedNotice` and for the same reason: the counter document\n * persists from its first trip forever, and \"0 refused\" on every later month\n * trains the owner to ignore the row that will one day be real.\n */\nexport function visitorRecordsPausedNotice(input: {\n kind: VisitorRecordKind\n /** Refusals recorded for the month being displayed. */\n refused: number\n /** The ceiling that was crossed, from the counter document. */\n ceiling?: number\n}): VisitorRecordsPausedNotice | null {\n const refused = Math.floor(Number(input.refused) || 0)\n if (refused < 1) return null\n const ceiling =\n typeof input.ceiling === 'number' && Number.isFinite(input.ceiling)\n ? Math.floor(input.ceiling)\n : undefined\n const leads = input.kind === 'leads'\n const noun = leads ? 'lead' : 'sign-up'\n return {\n title: leads\n ? 'Lead capture is paused'\n : 'New member sign-ups are paused',\n message:\n `${refused.toLocaleString()} ${noun}${refused === 1 ? '' : 's'} on ` +\n `this site ${refused === 1 ? 'has' : 'have'} been refused this month` +\n (ceiling\n ? ` after it reached ${ceiling.toLocaleString()} ` +\n `${leads ? 'leads' : 'member accounts'}`\n : '') +\n '. Refused records are not stored and are not billed. This is a ' +\n 'platform safety limit, not part of your plan — every plan includes ' +\n `unlimited ${leads ? 'leads' : 'member accounts'}. Removing ` +\n `${leads ? 'leads' : 'members'} below the limit starts acceptance ` +\n 'again immediately; if this is real traffic, contact support and we ' +\n 'will raise it.',\n }\n}\n"],"names":["SITE_MEMBERS_MAX_PER_HOST","LEADS_MAX_PER_HOST","SITE_MEMBER_CEILING_CODE","SITE_MEMBER_UNAVAILABLE_MESSAGE","checkVisitorRecordCeiling","used","ceiling","resolvedCeiling","Math","max","floor","Number","resolvedUsed","exceeded","checkSiteMemberCeiling","checkLeadCeiling","visitorRecordRefusedCounterId","kind","VISITOR_RECORD_NOTICE_SENT_FIELD","REFUSAL_MONTH_KEY","visitorRecordCeilingAnnounced","counter","sent","undefined","Object","entries","some","key","value","test","visitorRecordAcceptFillsCeiling","visitorRecordsPausedNotice","input","refused","isFinite","leads","noun","title","message","toLocaleString"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/visitor-record-ceiling.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 * Flat platform ceilings on the two host subcollections an ANONYMOUS VISITOR\n * can create documents in (AGL-1529).\n *\n * ## What this is\n *\n * A platform-wide ceiling, and deliberately NOT a plan dimension — so\n * \"unlimited member accounts on every plan\" stays true, because an abuse\n * control is not something we sell. The same instrument is already in place\n * twice: AGL-1655 for forms and AGL-2155 for bandwidth.\n *\n * ## Why it is not a plan dimension, and must never become one\n *\n * AGL-889 decided member accounts are UNLIMITED on every plan as a PRICING\n * matter, and `/pricing` says so. A cap that varied by plan would make that\n * sentence false the moment it shipped. So there is no `OrgEntitlements` key\n * here, no `PLAN_ENTITLEMENTS` row, no band and nothing to re-price: every\n * plan, free through enterprise, gets the same number, and the number exists\n * only to answer the question `checkFormSubmissionAbuseCeiling`'s docblock\n * poses — *\"is this still a customer's traffic at all?\"* A plan gate asks\n * whether the customer bought this; this asks whether a customer is involved.\n *\n * ## The hole this closes\n *\n * `hosts/{hostId}/siteMembers` and `hosts/{hostId}/leads` were bounded by\n * NOTHING. Both are written on behalf of anonymous visitors on a public site\n * (`membershipRegisterHandler`, the two bookings lead paths), and the only\n * bound was `visitorWriteRateLimitRefusal` — keyed on (host, IP), fails soft,\n * and bounds the RATE rather than the TOTAL. That is verbatim the sentence\n * AGL-2265 used as its reason for adding the free-workspace ceiling, and the\n * shape AGL-2266 closed for `actions` and `entries`: unbounded Firestore\n * documents behind a $0 subscription.\n *\n * ## Why the shape here is the FLAT family, not the MULTIPLE family\n *\n * The two ceilings named as precedent — `FORM_ABUSE_CEILING_*` and\n * `BANDWIDTH_ABUSE_CEILING_*` — are both `max(floor, included × 10)`. They\n * can be, because forms and bandwidth each have an *included band* on the\n * price list to multiply. Members and leads have no included band by\n * construction (that is the whole point of AGL-889), so there is nothing to\n * multiply and a multiple would have to invent a plan dimension to have an\n * operand — the exact thing the decision forbids.\n *\n * The repo already has the family for that case, and those same docblocks\n * point at it: `WEBHOOK_MAX_PER_HOST` (AGL-1360), `NON_PAGE_SCREEN_MAX_PER_HOST`\n * (AGL-1399), `ACTIONS_MAX_PER_HOST` / `ENTRIES_MAX_PER_COLLECTION` (AGL-2266),\n * `AUTHORS_MAX_PER_HOST` (AGL-2486) — *\"a flat PLATFORM cap with no\n * `OrgEntitlements` key, no variation by plan, and nothing on the price list\n * to explain.\"* So the two named precedents supply the POSTURE (containment,\n * not capacity; a trip is an incident, not a quota; the refusal must be\n * visible to the host and opaque to the visitor) and the flat family supplies\n * the SHAPE. They do not disagree; they answer different halves.\n *\n * ## A TOTAL, not a monthly count — the one place the posture differs\n *\n * `FORM_ABUSE_CEILING` and `BANDWIDTH_ABUSE_CEILING` both count a month and\n * therefore self-clear at the month boundary with nobody doing anything. These\n * count LIVE DOCUMENTS, which do not. The refusal counters below are still\n * month-keyed — that is a rate of refusals, and it is what the owner's notice\n * reports — but the ceiling itself is cleared by deleting members or leads (or\n * by support raising it), and the owner's notice must not promise a date. A\n * notice that said \"accepting again on 1 September\" would be a promise the\n * platform does not keep.\n */\n\n/**\n * How many member accounts one site may hold (AGL-1529). the figure.\n *\n * Sized to the busiest plausible membership site rather than to today's data,\n * which is the only property a flat ceiling needs. A site member is a human\n * who chose a password on ONE site; 50,000 of them is a large membership\n * roster — larger than anything on the platform and larger than anything the\n * beta will produce — while a script wants millions and reaches this in an\n * afternoon. Every account past it is refused, none is billed, and none of\n * it changes what any plan includes.\n *\n * Counted as LIVE documents, so removing a member in the inbox frees the\n * slot. Counting tombstones would be AGL-1173's bug one collection over.\n */\nexport const SITE_MEMBERS_MAX_PER_HOST = 50_000\n\n/**\n * How many lead records one site may hold (AGL-1529).\n *\n * **Deliberately NOT the same number as {@link SITE_MEMBERS_MAX_PER_HOST}**,\n * and the reason changed when leads were deduped.\n *\n * A lead used to be APPEND-ONLY, keyed by nothing: every sign-up wrote one\n * and so did every booking, so one returning customer legitimately produced\n * many rows. 4× was sized to keep the MEMBER ceiling the binding one on the\n * path they share, rather than refusing a record on a site doing nothing\n * wrong. Since `docs/specs/reusable-forms.md` §4 a lead is one document per\n * person per host, keyed on `personKey`, so leads and members now count the\n * same population and that arithmetic no longer applies.\n *\n * **The number stays where it is.** Lowering it because the collection stopped\n * growing per event would convert a safety margin into a plan limit, which\n * `docs/DECISION_LOG.md` forbids — the 2026-08-23 entry made this ceiling a\n * flat platform abuse control precisely so \"unlimited leads on every plan\"\n * stays literally true. What the headroom now buys is a ceiling that is very\n * unlikely to trip at all, which is the right shape for a control that\n * refuses a person: 200,000 lead rows at ~200 bytes is ~40 MB, which is a\n * number, and unbounded is not.\n *\n * ⚠️ It must stay ABOVE {@link SITE_MEMBERS_MAX_PER_HOST}. A site member is\n * also a lead, so an equal or lower lead ceiling would refuse the side effect\n * of a sign-up the member ceiling had just allowed.\n */\nexport const LEADS_MAX_PER_HOST = 200_000\n\n/**\n * Refusal code carried by the sign-up 429 (AGL-1529).\n *\n * The dispatcher's per-(host, IP) rate limiter answers 429 too, so the STATUS\n * discriminates nothing — this code is the whole discriminator, and one means\n * \"slow down\" while the other means \"this site is not taking new accounts\".\n * Shared rather than restated so producer and consumer cannot drift, exactly\n * as the form door's `form-abuse-ceiling` is.\n */\nexport const SITE_MEMBER_CEILING_CODE = 'site-member-ceiling'\n\n/**\n * The visitor's sentence, under the three rules the form door's refusal\n * established (AGL-1666) and for the identical reason: the person reading it\n * is a stranger to the site and NOT our customer.\n *\n * 1. **It does not blame them.** They filled the form in correctly. Nothing\n * about their address, their password or their retry is the reason.\n * 2. **It does not explain.** \"Unusual volume\", \"this site is at its member\n * limit\", or anything naming Aglyn, tells a stranger something about the\n * site owner's account that the owner never chose to publish — and names\n * a platform the visitor did not come here to hear about.\n * 3. **It does not sound successful.** It says, in as many words, that the\n * account was not created, so nobody walks away believing they have one\n * and waiting for an email that is never coming.\n */\nexport const SITE_MEMBER_UNAVAILABLE_MESSAGE =\n 'This site isn’t accepting new accounts right now, so your account was ' +\n 'not created. Please try again later.'\n\n/** The verdict every enforcement point below asks for. */\nexport interface VisitorRecordCeilingResult {\n /** True once the live document count has REACHED the ceiling. */\n exceeded: boolean\n /** The ceiling that was compared against. Always a finite integer. */\n ceiling: number\n /** The count that was compared, floored at 0. */\n used: number\n}\n\n/**\n * Is this site at a flat visitor-record ceiling?\n *\n * The ceiling is an explicit argument rather than read from a table, so the\n * suite can re-drive the SAME usage against a ceiling one higher and require\n * it to succeed — the causation leg of `free-tier-caps-refuse.spec.ts`, which\n * is the only thing that distinguishes \"refused by THIS cap\" from \"refuses\n * everything\". No production caller passes anything but the two constants\n * above; {@link checkSiteMemberCeiling} and {@link checkLeadCeiling} bind them.\n *\n * `strictNullChecks` is OFF repo-wide, so both inputs are normalised rather\n * than trusted, and the two directions are deliberately NOT symmetric:\n *\n * - `used` folds a missing/NaN count to 0, matching `checkBandwidthAbuseCeiling`.\n * An unreadable count must not refuse a site; the count is read inside a\n * transaction that throws rather than returning nothing, so this is a\n * belt, not a path.\n * - `ceiling` folds a missing/NaN ceiling to 0, and a ceiling of 0 REFUSES\n * EVERYTHING. `0` is a legitimate ceiling meaning \"none allowed\" and must\n * never read as unlimited — the `if (!count)` trap this repo keeps being\n * bitten by, in the argument that decides the refusal.\n */\nexport function checkVisitorRecordCeiling(\n used: number,\n ceiling: number,\n): VisitorRecordCeilingResult {\n const resolvedCeiling = Math.max(0, Math.floor(Number(ceiling) || 0))\n const resolvedUsed = Math.max(0, Math.floor(Number(used) || 0))\n return {\n exceeded: resolvedUsed >= resolvedCeiling,\n ceiling: resolvedCeiling,\n used: resolvedUsed,\n }\n}\n\n/** {@link checkVisitorRecordCeiling} bound to {@link SITE_MEMBERS_MAX_PER_HOST}. */\nexport function checkSiteMemberCeiling(\n used: number,\n): VisitorRecordCeilingResult {\n return checkVisitorRecordCeiling(used, SITE_MEMBERS_MAX_PER_HOST)\n}\n\n/** {@link checkVisitorRecordCeiling} bound to {@link LEADS_MAX_PER_HOST}. */\nexport function checkLeadCeiling(used: number): VisitorRecordCeilingResult {\n return checkVisitorRecordCeiling(used, LEADS_MAX_PER_HOST)\n}\n\n/** Which collection a trip belongs to — the counter id and the notice differ. */\nexport type VisitorRecordKind = 'siteMembers' | 'leads'\n\n/**\n * `hosts/{hostId}/counters/{…}` — the durable, client-unwritable record of a\n * trip, in the same counters-document shape `formSubmissionsRefused` uses\n * (AGL-1367/1655). Named through a function so the writer in\n * `@aglyn/tenant-data-admin` and the reader in the inbox console page cannot\n * spell it two different ways.\n */\nexport function visitorRecordRefusedCounterId(kind: VisitorRecordKind): string {\n return kind === 'leads' ? 'leadsRefused' : 'siteMembersRefused'\n}\n\n/**\n * The field on the refusal counter that says whether the site's managers have\n * been told about the crossing the site is in: when the notice went out, or\n * `null` once the site has been seen below the ceiling again (AGL-3442).\n */\nexport const VISITOR_RECORD_NOTICE_SENT_FIELD = 'noticeSentAtMs'\n\n/** A month key on the refusal counter — `utcMonthKey`'s `YYYY-MM`. */\nconst REFUSAL_MONTH_KEY = /^\\d{4}-\\d{2}$/\n\n/**\n * Whether the managers have already been told this site is at the ceiling.\n *\n * The notice goes out once per CROSSING (AGL-3442): on the first refusal after\n * the site reaches the ceiling, never again while it stays there, and again\n * only after it has been seen below the ceiling and reached it once more. A\n * site can sit at this ceiling for months, and a notice every month would be\n * an alarm about a state nobody needs telling about twice.\n *\n * A counter written before {@link VISITOR_RECORD_NOTICE_SENT_FIELD} existed\n * does not carry it. Its writer sent the notice on the first refusal of each\n * month, so any month with a refusal is a month the notice went out, and such\n * a counter reads as announced. Otherwise every site already at its ceiling\n * would be notified again on its next refusal.\n */\nexport function visitorRecordCeilingAnnounced(\n counter: Record<string, unknown> | null | undefined,\n): boolean {\n if (!counter) return false\n const sent = counter[VISITOR_RECORD_NOTICE_SENT_FIELD]\n if (sent === null) return false\n if (sent !== undefined) return true\n return Object.entries(counter).some(\n ([key, value]) => REFUSAL_MONTH_KEY.test(key) && Number(value) > 0,\n )\n}\n\n/**\n * Whether accepting one more record, with `used` already held, takes the\n * site's last free slot under `ceiling`.\n *\n * Every new record is judged against the ceiling one at a time, so a site that\n * dropped below the ceiling can only reach it again through this accept. That\n * makes it the one accept where the notice is re-armed, and the only one that\n * pays a read for it.\n */\nexport function visitorRecordAcceptFillsCeiling(\n used: number,\n ceiling: number,\n): boolean {\n const resolvedUsed = Math.max(0, Math.floor(Number(used) || 0))\n return checkVisitorRecordCeiling(resolvedUsed + 1, ceiling).exceeded\n}\n\n/** What the site's owner is shown. Shaped like `VisitorDoorPausedNotice`. */\nexport interface VisitorRecordsPausedNotice {\n title: string\n /** What happened, how many, and what actually clears it. */\n message: string\n}\n\n/**\n * The owner's notice — the half deliberately withheld from the visitor.\n *\n * Two `{title, message}` lines rather than `LockdownRefusalNotice`'s three:\n * there is **no `until`**, and its absence is the point. The form and\n * bandwidth ceilings count a month and genuinely do lift on the 1st, so their\n * notices name a date. This ceiling counts live documents, so no date lifts\n * it — deleting records does, or support raising it does. Borrowing the third\n * line for symmetry would print a date that means nothing.\n *\n * Returns `null` below one refusal, the same rule as\n * `visitorDoorPausedNotice` and for the same reason: the counter document\n * persists from its first trip forever, and \"0 refused\" on every later month\n * trains the owner to ignore the row that will one day be real.\n */\nexport function visitorRecordsPausedNotice(input: {\n kind: VisitorRecordKind\n /** Refusals recorded for the month being displayed. */\n refused: number\n /** The ceiling that was crossed, from the counter document. */\n ceiling?: number\n}): VisitorRecordsPausedNotice | null {\n const refused = Math.floor(Number(input.refused) || 0)\n if (refused < 1) return null\n const ceiling =\n typeof input.ceiling === 'number' && Number.isFinite(input.ceiling)\n ? Math.floor(input.ceiling)\n : undefined\n const leads = input.kind === 'leads'\n const noun = leads ? 'lead' : 'sign-up'\n return {\n title: leads\n ? 'Lead capture is paused'\n : 'New member sign-ups are paused',\n message:\n `${refused.toLocaleString()} ${noun}${refused === 1 ? '' : 's'} on ` +\n `this site ${refused === 1 ? 'has' : 'have'} been refused this month` +\n (ceiling\n ? ` after it reached ${ceiling.toLocaleString()} ` +\n `${leads ? 'leads' : 'member accounts'}`\n : '') +\n '. Refused records are not stored and are not billed. This is a ' +\n 'platform safety limit, not part of your plan — every plan includes ' +\n `unlimited ${leads ? 'leads' : 'member accounts'}. Removing ` +\n `${leads ? 'leads' : 'members'} below the limit starts acceptance ` +\n 'again immediately; if this is real traffic, contact support and we ' +\n 'will raise it.',\n }\n}\n"],"names":["SITE_MEMBERS_MAX_PER_HOST","LEADS_MAX_PER_HOST","SITE_MEMBER_CEILING_CODE","SITE_MEMBER_UNAVAILABLE_MESSAGE","checkVisitorRecordCeiling","used","ceiling","resolvedCeiling","Math","max","floor","Number","resolvedUsed","exceeded","checkSiteMemberCeiling","checkLeadCeiling","visitorRecordRefusedCounterId","kind","VISITOR_RECORD_NOTICE_SENT_FIELD","REFUSAL_MONTH_KEY","visitorRecordCeilingAnnounced","counter","sent","undefined","Object","entries","some","key","value","test","visitorRecordAcceptFillsCeiling","visitorRecordsPausedNotice","input","refused","isFinite","leads","noun","title","message","toLocaleString"],"mappings":"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"}