@aglyn/aglyn 1.0.0-beta.218 → 1.0.0-beta.220

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 (455) hide show
  1. package/package.json +12 -12
  2. package/src/lib/app-utils/actions.d.ts +26 -433
  3. package/src/lib/app-utils/actions.js +108 -633
  4. package/src/lib/app-utils/actions.js.map +1 -1
  5. package/src/lib/app-utils/activity-presenter.d.ts +3 -3
  6. package/src/lib/app-utils/activity-presenter.js +36 -52
  7. package/src/lib/app-utils/activity-presenter.js.map +1 -1
  8. package/src/lib/app-utils/analytics-events.d.ts +1 -1
  9. package/src/lib/app-utils/analytics-events.js.map +1 -1
  10. package/src/lib/app-utils/analytics-provider.d.ts +182 -0
  11. package/src/lib/app-utils/analytics-provider.js +133 -0
  12. package/src/lib/app-utils/analytics-provider.js.map +1 -0
  13. package/src/lib/app-utils/api-plugins.d.ts +46 -0
  14. package/src/lib/app-utils/api-plugins.js +19 -0
  15. package/src/lib/app-utils/api-plugins.js.map +1 -1
  16. package/src/lib/app-utils/{marketplace-overrides.d.ts → artifact-overrides.d.ts} +9 -6
  17. package/src/lib/app-utils/{marketplace-overrides.js → artifact-overrides.js} +10 -7
  18. package/src/lib/app-utils/artifact-overrides.js.map +1 -0
  19. package/src/lib/app-utils/{marketplace-provenance.d.ts → artifact-provenance.d.ts} +12 -10
  20. package/src/lib/app-utils/{marketplace-provenance.js → artifact-provenance.js} +9 -7
  21. package/src/lib/app-utils/artifact-provenance.js.map +1 -0
  22. package/src/lib/app-utils/binding-token-catalog.d.ts +5 -13
  23. package/src/lib/app-utils/binding-token-catalog.js +17 -41
  24. package/src/lib/app-utils/binding-token-catalog.js.map +1 -1
  25. package/src/lib/app-utils/brand-support-line.d.ts +36 -0
  26. package/src/lib/app-utils/brand-support-line.js +38 -0
  27. package/src/lib/app-utils/brand-support-line.js.map +1 -0
  28. package/src/lib/app-utils/collection-entries.d.ts +2 -2
  29. package/src/lib/app-utils/collection-entries.js.map +1 -1
  30. package/src/lib/app-utils/consent-group-change.d.ts +4 -0
  31. package/src/lib/app-utils/consent-group-change.js.map +1 -1
  32. package/src/lib/app-utils/consent-groups.d.ts +1 -1
  33. package/src/lib/app-utils/consent-groups.js.map +1 -1
  34. package/src/lib/app-utils/console-routes.d.ts +15 -42
  35. package/src/lib/app-utils/console-routes.js +10 -39
  36. package/src/lib/app-utils/console-routes.js.map +1 -1
  37. package/src/lib/app-utils/contacts.d.ts +24 -2
  38. package/src/lib/app-utils/contacts.js +21 -5
  39. package/src/lib/app-utils/contacts.js.map +1 -1
  40. package/src/lib/app-utils/container-membership.d.ts +93 -0
  41. package/src/lib/app-utils/container-membership.js +165 -0
  42. package/src/lib/app-utils/container-membership.js.map +1 -0
  43. package/src/lib/app-utils/crm-task-reminders.d.ts +23 -6
  44. package/src/lib/app-utils/crm-task-reminders.js +32 -11
  45. package/src/lib/app-utils/crm-task-reminders.js.map +1 -1
  46. package/src/lib/app-utils/crm.d.ts +24 -4
  47. package/src/lib/app-utils/crm.js +23 -0
  48. package/src/lib/app-utils/crm.js.map +1 -1
  49. package/src/lib/app-utils/cross-pool-desync.d.ts +39 -0
  50. package/src/lib/app-utils/cross-pool-desync.js +47 -0
  51. package/src/lib/app-utils/cross-pool-desync.js.map +1 -0
  52. package/src/lib/app-utils/csv-upload.d.ts +29 -0
  53. package/src/lib/app-utils/csv-upload.js +29 -0
  54. package/src/lib/app-utils/csv-upload.js.map +1 -0
  55. package/src/lib/app-utils/{dataset-csv.d.ts → csv.d.ts} +10 -43
  56. package/src/lib/app-utils/{dataset-csv.js → csv.js} +10 -81
  57. package/src/lib/app-utils/csv.js.map +1 -0
  58. package/src/lib/app-utils/dataset-models.d.ts +5 -5
  59. package/src/lib/app-utils/dataset-models.js +19 -2
  60. package/src/lib/app-utils/dataset-models.js.map +1 -1
  61. package/src/lib/app-utils/datasets.d.ts +65 -30
  62. package/src/lib/app-utils/datasets.js +74 -24
  63. package/src/lib/app-utils/datasets.js.map +1 -1
  64. package/src/lib/app-utils/docs-deflection.d.ts +6 -6
  65. package/src/lib/app-utils/docs-deflection.js.map +1 -1
  66. package/src/lib/app-utils/docs-help.generated.d.ts +14 -8
  67. package/src/lib/app-utils/docs-help.generated.js +23 -6
  68. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  69. package/src/lib/app-utils/docs-index.generated.d.ts +2 -2
  70. package/src/lib/app-utils/docs-index.generated.js +570 -346
  71. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  72. package/src/lib/app-utils/docs-retrieval.d.ts +3 -3
  73. package/src/lib/app-utils/docs-retrieval.js +14 -10
  74. package/src/lib/app-utils/docs-retrieval.js.map +1 -1
  75. package/src/lib/app-utils/duplicate-resource.d.ts +4 -1
  76. package/src/lib/app-utils/duplicate-resource.js +7 -4
  77. package/src/lib/app-utils/duplicate-resource.js.map +1 -1
  78. package/src/lib/app-utils/dynamic-list-rule.d.ts +1 -1
  79. package/src/lib/app-utils/dynamic-list-rule.js +3 -3
  80. package/src/lib/app-utils/dynamic-list-rule.js.map +1 -1
  81. package/src/lib/app-utils/element-ui.d.ts +1 -1
  82. package/src/lib/app-utils/element-ui.js +1 -1
  83. package/src/lib/app-utils/element-ui.js.map +1 -1
  84. package/src/lib/app-utils/email-starter-policy.js +3 -3
  85. package/src/lib/app-utils/email-starter-policy.js.map +1 -1
  86. package/src/lib/app-utils/email-text.d.ts +80 -0
  87. package/src/lib/app-utils/email-text.js +189 -0
  88. package/src/lib/app-utils/email-text.js.map +1 -0
  89. package/src/lib/app-utils/{list-assignment-policy.d.ts → enrollment-basis.d.ts} +4 -0
  90. package/src/lib/app-utils/{list-assignment-policy.js → enrollment-basis.js} +5 -1
  91. package/src/lib/app-utils/enrollment-basis.js.map +1 -0
  92. package/src/lib/app-utils/entity-picker-context.d.ts +10 -5
  93. package/src/lib/app-utils/entity-picker-context.js.map +1 -1
  94. package/src/lib/app-utils/error-beacon.d.ts +75 -6
  95. package/src/lib/app-utils/error-beacon.js +160 -21
  96. package/src/lib/app-utils/error-beacon.js.map +1 -1
  97. package/src/lib/app-utils/expand-repeatables.d.ts +10 -10
  98. package/src/lib/app-utils/expand-repeatables.js +14 -14
  99. package/src/lib/app-utils/expand-repeatables.js.map +1 -1
  100. package/src/lib/app-utils/forms.js +2 -2
  101. package/src/lib/app-utils/forms.js.map +1 -1
  102. package/src/lib/app-utils/health-report.d.ts +2 -3
  103. package/src/lib/app-utils/health-report.js +8 -6
  104. package/src/lib/app-utils/health-report.js.map +1 -1
  105. package/src/lib/app-utils/host-events.d.ts +82 -0
  106. package/src/lib/app-utils/host-events.js +62 -0
  107. package/src/lib/app-utils/host-events.js.map +1 -0
  108. package/src/lib/app-utils/inbound-mail-domain.d.ts +27 -0
  109. package/src/lib/app-utils/inbound-mail-domain.js +31 -0
  110. package/src/lib/app-utils/inbound-mail-domain.js.map +1 -0
  111. package/src/lib/app-utils/lockdown.d.ts +11 -0
  112. package/src/lib/app-utils/lockdown.js +33 -3
  113. package/src/lib/app-utils/lockdown.js.map +1 -1
  114. package/src/lib/app-utils/margin-utilization.d.ts +4 -69
  115. package/src/lib/app-utils/margin-utilization.js +82 -69
  116. package/src/lib/app-utils/margin-utilization.js.map +1 -1
  117. package/src/lib/app-utils/member-email-aliases.d.ts +25 -0
  118. package/src/lib/app-utils/member-email-aliases.js +29 -0
  119. package/src/lib/app-utils/member-email-aliases.js.map +1 -1
  120. package/src/lib/app-utils/node-definition-sanitizer.d.ts +16 -16
  121. package/src/lib/app-utils/node-definition-sanitizer.js +24 -20
  122. package/src/lib/app-utils/node-definition-sanitizer.js.map +1 -1
  123. package/src/lib/app-utils/node-interactions.d.ts +7 -7
  124. package/src/lib/app-utils/node-interactions.js.map +1 -1
  125. package/src/lib/app-utils/notifications.d.ts +147 -47
  126. package/src/lib/app-utils/notifications.js +182 -44
  127. package/src/lib/app-utils/notifications.js.map +1 -1
  128. package/src/lib/app-utils/operator-alerts.d.ts +15 -1
  129. package/src/lib/app-utils/operator-alerts.js +47 -10
  130. package/src/lib/app-utils/operator-alerts.js.map +1 -1
  131. package/src/lib/app-utils/payments-configured.d.ts +8 -21
  132. package/src/lib/app-utils/payments-configured.js +8 -23
  133. package/src/lib/app-utils/payments-configured.js.map +1 -1
  134. package/src/lib/app-utils/plan-entitlements.d.ts +207 -306
  135. package/src/lib/app-utils/plan-entitlements.js +345 -528
  136. package/src/lib/app-utils/plan-entitlements.js.map +1 -1
  137. package/src/lib/app-utils/platform-brand.d.ts +10 -0
  138. package/src/lib/app-utils/platform-brand.js +14 -3
  139. package/src/lib/app-utils/platform-brand.js.map +1 -1
  140. package/src/lib/app-utils/platform-marketing-consent.d.ts +56 -2
  141. package/src/lib/app-utils/platform-marketing-consent.js +6 -5
  142. package/src/lib/app-utils/platform-marketing-consent.js.map +1 -1
  143. package/src/lib/app-utils/plugin-api-rate-limit.d.ts +7 -1
  144. package/src/lib/app-utils/plugin-api-rate-limit.js +11 -3
  145. package/src/lib/app-utils/plugin-api-rate-limit.js.map +1 -1
  146. package/src/lib/app-utils/plugin-bundle-checks.js +2 -2
  147. package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -1
  148. package/src/lib/app-utils/plugin-host-events.generated.d.ts +12 -0
  149. package/src/lib/app-utils/plugin-host-events.generated.js +169 -0
  150. package/src/lib/app-utils/plugin-host-events.generated.js.map +1 -0
  151. package/src/lib/app-utils/plugin-release-flags.generated.d.ts +12 -0
  152. package/src/lib/app-utils/plugin-release-flags.generated.js +106 -0
  153. package/src/lib/app-utils/plugin-release-flags.generated.js.map +1 -0
  154. package/src/lib/app-utils/plugin-starter-templates.generated.d.ts +12 -0
  155. package/src/lib/app-utils/plugin-starter-templates.generated.js +711 -0
  156. package/src/lib/app-utils/plugin-starter-templates.generated.js.map +1 -0
  157. package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
  158. package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
  159. package/src/lib/app-utils/release-flags.d.ts +14 -5
  160. package/src/lib/app-utils/release-flags.js +21 -125
  161. package/src/lib/app-utils/release-flags.js.map +1 -1
  162. package/src/lib/app-utils/repeat-query.d.ts +56 -0
  163. package/src/lib/app-utils/{dataset-query.js → repeat-query.js} +16 -16
  164. package/src/lib/app-utils/repeat-query.js.map +1 -0
  165. package/src/lib/app-utils/repeat-sources.d.ts +9 -0
  166. package/src/lib/app-utils/repeat-sources.js.map +1 -1
  167. package/src/lib/app-utils/retained-refusals.d.ts +73 -0
  168. package/src/lib/app-utils/retained-refusals.js +186 -0
  169. package/src/lib/app-utils/retained-refusals.js.map +1 -0
  170. package/src/lib/app-utils/scope-tokens.d.ts +27 -0
  171. package/src/lib/app-utils/scope-tokens.js +61 -0
  172. package/src/lib/app-utils/scope-tokens.js.map +1 -1
  173. package/src/lib/app-utils/screen-link-context.js +3 -3
  174. package/src/lib/app-utils/screen-link-context.js.map +1 -1
  175. package/src/lib/app-utils/screen-route.js +1 -1
  176. package/src/lib/app-utils/screen-route.js.map +1 -1
  177. package/src/lib/app-utils/seo-audit.d.ts +132 -0
  178. package/src/lib/app-utils/seo-audit.js +246 -0
  179. package/src/lib/app-utils/seo-audit.js.map +1 -0
  180. package/src/lib/app-utils/seo-keywords.d.ts +48 -0
  181. package/src/lib/app-utils/seo-keywords.js +60 -0
  182. package/src/lib/app-utils/seo-keywords.js.map +1 -0
  183. package/src/lib/app-utils/seo-page-facts.d.ts +66 -0
  184. package/src/lib/app-utils/seo-page-facts.js +155 -0
  185. package/src/lib/app-utils/seo-page-facts.js.map +1 -0
  186. package/src/lib/app-utils/seo-site-scan.d.ts +126 -0
  187. package/src/lib/app-utils/seo-site-scan.js +162 -0
  188. package/src/lib/app-utils/seo-site-scan.js.map +1 -0
  189. package/src/lib/app-utils/seo-title-variables.js +1 -1
  190. package/src/lib/app-utils/seo-title-variables.js.map +1 -1
  191. package/src/lib/app-utils/server.d.ts +13 -18
  192. package/src/lib/app-utils/server.js +33 -51
  193. package/src/lib/app-utils/server.js.map +1 -1
  194. package/src/lib/app-utils/site-interactions.d.ts +501 -0
  195. package/src/lib/app-utils/site-interactions.js +554 -0
  196. package/src/lib/app-utils/site-interactions.js.map +1 -0
  197. package/src/lib/app-utils/{marketplace-theme.d.ts → site-theme.d.ts} +13 -2
  198. package/src/lib/app-utils/{marketplace-theme.js → site-theme.js} +2 -2
  199. package/src/lib/app-utils/site-theme.js.map +1 -0
  200. package/src/lib/app-utils/sitemap.d.ts +4 -3
  201. package/src/lib/app-utils/sitemap.js +9 -7
  202. package/src/lib/app-utils/sitemap.js.map +1 -1
  203. package/src/lib/app-utils/stale-build-error.d.ts +51 -0
  204. package/src/lib/app-utils/stale-build-error.js +100 -1
  205. package/src/lib/app-utils/stale-build-error.js.map +1 -1
  206. package/src/lib/app-utils/starter-template-nodes.d.ts +137 -0
  207. package/src/lib/app-utils/starter-template-nodes.js +129 -0
  208. package/src/lib/app-utils/starter-template-nodes.js.map +1 -0
  209. package/src/lib/app-utils/starter-templates.d.ts +11 -34
  210. package/src/lib/app-utils/starter-templates.js +19 -230
  211. package/src/lib/app-utils/starter-templates.js.map +1 -1
  212. package/src/lib/app-utils/{email-topics.d.ts → subscription-topics.d.ts} +64 -48
  213. package/src/lib/app-utils/{email-topics.js → subscription-topics.js} +39 -109
  214. package/src/lib/app-utils/subscription-topics.js.map +1 -0
  215. package/src/lib/app-utils/tax-jurisdiction-figures.d.ts +118 -0
  216. package/src/lib/app-utils/tax-jurisdiction-figures.js +123 -0
  217. package/src/lib/app-utils/tax-jurisdiction-figures.js.map +1 -0
  218. package/src/lib/app-utils/theme-library.d.ts +10 -3
  219. package/src/lib/app-utils/theme-library.js +13 -4
  220. package/src/lib/app-utils/theme-library.js.map +1 -1
  221. package/src/lib/app-utils/usage-budget.d.ts +73 -188
  222. package/src/lib/app-utils/usage-budget.js +58 -194
  223. package/src/lib/app-utils/usage-budget.js.map +1 -1
  224. package/src/lib/app-utils/{campaign-attribution.d.ts → utm-attribution.d.ts} +11 -6
  225. package/src/lib/app-utils/{campaign-attribution.js → utm-attribution.js} +13 -8
  226. package/src/lib/app-utils/utm-attribution.js.map +1 -0
  227. package/src/lib/app-utils/{campaign-forwarding.d.ts → utm-forwarding.d.ts} +14 -14
  228. package/src/lib/app-utils/{campaign-forwarding.js → utm-forwarding.js} +26 -26
  229. package/src/lib/app-utils/utm-forwarding.js.map +1 -0
  230. package/src/lib/app-utils/{campaign-touch.d.ts → utm-touch.d.ts} +25 -17
  231. package/src/lib/app-utils/{campaign-touch.js → utm-touch.js} +38 -30
  232. package/src/lib/app-utils/utm-touch.js.map +1 -0
  233. package/src/lib/app-utils/variables.d.ts +9 -9
  234. package/src/lib/app-utils/variables.js.map +1 -1
  235. package/src/lib/app-utils/video-object.js +6 -5
  236. package/src/lib/app-utils/video-object.js.map +1 -1
  237. package/src/lib/app-utils/visitor-consent.d.ts +41 -164
  238. package/src/lib/app-utils/visitor-consent.js +66 -277
  239. package/src/lib/app-utils/visitor-consent.js.map +1 -1
  240. package/src/lib/app-utils/visitor-record-ceiling.d.ts +32 -0
  241. package/src/lib/app-utils/visitor-record-ceiling.js +39 -0
  242. package/src/lib/app-utils/visitor-record-ceiling.js.map +1 -1
  243. package/src/lib/app-utils/web-vitals-rum.d.ts +7 -7
  244. package/src/lib/app-utils/web-vitals-rum.js +31 -63
  245. package/src/lib/app-utils/web-vitals-rum.js.map +1 -1
  246. package/src/lib/app-utils/where-used.d.ts +11 -6
  247. package/src/lib/app-utils/where-used.js +3 -8
  248. package/src/lib/app-utils/where-used.js.map +1 -1
  249. package/src/lib/foundation/definitions/components.types.d.ts +1 -1
  250. package/src/lib/foundation/definitions/components.types.js +1 -1
  251. package/src/lib/foundation/definitions/components.types.js.map +1 -1
  252. package/src/lib/foundation/definitions/org-billing.types.d.ts +11 -137
  253. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
  254. package/src/lib/foundation/definitions/organization.types.d.ts +1 -3
  255. package/src/lib/foundation/definitions/organization.types.js.map +1 -1
  256. package/src/lib/foundation/definitions/platform.types.d.ts +11 -5
  257. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  258. package/src/lib/foundation/definitions/write-deny-coverage.util.d.ts +16 -0
  259. package/src/lib/foundation/definitions/write-deny-coverage.util.js +20 -0
  260. package/src/lib/foundation/definitions/write-deny-coverage.util.js.map +1 -1
  261. package/src/lib/foundation/index.d.ts +1 -1
  262. package/src/lib/foundation/index.js.map +1 -1
  263. package/src/lib/plugin-manager/besigner-documents.d.ts +96 -0
  264. package/src/lib/plugin-manager/besigner-documents.js +61 -0
  265. package/src/lib/plugin-manager/besigner-documents.js.map +1 -0
  266. package/src/lib/plugin-manager/computed-variables.d.ts +71 -0
  267. package/src/lib/plugin-manager/computed-variables.js +74 -0
  268. package/src/lib/plugin-manager/computed-variables.js.map +1 -0
  269. package/src/lib/plugin-manager/custom-fields.d.ts +29 -2
  270. package/src/lib/plugin-manager/custom-fields.js +47 -1
  271. package/src/lib/plugin-manager/custom-fields.js.map +1 -1
  272. package/src/lib/plugin-manager/feature-plugins.d.ts +372 -99
  273. package/src/lib/plugin-manager/feature-plugins.js +126 -51
  274. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  275. package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +134 -0
  276. package/src/lib/plugin-manager/first-party-plugins.generated.js +840 -10
  277. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  278. package/src/lib/plugin-manager/index.d.ts +0 -1
  279. package/src/lib/plugin-manager/index.js +0 -1
  280. package/src/lib/plugin-manager/index.js.map +1 -1
  281. package/src/lib/plugin-manager/plugin-activity-actions.d.ts +12 -0
  282. package/src/lib/plugin-manager/plugin-activity-actions.js +25 -0
  283. package/src/lib/plugin-manager/plugin-activity-actions.js.map +1 -1
  284. package/src/lib/plugin-manager/plugin-contact-capture.d.ts +9 -4
  285. package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
  286. package/src/lib/plugin-manager/plugin-containers.d.ts +68 -0
  287. package/src/lib/plugin-manager/plugin-containers.generated.d.ts +10 -0
  288. package/src/lib/plugin-manager/plugin-containers.generated.js +20 -0
  289. package/src/lib/plugin-manager/plugin-containers.generated.js.map +1 -0
  290. package/src/lib/plugin-manager/plugin-containers.js +27 -0
  291. package/src/lib/plugin-manager/plugin-containers.js.map +1 -0
  292. package/src/lib/plugin-manager/plugin-contributions.d.ts +1 -1
  293. package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
  294. package/src/lib/plugin-manager/plugin-declarations-repair.d.ts +36 -0
  295. package/src/lib/plugin-manager/plugin-declarations-repair.js +64 -0
  296. package/src/lib/plugin-manager/plugin-declarations-repair.js.map +1 -0
  297. package/src/lib/plugin-manager/plugin-dependents.d.ts +101 -0
  298. package/src/lib/plugin-manager/plugin-dependents.js +75 -0
  299. package/src/lib/plugin-manager/plugin-dependents.js.map +1 -0
  300. package/src/lib/plugin-manager/plugin-distribution.d.ts +55 -0
  301. package/src/lib/plugin-manager/plugin-distribution.js +45 -0
  302. package/src/lib/plugin-manager/plugin-distribution.js.map +1 -0
  303. package/src/lib/plugin-manager/plugin-entitlements.d.ts +8 -0
  304. package/src/lib/plugin-manager/plugin-entitlements.js.map +1 -1
  305. package/src/lib/plugin-manager/plugin-host-collections.d.ts +17 -0
  306. package/src/lib/plugin-manager/plugin-host-collections.js +6 -0
  307. package/src/lib/plugin-manager/plugin-host-collections.js.map +1 -1
  308. package/src/lib/plugin-manager/plugin-host-resources.d.ts +133 -0
  309. package/src/lib/plugin-manager/plugin-host-resources.js +52 -0
  310. package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -0
  311. package/src/lib/plugin-manager/plugin-membership-detach.js +1 -1
  312. package/src/lib/plugin-manager/plugin-membership-detach.js.map +1 -1
  313. package/src/lib/plugin-manager/plugin-org-erasure.d.ts +28 -2
  314. package/src/lib/plugin-manager/plugin-org-erasure.js +61 -2
  315. package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
  316. package/src/lib/plugin-manager/plugin-plan-entitlements.d.ts +123 -0
  317. package/src/lib/plugin-manager/plugin-plan-entitlements.js +101 -0
  318. package/src/lib/plugin-manager/plugin-plan-entitlements.js.map +1 -0
  319. package/src/lib/plugin-manager/plugin-record-email-state.d.ts +69 -0
  320. package/src/lib/plugin-manager/plugin-record-email-state.js +32 -0
  321. package/src/lib/plugin-manager/plugin-record-email-state.js.map +1 -1
  322. package/src/lib/plugin-manager/plugin-record-index.d.ts +95 -0
  323. package/src/lib/plugin-manager/plugin-record-index.js +51 -0
  324. package/src/lib/plugin-manager/plugin-record-index.js.map +1 -0
  325. package/src/lib/plugin-manager/plugin-record-lists.d.ts +115 -0
  326. package/src/lib/plugin-manager/plugin-record-lists.js +75 -0
  327. package/src/lib/plugin-manager/plugin-record-lists.js.map +1 -0
  328. package/src/lib/plugin-manager/plugin-record-timeline.d.ts +42 -8
  329. package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
  330. package/src/lib/plugin-manager/plugin-record-written.d.ts +58 -0
  331. package/src/lib/plugin-manager/plugin-record-written.js +42 -0
  332. package/src/lib/plugin-manager/plugin-record-written.js.map +1 -0
  333. package/src/lib/plugin-manager/plugin-revenue-sources.d.ts +213 -0
  334. package/src/lib/plugin-manager/plugin-revenue-sources.js +194 -0
  335. package/src/lib/plugin-manager/plugin-revenue-sources.js.map +1 -0
  336. package/src/lib/plugin-manager/plugin-site-beacons.d.ts +71 -0
  337. package/src/lib/plugin-manager/plugin-site-beacons.js +54 -0
  338. package/src/lib/plugin-manager/plugin-site-beacons.js.map +1 -0
  339. package/src/lib/plugin-manager/plugin-site-bundle.d.ts +145 -0
  340. package/src/lib/plugin-manager/plugin-site-bundle.js +78 -0
  341. package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -0
  342. package/src/lib/plugin-manager/plugin-site-cache.d.ts +12 -0
  343. package/src/lib/plugin-manager/plugin-site-cache.js.map +1 -1
  344. package/src/lib/plugin-manager/plugin-site-export.d.ts +81 -0
  345. package/src/lib/plugin-manager/plugin-site-export.js +40 -0
  346. package/src/lib/plugin-manager/plugin-site-export.js.map +1 -0
  347. package/src/lib/plugin-manager/plugin-sitemap-sections.d.ts +88 -0
  348. package/src/lib/plugin-manager/plugin-sitemap-sections.js +29 -0
  349. package/src/lib/plugin-manager/plugin-sitemap-sections.js.map +1 -0
  350. package/src/lib/plugin-manager/plugin-subscription-topics.generated.d.ts +11 -0
  351. package/src/lib/plugin-manager/plugin-subscription-topics.generated.js +41 -0
  352. package/src/lib/plugin-manager/plugin-subscription-topics.generated.js.map +1 -0
  353. package/src/lib/plugin-manager/plugin-tax-profile.d.ts +20 -4
  354. package/src/lib/plugin-manager/plugin-tax-profile.js.map +1 -1
  355. package/src/lib/plugin-manager/plugin-tax-return-sources.d.ts +255 -0
  356. package/src/lib/plugin-manager/plugin-tax-return-sources.js +191 -0
  357. package/src/lib/plugin-manager/plugin-tax-return-sources.js.map +1 -0
  358. package/src/lib/plugin-manager/plugin-template-sources.d.ts +75 -0
  359. package/src/lib/plugin-manager/plugin-template-sources.js +37 -0
  360. package/src/lib/plugin-manager/plugin-template-sources.js.map +1 -0
  361. package/src/lib/plugin-manager/plugin-usage-axes.d.ts +319 -0
  362. package/src/lib/plugin-manager/plugin-usage-axes.js +186 -0
  363. package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -0
  364. package/src/lib/plugin-manager/plugin-usage-meters.d.ts +108 -0
  365. package/src/lib/plugin-manager/plugin-usage-meters.js +105 -0
  366. package/src/lib/plugin-manager/plugin-usage-meters.js.map +1 -0
  367. package/src/lib/plugin-manager/plugin-zones.d.ts +4 -5
  368. package/src/lib/plugin-manager/plugin-zones.js.map +1 -1
  369. package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
  370. package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
  371. package/src/lib/plugin-manager/record-captured-contact.d.ts +1 -14
  372. package/src/lib/plugin-manager/record-captured-contact.js +10 -32
  373. package/src/lib/plugin-manager/record-captured-contact.js.map +1 -1
  374. package/src/lib/plugin-manager/repeat-rows.d.ts +124 -0
  375. package/src/lib/plugin-manager/repeat-rows.js +108 -0
  376. package/src/lib/plugin-manager/repeat-rows.js.map +1 -0
  377. package/src/lib/plugin-manager/submission-record-target.d.ts +144 -0
  378. package/src/lib/plugin-manager/submission-record-target.js +125 -0
  379. package/src/lib/plugin-manager/submission-record-target.js.map +1 -0
  380. package/src/lib/plugin-manager/usage-alert-contributors.d.ts +76 -6
  381. package/src/lib/plugin-manager/usage-alert-contributors.js +23 -10
  382. package/src/lib/plugin-manager/usage-alert-contributors.js.map +1 -1
  383. package/src/lib/plugin-manager/video-embed-provider.d.ts +108 -0
  384. package/src/lib/plugin-manager/video-embed-provider.js +165 -0
  385. package/src/lib/plugin-manager/video-embed-provider.js.map +1 -0
  386. package/src/lib/types/nodes.d.ts +2 -2
  387. package/src/lib/types/nodes.js.map +1 -1
  388. package/src/lib/app-utils/assist-credits.d.ts +0 -482
  389. package/src/lib/app-utils/assist-credits.js +0 -509
  390. package/src/lib/app-utils/assist-credits.js.map +0 -1
  391. package/src/lib/app-utils/campaign-attribution.js.map +0 -1
  392. package/src/lib/app-utils/campaign-forwarding.js.map +0 -1
  393. package/src/lib/app-utils/campaign-membership.d.ts +0 -121
  394. package/src/lib/app-utils/campaign-membership.js +0 -189
  395. package/src/lib/app-utils/campaign-membership.js.map +0 -1
  396. package/src/lib/app-utils/campaign-touch.js.map +0 -1
  397. package/src/lib/app-utils/console-record-links.d.ts +0 -163
  398. package/src/lib/app-utils/console-record-links.js +0 -180
  399. package/src/lib/app-utils/console-record-links.js.map +0 -1
  400. package/src/lib/app-utils/contact-holder.d.ts +0 -68
  401. package/src/lib/app-utils/contact-holder.js +0 -129
  402. package/src/lib/app-utils/contact-holder.js.map +0 -1
  403. package/src/lib/app-utils/contact-merge.d.ts +0 -136
  404. package/src/lib/app-utils/contact-merge.js +0 -412
  405. package/src/lib/app-utils/contact-merge.js.map +0 -1
  406. package/src/lib/app-utils/crm-booking.d.ts +0 -75
  407. package/src/lib/app-utils/crm-booking.js +0 -133
  408. package/src/lib/app-utils/crm-booking.js.map +0 -1
  409. package/src/lib/app-utils/crm-csv.d.ts +0 -239
  410. package/src/lib/app-utils/crm-csv.js +0 -467
  411. package/src/lib/app-utils/crm-csv.js.map +0 -1
  412. package/src/lib/app-utils/crm-digest.d.ts +0 -185
  413. package/src/lib/app-utils/crm-digest.js +0 -272
  414. package/src/lib/app-utils/crm-digest.js.map +0 -1
  415. package/src/lib/app-utils/crm-inbound.d.ts +0 -245
  416. package/src/lib/app-utils/crm-inbound.js +0 -407
  417. package/src/lib/app-utils/crm-inbound.js.map +0 -1
  418. package/src/lib/app-utils/crm-reports.d.ts +0 -479
  419. package/src/lib/app-utils/crm-reports.js +0 -680
  420. package/src/lib/app-utils/crm-reports.js.map +0 -1
  421. package/src/lib/app-utils/dataset-csv.js.map +0 -1
  422. package/src/lib/app-utils/dataset-query.d.ts +0 -57
  423. package/src/lib/app-utils/dataset-query.js.map +0 -1
  424. package/src/lib/app-utils/email-topics.js.map +0 -1
  425. package/src/lib/app-utils/form-dataset-binding.d.ts +0 -50
  426. package/src/lib/app-utils/form-dataset-binding.js +0 -74
  427. package/src/lib/app-utils/form-dataset-binding.js.map +0 -1
  428. package/src/lib/app-utils/list-assignment-policy.js.map +0 -1
  429. package/src/lib/app-utils/list-import.d.ts +0 -158
  430. package/src/lib/app-utils/list-import.js +0 -332
  431. package/src/lib/app-utils/list-import.js.map +0 -1
  432. package/src/lib/app-utils/marketplace-listing-visibility.d.ts +0 -122
  433. package/src/lib/app-utils/marketplace-listing-visibility.js +0 -147
  434. package/src/lib/app-utils/marketplace-listing-visibility.js.map +0 -1
  435. package/src/lib/app-utils/marketplace-overrides.js.map +0 -1
  436. package/src/lib/app-utils/marketplace-provenance.js.map +0 -1
  437. package/src/lib/app-utils/marketplace-theme.js.map +0 -1
  438. package/src/lib/app-utils/marketplace-update-state.d.ts +0 -119
  439. package/src/lib/app-utils/marketplace-update-state.js +0 -132
  440. package/src/lib/app-utils/marketplace-update-state.js.map +0 -1
  441. package/src/lib/app-utils/publisher-agreement.d.ts +0 -289
  442. package/src/lib/app-utils/publisher-agreement.js +0 -321
  443. package/src/lib/app-utils/publisher-agreement.js.map +0 -1
  444. package/src/lib/app-utils/wistia-embed.d.ts +0 -104
  445. package/src/lib/app-utils/wistia-embed.js +0 -138
  446. package/src/lib/app-utils/wistia-embed.js.map +0 -1
  447. package/src/lib/app-utils/workflows.d.ts +0 -136
  448. package/src/lib/app-utils/workflows.js +0 -346
  449. package/src/lib/app-utils/workflows.js.map +0 -1
  450. package/src/lib/plugin-manager/commerce-zone-props.d.ts +0 -172
  451. package/src/lib/plugin-manager/commerce-zone-props.js +0 -31
  452. package/src/lib/plugin-manager/commerce-zone-props.js.map +0 -1
  453. package/src/lib/plugin-manager/plugin-metered-lines.d.ts +0 -97
  454. package/src/lib/plugin-manager/plugin-metered-lines.js +0 -123
  455. package/src/lib/plugin-manager/plugin-metered-lines.js.map +0 -1
@@ -46,7 +46,33 @@
46
46
  * `consoleServerDeclarations` for an eraser that opens a credential only the
47
47
  * console holds — so the eraser is in place in a server process that never
48
48
  * loaded the plugin's API surface.
49
+ *
50
+ * ## A REQUIRED eraser (AGL-3080)
51
+ *
52
+ * Isolation is right for a share whose failure leaves the workspace's data
53
+ * gone and only a provider-side courtesy undone. It is wrong for a share the
54
+ * erasure PROMISES: a public record naming the erased organization, a payout
55
+ * identifier on a world-readable document. A plugin holding such a record
56
+ * declares `"requiredOrgEraser": true` in `plugins.config.json`, compiled into
57
+ * {@link PLUGIN_REQUIRED_ORG_ERASERS} so no registration can opt out of it, and:
58
+ *
59
+ * - an erasure REFUSES to start while a required eraser is not registered —
60
+ * a boot that failed to declare it must not produce a "complete" erasure
61
+ * with the record still standing;
62
+ * - a required eraser that throws still lets every other eraser run, and then
63
+ * fails the erasure's `plugins` step, which the cron retries.
64
+ *
65
+ * ## Plugin collections keyed by the organization (AGL-3080)
66
+ *
67
+ * A plugin's TOP-LEVEL collection whose documents carry the organization's id
68
+ * in a field is invisible to the erasure's path deletes. The plugin declares
69
+ * it (`orgKeyedCollections`, compiled into {@link PLUGIN_ORG_KEYED_COLLECTIONS})
70
+ * and the erasure sweeps it by that field in every process, with or without
71
+ * the plugin loaded — a deletion that must not depend on which bundles an
72
+ * erasing process happened to load, so it is data rather than an eraser.
49
73
  */ import { getRegisteringPluginId } from "../app-utils/registering-plugin.js";
74
+ import { PLUGIN_ORG_KEYED_COLLECTIONS, PLUGIN_REQUIRED_ORG_ERASERS } from "./first-party-plugins.generated.js";
75
+ export { PLUGIN_ORG_KEYED_COLLECTIONS, PLUGIN_REQUIRED_ORG_ERASERS };
50
76
  const registrations = [];
51
77
  /**
52
78
  * Registers a plugin's workspace eraser. Owner = the loader's marker inside a
@@ -72,9 +98,20 @@ const registrations = [];
72
98
  /**
73
99
  * Runs every eraser, in registration order, and answers each plugin's
74
100
  * report by plugin id: the eraser's own report, or `null` for one that
75
- * threw. Never throws.
76
- */ export async function runPluginOrgErasers(request) {
101
+ * threw.
102
+ *
103
+ * Throws in exactly two cases, both about a REQUIRED eraser (`required`,
104
+ * the compiled {@link PLUGIN_REQUIRED_ORG_ERASERS} unless a spec passes its
105
+ * own): before running anything, when one is not registered; and after
106
+ * running every eraser, when one threw.
107
+ */ export async function runPluginOrgErasers(request, required = PLUGIN_REQUIRED_ORG_ERASERS) {
108
+ const registered = new Set(registrations.map((entry)=>entry.pluginId));
109
+ const missing = required.filter((pluginId)=>!registered.has(pluginId));
110
+ if (missing.length) {
111
+ throw new Error(`[plugins] erasing org ${request.orgId} refused: ${missing.join(', ')} ` + 'declared a required org eraser and none is registered in this process');
112
+ }
77
113
  const reports = {};
114
+ const failedRequired = [];
78
115
  for (const { pluginId, eraser } of [
79
116
  ...registrations
80
117
  ]){
@@ -86,8 +123,12 @@ const registrations = [];
86
123
  } catch (error) {
87
124
  reports[pluginId] = null;
88
125
  console.error(`[plugins] ${pluginId} failed to erase org ${request.orgId}`, error);
126
+ if (required.includes(pluginId)) failedRequired.push(pluginId);
89
127
  }
90
128
  }
129
+ if (failedRequired.length) {
130
+ throw new Error(`[plugins] erasing org ${request.orgId} incomplete: the required eraser of ` + `${failedRequired.join(', ')} failed`);
131
+ }
91
132
  return reports;
92
133
  }
93
134
  /** The plugins with a workspace eraser, in the order they run. */ export function listPluginOrgErasers() {
@@ -96,5 +137,23 @@ const registrations = [];
96
137
  /** Test seam: forget every eraser. */ export function resetPluginOrgErasersForTests() {
97
138
  registrations.length = 0;
98
139
  }
140
+ /**
141
+ * Test seam: a no-op eraser for every REQUIRED plugin that has none, so a
142
+ * spec about the erasure's own sweeps can run it without loading a plugin
143
+ * (core and the data layer may not import one). A spec about a required
144
+ * plugin's share registers the real one through the app's manifest instead.
145
+ */ export function standInRequiredOrgErasersForTests() {
146
+ const registered = new Set(registrations.map((entry)=>entry.pluginId));
147
+ for (const pluginId of PLUGIN_REQUIRED_ORG_ERASERS){
148
+ if (!registered.has(pluginId)) {
149
+ registrations.push({
150
+ pluginId,
151
+ eraser: async ()=>({
152
+ standIn: true
153
+ })
154
+ });
155
+ }
156
+ }
157
+ }
99
158
 
100
159
  //# sourceMappingURL=plugin-org-erasure.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-org-erasure.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 * A plugin's share of a workspace erasure (AGL-2978).\n *\n * The workspace erasure destroys what the platform holds for an\n * organization: its document tree, its sites, and the top-level records\n * keyed to it by a field. A plugin that holds something those deletes cannot\n * finish on their own registers an eraser here — a grant at a provider that\n * only the plugin can revoke, or a record outside every path and field the\n * erasure sweeps — and the erasure runs every eraser with the organization's\n * id.\n *\n * Erasers run BEFORE the erasure deletes anything of its own. The\n * organization, its roster and every record the erasure is about to sweep\n * still exist when an eraser runs, so an eraser that has to act through a\n * record — open a stored grant to revoke it — finds the record there. What\n * the erasure deletes on its own stays its to delete: an eraser adds to the\n * erasure, and the erasure does not wait on one to finish its own sweeps.\n *\n * Erasers run in registration order and are ISOLATED: a throw is logged\n * against its plugin, recorded as `null`, and does not stop the next one or\n * the erasure. An erasure runs against a legal clock, and one plugin's\n * outage must not keep every other plugin's data, or the workspace, past it.\n * The `null` is how the erasure's audit record says that plugin's share may\n * not be done, which a report of zero would hide.\n *\n * A DRY RUN is handed to every eraser, which must then touch no provider and\n * write nothing: it counts what it would do. A plan that reached a vendor\n * would be an erasure that happened without anybody deciding it should.\n *\n * Registered from a plugin's declarations — `serverDeclarations`, or\n * `consoleServerDeclarations` for an eraser that opens a credential only the\n * console holds — so the eraser is in place in a server process that never\n * loaded the plugin's API surface.\n */\n\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\n\n/** What an eraser is asked to erase. */\nexport interface PluginOrgErasureRequest {\n /** The workspace being erased. */\n orgId: string\n /**\n * A plan, not an erasure: count, touch no provider, write nothing. The\n * erasure's own sweeps honor the same flag.\n */\n dryRun: boolean\n}\n\n/**\n * What an eraser reports for the erasure's audit record: counts and flags,\n * never the erased content, because the record outlives the data by design.\n * A `null` field is a figure the eraser could not measure, which is not zero.\n */\nexport type PluginOrgErasureReport = Readonly<Record<string, number | boolean | null>>\n\nexport type PluginOrgEraser = (\n request: PluginOrgErasureRequest,\n) => Promise<PluginOrgErasureReport>\n\ninterface Registration {\n pluginId: string\n eraser: PluginOrgEraser\n}\n\nconst registrations: Registration[] = []\n\n/**\n * Registers a plugin's workspace eraser. Owner = the loader's marker inside a\n * register fn, else `options.pluginId`; an eraser with neither throws. One\n * eraser per plugin: registering again replaces the plugin's earlier eraser\n * in place, so a module evaluated twice does not erase twice.\n */\nexport function registerPluginOrgEraser(\n eraser: PluginOrgEraser,\n options?: { pluginId?: string },\n): void {\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n if (!pluginId) {\n throw new Error(\n 'a plugin org eraser was registered with no owner: pass { pluginId } ' +\n 'when registering outside a plugin register fn',\n )\n }\n const index = registrations.findIndex((entry) => entry.pluginId === pluginId)\n if (index >= 0) registrations[index] = { pluginId, eraser }\n else registrations.push({ pluginId, eraser })\n}\n\n/**\n * Runs every eraser, in registration order, and answers each plugin's\n * report by plugin id: the eraser's own report, or `null` for one that\n * threw. Never throws.\n */\nexport async function runPluginOrgErasers(\n request: PluginOrgErasureRequest,\n): Promise<Record<string, PluginOrgErasureReport | null>> {\n const reports: Record<string, PluginOrgErasureReport | null> = {}\n for (const { pluginId, eraser } of [...registrations]) {\n try {\n reports[pluginId] = await eraser({ orgId: request.orgId, dryRun: request.dryRun })\n } catch (error) {\n reports[pluginId] = null\n console.error(`[plugins] ${pluginId} failed to erase org ${request.orgId}`, error)\n }\n }\n return reports\n}\n\n/** The plugins with a workspace eraser, in the order they run. */\nexport function listPluginOrgErasers(): string[] {\n return registrations.map((entry) => entry.pluginId)\n}\n\n/** Test seam: forget every eraser. */\nexport function resetPluginOrgErasersForTests(): void {\n registrations.length = 0\n}\n"],"names":["getRegisteringPluginId","registrations","registerPluginOrgEraser","eraser","options","pluginId","trim","Error","index","findIndex","entry","push","runPluginOrgErasers","request","reports","orgId","dryRun","error","console","listPluginOrgErasers","map","resetPluginOrgErasersForTests","length"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCC,GAED,SAASA,sBAAsB,QAAQ,qCAAiC;AA6BxE,MAAMC,gBAAgC,EAAE;AAExC;;;;;CAKC,GACD,OAAO,SAASC,wBACdC,MAAuB,EACvBC,OAA+B;QAEbJ,MAAAA;IAAlB,MAAMK,WAAW,EAACL,QAAAA,0BAAAA,oCAAAA,0BAA4BI,2BAAAA,QAASC,QAAQ,YAA7CL,OAAiD,IAAIM,IAAI;IAC3E,IAAI,CAACD,UAAU;QACb,MAAM,IAAIE,MACR,yEACE;IAEN;IACA,MAAMC,QAAQP,cAAcQ,SAAS,CAAC,CAACC,QAAUA,MAAML,QAAQ,KAAKA;IACpE,IAAIG,SAAS,GAAGP,aAAa,CAACO,MAAM,GAAG;QAAEH;QAAUF;IAAO;SACrDF,cAAcU,IAAI,CAAC;QAAEN;QAAUF;IAAO;AAC7C;AAEA;;;;CAIC,GACD,OAAO,eAAeS,oBACpBC,OAAgC;IAEhC,MAAMC,UAAyD,CAAC;IAChE,KAAK,MAAM,EAAET,QAAQ,EAAEF,MAAM,EAAE,IAAI;WAAIF;KAAc,CAAE;QACrD,IAAI;YACFa,OAAO,CAACT,SAAS,GAAG,MAAMF,OAAO;gBAAEY,OAAOF,QAAQE,KAAK;gBAAEC,QAAQH,QAAQG,MAAM;YAAC;QAClF,EAAE,OAAOC,OAAO;YACdH,OAAO,CAACT,SAAS,GAAG;YACpBa,QAAQD,KAAK,CAAC,CAAC,UAAU,EAAEZ,SAAS,qBAAqB,EAAEQ,QAAQE,KAAK,EAAE,EAAEE;QAC9E;IACF;IACA,OAAOH;AACT;AAEA,gEAAgE,GAChE,OAAO,SAASK;IACd,OAAOlB,cAAcmB,GAAG,CAAC,CAACV,QAAUA,MAAML,QAAQ;AACpD;AAEA,oCAAoC,GACpC,OAAO,SAASgB;IACdpB,cAAcqB,MAAM,GAAG;AACzB"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-org-erasure.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 * A plugin's share of a workspace erasure (AGL-2978).\n *\n * The workspace erasure destroys what the platform holds for an\n * organization: its document tree, its sites, and the top-level records\n * keyed to it by a field. A plugin that holds something those deletes cannot\n * finish on their own registers an eraser here — a grant at a provider that\n * only the plugin can revoke, or a record outside every path and field the\n * erasure sweeps — and the erasure runs every eraser with the organization's\n * id.\n *\n * Erasers run BEFORE the erasure deletes anything of its own. The\n * organization, its roster and every record the erasure is about to sweep\n * still exist when an eraser runs, so an eraser that has to act through a\n * record — open a stored grant to revoke it — finds the record there. What\n * the erasure deletes on its own stays its to delete: an eraser adds to the\n * erasure, and the erasure does not wait on one to finish its own sweeps.\n *\n * Erasers run in registration order and are ISOLATED: a throw is logged\n * against its plugin, recorded as `null`, and does not stop the next one or\n * the erasure. An erasure runs against a legal clock, and one plugin's\n * outage must not keep every other plugin's data, or the workspace, past it.\n * The `null` is how the erasure's audit record says that plugin's share may\n * not be done, which a report of zero would hide.\n *\n * A DRY RUN is handed to every eraser, which must then touch no provider and\n * write nothing: it counts what it would do. A plan that reached a vendor\n * would be an erasure that happened without anybody deciding it should.\n *\n * Registered from a plugin's declarations — `serverDeclarations`, or\n * `consoleServerDeclarations` for an eraser that opens a credential only the\n * console holds — so the eraser is in place in a server process that never\n * loaded the plugin's API surface.\n *\n * ## A REQUIRED eraser (AGL-3080)\n *\n * Isolation is right for a share whose failure leaves the workspace's data\n * gone and only a provider-side courtesy undone. It is wrong for a share the\n * erasure PROMISES: a public record naming the erased organization, a payout\n * identifier on a world-readable document. A plugin holding such a record\n * declares `\"requiredOrgEraser\": true` in `plugins.config.json`, compiled into\n * {@link PLUGIN_REQUIRED_ORG_ERASERS} so no registration can opt out of it, and:\n *\n * - an erasure REFUSES to start while a required eraser is not registered —\n * a boot that failed to declare it must not produce a \"complete\" erasure\n * with the record still standing;\n * - a required eraser that throws still lets every other eraser run, and then\n * fails the erasure's `plugins` step, which the cron retries.\n *\n * ## Plugin collections keyed by the organization (AGL-3080)\n *\n * A plugin's TOP-LEVEL collection whose documents carry the organization's id\n * in a field is invisible to the erasure's path deletes. The plugin declares\n * it (`orgKeyedCollections`, compiled into {@link PLUGIN_ORG_KEYED_COLLECTIONS})\n * and the erasure sweeps it by that field in every process, with or without\n * the plugin loaded — a deletion that must not depend on which bundles an\n * erasing process happened to load, so it is data rather than an eraser.\n */\n\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\nimport {\n PLUGIN_ORG_KEYED_COLLECTIONS,\n PLUGIN_REQUIRED_ORG_ERASERS,\n} from './first-party-plugins.generated'\n\nexport { PLUGIN_ORG_KEYED_COLLECTIONS, PLUGIN_REQUIRED_ORG_ERASERS }\n\n/**\n * A plugin's top-level collection whose documents name an organization in a\n * field, declared by that plugin. See the module note.\n */\nexport interface PluginOrgKeyedCollection {\n /** The plugin that declared it. */\n pluginId: string\n /** The top-level collection. */\n name: string\n /** The field naming the organization a document belongs to. */\n orgField: string\n}\n\n/** What an eraser is asked to erase. */\nexport interface PluginOrgErasureRequest {\n /** The workspace being erased. */\n orgId: string\n /**\n * A plan, not an erasure: count, touch no provider, write nothing. The\n * erasure's own sweeps honor the same flag.\n */\n dryRun: boolean\n}\n\n/**\n * What an eraser reports for the erasure's audit record: counts and flags,\n * never the erased content, because the record outlives the data by design.\n * A `null` field is a figure the eraser could not measure, which is not zero.\n */\nexport type PluginOrgErasureReport = Readonly<Record<string, number | boolean | null>>\n\nexport type PluginOrgEraser = (\n request: PluginOrgErasureRequest,\n) => Promise<PluginOrgErasureReport>\n\ninterface Registration {\n pluginId: string\n eraser: PluginOrgEraser\n}\n\nconst registrations: Registration[] = []\n\n/**\n * Registers a plugin's workspace eraser. Owner = the loader's marker inside a\n * register fn, else `options.pluginId`; an eraser with neither throws. One\n * eraser per plugin: registering again replaces the plugin's earlier eraser\n * in place, so a module evaluated twice does not erase twice.\n */\nexport function registerPluginOrgEraser(\n eraser: PluginOrgEraser,\n options?: { pluginId?: string },\n): void {\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n if (!pluginId) {\n throw new Error(\n 'a plugin org eraser was registered with no owner: pass { pluginId } ' +\n 'when registering outside a plugin register fn',\n )\n }\n const index = registrations.findIndex((entry) => entry.pluginId === pluginId)\n if (index >= 0) registrations[index] = { pluginId, eraser }\n else registrations.push({ pluginId, eraser })\n}\n\n/**\n * Runs every eraser, in registration order, and answers each plugin's\n * report by plugin id: the eraser's own report, or `null` for one that\n * threw.\n *\n * Throws in exactly two cases, both about a REQUIRED eraser (`required`,\n * the compiled {@link PLUGIN_REQUIRED_ORG_ERASERS} unless a spec passes its\n * own): before running anything, when one is not registered; and after\n * running every eraser, when one threw.\n */\nexport async function runPluginOrgErasers(\n request: PluginOrgErasureRequest,\n required: readonly string[] = PLUGIN_REQUIRED_ORG_ERASERS,\n): Promise<Record<string, PluginOrgErasureReport | null>> {\n const registered = new Set(registrations.map((entry) => entry.pluginId))\n const missing = required.filter((pluginId) => !registered.has(pluginId))\n if (missing.length) {\n throw new Error(\n `[plugins] erasing org ${request.orgId} refused: ${missing.join(', ')} ` +\n 'declared a required org eraser and none is registered in this process',\n )\n }\n const reports: Record<string, PluginOrgErasureReport | null> = {}\n const failedRequired: string[] = []\n for (const { pluginId, eraser } of [...registrations]) {\n try {\n reports[pluginId] = await eraser({ orgId: request.orgId, dryRun: request.dryRun })\n } catch (error) {\n reports[pluginId] = null\n console.error(`[plugins] ${pluginId} failed to erase org ${request.orgId}`, error)\n if (required.includes(pluginId)) failedRequired.push(pluginId)\n }\n }\n if (failedRequired.length) {\n throw new Error(\n `[plugins] erasing org ${request.orgId} incomplete: the required eraser of ` +\n `${failedRequired.join(', ')} failed`,\n )\n }\n return reports\n}\n\n/** The plugins with a workspace eraser, in the order they run. */\nexport function listPluginOrgErasers(): string[] {\n return registrations.map((entry) => entry.pluginId)\n}\n\n/** Test seam: forget every eraser. */\nexport function resetPluginOrgErasersForTests(): void {\n registrations.length = 0\n}\n\n/**\n * Test seam: a no-op eraser for every REQUIRED plugin that has none, so a\n * spec about the erasure's own sweeps can run it without loading a plugin\n * (core and the data layer may not import one). A spec about a required\n * plugin's share registers the real one through the app's manifest instead.\n */\nexport function standInRequiredOrgErasersForTests(): void {\n const registered = new Set(registrations.map((entry) => entry.pluginId))\n for (const pluginId of PLUGIN_REQUIRED_ORG_ERASERS) {\n if (!registered.has(pluginId)) {\n registrations.push({ pluginId, eraser: async () => ({ standIn: true }) })\n }\n }\n}\n"],"names":["getRegisteringPluginId","PLUGIN_ORG_KEYED_COLLECTIONS","PLUGIN_REQUIRED_ORG_ERASERS","registrations","registerPluginOrgEraser","eraser","options","pluginId","trim","Error","index","findIndex","entry","push","runPluginOrgErasers","request","required","registered","Set","map","missing","filter","has","length","orgId","join","reports","failedRequired","dryRun","error","console","includes","listPluginOrgErasers","resetPluginOrgErasersForTests","standInRequiredOrgErasersForTests","standIn"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyDC,GAED,SAASA,sBAAsB,QAAQ,qCAAiC;AACxE,SACEC,4BAA4B,EAC5BC,2BAA2B,QACtB,qCAAiC;AAExC,SAASD,4BAA4B,EAAEC,2BAA2B,GAAE;AA0CpE,MAAMC,gBAAgC,EAAE;AAExC;;;;;CAKC,GACD,OAAO,SAASC,wBACdC,MAAuB,EACvBC,OAA+B;QAEbN,MAAAA;IAAlB,MAAMO,WAAW,EAACP,QAAAA,0BAAAA,oCAAAA,0BAA4BM,2BAAAA,QAASC,QAAQ,YAA7CP,OAAiD,IAAIQ,IAAI;IAC3E,IAAI,CAACD,UAAU;QACb,MAAM,IAAIE,MACR,yEACE;IAEN;IACA,MAAMC,QAAQP,cAAcQ,SAAS,CAAC,CAACC,QAAUA,MAAML,QAAQ,KAAKA;IACpE,IAAIG,SAAS,GAAGP,aAAa,CAACO,MAAM,GAAG;QAAEH;QAAUF;IAAO;SACrDF,cAAcU,IAAI,CAAC;QAAEN;QAAUF;IAAO;AAC7C;AAEA;;;;;;;;;CASC,GACD,OAAO,eAAeS,oBACpBC,OAAgC,EAChCC,WAA8Bd,2BAA2B;IAEzD,MAAMe,aAAa,IAAIC,IAAIf,cAAcgB,GAAG,CAAC,CAACP,QAAUA,MAAML,QAAQ;IACtE,MAAMa,UAAUJ,SAASK,MAAM,CAAC,CAACd,WAAa,CAACU,WAAWK,GAAG,CAACf;IAC9D,IAAIa,QAAQG,MAAM,EAAE;QAClB,MAAM,IAAId,MACR,CAAC,sBAAsB,EAAEM,QAAQS,KAAK,CAAC,UAAU,EAAEJ,QAAQK,IAAI,CAAC,MAAM,CAAC,CAAC,GACtE;IAEN;IACA,MAAMC,UAAyD,CAAC;IAChE,MAAMC,iBAA2B,EAAE;IACnC,KAAK,MAAM,EAAEpB,QAAQ,EAAEF,MAAM,EAAE,IAAI;WAAIF;KAAc,CAAE;QACrD,IAAI;YACFuB,OAAO,CAACnB,SAAS,GAAG,MAAMF,OAAO;gBAAEmB,OAAOT,QAAQS,KAAK;gBAAEI,QAAQb,QAAQa,MAAM;YAAC;QAClF,EAAE,OAAOC,OAAO;YACdH,OAAO,CAACnB,SAAS,GAAG;YACpBuB,QAAQD,KAAK,CAAC,CAAC,UAAU,EAAEtB,SAAS,qBAAqB,EAAEQ,QAAQS,KAAK,EAAE,EAAEK;YAC5E,IAAIb,SAASe,QAAQ,CAACxB,WAAWoB,eAAed,IAAI,CAACN;QACvD;IACF;IACA,IAAIoB,eAAeJ,MAAM,EAAE;QACzB,MAAM,IAAId,MACR,CAAC,sBAAsB,EAAEM,QAAQS,KAAK,CAAC,oCAAoC,CAAC,GAC1E,GAAGG,eAAeF,IAAI,CAAC,MAAM,OAAO,CAAC;IAE3C;IACA,OAAOC;AACT;AAEA,gEAAgE,GAChE,OAAO,SAASM;IACd,OAAO7B,cAAcgB,GAAG,CAAC,CAACP,QAAUA,MAAML,QAAQ;AACpD;AAEA,oCAAoC,GACpC,OAAO,SAAS0B;IACd9B,cAAcoB,MAAM,GAAG;AACzB;AAEA;;;;;CAKC,GACD,OAAO,SAASW;IACd,MAAMjB,aAAa,IAAIC,IAAIf,cAAcgB,GAAG,CAAC,CAACP,QAAUA,MAAML,QAAQ;IACtE,KAAK,MAAMA,YAAYL,4BAA6B;QAClD,IAAI,CAACe,WAAWK,GAAG,CAACf,WAAW;YAC7BJ,cAAcU,IAAI,CAAC;gBAAEN;gBAAUF,QAAQ,UAAa,CAAA;wBAAE8B,SAAS;oBAAK,CAAA;YAAG;QACzE;IACF;AACF"}
@@ -0,0 +1,123 @@
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
+ * WHAT EACH PLAN INCLUDES OF A PLUGIN'S OWN KEYS (AGL-3080).
19
+ *
20
+ * `plugin-entitlement-keys` gives a plugin the TYPE of a key it owns, by
21
+ * augmentation, so `OrgEntitlements` and `OrgFeatureFlags` carry the key
22
+ * without core naming it. That leaves the VALUE: what Free, Starter and every
23
+ * other plan include of it. A key that only a plugin's type declares still
24
+ * needs a figure in every row of `PLAN_ENTITLEMENTS`, and core writing the
25
+ * figure is core naming the key.
26
+ *
27
+ * So the owning plugin declares the figure per plan, beside the reasoning
28
+ * that sized it, and core composes it into the plan table. A plan row in
29
+ * `PLAN_ENTITLEMENTS` still answers every key the platform sells, at the same
30
+ * number — `/pricing`, the plan comparison, the pricing-table generator and
31
+ * every quota gate read the composed table and cannot tell the difference.
32
+ *
33
+ * ## Compiled, never registered
34
+ *
35
+ * The readers include the published pricing tables, which a generator builds
36
+ * from `PLAN_ENTITLEMENTS` with no plugin loaded, and the customer's plan
37
+ * comparison, which renders before any plugin surface does. A registry one of
38
+ * them had not filled would publish a plan without the band — a missing row on
39
+ * the price list, or a quota gate reading nothing — with nothing red. So a
40
+ * first-party plugin names a function under `register.planEntitlements` in
41
+ * `plugins.config.json`; `tools/scripts/generate-plugin-manifests.mjs` loads
42
+ * `${package}/plan-entitlements`, calls it, validates the answer and compiles
43
+ * it into `first-party-plugins.generated.ts` as data. There is no runtime
44
+ * registrar, for the reason `plugin-org-capacity` gives: a registrar nothing
45
+ * can safely call is worse than none.
46
+ *
47
+ * ## What a missing figure means
48
+ *
49
+ * A declaration names every plan: the generated rows are typed
50
+ * `Record<OrgPlan, …>`, so a plan left out does not compile. A key nobody
51
+ * declares is absent from the table, and each reader answers absence in the
52
+ * closed direction — a quota reads as nothing included, a feature as off.
53
+ * `planQuotaOf` below is that reading, and it never answers "unlimited" or
54
+ * "free" for a key that has no figure.
55
+ *
56
+ * ## One key, one owner, and never one of core's
57
+ *
58
+ * The generator refuses a key two plugins declare. A key the platform's own
59
+ * rows already carry is refused by `plan-entitlements.spec.ts` rather than
60
+ * here, because the generator cannot read core's rows without loading the
61
+ * module that imports this one; the composition keeps core's figure either
62
+ * way, so a collision can shadow nothing a customer is charged.
63
+ */
64
+ import type { OrgPlan } from '../foundation';
65
+ /** A numeric entitlement a plugin owns: a band, a daily pace, a take rate. */
66
+ export interface PluginPlanQuotaDeclaration {
67
+ /** The stored key, exactly as `OrgEntitlements` and an override spell it. */
68
+ key: string;
69
+ /** What a staff page calls it. */
70
+ label: string;
71
+ /**
72
+ * What each plan includes. `Infinity` is `UNLIMITED`; every other value is
73
+ * a finite, non-negative number.
74
+ */
75
+ byPlan: Readonly<Record<OrgPlan, number>>;
76
+ /**
77
+ * A PRICE rather than a cap — a percentage of a sale — so an uncapped plan
78
+ * comp must never lift it (`PRICE_ENTITLEMENT_KEYS`).
79
+ */
80
+ price?: boolean;
81
+ }
82
+ /** A boolean gate a plugin owns. */
83
+ export interface PluginPlanFeatureDeclaration {
84
+ /** The stored key, exactly as `OrgFeatureFlags` and an override spell it. */
85
+ key: string;
86
+ label: string;
87
+ byPlan: Readonly<Record<OrgPlan, boolean>>;
88
+ }
89
+ /** What a plugin's `planEntitlements` function answers. */
90
+ export interface PluginPlanEntitlementsDeclaration {
91
+ quotas?: readonly PluginPlanQuotaDeclaration[];
92
+ features?: readonly PluginPlanFeatureDeclaration[];
93
+ }
94
+ /** A quota declaration with the plugin that made it. */
95
+ export type ResolvedPluginPlanQuota = PluginPlanQuotaDeclaration & {
96
+ pluginId: string;
97
+ };
98
+ /** A feature declaration with the plugin that made it. */
99
+ export type ResolvedPluginPlanFeature = PluginPlanFeatureDeclaration & {
100
+ pluginId: string;
101
+ };
102
+ /** Every declared quota, in catalog order. */
103
+ export declare function pluginPlanQuotas(): readonly ResolvedPluginPlanQuota[];
104
+ /** Every declared feature, in catalog order. */
105
+ export declare function pluginPlanFeatures(): readonly ResolvedPluginPlanFeature[];
106
+ /** The plugin that declared a quota or feature key, or `null`. */
107
+ export declare function pluginPlanEntitlementOwner(key: string): string | null;
108
+ /** The declared quotas' figures for one plan, keyed by the stored key. */
109
+ export declare function pluginPlanQuotaRow(plan: OrgPlan): Record<string, number>;
110
+ /** The declared features' answers for one plan, keyed by the stored key. */
111
+ export declare function pluginPlanFeatureRow(plan: OrgPlan): Record<string, boolean>;
112
+ /**
113
+ * A resolved quota read by its key — the reading core uses for a key a plugin
114
+ * owns, since core compiles without the plugin's type augmentation.
115
+ *
116
+ * ⚑ ABSENT IS NOTHING INCLUDED. A key no plugin declares, or a value that is
117
+ * not a number, reads as `0`: every gate that compares usage against it
118
+ * refuses, and every meter drawn from it shows full. That is the same
119
+ * direction `plugin-org-capacity` answers an unknown capacity in. Reading
120
+ * absence as `UNLIMITED` would be a plan giving away what nobody declared it
121
+ * sells.
122
+ */
123
+ export declare function planQuotaOf(entitlements: object, key: string): number;
@@ -0,0 +1,101 @@
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
+ * WHAT EACH PLAN INCLUDES OF A PLUGIN'S OWN KEYS (AGL-3080).
18
+ *
19
+ * `plugin-entitlement-keys` gives a plugin the TYPE of a key it owns, by
20
+ * augmentation, so `OrgEntitlements` and `OrgFeatureFlags` carry the key
21
+ * without core naming it. That leaves the VALUE: what Free, Starter and every
22
+ * other plan include of it. A key that only a plugin's type declares still
23
+ * needs a figure in every row of `PLAN_ENTITLEMENTS`, and core writing the
24
+ * figure is core naming the key.
25
+ *
26
+ * So the owning plugin declares the figure per plan, beside the reasoning
27
+ * that sized it, and core composes it into the plan table. A plan row in
28
+ * `PLAN_ENTITLEMENTS` still answers every key the platform sells, at the same
29
+ * number — `/pricing`, the plan comparison, the pricing-table generator and
30
+ * every quota gate read the composed table and cannot tell the difference.
31
+ *
32
+ * ## Compiled, never registered
33
+ *
34
+ * The readers include the published pricing tables, which a generator builds
35
+ * from `PLAN_ENTITLEMENTS` with no plugin loaded, and the customer's plan
36
+ * comparison, which renders before any plugin surface does. A registry one of
37
+ * them had not filled would publish a plan without the band — a missing row on
38
+ * the price list, or a quota gate reading nothing — with nothing red. So a
39
+ * first-party plugin names a function under `register.planEntitlements` in
40
+ * `plugins.config.json`; `tools/scripts/generate-plugin-manifests.mjs` loads
41
+ * `${package}/plan-entitlements`, calls it, validates the answer and compiles
42
+ * it into `first-party-plugins.generated.ts` as data. There is no runtime
43
+ * registrar, for the reason `plugin-org-capacity` gives: a registrar nothing
44
+ * can safely call is worse than none.
45
+ *
46
+ * ## What a missing figure means
47
+ *
48
+ * A declaration names every plan: the generated rows are typed
49
+ * `Record<OrgPlan, …>`, so a plan left out does not compile. A key nobody
50
+ * declares is absent from the table, and each reader answers absence in the
51
+ * closed direction — a quota reads as nothing included, a feature as off.
52
+ * `planQuotaOf` below is that reading, and it never answers "unlimited" or
53
+ * "free" for a key that has no figure.
54
+ *
55
+ * ## One key, one owner, and never one of core's
56
+ *
57
+ * The generator refuses a key two plugins declare. A key the platform's own
58
+ * rows already carry is refused by `plan-entitlements.spec.ts` rather than
59
+ * here, because the generator cannot read core's rows without loading the
60
+ * module that imports this one; the composition keeps core's figure either
61
+ * way, so a collision can shadow nothing a customer is charged.
62
+ */ import { PLUGIN_PLAN_FEATURES_DECLARED, PLUGIN_PLAN_QUOTAS_DECLARED } from "./first-party-plugins.generated.js";
63
+ /** Every declared quota, in catalog order. */ export function pluginPlanQuotas() {
64
+ return PLUGIN_PLAN_QUOTAS_DECLARED;
65
+ }
66
+ /** Every declared feature, in catalog order. */ export function pluginPlanFeatures() {
67
+ return PLUGIN_PLAN_FEATURES_DECLARED;
68
+ }
69
+ /** The plugin that declared a quota or feature key, or `null`. */ export function pluginPlanEntitlementOwner(key) {
70
+ var _ref, _ref1;
71
+ var _PLUGIN_PLAN_QUOTAS_DECLARED_find, _PLUGIN_PLAN_FEATURES_DECLARED_find;
72
+ return (_ref = (_ref1 = (_PLUGIN_PLAN_QUOTAS_DECLARED_find = PLUGIN_PLAN_QUOTAS_DECLARED.find((one)=>one.key === key)) == null ? void 0 : _PLUGIN_PLAN_QUOTAS_DECLARED_find.pluginId) != null ? _ref1 : (_PLUGIN_PLAN_FEATURES_DECLARED_find = PLUGIN_PLAN_FEATURES_DECLARED.find((one)=>one.key === key)) == null ? void 0 : _PLUGIN_PLAN_FEATURES_DECLARED_find.pluginId) != null ? _ref : null;
73
+ }
74
+ /** The declared quotas' figures for one plan, keyed by the stored key. */ export function pluginPlanQuotaRow(plan) {
75
+ return Object.fromEntries(PLUGIN_PLAN_QUOTAS_DECLARED.map((one)=>[
76
+ one.key,
77
+ one.byPlan[plan]
78
+ ]));
79
+ }
80
+ /** The declared features' answers for one plan, keyed by the stored key. */ export function pluginPlanFeatureRow(plan) {
81
+ return Object.fromEntries(PLUGIN_PLAN_FEATURES_DECLARED.map((one)=>[
82
+ one.key,
83
+ one.byPlan[plan]
84
+ ]));
85
+ }
86
+ /**
87
+ * A resolved quota read by its key — the reading core uses for a key a plugin
88
+ * owns, since core compiles without the plugin's type augmentation.
89
+ *
90
+ * ⚑ ABSENT IS NOTHING INCLUDED. A key no plugin declares, or a value that is
91
+ * not a number, reads as `0`: every gate that compares usage against it
92
+ * refuses, and every meter drawn from it shows full. That is the same
93
+ * direction `plugin-org-capacity` answers an unknown capacity in. Reading
94
+ * absence as `UNLIMITED` would be a plan giving away what nobody declared it
95
+ * sells.
96
+ */ export function planQuotaOf(entitlements, key) {
97
+ const value = entitlements[key];
98
+ return typeof value === 'number' && !Number.isNaN(value) ? value : 0;
99
+ }
100
+
101
+ //# sourceMappingURL=plugin-plan-entitlements.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-plan-entitlements.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 * WHAT EACH PLAN INCLUDES OF A PLUGIN'S OWN KEYS (AGL-3080).\n *\n * `plugin-entitlement-keys` gives a plugin the TYPE of a key it owns, by\n * augmentation, so `OrgEntitlements` and `OrgFeatureFlags` carry the key\n * without core naming it. That leaves the VALUE: what Free, Starter and every\n * other plan include of it. A key that only a plugin's type declares still\n * needs a figure in every row of `PLAN_ENTITLEMENTS`, and core writing the\n * figure is core naming the key.\n *\n * So the owning plugin declares the figure per plan, beside the reasoning\n * that sized it, and core composes it into the plan table. A plan row in\n * `PLAN_ENTITLEMENTS` still answers every key the platform sells, at the same\n * number — `/pricing`, the plan comparison, the pricing-table generator and\n * every quota gate read the composed table and cannot tell the difference.\n *\n * ## Compiled, never registered\n *\n * The readers include the published pricing tables, which a generator builds\n * from `PLAN_ENTITLEMENTS` with no plugin loaded, and the customer's plan\n * comparison, which renders before any plugin surface does. A registry one of\n * them had not filled would publish a plan without the band — a missing row on\n * the price list, or a quota gate reading nothing — with nothing red. So a\n * first-party plugin names a function under `register.planEntitlements` in\n * `plugins.config.json`; `tools/scripts/generate-plugin-manifests.mjs` loads\n * `${package}/plan-entitlements`, calls it, validates the answer and compiles\n * it into `first-party-plugins.generated.ts` as data. There is no runtime\n * registrar, for the reason `plugin-org-capacity` gives: a registrar nothing\n * can safely call is worse than none.\n *\n * ## What a missing figure means\n *\n * A declaration names every plan: the generated rows are typed\n * `Record<OrgPlan, …>`, so a plan left out does not compile. A key nobody\n * declares is absent from the table, and each reader answers absence in the\n * closed direction — a quota reads as nothing included, a feature as off.\n * `planQuotaOf` below is that reading, and it never answers \"unlimited\" or\n * \"free\" for a key that has no figure.\n *\n * ## One key, one owner, and never one of core's\n *\n * The generator refuses a key two plugins declare. A key the platform's own\n * rows already carry is refused by `plan-entitlements.spec.ts` rather than\n * here, because the generator cannot read core's rows without loading the\n * module that imports this one; the composition keeps core's figure either\n * way, so a collision can shadow nothing a customer is charged.\n */\n\nimport type { OrgPlan } from '../foundation'\nimport {\n PLUGIN_PLAN_FEATURES_DECLARED,\n PLUGIN_PLAN_QUOTAS_DECLARED,\n} from './first-party-plugins.generated'\n\n/** A numeric entitlement a plugin owns: a band, a daily pace, a take rate. */\nexport interface PluginPlanQuotaDeclaration {\n /** The stored key, exactly as `OrgEntitlements` and an override spell it. */\n key: string\n /** What a staff page calls it. */\n label: string\n /**\n * What each plan includes. `Infinity` is `UNLIMITED`; every other value is\n * a finite, non-negative number.\n */\n byPlan: Readonly<Record<OrgPlan, number>>\n /**\n * A PRICE rather than a cap — a percentage of a sale — so an uncapped plan\n * comp must never lift it (`PRICE_ENTITLEMENT_KEYS`).\n */\n price?: boolean\n}\n\n/** A boolean gate a plugin owns. */\nexport interface PluginPlanFeatureDeclaration {\n /** The stored key, exactly as `OrgFeatureFlags` and an override spell it. */\n key: string\n label: string\n byPlan: Readonly<Record<OrgPlan, boolean>>\n}\n\n/** What a plugin's `planEntitlements` function answers. */\nexport interface PluginPlanEntitlementsDeclaration {\n quotas?: readonly PluginPlanQuotaDeclaration[]\n features?: readonly PluginPlanFeatureDeclaration[]\n}\n\n/** A quota declaration with the plugin that made it. */\nexport type ResolvedPluginPlanQuota = PluginPlanQuotaDeclaration & {\n pluginId: string\n}\n\n/** A feature declaration with the plugin that made it. */\nexport type ResolvedPluginPlanFeature = PluginPlanFeatureDeclaration & {\n pluginId: string\n}\n\n/** Every declared quota, in catalog order. */\nexport function pluginPlanQuotas(): readonly ResolvedPluginPlanQuota[] {\n return PLUGIN_PLAN_QUOTAS_DECLARED\n}\n\n/** Every declared feature, in catalog order. */\nexport function pluginPlanFeatures(): readonly ResolvedPluginPlanFeature[] {\n return PLUGIN_PLAN_FEATURES_DECLARED\n}\n\n/** The plugin that declared a quota or feature key, or `null`. */\nexport function pluginPlanEntitlementOwner(key: string): string | null {\n return (\n PLUGIN_PLAN_QUOTAS_DECLARED.find((one) => one.key === key)?.pluginId ??\n PLUGIN_PLAN_FEATURES_DECLARED.find((one) => one.key === key)?.pluginId ??\n null\n )\n}\n\n/** The declared quotas' figures for one plan, keyed by the stored key. */\nexport function pluginPlanQuotaRow(plan: OrgPlan): Record<string, number> {\n return Object.fromEntries(\n PLUGIN_PLAN_QUOTAS_DECLARED.map((one) => [one.key, one.byPlan[plan]]),\n )\n}\n\n/** The declared features' answers for one plan, keyed by the stored key. */\nexport function pluginPlanFeatureRow(plan: OrgPlan): Record<string, boolean> {\n return Object.fromEntries(\n PLUGIN_PLAN_FEATURES_DECLARED.map((one) => [one.key, one.byPlan[plan]]),\n )\n}\n\n/**\n * A resolved quota read by its key — the reading core uses for a key a plugin\n * owns, since core compiles without the plugin's type augmentation.\n *\n * ⚑ ABSENT IS NOTHING INCLUDED. A key no plugin declares, or a value that is\n * not a number, reads as `0`: every gate that compares usage against it\n * refuses, and every meter drawn from it shows full. That is the same\n * direction `plugin-org-capacity` answers an unknown capacity in. Reading\n * absence as `UNLIMITED` would be a plan giving away what nobody declared it\n * sells.\n */\nexport function planQuotaOf(entitlements: object, key: string): number {\n const value = (entitlements as Record<string, unknown>)[key]\n return typeof value === 'number' && !Number.isNaN(value) ? value : 0\n}\n"],"names":["PLUGIN_PLAN_FEATURES_DECLARED","PLUGIN_PLAN_QUOTAS_DECLARED","pluginPlanQuotas","pluginPlanFeatures","pluginPlanEntitlementOwner","key","find","one","pluginId","pluginPlanQuotaRow","plan","Object","fromEntries","map","byPlan","pluginPlanFeatureRow","planQuotaOf","entitlements","value","Number","isNaN"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8CC,GAGD,SACEA,6BAA6B,EAC7BC,2BAA2B,QACtB,qCAAiC;AA4CxC,4CAA4C,GAC5C,OAAO,SAASC;IACd,OAAOD;AACT;AAEA,8CAA8C,GAC9C,OAAO,SAASE;IACd,OAAOH;AACT;AAEA,gEAAgE,GAChE,OAAO,SAASI,2BAA2BC,GAAW;QAElDJ;QAAAA,mCACAD;IAFF,QACEC,iBAAAA,oCAAAA,4BAA4BK,IAAI,CAAC,CAACC,MAAQA,IAAIF,GAAG,KAAKA,yBAAtDJ,kCAA4DO,QAAQ,qBACpER,sCAAAA,8BAA8BM,IAAI,CAAC,CAACC,MAAQA,IAAIF,GAAG,KAAKA,yBAAxDL,oCAA8DQ,QAAQ,YADtEP,OAEA;AAEJ;AAEA,wEAAwE,GACxE,OAAO,SAASQ,mBAAmBC,IAAa;IAC9C,OAAOC,OAAOC,WAAW,CACvBX,4BAA4BY,GAAG,CAAC,CAACN,MAAQ;YAACA,IAAIF,GAAG;YAAEE,IAAIO,MAAM,CAACJ,KAAK;SAAC;AAExE;AAEA,0EAA0E,GAC1E,OAAO,SAASK,qBAAqBL,IAAa;IAChD,OAAOC,OAAOC,WAAW,CACvBZ,8BAA8Ba,GAAG,CAAC,CAACN,MAAQ;YAACA,IAAIF,GAAG;YAAEE,IAAIO,MAAM,CAACJ,KAAK;SAAC;AAE1E;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASM,YAAYC,YAAoB,EAAEZ,GAAW;IAC3D,MAAMa,QAAQ,AAACD,YAAwC,CAACZ,IAAI;IAC5D,OAAO,OAAOa,UAAU,YAAY,CAACC,OAAOC,KAAK,CAACF,SAASA,QAAQ;AACrE"}
@@ -63,9 +63,63 @@ export interface PluginRecordEmailStateReport {
63
63
  /** Records whose state moved. */
64
64
  records: number;
65
65
  }
66
+ /**
67
+ * One delivery event a send's webhook recorded, as the delivery log
68
+ * answered it: who it is about, what happened, when, and whether it is the
69
+ * first event of its type for its message.
70
+ */
71
+ export interface PluginRecordEmailEvent {
72
+ /** The recipient, lowercased. */
73
+ to: string;
74
+ /** `opened`, `clicked`, `delivered`, `bounced`, … */
75
+ type: string;
76
+ /** When it happened, epoch ms. */
77
+ at: number;
78
+ /** No event of this type had been recorded against this message before. */
79
+ firstOfType: boolean;
80
+ }
81
+ /**
82
+ * The events one webhook delivery recorded for a send from a site — what
83
+ * the record system reads a person's engagement off (AGL-2616).
84
+ */
85
+ export interface PluginRecordEmailEngagementRequest {
86
+ /**
87
+ * The site the send went out from: the `hostId` tag the send stamped,
88
+ * which is the only tenant identity a delivery event carries.
89
+ */
90
+ hostId: string;
91
+ events: readonly PluginRecordEmailEvent[];
92
+ }
93
+ /**
94
+ * The people a marketing send was delivered to (AGL-3446), so the record
95
+ * system can say automated email is reaching them — a lead's Nurturing
96
+ * stage. Only what was actually accepted for delivery: a refused or
97
+ * deferred address reached nobody.
98
+ */
99
+ export interface PluginRecordEmailReachRequest {
100
+ /** The organization whose records the send reached. */
101
+ orgId: string;
102
+ /** The site the send went out as; only records it holds are moved. */
103
+ hostId: string;
104
+ /** The addresses delivered to, as the send addressed them. */
105
+ emails: readonly string[];
106
+ }
66
107
  export interface PluginRecordEmailStateWriter {
67
108
  /** Never throws: a list is the control, the stamp is what a person reads. */
68
109
  stamp(request: PluginRecordEmailStateRequest): Promise<PluginRecordEmailStateReport>;
110
+ /**
111
+ * Stamps when each person these events name last engaged with a send from
112
+ * the site, on the records the site may see. Optional: a record system
113
+ * that keeps no engagement answers nothing. Never throws, for the reason
114
+ * `stamp` does not: the webhook's acknowledgement must not depend on it.
115
+ */
116
+ engaged?(request: PluginRecordEmailEngagementRequest): Promise<PluginRecordEmailStateReport>;
117
+ /**
118
+ * Notes that a marketing send reached these people. Optional: a record
119
+ * system with no stage for it answers nothing. Never throws: the mail has
120
+ * already gone.
121
+ */
122
+ reached?(request: PluginRecordEmailReachRequest): Promise<PluginRecordEmailStateReport>;
69
123
  }
70
124
  export declare const PLUGIN_RECORD_EMAIL_STATE: import("./plugin-services").PluginServiceContract<PluginRecordEmailStateWriter>;
71
125
  /**
@@ -90,3 +144,18 @@ export declare function pluginRecordEmailStateWriter(): ResolvedPluginRecordEmai
90
144
  * the stamp is a courtesy beside the list it already wrote.
91
145
  */
92
146
  export declare function stampRecordEmailState(request: PluginRecordEmailStateRequest): Promise<PluginRecordEmailStateReport | null>;
147
+ /**
148
+ * Hands a send's delivery events to whichever plugin keeps the records, so
149
+ * it can stamp the people who engaged (AGL-2616). Answers `null` when no
150
+ * plugin keeps records, or the one that does keeps no engagement. Never
151
+ * throws: the caller is a webhook whose acknowledgement must not hang on a
152
+ * courtesy stamp.
153
+ */
154
+ export declare function stampRecordEmailEngagement(request: PluginRecordEmailEngagementRequest): Promise<PluginRecordEmailStateReport | null>;
155
+ /**
156
+ * Hands the addresses a marketing send was delivered to to whichever plugin
157
+ * keeps the records (AGL-3446). Answers `null` when no plugin keeps
158
+ * records, or the one that does keeps no such stage. Never throws: the
159
+ * send has already delivered, and its bookkeeping must not fail it.
160
+ */
161
+ export declare function stampRecordEmailReach(request: PluginRecordEmailReachRequest): Promise<PluginRecordEmailStateReport | null>;
@@ -49,5 +49,37 @@ export const PLUGIN_RECORD_EMAIL_STATE = definePluginServiceContract('core.recor
49
49
  return null;
50
50
  }
51
51
  }
52
+ /**
53
+ * Hands a send's delivery events to whichever plugin keeps the records, so
54
+ * it can stamp the people who engaged (AGL-2616). Answers `null` when no
55
+ * plugin keeps records, or the one that does keeps no engagement. Never
56
+ * throws: the caller is a webhook whose acknowledgement must not hang on a
57
+ * courtesy stamp.
58
+ */ export async function stampRecordEmailEngagement(request) {
59
+ const resolved = pluginRecordEmailStateWriter();
60
+ if (!(resolved == null ? void 0 : resolved.writer.engaged)) return null;
61
+ try {
62
+ return await resolved.writer.engaged(request);
63
+ } catch (error) {
64
+ console.error('[record-email-state] the record system could not stamp an engagement', error);
65
+ return null;
66
+ }
67
+ }
68
+ /**
69
+ * Hands the addresses a marketing send was delivered to to whichever plugin
70
+ * keeps the records (AGL-3446). Answers `null` when no plugin keeps
71
+ * records, or the one that does keeps no such stage. Never throws: the
72
+ * send has already delivered, and its bookkeeping must not fail it.
73
+ */ export async function stampRecordEmailReach(request) {
74
+ if (!request.emails.length) return null;
75
+ const resolved = pluginRecordEmailStateWriter();
76
+ if (!(resolved == null ? void 0 : resolved.writer.reached)) return null;
77
+ try {
78
+ return await resolved.writer.reached(request);
79
+ } catch (error) {
80
+ console.error('[record-email-state] the record system could not note a delivered send', error);
81
+ return null;
82
+ }
83
+ }
52
84
 
53
85
  //# sourceMappingURL=plugin-record-email-state.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-record-email-state.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { EmailState } from '../app-utils/email-state'\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * The verdict on an address, written onto the records that carry it\n * (AGL-3245).\n *\n * The senders keep their lists — the platform's suppression list, a site's\n * suppressions, an organization's do-not-contact list — and those are what\n * a send consults. The record a person reads is the record system's: a\n * lead, a contact, whatever the plugin that keeps the workspace's records\n * calls them. A sender must not write that plugin's documents itself, nor\n * import it; so the record system registers a WRITER here, and a sender\n * that filed a verdict on a list says so through it.\n *\n * The writer finds every record the address is — the plugin knows its own\n * storage and its own index — and stamps `emailState` on each, keeping the\n * stronger verdict (`nextEmailState`). A single-implementation contract: a\n * workspace keeps one record system.\n *\n * ## The caller proves who is asking\n *\n * Like the timeline seam, the registry authenticates nobody. The caller\n * decides, in its own terms and before it asks, that the verdict is the\n * workspace's to write: an address it sent to, a list it wrote.\n */\n\nexport interface PluginRecordEmailStateRequest {\n /** The organization whose records are stamped. */\n orgId?: string\n /**\n * Or the site the send left from, when the caller knows only that — a\n * campaign webhook, an unsubscribe link. The writer reads the\n * organization off it.\n */\n hostId?: string\n email: string\n state: EmailState\n /** Write the state whatever the record holds — a release. */\n force?: boolean\n /**\n * Take the verdict back instead of writing it: a record whose standing\n * state has `state.status` loses it, and any other record is left alone.\n * The deliverability check's way back from `undeliverable` once the\n * domain takes mail (AGL-3328); it never clears a verdict it did not give.\n */\n withdraw?: boolean\n}\n\nexport interface PluginRecordEmailStateReport {\n /** Records whose state moved. */\n records: number\n}\n\nexport interface PluginRecordEmailStateWriter {\n /** Never throws: a list is the control, the stamp is what a person reads. */\n stamp(request: PluginRecordEmailStateRequest): Promise<PluginRecordEmailStateReport>\n}\n\nexport const PLUGIN_RECORD_EMAIL_STATE = definePluginServiceContract<PluginRecordEmailStateWriter>(\n 'core.record-email-state',\n { multiple: false },\n)\n\n/**\n * Registers the workspace's record email-state writer. The owner is the\n * loader's marker when a register fn is running, else `options.pluginId`;\n * with neither the registration throws, and a second plugin's writer is\n * refused naming both — the incumbent keeps serving.\n */\nexport function registerPluginRecordEmailStateWriter(\n writer: PluginRecordEmailStateWriter,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_RECORD_EMAIL_STATE, writer, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\nexport interface ResolvedPluginRecordEmailStateWriter {\n /** The plugin that keeps the records. */\n pluginId: string\n writer: PluginRecordEmailStateWriter\n}\n\n/** The writer, with its owner, or `null` when no plugin keeps records here. */\nexport function pluginRecordEmailStateWriter(): ResolvedPluginRecordEmailStateWriter | null {\n const entry = resolvePluginServices(PLUGIN_RECORD_EMAIL_STATE)[0]\n return entry ? { pluginId: entry.pluginId, writer: entry.impl } : null\n}\n\n/**\n * Stamps the verdict through whichever plugin keeps the records, or answers\n * `null` when none does. Never throws: every caller is a sender for whom\n * the stamp is a courtesy beside the list it already wrote.\n */\nexport async function stampRecordEmailState(\n request: PluginRecordEmailStateRequest,\n): Promise<PluginRecordEmailStateReport | null> {\n const resolved = pluginRecordEmailStateWriter()\n if (!resolved) return null\n try {\n return await resolved.writer.stamp(request)\n } catch (error) {\n console.error('[record-email-state] the record system could not stamp the address', error)\n return null\n }\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_RECORD_EMAIL_STATE","multiple","registerPluginRecordEmailStateWriter","writer","options","pluginId","pluginRecordEmailStateWriter","entry","impl","stampRecordEmailState","request","resolved","stamp","error","console"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AA0D1B,OAAO,MAAMC,4BAA4BH,4BACvC,2BACA;IAAEI,UAAU;AAAM,GACnB;AAED;;;;;CAKC,GACD,OAAO,SAASC,qCACdC,MAAoC,EACpCC,OAA+B;IAE/BN,sBAAsBE,2BAA2BG,QAAQ,aACnDC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAQA,6EAA6E,GAC7E,OAAO,SAASC;IACd,MAAMC,QAAQR,sBAAsBC,0BAA0B,CAAC,EAAE;IACjE,OAAOO,QAAQ;QAAEF,UAAUE,MAAMF,QAAQ;QAAEF,QAAQI,MAAMC,IAAI;IAAC,IAAI;AACpE;AAEA;;;;CAIC,GACD,OAAO,eAAeC,sBACpBC,OAAsC;IAEtC,MAAMC,WAAWL;IACjB,IAAI,CAACK,UAAU,OAAO;IACtB,IAAI;QACF,OAAO,MAAMA,SAASR,MAAM,CAACS,KAAK,CAACF;IACrC,EAAE,OAAOG,OAAO;QACdC,QAAQD,KAAK,CAAC,sEAAsEA;QACpF,OAAO;IACT;AACF"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-record-email-state.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { EmailState } from '../app-utils/email-state'\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * The verdict on an address, written onto the records that carry it\n * (AGL-3245).\n *\n * The senders keep their lists — the platform's suppression list, a site's\n * suppressions, an organization's do-not-contact list — and those are what\n * a send consults. The record a person reads is the record system's: a\n * lead, a contact, whatever the plugin that keeps the workspace's records\n * calls them. A sender must not write that plugin's documents itself, nor\n * import it; so the record system registers a WRITER here, and a sender\n * that filed a verdict on a list says so through it.\n *\n * The writer finds every record the address is — the plugin knows its own\n * storage and its own index — and stamps `emailState` on each, keeping the\n * stronger verdict (`nextEmailState`). A single-implementation contract: a\n * workspace keeps one record system.\n *\n * ## The caller proves who is asking\n *\n * Like the timeline seam, the registry authenticates nobody. The caller\n * decides, in its own terms and before it asks, that the verdict is the\n * workspace's to write: an address it sent to, a list it wrote.\n */\n\nexport interface PluginRecordEmailStateRequest {\n /** The organization whose records are stamped. */\n orgId?: string\n /**\n * Or the site the send left from, when the caller knows only that — a\n * campaign webhook, an unsubscribe link. The writer reads the\n * organization off it.\n */\n hostId?: string\n email: string\n state: EmailState\n /** Write the state whatever the record holds — a release. */\n force?: boolean\n /**\n * Take the verdict back instead of writing it: a record whose standing\n * state has `state.status` loses it, and any other record is left alone.\n * The deliverability check's way back from `undeliverable` once the\n * domain takes mail (AGL-3328); it never clears a verdict it did not give.\n */\n withdraw?: boolean\n}\n\nexport interface PluginRecordEmailStateReport {\n /** Records whose state moved. */\n records: number\n}\n\n/**\n * One delivery event a send's webhook recorded, as the delivery log\n * answered it: who it is about, what happened, when, and whether it is the\n * first event of its type for its message.\n */\nexport interface PluginRecordEmailEvent {\n /** The recipient, lowercased. */\n to: string\n /** `opened`, `clicked`, `delivered`, `bounced`, … */\n type: string\n /** When it happened, epoch ms. */\n at: number\n /** No event of this type had been recorded against this message before. */\n firstOfType: boolean\n}\n\n/**\n * The events one webhook delivery recorded for a send from a site — what\n * the record system reads a person's engagement off (AGL-2616).\n */\nexport interface PluginRecordEmailEngagementRequest {\n /**\n * The site the send went out from: the `hostId` tag the send stamped,\n * which is the only tenant identity a delivery event carries.\n */\n hostId: string\n events: readonly PluginRecordEmailEvent[]\n}\n\n/**\n * The people a marketing send was delivered to (AGL-3446), so the record\n * system can say automated email is reaching them — a lead's Nurturing\n * stage. Only what was actually accepted for delivery: a refused or\n * deferred address reached nobody.\n */\nexport interface PluginRecordEmailReachRequest {\n /** The organization whose records the send reached. */\n orgId: string\n /** The site the send went out as; only records it holds are moved. */\n hostId: string\n /** The addresses delivered to, as the send addressed them. */\n emails: readonly string[]\n}\n\nexport interface PluginRecordEmailStateWriter {\n /** Never throws: a list is the control, the stamp is what a person reads. */\n stamp(request: PluginRecordEmailStateRequest): Promise<PluginRecordEmailStateReport>\n /**\n * Stamps when each person these events name last engaged with a send from\n * the site, on the records the site may see. Optional: a record system\n * that keeps no engagement answers nothing. Never throws, for the reason\n * `stamp` does not: the webhook's acknowledgement must not depend on it.\n */\n engaged?(request: PluginRecordEmailEngagementRequest): Promise<PluginRecordEmailStateReport>\n /**\n * Notes that a marketing send reached these people. Optional: a record\n * system with no stage for it answers nothing. Never throws: the mail has\n * already gone.\n */\n reached?(request: PluginRecordEmailReachRequest): Promise<PluginRecordEmailStateReport>\n}\n\nexport const PLUGIN_RECORD_EMAIL_STATE = definePluginServiceContract<PluginRecordEmailStateWriter>(\n 'core.record-email-state',\n { multiple: false },\n)\n\n/**\n * Registers the workspace's record email-state writer. The owner is the\n * loader's marker when a register fn is running, else `options.pluginId`;\n * with neither the registration throws, and a second plugin's writer is\n * refused naming both — the incumbent keeps serving.\n */\nexport function registerPluginRecordEmailStateWriter(\n writer: PluginRecordEmailStateWriter,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_RECORD_EMAIL_STATE, writer, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\nexport interface ResolvedPluginRecordEmailStateWriter {\n /** The plugin that keeps the records. */\n pluginId: string\n writer: PluginRecordEmailStateWriter\n}\n\n/** The writer, with its owner, or `null` when no plugin keeps records here. */\nexport function pluginRecordEmailStateWriter(): ResolvedPluginRecordEmailStateWriter | null {\n const entry = resolvePluginServices(PLUGIN_RECORD_EMAIL_STATE)[0]\n return entry ? { pluginId: entry.pluginId, writer: entry.impl } : null\n}\n\n/**\n * Stamps the verdict through whichever plugin keeps the records, or answers\n * `null` when none does. Never throws: every caller is a sender for whom\n * the stamp is a courtesy beside the list it already wrote.\n */\nexport async function stampRecordEmailState(\n request: PluginRecordEmailStateRequest,\n): Promise<PluginRecordEmailStateReport | null> {\n const resolved = pluginRecordEmailStateWriter()\n if (!resolved) return null\n try {\n return await resolved.writer.stamp(request)\n } catch (error) {\n console.error('[record-email-state] the record system could not stamp the address', error)\n return null\n }\n}\n\n/**\n * Hands a send's delivery events to whichever plugin keeps the records, so\n * it can stamp the people who engaged (AGL-2616). Answers `null` when no\n * plugin keeps records, or the one that does keeps no engagement. Never\n * throws: the caller is a webhook whose acknowledgement must not hang on a\n * courtesy stamp.\n */\nexport async function stampRecordEmailEngagement(\n request: PluginRecordEmailEngagementRequest,\n): Promise<PluginRecordEmailStateReport | null> {\n const resolved = pluginRecordEmailStateWriter()\n if (!resolved?.writer.engaged) return null\n try {\n return await resolved.writer.engaged(request)\n } catch (error) {\n console.error('[record-email-state] the record system could not stamp an engagement', error)\n return null\n }\n}\n\n/**\n * Hands the addresses a marketing send was delivered to to whichever plugin\n * keeps the records (AGL-3446). Answers `null` when no plugin keeps\n * records, or the one that does keeps no such stage. Never throws: the\n * send has already delivered, and its bookkeeping must not fail it.\n */\nexport async function stampRecordEmailReach(\n request: PluginRecordEmailReachRequest,\n): Promise<PluginRecordEmailStateReport | null> {\n if (!request.emails.length) return null\n const resolved = pluginRecordEmailStateWriter()\n if (!resolved?.writer.reached) return null\n try {\n return await resolved.writer.reached(request)\n } catch (error) {\n console.error('[record-email-state] the record system could not note a delivered send', error)\n return null\n }\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_RECORD_EMAIL_STATE","multiple","registerPluginRecordEmailStateWriter","writer","options","pluginId","pluginRecordEmailStateWriter","entry","impl","stampRecordEmailState","request","resolved","stamp","error","console","stampRecordEmailEngagement","engaged","stampRecordEmailReach","emails","length","reached"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AAmH1B,OAAO,MAAMC,4BAA4BH,4BACvC,2BACA;IAAEI,UAAU;AAAM,GACnB;AAED;;;;;CAKC,GACD,OAAO,SAASC,qCACdC,MAAoC,EACpCC,OAA+B;IAE/BN,sBAAsBE,2BAA2BG,QAAQ,aACnDC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAQA,6EAA6E,GAC7E,OAAO,SAASC;IACd,MAAMC,QAAQR,sBAAsBC,0BAA0B,CAAC,EAAE;IACjE,OAAOO,QAAQ;QAAEF,UAAUE,MAAMF,QAAQ;QAAEF,QAAQI,MAAMC,IAAI;IAAC,IAAI;AACpE;AAEA;;;;CAIC,GACD,OAAO,eAAeC,sBACpBC,OAAsC;IAEtC,MAAMC,WAAWL;IACjB,IAAI,CAACK,UAAU,OAAO;IACtB,IAAI;QACF,OAAO,MAAMA,SAASR,MAAM,CAACS,KAAK,CAACF;IACrC,EAAE,OAAOG,OAAO;QACdC,QAAQD,KAAK,CAAC,sEAAsEA;QACpF,OAAO;IACT;AACF;AAEA;;;;;;CAMC,GACD,OAAO,eAAeE,2BACpBL,OAA2C;IAE3C,MAAMC,WAAWL;IACjB,IAAI,EAACK,4BAAAA,SAAUR,MAAM,CAACa,OAAO,GAAE,OAAO;IACtC,IAAI;QACF,OAAO,MAAML,SAASR,MAAM,CAACa,OAAO,CAACN;IACvC,EAAE,OAAOG,OAAO;QACdC,QAAQD,KAAK,CAAC,wEAAwEA;QACtF,OAAO;IACT;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAeI,sBACpBP,OAAsC;IAEtC,IAAI,CAACA,QAAQQ,MAAM,CAACC,MAAM,EAAE,OAAO;IACnC,MAAMR,WAAWL;IACjB,IAAI,EAACK,4BAAAA,SAAUR,MAAM,CAACiB,OAAO,GAAE,OAAO;IACtC,IAAI;QACF,OAAO,MAAMT,SAASR,MAAM,CAACiB,OAAO,CAACV;IACvC,EAAE,OAAOG,OAAO;QACdC,QAAQD,KAAK,CAAC,0EAA0EA;QACxF,OAAO;IACT;AACF"}
@@ -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
+ /**
18
+ * A plugin's records, listed and read for ANOTHER plugin (AGL-3080).
19
+ *
20
+ * A plugin that works over records it does not own — an AI job enriching a
21
+ * site's products, a composer binding the products an email names — used to
22
+ * read the owner's Firestore collection itself: the path, the field names,
23
+ * what "deleted" looks like. That is a plugin's storage reached around the
24
+ * plugin, invisible to `check:lib-boundaries` because a collection name is
25
+ * not an import, and it breaks the day the owner changes a field.
26
+ *
27
+ * So the owner publishes an INDEX for each record kind it keeps: `list` names
28
+ * the live records of a scope, `get` reads one — each as an id, a name and the
29
+ * FACTS the owner chooses to share, in a shape its registration documents.
30
+ * The reader never learns where the records are stored or what a deleted one
31
+ * looks like; the owner answers only live records.
32
+ *
33
+ * Its siblings: `plugin-record-cards` (how one record LOOKS, for a campaign
34
+ * sender) and `plugin-record-facts` (what a record SAYS, for a summary);
35
+ * `plugin-resource-drafts` is the WRITE side. This is the side that finds and
36
+ * reads records to work on.
37
+ *
38
+ * ## Not registered is an answer
39
+ *
40
+ * No index for a kind means no plugin keeps it in this process — the owner is
41
+ * not installed, or not loaded. A reader treats that as "none here" and says
42
+ * so, rather than reaching for the collection itself. Registered from the
43
+ * owner's server declarations, so every server process that boots the
44
+ * declarations has it.
45
+ *
46
+ * ## One index per kind
47
+ *
48
+ * A second plugin registering an index for a kind another plugin already
49
+ * keeps is refused, naming both, and the incumbent keeps serving.
50
+ */
51
+ /** One record, as its owner shares it. */
52
+ export interface PluginIndexedRecord {
53
+ id: string;
54
+ /** What a person calls it. Never empty: the owner leaves an unnamed record out. */
55
+ name: string;
56
+ /** What else the owner shares, in the shape its registration documents. */
57
+ facts: Readonly<Record<string, unknown>>;
58
+ }
59
+ /** Which records a reader asks about. */
60
+ export interface PluginRecordIndexScope {
61
+ /** The organization the records belong to, where the kind is org-scoped. */
62
+ orgId?: string | null;
63
+ /** The site the records belong to, where the kind is site-scoped. */
64
+ hostId?: string | null;
65
+ }
66
+ export interface PluginRecordIndex {
67
+ /**
68
+ * The live records of the scope, at most `limit`, in the owner's order.
69
+ * `truncated` says the scope holds more than were answered.
70
+ */
71
+ list(request: PluginRecordIndexScope & {
72
+ limit: number;
73
+ }): Promise<{
74
+ records: PluginIndexedRecord[];
75
+ truncated: boolean;
76
+ }>;
77
+ /** One live record, or `null` — gone, deleted, or not the scope's. */
78
+ get(request: PluginRecordIndexScope & {
79
+ id: string;
80
+ }): Promise<PluginIndexedRecord | null>;
81
+ }
82
+ export declare const PLUGIN_RECORD_INDEXES: import("./plugin-services").PluginServiceContract<PluginRecordIndex>;
83
+ /**
84
+ * Publishes the index for one record kind. The owner is the loader's marker
85
+ * when a register fn is running, else `options.pluginId`; with neither the
86
+ * registration throws. A kind another plugin keeps throws naming both.
87
+ */
88
+ export declare function registerPluginRecordIndex(kind: string, index: PluginRecordIndex, options?: {
89
+ pluginId?: string;
90
+ }): void;
91
+ /** The index for a kind, with the plugin that keeps it, or `null` when none does here. */
92
+ export declare function pluginRecordIndex(kind: string): {
93
+ pluginId: string;
94
+ index: PluginRecordIndex;
95
+ } | null;