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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/package.json +15 -11
  2. package/src/lib/app-utils/agent-openapi.js +1 -1
  3. package/src/lib/app-utils/agent-openapi.js.map +1 -1
  4. package/src/lib/app-utils/analytics-events.d.ts +3 -2
  5. package/src/lib/app-utils/analytics-events.js +3 -2
  6. package/src/lib/app-utils/analytics-events.js.map +1 -1
  7. package/src/lib/app-utils/api-plugins.d.ts +30 -0
  8. package/src/lib/app-utils/api-plugins.js +24 -1
  9. package/src/lib/app-utils/api-plugins.js.map +1 -1
  10. package/src/lib/app-utils/author-html.d.ts +1 -1
  11. package/src/lib/app-utils/author-html.js.map +1 -1
  12. package/src/lib/app-utils/bandwidth-cap.d.ts +12 -1
  13. package/src/lib/app-utils/bandwidth-cap.js +17 -1
  14. package/src/lib/app-utils/bandwidth-cap.js.map +1 -1
  15. package/src/lib/app-utils/compose-reusable-components.d.ts +19 -0
  16. package/src/lib/app-utils/compose-reusable-components.js +116 -26
  17. package/src/lib/app-utils/compose-reusable-components.js.map +1 -1
  18. package/src/lib/app-utils/console-routes.d.ts +4 -0
  19. package/src/lib/app-utils/console-routes.js +3 -0
  20. package/src/lib/app-utils/console-routes.js.map +1 -1
  21. package/src/lib/app-utils/contacts.d.ts +71 -0
  22. package/src/lib/app-utils/contacts.js +55 -0
  23. package/src/lib/app-utils/contacts.js.map +1 -1
  24. package/src/lib/app-utils/contexts.d.ts +1 -0
  25. package/src/lib/app-utils/contexts.js +1 -0
  26. package/src/lib/app-utils/contexts.js.map +1 -1
  27. package/src/lib/app-utils/crm-email-templates.js +21 -4
  28. package/src/lib/app-utils/crm-email-templates.js.map +1 -1
  29. package/src/lib/app-utils/crm-kinds.d.ts +2 -0
  30. package/src/lib/app-utils/crm-kinds.js +1 -0
  31. package/src/lib/app-utils/crm-kinds.js.map +1 -1
  32. package/src/lib/app-utils/crm.d.ts +1199 -67
  33. package/src/lib/app-utils/crm.js +1951 -310
  34. package/src/lib/app-utils/crm.js.map +1 -1
  35. package/src/lib/app-utils/csv-import.d.ts +10 -38
  36. package/src/lib/app-utils/csv-import.js +17 -42
  37. package/src/lib/app-utils/csv-import.js.map +1 -1
  38. package/src/lib/app-utils/csv.d.ts +32 -11
  39. package/src/lib/app-utils/csv.js +43 -5
  40. package/src/lib/app-utils/csv.js.map +1 -1
  41. package/src/lib/app-utils/default-site.d.ts +116 -0
  42. package/src/lib/app-utils/default-site.js +1024 -0
  43. package/src/lib/app-utils/default-site.js.map +1 -0
  44. package/src/lib/app-utils/docs-help.generated.d.ts +22 -22
  45. package/src/lib/app-utils/docs-help.generated.js +44 -10
  46. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  47. package/src/lib/app-utils/docs-index.generated.js +738 -150
  48. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  49. package/src/lib/app-utils/draft-placeholders.d.ts +76 -0
  50. package/src/lib/app-utils/draft-placeholders.js +115 -0
  51. package/src/lib/app-utils/draft-placeholders.js.map +1 -0
  52. package/src/lib/app-utils/entity-picker-context.d.ts +19 -15
  53. package/src/lib/app-utils/entity-picker-context.js +4 -4
  54. package/src/lib/app-utils/entity-picker-context.js.map +1 -1
  55. package/src/lib/app-utils/expand-repeatables.d.ts +66 -10
  56. package/src/lib/app-utils/expand-repeatables.js +88 -14
  57. package/src/lib/app-utils/expand-repeatables.js.map +1 -1
  58. package/src/lib/app-utils/forms.d.ts +33 -22
  59. package/src/lib/app-utils/forms.js +47 -28
  60. package/src/lib/app-utils/forms.js.map +1 -1
  61. package/src/lib/app-utils/full-use-cost.d.ts +277 -0
  62. package/src/lib/app-utils/full-use-cost.js +421 -0
  63. package/src/lib/app-utils/full-use-cost.js.map +1 -0
  64. package/src/lib/app-utils/health-report.js +11 -0
  65. package/src/lib/app-utils/health-report.js.map +1 -1
  66. package/src/lib/app-utils/llms-txt.d.ts +20 -0
  67. package/src/lib/app-utils/llms-txt.js +7 -1
  68. package/src/lib/app-utils/llms-txt.js.map +1 -1
  69. package/src/lib/app-utils/media-asset-facts.d.ts +16 -1
  70. package/src/lib/app-utils/media-asset-facts.js +40 -13
  71. package/src/lib/app-utils/media-asset-facts.js.map +1 -1
  72. package/src/lib/app-utils/media-bandwidth.d.ts +52 -0
  73. package/src/lib/app-utils/media-bandwidth.js +94 -0
  74. package/src/lib/app-utils/media-bandwidth.js.map +1 -0
  75. package/src/lib/app-utils/media-ref.d.ts +241 -33
  76. package/src/lib/app-utils/media-ref.js +324 -43
  77. package/src/lib/app-utils/media-ref.js.map +1 -1
  78. package/src/lib/app-utils/media-src.d.ts +8 -8
  79. package/src/lib/app-utils/media-src.js +27 -11
  80. package/src/lib/app-utils/media-src.js.map +1 -1
  81. package/src/lib/app-utils/media-takedown-reach.d.ts +4 -1
  82. package/src/lib/app-utils/media-takedown-reach.js +6 -3
  83. package/src/lib/app-utils/media-takedown-reach.js.map +1 -1
  84. package/src/lib/app-utils/notifications.d.ts +1 -1
  85. package/src/lib/app-utils/notifications.js +2 -0
  86. package/src/lib/app-utils/notifications.js.map +1 -1
  87. package/src/lib/app-utils/organizations.d.ts +51 -2
  88. package/src/lib/app-utils/organizations.js +56 -3
  89. package/src/lib/app-utils/organizations.js.map +1 -1
  90. package/src/lib/app-utils/page-record-sources.d.ts +87 -0
  91. package/src/lib/app-utils/page-record-sources.js +53 -0
  92. package/src/lib/app-utils/page-record-sources.js.map +1 -0
  93. package/src/lib/app-utils/paid-media-source.d.ts +3 -7
  94. package/src/lib/app-utils/paid-media-source.js +5 -19
  95. package/src/lib/app-utils/paid-media-source.js.map +1 -1
  96. package/src/lib/app-utils/person-erasure.d.ts +5 -2
  97. package/src/lib/app-utils/person-erasure.js.map +1 -1
  98. package/src/lib/app-utils/picklists.d.ts +187 -0
  99. package/src/lib/app-utils/picklists.js +320 -0
  100. package/src/lib/app-utils/picklists.js.map +1 -0
  101. package/src/lib/app-utils/plan-entitlements.d.ts +220 -50
  102. package/src/lib/app-utils/plan-entitlements.js +309 -115
  103. package/src/lib/app-utils/plan-entitlements.js.map +1 -1
  104. package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
  105. package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
  106. package/src/lib/app-utils/release-flags.d.ts +1 -1
  107. package/src/lib/app-utils/release-flags.js +11 -0
  108. package/src/lib/app-utils/release-flags.js.map +1 -1
  109. package/src/lib/app-utils/rendered-widths.d.ts +121 -0
  110. package/src/lib/app-utils/rendered-widths.js +211 -0
  111. package/src/lib/app-utils/rendered-widths.js.map +1 -0
  112. package/src/lib/app-utils/repeat-sources.d.ts +2 -1
  113. package/src/lib/app-utils/repeat-sources.js.map +1 -1
  114. package/src/lib/app-utils/screen-route.d.ts +154 -36
  115. package/src/lib/app-utils/screen-route.js +185 -40
  116. package/src/lib/app-utils/screen-route.js.map +1 -1
  117. package/src/lib/app-utils/seo-audit.d.ts +11 -1
  118. package/src/lib/app-utils/seo-audit.js +33 -13
  119. package/src/lib/app-utils/seo-audit.js.map +1 -1
  120. package/src/lib/app-utils/seo-keywords.d.ts +11 -0
  121. package/src/lib/app-utils/seo-keywords.js +19 -5
  122. package/src/lib/app-utils/seo-keywords.js.map +1 -1
  123. package/src/lib/app-utils/seo-page-facts.d.ts +47 -4
  124. package/src/lib/app-utils/seo-page-facts.js +136 -31
  125. package/src/lib/app-utils/seo-page-facts.js.map +1 -1
  126. package/src/lib/app-utils/seo-site-scan.d.ts +42 -4
  127. package/src/lib/app-utils/seo-site-scan.js +113 -12
  128. package/src/lib/app-utils/seo-site-scan.js.map +1 -1
  129. package/src/lib/app-utils/server.d.ts +5 -4
  130. package/src/lib/app-utils/server.js +14 -5
  131. package/src/lib/app-utils/server.js.map +1 -1
  132. package/src/lib/app-utils/site-icon-set.d.ts +195 -0
  133. package/src/lib/app-utils/site-icon-set.js +351 -0
  134. package/src/lib/app-utils/site-icon-set.js.map +1 -0
  135. package/src/lib/app-utils/site-interactions.d.ts +101 -4
  136. package/src/lib/app-utils/site-interactions.js +120 -4
  137. package/src/lib/app-utils/site-interactions.js.map +1 -1
  138. package/src/lib/app-utils/starter-template-nodes.d.ts +7 -0
  139. package/src/lib/app-utils/starter-template-nodes.js +3 -0
  140. package/src/lib/app-utils/starter-template-nodes.js.map +1 -1
  141. package/src/lib/app-utils/starter-templates.d.ts +5 -19
  142. package/src/lib/app-utils/starter-templates.js +5 -49
  143. package/src/lib/app-utils/starter-templates.js.map +1 -1
  144. package/src/lib/app-utils/transfer-launcher-context.d.ts +136 -0
  145. package/src/lib/app-utils/transfer-launcher-context.js +23 -0
  146. package/src/lib/app-utils/transfer-launcher-context.js.map +1 -0
  147. package/src/lib/app-utils/transfer-resume-import.d.ts +39 -0
  148. package/src/lib/app-utils/transfer-resume-import.js +128 -0
  149. package/src/lib/app-utils/transfer-resume-import.js.map +1 -0
  150. package/src/lib/app-utils/upgrade-proposal.d.ts +66 -0
  151. package/src/lib/app-utils/upgrade-proposal.js +95 -0
  152. package/src/lib/app-utils/upgrade-proposal.js.map +1 -0
  153. package/src/lib/app-utils/utc-month.d.ts +33 -0
  154. package/src/lib/app-utils/utc-month.js +35 -0
  155. package/src/lib/app-utils/utc-month.js.map +1 -0
  156. package/src/lib/app-utils/visitor-record-ceiling.d.ts +4 -4
  157. package/src/lib/app-utils/visitor-record-ceiling.js +4 -4
  158. package/src/lib/app-utils/visitor-record-ceiling.js.map +1 -1
  159. package/src/lib/app-utils/wire-nodes.d.ts +39 -19
  160. package/src/lib/app-utils/wire-nodes.js +120 -34
  161. package/src/lib/app-utils/wire-nodes.js.map +1 -1
  162. package/src/lib/data-transfer/access.d.ts +74 -0
  163. package/src/lib/data-transfer/access.js +93 -0
  164. package/src/lib/data-transfer/access.js.map +1 -0
  165. package/src/lib/data-transfer/derive.d.ts +246 -0
  166. package/src/lib/data-transfer/derive.js +1007 -0
  167. package/src/lib/data-transfer/derive.js.map +1 -0
  168. package/src/lib/data-transfer/export-file.d.ts +46 -0
  169. package/src/lib/data-transfer/export-file.js +146 -0
  170. package/src/lib/data-transfer/export-file.js.map +1 -0
  171. package/src/lib/data-transfer/field-catalog.d.ts +134 -0
  172. package/src/lib/data-transfer/field-catalog.js +278 -0
  173. package/src/lib/data-transfer/field-catalog.js.map +1 -0
  174. package/src/lib/data-transfer/header-match.d.ts +129 -0
  175. package/src/lib/data-transfer/header-match.js +367 -0
  176. package/src/lib/data-transfer/header-match.js.map +1 -0
  177. package/src/lib/data-transfer/index.d.ts +34 -0
  178. package/src/lib/data-transfer/index.js +40 -0
  179. package/src/lib/data-transfer/index.js.map +1 -0
  180. package/src/lib/data-transfer/job.d.ts +190 -0
  181. package/src/lib/data-transfer/job.js +229 -0
  182. package/src/lib/data-transfer/job.js.map +1 -0
  183. package/src/lib/data-transfer/lookup.d.ts +45 -0
  184. package/src/lib/data-transfer/lookup.js +71 -0
  185. package/src/lib/data-transfer/lookup.js.map +1 -0
  186. package/src/lib/data-transfer/match.d.ts +99 -0
  187. package/src/lib/data-transfer/match.js +244 -0
  188. package/src/lib/data-transfer/match.js.map +1 -0
  189. package/src/lib/data-transfer/package-plan.d.ts +205 -0
  190. package/src/lib/data-transfer/package-plan.js +475 -0
  191. package/src/lib/data-transfer/package-plan.js.map +1 -0
  192. package/src/lib/data-transfer/package.d.ts +181 -0
  193. package/src/lib/data-transfer/package.js +426 -0
  194. package/src/lib/data-transfer/package.js.map +1 -0
  195. package/src/lib/data-transfer/picklist-map.d.ts +139 -0
  196. package/src/lib/data-transfer/picklist-map.js +271 -0
  197. package/src/lib/data-transfer/picklist-map.js.map +1 -0
  198. package/src/lib/data-transfer/plan.d.ts +176 -0
  199. package/src/lib/data-transfer/plan.js +451 -0
  200. package/src/lib/data-transfer/plan.js.map +1 -0
  201. package/src/lib/data-transfer/policy.d.ts +170 -0
  202. package/src/lib/data-transfer/policy.js +378 -0
  203. package/src/lib/data-transfer/policy.js.map +1 -0
  204. package/src/lib/data-transfer/resource.d.ts +211 -0
  205. package/src/lib/data-transfer/resource.js +241 -0
  206. package/src/lib/data-transfer/resource.js.map +1 -0
  207. package/src/lib/data-transfer/review.d.ts +70 -0
  208. package/src/lib/data-transfer/review.js +233 -0
  209. package/src/lib/data-transfer/review.js.map +1 -0
  210. package/src/lib/data-transfer/similarity.d.ts +40 -0
  211. package/src/lib/data-transfer/similarity.js +106 -0
  212. package/src/lib/data-transfer/similarity.js.map +1 -0
  213. package/src/lib/data-transfer/site-package.d.ts +365 -0
  214. package/src/lib/data-transfer/site-package.js +1077 -0
  215. package/src/lib/data-transfer/site-package.js.map +1 -0
  216. package/src/lib/data-transfer/source.d.ts +74 -0
  217. package/src/lib/data-transfer/source.js +293 -0
  218. package/src/lib/data-transfer/source.js.map +1 -0
  219. package/src/lib/data-transfer/transfer-api.d.ts +898 -0
  220. package/src/lib/data-transfer/transfer-api.js +107 -0
  221. package/src/lib/data-transfer/transfer-api.js.map +1 -0
  222. package/src/lib/foundation/definitions/org-billing.types.d.ts +31 -0
  223. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
  224. package/src/lib/foundation/definitions/organization.types.d.ts +42 -1
  225. package/src/lib/foundation/definitions/organization.types.js.map +1 -1
  226. package/src/lib/foundation/definitions/platform.types.d.ts +6 -4
  227. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  228. package/src/lib/foundation/index.d.ts +1 -1
  229. package/src/lib/foundation/index.js.map +1 -1
  230. package/src/lib/plugin-manager/feature-plugins.d.ts +91 -2
  231. package/src/lib/plugin-manager/feature-plugins.js +48 -3
  232. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  233. package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +50 -0
  234. package/src/lib/plugin-manager/first-party-plugins.generated.js +1129 -28
  235. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  236. package/src/lib/plugin-manager/index.d.ts +0 -1
  237. package/src/lib/plugin-manager/index.js +0 -1
  238. package/src/lib/plugin-manager/index.js.map +1 -1
  239. package/src/lib/plugin-manager/interaction-recipes.d.ts +142 -0
  240. package/src/lib/plugin-manager/interaction-recipes.js +104 -0
  241. package/src/lib/plugin-manager/interaction-recipes.js.map +1 -0
  242. package/src/lib/plugin-manager/interaction-step-checks.d.ts +66 -0
  243. package/src/lib/plugin-manager/interaction-step-checks.js +86 -0
  244. package/src/lib/plugin-manager/interaction-step-checks.js.map +1 -0
  245. package/src/lib/plugin-manager/plugin-artifact-types.d.ts +235 -0
  246. package/src/lib/plugin-manager/plugin-artifact-types.js +99 -0
  247. package/src/lib/plugin-manager/plugin-artifact-types.js.map +1 -0
  248. package/src/lib/plugin-manager/plugin-contact-capture.d.ts +8 -0
  249. package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
  250. package/src/lib/plugin-manager/plugin-conversion-credit.d.ts +216 -0
  251. package/src/lib/plugin-manager/plugin-conversion-credit.js +91 -0
  252. package/src/lib/plugin-manager/plugin-conversion-credit.js.map +1 -0
  253. package/src/lib/plugin-manager/plugin-entity-pickers.d.ts +83 -0
  254. package/src/lib/plugin-manager/plugin-entity-pickers.js +87 -0
  255. package/src/lib/plugin-manager/plugin-entity-pickers.js.map +1 -0
  256. package/src/lib/plugin-manager/plugin-host-resources.d.ts +6 -0
  257. package/src/lib/plugin-manager/plugin-host-resources.js +6 -1
  258. package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -1
  259. package/src/lib/plugin-manager/plugin-intake-gates.d.ts +75 -0
  260. package/src/lib/plugin-manager/plugin-intake-gates.js +49 -0
  261. package/src/lib/plugin-manager/plugin-intake-gates.js.map +1 -0
  262. package/src/lib/plugin-manager/plugin-jobs.d.ts +5 -5
  263. package/src/lib/plugin-manager/plugin-jobs.js +7 -7
  264. package/src/lib/plugin-manager/plugin-jobs.js.map +1 -1
  265. package/src/lib/plugin-manager/plugin-live-paths.d.ts +70 -0
  266. package/src/lib/plugin-manager/plugin-live-paths.js +66 -0
  267. package/src/lib/plugin-manager/plugin-live-paths.js.map +1 -0
  268. package/src/lib/plugin-manager/plugin-org-erasure.d.ts +3 -2
  269. package/src/lib/plugin-manager/plugin-org-erasure.js +30 -5
  270. package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
  271. package/src/lib/plugin-manager/plugin-person-erasure.d.ts +68 -9
  272. package/src/lib/plugin-manager/plugin-person-erasure.js +196 -51
  273. package/src/lib/plugin-manager/plugin-person-erasure.js.map +1 -1
  274. package/src/lib/plugin-manager/plugin-person-records.d.ts +275 -0
  275. package/src/lib/plugin-manager/plugin-person-records.js +107 -0
  276. package/src/lib/plugin-manager/plugin-person-records.js.map +1 -0
  277. package/src/lib/plugin-manager/plugin-record-counts.d.ts +76 -0
  278. package/src/lib/plugin-manager/plugin-record-counts.js +49 -0
  279. package/src/lib/plugin-manager/plugin-record-counts.js.map +1 -0
  280. package/src/lib/plugin-manager/plugin-record-email-state.d.ts +26 -0
  281. package/src/lib/plugin-manager/plugin-record-email-state.js +16 -0
  282. package/src/lib/plugin-manager/plugin-record-email-state.js.map +1 -1
  283. package/src/lib/plugin-manager/plugin-record-index.d.ts +10 -0
  284. package/src/lib/plugin-manager/plugin-record-index.js.map +1 -1
  285. package/src/lib/plugin-manager/plugin-record-lists.d.ts +86 -6
  286. package/src/lib/plugin-manager/plugin-record-lists.js +42 -5
  287. package/src/lib/plugin-manager/plugin-record-lists.js.map +1 -1
  288. package/src/lib/plugin-manager/plugin-record-origin.d.ts +79 -0
  289. package/src/lib/plugin-manager/plugin-record-origin.js +46 -0
  290. package/src/lib/plugin-manager/plugin-record-origin.js.map +1 -0
  291. package/src/lib/plugin-manager/plugin-record-pages.d.ts +37 -0
  292. package/src/lib/plugin-manager/plugin-record-pages.js +52 -0
  293. package/src/lib/plugin-manager/plugin-record-pages.js.map +1 -0
  294. package/src/lib/plugin-manager/plugin-record-timeline.d.ts +60 -0
  295. package/src/lib/plugin-manager/plugin-record-timeline.js +16 -0
  296. package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
  297. package/src/lib/plugin-manager/plugin-resource-drafts.d.ts +5 -5
  298. package/src/lib/plugin-manager/plugin-resource-drafts.js.map +1 -1
  299. package/src/lib/plugin-manager/plugin-revocations.d.ts +64 -0
  300. package/src/lib/plugin-manager/plugin-revocations.js +47 -0
  301. package/src/lib/plugin-manager/plugin-revocations.js.map +1 -0
  302. package/src/lib/plugin-manager/plugin-send-tallies.d.ts +69 -0
  303. package/src/lib/plugin-manager/plugin-send-tallies.js +45 -0
  304. package/src/lib/plugin-manager/plugin-send-tallies.js.map +1 -0
  305. package/src/lib/plugin-manager/plugin-site-bundle.d.ts +40 -0
  306. package/src/lib/plugin-manager/plugin-site-bundle.js +4 -0
  307. package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -1
  308. package/src/lib/plugin-manager/plugin-site-export.d.ts +57 -1
  309. package/src/lib/plugin-manager/plugin-site-export.js +23 -10
  310. package/src/lib/plugin-manager/plugin-site-export.js.map +1 -1
  311. package/src/lib/plugin-manager/plugin-sitemap-readers.d.ts +146 -0
  312. package/src/lib/plugin-manager/plugin-sitemap-readers.js +110 -0
  313. package/src/lib/plugin-manager/plugin-sitemap-readers.js.map +1 -0
  314. package/src/lib/plugin-manager/plugin-transfer-resources.d.ts +714 -0
  315. package/src/lib/plugin-manager/plugin-transfer-resources.js +443 -0
  316. package/src/lib/plugin-manager/plugin-transfer-resources.js.map +1 -0
  317. package/src/lib/plugin-manager/plugin-usage-axes.d.ts +54 -5
  318. package/src/lib/plugin-manager/plugin-usage-axes.js +11 -3
  319. package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -1
  320. package/src/lib/plugin-manager/plugin-user-erasure.js +9 -1
  321. package/src/lib/plugin-manager/plugin-user-erasure.js.map +1 -1
  322. package/src/lib/plugin-manager/plugin-visitor-doors.d.ts +104 -0
  323. package/src/lib/plugin-manager/plugin-visitor-doors.js +108 -0
  324. package/src/lib/plugin-manager/plugin-visitor-doors.js.map +1 -0
  325. package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
  326. package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
  327. package/src/lib/plugin-manager/repeat-rows.d.ts +5 -6
  328. package/src/lib/plugin-manager/repeat-rows.js.map +1 -1
  329. package/src/lib/app-utils/actions.d.ts +0 -310
  330. package/src/lib/app-utils/actions.js +0 -506
  331. package/src/lib/app-utils/actions.js.map +0 -1
  332. package/src/lib/app-utils/automation-placeholders.d.ts +0 -72
  333. package/src/lib/app-utils/automation-placeholders.js +0 -207
  334. package/src/lib/app-utils/automation-placeholders.js.map +0 -1
  335. package/src/lib/app-utils/dataset-models.d.ts +0 -339
  336. package/src/lib/app-utils/dataset-models.js +0 -722
  337. package/src/lib/app-utils/dataset-models.js.map +0 -1
  338. package/src/lib/app-utils/datasets.d.ts +0 -171
  339. package/src/lib/app-utils/datasets.js +0 -227
  340. package/src/lib/app-utils/datasets.js.map +0 -1
  341. package/src/lib/app-utils/form-abuse-ceiling.d.ts +0 -146
  342. package/src/lib/app-utils/form-abuse-ceiling.js +0 -158
  343. package/src/lib/app-utils/form-abuse-ceiling.js.map +0 -1
  344. package/src/lib/plugin-manager/order-fulfilment.d.ts +0 -142
  345. package/src/lib/plugin-manager/order-fulfilment.js +0 -104
  346. package/src/lib/plugin-manager/order-fulfilment.js.map +0 -1
@@ -1,3 +1,4 @@
1
+ var _globalScope_ERASERS_KEY;
1
2
  /**
2
3
  * @license
3
4
  * Copyright 2026 Aglyn LLC
@@ -71,9 +72,17 @@
71
72
  * the plugin loaded — a deletion that must not depend on which bundles an
72
73
  * erasing process happened to load, so it is data rather than an eraser.
73
74
  */ import { getRegisteringPluginId } from "../app-utils/registering-plugin.js";
75
+ import { runPluginDeclarationsRepair } from "./plugin-declarations-repair.js";
74
76
  import { PLUGIN_ORG_KEYED_COLLECTIONS, PLUGIN_REQUIRED_ORG_ERASERS } from "./first-party-plugins.generated.js";
75
77
  export { PLUGIN_ORG_KEYED_COLLECTIONS, PLUGIN_REQUIRED_ORG_ERASERS };
76
- const registrations = [];
78
+ /**
79
+ * One list per process, on `globalThis` (AGL-3464): the app registers its
80
+ * plugins' erasers from `instrumentation.ts`, which Next compiles apart from
81
+ * the route that runs the erasure, and a module-scoped list is filled in one
82
+ * copy and read empty in the other — the AGL-3412 shape.
83
+ */ const ERASERS_KEY = Symbol.for('@aglyn/aglyn:plugin-org-erasers');
84
+ const globalScope = globalThis;
85
+ const registrations = (_globalScope_ERASERS_KEY = globalScope[ERASERS_KEY]) != null ? _globalScope_ERASERS_KEY : globalScope[ERASERS_KEY] = [];
77
86
  /**
78
87
  * Registers a plugin's workspace eraser. Owner = the loader's marker inside a
79
88
  * register fn, else `options.pluginId`; an eraser with neither throws. One
@@ -102,11 +111,11 @@ const registrations = [];
102
111
  *
103
112
  * Throws in exactly two cases, both about a REQUIRED eraser (`required`,
104
113
  * 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.
114
+ * own): before running anything, when one is not registered even after the
115
+ * app's boot step has run once more; and after running every eraser, when
116
+ * one threw.
107
117
  */ 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));
118
+ const missing = await missingAfterRepair(required);
110
119
  if (missing.length) {
111
120
  throw new Error(`[plugins] erasing org ${request.orgId} refused: ${missing.join(', ')} ` + 'declared a required org eraser and none is registered in this process');
112
121
  }
@@ -131,6 +140,22 @@ const registrations = [];
131
140
  }
132
141
  return reports;
133
142
  }
143
+ /**
144
+ * The required erasers not registered in this process, after running the
145
+ * app's boot step once when any is missing (AGL-3464): a boot whose
146
+ * declarations failed looks the same from here as one that never declared
147
+ * the eraser, and only the second is a reason to refuse.
148
+ */ async function missingAfterRepair(required) {
149
+ const missing = ()=>{
150
+ const registered = new Set(registrations.map((entry)=>entry.pluginId));
151
+ return required.filter((pluginId)=>!registered.has(pluginId));
152
+ };
153
+ if (!missing().length) return [];
154
+ await runPluginDeclarationsRepair().catch((error)=>{
155
+ console.error('[plugins] the declarations repair failed before an org erasure', error);
156
+ });
157
+ return missing();
158
+ }
134
159
  /** The plugins with a workspace eraser, in the order they run. */ export function listPluginOrgErasers() {
135
160
  return registrations.map((entry)=>entry.pluginId);
136
161
  }
@@ -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 * ## 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"}
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 { runPluginDeclarationsRepair } from './plugin-declarations-repair'\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\n/**\n * One list per process, on `globalThis` (AGL-3464): the app registers its\n * plugins' erasers from `instrumentation.ts`, which Next compiles apart from\n * the route that runs the erasure, and a module-scoped list is filled in one\n * copy and read empty in the other — the AGL-3412 shape.\n */\nconst ERASERS_KEY = Symbol.for('@aglyn/aglyn:plugin-org-erasers')\n\nconst globalScope = globalThis as typeof globalThis & {\n [ERASERS_KEY]?: Registration[]\n}\n\nconst registrations: Registration[] =\n globalScope[ERASERS_KEY] ?? (globalScope[ERASERS_KEY] = [])\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 even after the\n * app's boot step has run once more; and after running every eraser, when\n * 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 missing = await missingAfterRepair(required)\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/**\n * The required erasers not registered in this process, after running the\n * app's boot step once when any is missing (AGL-3464): a boot whose\n * declarations failed looks the same from here as one that never declared\n * the eraser, and only the second is a reason to refuse.\n */\nasync function missingAfterRepair(required: readonly string[]): Promise<string[]> {\n const missing = () => {\n const registered = new Set(registrations.map((entry) => entry.pluginId))\n return required.filter((pluginId) => !registered.has(pluginId))\n }\n if (!missing().length) return []\n await runPluginDeclarationsRepair().catch((error: unknown) => {\n console.error('[plugins] the declarations repair failed before an org erasure', error)\n })\n return missing()\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":["globalScope","getRegisteringPluginId","runPluginDeclarationsRepair","PLUGIN_ORG_KEYED_COLLECTIONS","PLUGIN_REQUIRED_ORG_ERASERS","ERASERS_KEY","Symbol","for","globalThis","registrations","registerPluginOrgEraser","eraser","options","pluginId","trim","Error","index","findIndex","entry","push","runPluginOrgErasers","request","required","missing","missingAfterRepair","length","orgId","join","reports","failedRequired","dryRun","error","console","includes","registered","Set","map","filter","has","catch","listPluginOrgErasers","resetPluginOrgErasersForTests","standInRequiredOrgErasersForTests","standIn"],"mappings":"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"}
@@ -14,8 +14,10 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- /** Who is being erased, and from which workspace. */
18
- export interface PluginPersonErasureRequest {
17
+ import { PLUGIN_REQUIRED_PERSON_ERASERS } from './first-party-plugins.generated';
18
+ export { PLUGIN_REQUIRED_PERSON_ERASERS };
19
+ /** Who is being erased, and from which workspace: what is known before anyone erases. */
20
+ export interface PluginPersonErasureTarget {
19
21
  /** The workspace the person is erased from. */
20
22
  orgId: string;
21
23
  /** The person's address, normalized. */
@@ -25,10 +27,18 @@ export interface PluginPersonErasureRequest {
25
27
  * keeps in the same way, names the person without their address.
26
28
  */
27
29
  key: string;
28
- /** The contact documents the erasure is about to delete, by id. */
29
- contactIds: readonly string[];
30
30
  /** A plan, not an erasure: count, write nothing. */
31
31
  dryRun: boolean;
32
+ /** When the erasure runs, epoch ms: the one time every stamp it leaves carries. */
33
+ atMs: number;
34
+ }
35
+ /** What every eraser is handed. */
36
+ export interface PluginPersonErasureRequest extends PluginPersonErasureTarget {
37
+ /**
38
+ * The person's records, by the record system's ids, as its `locate`
39
+ * answered before anything was erased — empty when no plugin keeps people.
40
+ */
41
+ contactIds: readonly string[];
32
42
  }
33
43
  /**
34
44
  * What an eraser reports for the erasure's audit record: counts and flags,
@@ -37,6 +47,13 @@ export interface PluginPersonErasureRequest {
37
47
  */
38
48
  export type PluginPersonErasureReport = Readonly<Record<string, number | boolean | null>>;
39
49
  export type PluginPersonEraser = (request: PluginPersonErasureRequest) => Promise<PluginPersonErasureReport>;
50
+ /** The plugin that keeps the people: names the person's records first, erases them last. */
51
+ export interface PluginPersonRecordsEraser {
52
+ /** The ids of the person's records in this workspace. Writes nothing. */
53
+ locate(target: PluginPersonErasureTarget): Promise<readonly string[]>;
54
+ /** Its own share, run after every other eraser. */
55
+ erase: PluginPersonEraser;
56
+ }
40
57
  /**
41
58
  * Registers a plugin's person eraser. Owner = the loader's marker inside a
42
59
  * register fn, else `options.pluginId`; an eraser with neither throws. One
@@ -47,12 +64,54 @@ export declare function registerPluginPersonEraser(eraser: PluginPersonEraser, o
47
64
  pluginId?: string;
48
65
  }): void;
49
66
  /**
50
- * Runs every eraser, in registration order, and answers each plugin's
51
- * report by plugin id: the eraser's own report, or `null` for one that
52
- * threw. Never throws.
67
+ * Registers the records eraser of the plugin that keeps people. One per
68
+ * process: the same plugin registering again replaces its own, and a second
69
+ * plugin is refused naming both — a workspace keeps one set of people.
70
+ */
71
+ export declare function registerPluginPersonRecordsEraser(eraser: PluginPersonRecordsEraser, options?: {
72
+ pluginId?: string;
73
+ }): void;
74
+ /**
75
+ * The required erasers not registered in this process, in `required` order —
76
+ * empty when the erasure may run. The erasure asks before it writes anything.
77
+ */
78
+ export declare function missingRequiredPersonErasers(required?: readonly string[]): string[];
79
+ /**
80
+ * {@link missingRequiredPersonErasers}, after running the app's boot step
81
+ * once when any is missing (AGL-3464): a boot whose declarations failed looks
82
+ * the same from here as one that never declared the eraser, and only the
83
+ * second is a reason to refuse. What the erasure asks before it writes
84
+ * anything.
53
85
  */
54
- export declare function runPluginPersonErasers(request: PluginPersonErasureRequest): Promise<Record<string, PluginPersonErasureReport | null>>;
55
- /** The plugins with a person eraser, in the order they run. */
86
+ export declare function missingRequiredPersonErasersAfterRepair(required?: readonly string[]): Promise<string[]>;
87
+ /** What one erasure's plugins did. */
88
+ export interface PluginPersonErasureOutcome {
89
+ /** The records `locate` named, handed to every eraser. */
90
+ contactIds: string[];
91
+ /** Each plugin's report by plugin id, or `null` for an eraser that threw. */
92
+ reports: Record<string, PluginPersonErasureReport | null>;
93
+ }
94
+ /**
95
+ * Runs the plugins' shares of one person erasure: the records eraser's
96
+ * `locate`, every other eraser in registration order, then the records
97
+ * eraser's `erase`. See the module note.
98
+ *
99
+ * Throws in three cases, each about a share the erasure promises (`required`,
100
+ * the compiled {@link PLUGIN_REQUIRED_PERSON_ERASERS} unless a spec passes its
101
+ * own): before running anything, when a required eraser is not registered
102
+ * even after the app's boot step has run once more, or the records eraser
103
+ * cannot `locate`; and after running every eraser, when a required one
104
+ * threw. Otherwise never.
105
+ */
106
+ export declare function runPluginPersonErasure(target: PluginPersonErasureTarget, required?: readonly string[]): Promise<PluginPersonErasureOutcome>;
107
+ /** The plugins with a person eraser, in the order they run: the records eraser last. */
56
108
  export declare function listPluginPersonErasers(): string[];
57
109
  /** Test seam: forget every eraser. */
58
110
  export declare function resetPluginPersonErasersForTests(): void;
111
+ /**
112
+ * Test seam: a no-op eraser for every REQUIRED plugin that has none, so a spec
113
+ * about the erasure's own sweeps can run it without loading a plugin (core
114
+ * and the data layer may not import one). A spec about a plugin's share
115
+ * registers the real one through the app's manifest instead.
116
+ */
117
+ export declare function standInRequiredPersonErasersForTests(): void;
@@ -1,3 +1,5 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ var _globalScope_ERASERS_KEY;
1
3
  /**
2
4
  * @license
3
5
  * Copyright 2026 Aglyn LLC
@@ -14,54 +16,103 @@
14
16
  * See the License for the specific language governing permissions and
15
17
  * limitations under the License.
16
18
  */ /**
17
- * A plugin's share of a PERSON erasure (AGL-2981).
18
- *
19
- * The person erasure removes one person from one workspace — a contact the
20
- * workspace kept, not an account: the contact document, what the CRM files
21
- * beside it, the leads, list memberships and delivery log under the
22
- * address, and the person's identity on orders and bookings. A plugin that
23
- * keeps records about such a person where none of those sweeps reach — a
24
- * collection under the organization keyed by the contact or by the address
25
- * — registers an eraser here, and the erasure runs every eraser with the
26
- * person's address, its key and the contacts it is about to delete.
27
- *
28
- * Erasers run AFTER the erasure has closed the door and BEFORE it deletes
29
- * the contacts: every site's suppression row is already written, so nothing
30
- * a plugin sends can reach the person while its eraser runs, and the contact
31
- * documents still exist for an eraser that has to read one.
19
+ * The plugins' shares of a PERSON erasure (AGL-2981, AGL-3080).
20
+ *
21
+ * The person erasure removes one person from one workspace — a person the
22
+ * workspace kept, not an account. The platform closes every site's door to
23
+ * the address (a suppression row per site), sweeps the delivery log filed
24
+ * under it, and keeps the audit; everything else the workspace keeps about the
25
+ * person is a plugin's, and each plugin that keeps some registers an eraser
26
+ * here: the record system's people and what it files beside them, a shop's
27
+ * orders, a calendar's bookings, an audience's members, a sequence's
28
+ * enrollments.
29
+ *
30
+ * ## The plugin that keeps the people goes first, and last
31
+ *
32
+ * Other plugins key what they keep about a person on the record system's ids
33
+ * — an enrollment names the contact it enrolled — so the erasure has to know
34
+ * those ids before anybody erases, and the records themselves have to outlive
35
+ * every other eraser. The plugin that keeps people registers a RECORDS eraser
36
+ * (`registerPluginPersonRecordsEraser`, one per workspace) with two halves:
37
+ *
38
+ * 1. `locate` names the person's records, read before anything is erased; the
39
+ * ids are handed to every eraser as `contactIds`;
40
+ * 2. every other eraser runs, in registration order;
41
+ * 3. the records eraser's own `erase` runs LAST, while nothing else is left
42
+ * that needs the records to find what it keeps.
43
+ *
44
+ * Every eraser runs AFTER the erasure has closed the door — every site's
45
+ * suppression row is already written, so nothing a plugin sends can reach the
46
+ * person while it runs.
32
47
  *
33
48
  * The two halves of the rule that holds everywhere else in the erasure hold
34
49
  * here too. A record ABOUT the person goes. A record that the person asked
35
- * not to be contacted — keyed by `personKey`, the hash every suppression
36
- * list is keyed by, and holding no address — is KEPT, because the promise
37
- * it records has to outlive the person's data; an eraser strips from it
38
- * anything that could identify the person again.
50
+ * not to be contacted — keyed by `personKey`, the hash every suppression list
51
+ * is keyed by, and holding no address — is KEPT, because the promise it
52
+ * records has to outlive the person's data; an eraser strips from it anything
53
+ * that could identify the person again.
54
+ *
55
+ * ## Isolated, unless REQUIRED
39
56
  *
40
- * Erasers run in registration order and are ISOLATED: a throw is logged
41
- * against its plugin, recorded as `null`, and does not stop the next one or
42
- * the erasure. The `null` is how the erasure's audit record says that
43
- * plugin's share may remain, which a report of zero would hide.
57
+ * Erasers are ISOLATED: a throw is logged against its plugin, recorded as
58
+ * `null`, and does not stop the next one. The `null` is how the erasure's
59
+ * audit record says that plugin's share may remain, which a report of zero
60
+ * would hide.
61
+ *
62
+ * Isolation is right for a courtesy and wrong for a share the erasure
63
+ * PROMISES the person — their contact record, their leads, their name on an
64
+ * order. A plugin keeping such a share declares `"requiredPersonEraser": true`
65
+ * in `plugins.config.json`, compiled into {@link PLUGIN_REQUIRED_PERSON_ERASERS}
66
+ * so no registration can opt out of it, and:
67
+ *
68
+ * - the erasure REFUSES to start while a required eraser is not registered —
69
+ * a boot that failed to declare one must not produce an "erased" request
70
+ * with the person still on file;
71
+ * - a records eraser whose `locate` throws stops the erasure before anything
72
+ * is erased: nobody can be handed the ids;
73
+ * - a required eraser that throws still lets every other eraser run, and then
74
+ * fails the erasure, which the job retries.
44
75
  *
45
76
  * A DRY RUN is handed to every eraser, which must then write nothing: it
46
77
  * counts what it would do.
47
78
  *
48
79
  * Registered from a plugin's declarations — `serverDeclarations`, or
49
- * `consoleServerDeclarations` for a plugin whose server code runs only in
50
- * the console — so the eraser is in place in a process that never loaded
51
- * the plugin's API surface.
80
+ * `consoleServerDeclarations` for a plugin whose server code runs only in the
81
+ * console — so the eraser is in place in a process that never loaded the
82
+ * plugin's API surface.
52
83
  */ import { getRegisteringPluginId } from "../app-utils/registering-plugin.js";
53
- const registrations = [];
84
+ import { PLUGIN_REQUIRED_PERSON_ERASERS } from "./first-party-plugins.generated.js";
85
+ import { runPluginDeclarationsRepair } from "./plugin-declarations-repair.js";
86
+ export { PLUGIN_REQUIRED_PERSON_ERASERS };
87
+ /**
88
+ * One set of erasers per process, on `globalThis` (AGL-3464): the app registers its
89
+ * plugins' erasers from `instrumentation.ts`, which Next compiles apart from
90
+ * the route that runs the erasure, and a module-scoped list is filled in one
91
+ * copy and read empty in the other — the AGL-3412 shape.
92
+ * Here a required share registered in the other copy reads as missing, and
93
+ * every erasure refuses.
94
+ */ const ERASERS_KEY = Symbol.for('@aglyn/aglyn:plugin-person-erasers');
95
+ const globalScope = globalThis;
96
+ const erasers = (_globalScope_ERASERS_KEY = globalScope[ERASERS_KEY]) != null ? _globalScope_ERASERS_KEY : globalScope[ERASERS_KEY] = {
97
+ registrations: [],
98
+ records: null
99
+ };
100
+ const registrations = erasers.registrations;
101
+ function ownerOf(options, what) {
102
+ var _ref, _getRegisteringPluginId;
103
+ const pluginId = ((_ref = (_getRegisteringPluginId = getRegisteringPluginId()) != null ? _getRegisteringPluginId : options == null ? void 0 : options.pluginId) != null ? _ref : '').trim();
104
+ if (!pluginId) {
105
+ throw new Error(`a plugin ${what} was registered with no owner: pass { pluginId } ` + 'when registering outside a plugin register fn');
106
+ }
107
+ return pluginId;
108
+ }
54
109
  /**
55
110
  * Registers a plugin's person eraser. Owner = the loader's marker inside a
56
111
  * register fn, else `options.pluginId`; an eraser with neither throws. One
57
112
  * eraser per plugin: registering again replaces the plugin's earlier eraser
58
113
  * in place, so a module evaluated twice does not erase twice.
59
114
  */ export function registerPluginPersonEraser(eraser, options) {
60
- var _ref, _getRegisteringPluginId;
61
- const pluginId = ((_ref = (_getRegisteringPluginId = getRegisteringPluginId()) != null ? _getRegisteringPluginId : options == null ? void 0 : options.pluginId) != null ? _ref : '').trim();
62
- if (!pluginId) {
63
- throw new Error('a plugin person eraser was registered with no owner: pass { pluginId } ' + 'when registering outside a plugin register fn');
64
- }
115
+ const pluginId = ownerOf(options, 'person eraser');
65
116
  const index = registrations.findIndex((entry)=>entry.pluginId === pluginId);
66
117
  if (index >= 0) registrations[index] = {
67
118
  pluginId,
@@ -73,37 +124,131 @@ const registrations = [];
73
124
  });
74
125
  }
75
126
  /**
76
- * Runs every eraser, in registration order, and answers each plugin's
77
- * report by plugin id: the eraser's own report, or `null` for one that
78
- * threw. Never throws.
79
- */ export async function runPluginPersonErasers(request) {
127
+ * Registers the records eraser of the plugin that keeps people. One per
128
+ * process: the same plugin registering again replaces its own, and a second
129
+ * plugin is refused naming both — a workspace keeps one set of people.
130
+ */ export function registerPluginPersonRecordsEraser(eraser, options) {
131
+ const pluginId = ownerOf(options, 'person records eraser');
132
+ if (erasers.records && erasers.records.pluginId !== pluginId) {
133
+ throw new Error(`the person records eraser is "${erasers.records.pluginId}"'s; refused one from "${pluginId}"`);
134
+ }
135
+ erasers.records = {
136
+ pluginId,
137
+ eraser
138
+ };
139
+ }
140
+ /**
141
+ * The required erasers not registered in this process, in `required` order —
142
+ * empty when the erasure may run. The erasure asks before it writes anything.
143
+ */ export function missingRequiredPersonErasers(required = PLUGIN_REQUIRED_PERSON_ERASERS) {
144
+ const registered = new Set(registrations.map((entry)=>entry.pluginId));
145
+ if (erasers.records) registered.add(erasers.records.pluginId);
146
+ return required.filter((pluginId)=>!registered.has(pluginId));
147
+ }
148
+ /**
149
+ * {@link missingRequiredPersonErasers}, after running the app's boot step
150
+ * once when any is missing (AGL-3464): a boot whose declarations failed looks
151
+ * the same from here as one that never declared the eraser, and only the
152
+ * second is a reason to refuse. What the erasure asks before it writes
153
+ * anything.
154
+ */ export async function missingRequiredPersonErasersAfterRepair(required = PLUGIN_REQUIRED_PERSON_ERASERS) {
155
+ if (!missingRequiredPersonErasers(required).length) return [];
156
+ await runPluginDeclarationsRepair().catch((error)=>{
157
+ console.error('[plugins] the declarations repair failed before a person erasure', error);
158
+ });
159
+ return missingRequiredPersonErasers(required);
160
+ }
161
+ /**
162
+ * Runs the plugins' shares of one person erasure: the records eraser's
163
+ * `locate`, every other eraser in registration order, then the records
164
+ * eraser's `erase`. See the module note.
165
+ *
166
+ * Throws in three cases, each about a share the erasure promises (`required`,
167
+ * the compiled {@link PLUGIN_REQUIRED_PERSON_ERASERS} unless a spec passes its
168
+ * own): before running anything, when a required eraser is not registered
169
+ * even after the app's boot step has run once more, or the records eraser
170
+ * cannot `locate`; and after running every eraser, when a required one
171
+ * threw. Otherwise never.
172
+ */ export async function runPluginPersonErasure(target, required = PLUGIN_REQUIRED_PERSON_ERASERS) {
173
+ const missing = await missingRequiredPersonErasersAfterRepair(required);
174
+ if (missing.length) {
175
+ throw new Error(`[plugins] erasing a person in org ${target.orgId} refused: ${missing.join(', ')} ` + 'declared a required person eraser and none is registered in this process');
176
+ }
177
+ const base = {
178
+ orgId: target.orgId,
179
+ email: target.email,
180
+ key: target.key,
181
+ dryRun: target.dryRun,
182
+ atMs: target.atMs
183
+ };
184
+ const records = erasers.records;
185
+ const contactIds = records ? [
186
+ ...await records.eraser.locate(_extends({}, base))
187
+ ] : [];
80
188
  const reports = {};
81
- for (const { pluginId, eraser } of [
82
- ...registrations
83
- ]){
189
+ const failedRequired = [];
190
+ const run = async (pluginId, eraser)=>{
84
191
  try {
85
- reports[pluginId] = await eraser({
86
- orgId: request.orgId,
87
- email: request.email,
88
- key: request.key,
192
+ reports[pluginId] = await eraser(_extends({}, base, {
89
193
  contactIds: [
90
- ...request.contactIds
91
- ],
92
- dryRun: request.dryRun
93
- });
194
+ ...contactIds
195
+ ]
196
+ }));
94
197
  } catch (error) {
95
198
  reports[pluginId] = null;
96
199
  // The org, never the address: this line outlives the erasure.
97
- console.error(`[plugins] ${pluginId} failed to erase a person in org ${request.orgId}`, error);
200
+ console.error(`[plugins] ${pluginId} failed to erase a person in org ${target.orgId}`, error);
201
+ if (required.includes(pluginId)) failedRequired.push(pluginId);
98
202
  }
203
+ };
204
+ const owner = records;
205
+ for (const { pluginId, eraser } of [
206
+ ...registrations
207
+ ]){
208
+ if (pluginId === (owner == null ? void 0 : owner.pluginId)) continue;
209
+ await run(pluginId, eraser);
210
+ }
211
+ if (owner) await run(owner.pluginId, owner.eraser.erase);
212
+ if (failedRequired.length) {
213
+ throw new Error(`[plugins] erasing a person in org ${target.orgId} incomplete: the required eraser of ` + `${failedRequired.join(', ')} failed`);
99
214
  }
100
- return reports;
215
+ return {
216
+ contactIds,
217
+ reports
218
+ };
101
219
  }
102
- /** The plugins with a person eraser, in the order they run. */ export function listPluginPersonErasers() {
103
- return registrations.map((entry)=>entry.pluginId);
220
+ /** The plugins with a person eraser, in the order they run: the records eraser last. */ export function listPluginPersonErasers() {
221
+ var _erasers_records;
222
+ const owner = (_erasers_records = erasers.records) == null ? void 0 : _erasers_records.pluginId;
223
+ return [
224
+ ...registrations.map((entry)=>entry.pluginId).filter((pluginId)=>pluginId !== owner),
225
+ ...owner ? [
226
+ owner
227
+ ] : []
228
+ ];
104
229
  }
105
230
  /** Test seam: forget every eraser. */ export function resetPluginPersonErasersForTests() {
106
231
  registrations.length = 0;
232
+ erasers.records = null;
233
+ }
234
+ /**
235
+ * Test seam: a no-op eraser for every REQUIRED plugin that has none, so a spec
236
+ * about the erasure's own sweeps can run it without loading a plugin (core
237
+ * and the data layer may not import one). A spec about a plugin's share
238
+ * registers the real one through the app's manifest instead.
239
+ */ export function standInRequiredPersonErasersForTests() {
240
+ const registered = new Set(registrations.map((entry)=>entry.pluginId));
241
+ if (erasers.records) registered.add(erasers.records.pluginId);
242
+ for (const pluginId of PLUGIN_REQUIRED_PERSON_ERASERS){
243
+ if (!registered.has(pluginId)) {
244
+ registrations.push({
245
+ pluginId,
246
+ eraser: async ()=>({
247
+ standIn: true
248
+ })
249
+ });
250
+ }
251
+ }
107
252
  }
108
253
 
109
254
  //# sourceMappingURL=plugin-person-erasure.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-person-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 PERSON erasure (AGL-2981).\n *\n * The person erasure removes one person from one workspace — a contact the\n * workspace kept, not an account: the contact document, what the CRM files\n * beside it, the leads, list memberships and delivery log under the\n * address, and the person's identity on orders and bookings. A plugin that\n * keeps records about such a person where none of those sweeps reach — a\n * collection under the organization keyed by the contact or by the address\n * — registers an eraser here, and the erasure runs every eraser with the\n * person's address, its key and the contacts it is about to delete.\n *\n * Erasers run AFTER the erasure has closed the door and BEFORE it deletes\n * the contacts: every site's suppression row is already written, so nothing\n * a plugin sends can reach the person while its eraser runs, and the contact\n * documents still exist for an eraser that has to read one.\n *\n * The two halves of the rule that holds everywhere else in the erasure hold\n * here too. A record ABOUT the person goes. A record that the person asked\n * not to be contacted — keyed by `personKey`, the hash every suppression\n * list is keyed by, and holding no address — is KEPT, because the promise\n * it records has to outlive the person's data; an eraser strips from it\n * anything that could identify the person again.\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. The `null` is how the erasure's audit record says that\n * plugin's share may remain, which a report of zero would hide.\n *\n * A DRY RUN is handed to every eraser, which must then write nothing: it\n * counts what it would do.\n *\n * Registered from a plugin's declarations — `serverDeclarations`, or\n * `consoleServerDeclarations` for a plugin whose server code runs only in\n * the console — so the eraser is in place in a process that never loaded\n * the plugin's API surface.\n */\n\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\n\n/** Who is being erased, and from which workspace. */\nexport interface PluginPersonErasureRequest {\n /** The workspace the person is erased from. */\n orgId: string\n /** The person's address, normalized. */\n email: string\n /**\n * `personKey(email)`: how every suppression list, and any list a plugin\n * keeps in the same way, names the person without their address.\n */\n key: string\n /** The contact documents the erasure is about to delete, by id. */\n contactIds: readonly string[]\n /** A plan, not an erasure: count, write nothing. */\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. A `null` field is a figure the eraser could not\n * measure, which is not zero.\n */\nexport type PluginPersonErasureReport = Readonly<Record<string, number | boolean | null>>\n\nexport type PluginPersonEraser = (\n request: PluginPersonErasureRequest,\n) => Promise<PluginPersonErasureReport>\n\ninterface Registration {\n pluginId: string\n eraser: PluginPersonEraser\n}\n\nconst registrations: Registration[] = []\n\n/**\n * Registers a plugin's person 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 registerPluginPersonEraser(\n eraser: PluginPersonEraser,\n options?: { pluginId?: string },\n): void {\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n if (!pluginId) {\n throw new Error(\n 'a plugin person 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 runPluginPersonErasers(\n request: PluginPersonErasureRequest,\n): Promise<Record<string, PluginPersonErasureReport | null>> {\n const reports: Record<string, PluginPersonErasureReport | null> = {}\n for (const { pluginId, eraser } of [...registrations]) {\n try {\n reports[pluginId] = await eraser({\n orgId: request.orgId,\n email: request.email,\n key: request.key,\n contactIds: [...request.contactIds],\n dryRun: request.dryRun,\n })\n } catch (error) {\n reports[pluginId] = null\n // The org, never the address: this line outlives the erasure.\n console.error(`[plugins] ${pluginId} failed to erase a person in org ${request.orgId}`, error)\n }\n }\n return reports\n}\n\n/** The plugins with a person eraser, in the order they run. */\nexport function listPluginPersonErasers(): string[] {\n return registrations.map((entry) => entry.pluginId)\n}\n\n/** Test seam: forget every eraser. */\nexport function resetPluginPersonErasersForTests(): void {\n registrations.length = 0\n}\n"],"names":["getRegisteringPluginId","registrations","registerPluginPersonEraser","eraser","options","pluginId","trim","Error","index","findIndex","entry","push","runPluginPersonErasers","request","reports","orgId","email","key","contactIds","dryRun","error","console","listPluginPersonErasers","map","resetPluginPersonErasersForTests","length"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCC,GAED,SAASA,sBAAsB,QAAQ,qCAAiC;AAmCxE,MAAMC,gBAAgC,EAAE;AAExC;;;;;CAKC,GACD,OAAO,SAASC,2BACdC,MAA0B,EAC1BC,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,4EACE;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,uBACpBC,OAAmC;IAEnC,MAAMC,UAA4D,CAAC;IACnE,KAAK,MAAM,EAAET,QAAQ,EAAEF,MAAM,EAAE,IAAI;WAAIF;KAAc,CAAE;QACrD,IAAI;YACFa,OAAO,CAACT,SAAS,GAAG,MAAMF,OAAO;gBAC/BY,OAAOF,QAAQE,KAAK;gBACpBC,OAAOH,QAAQG,KAAK;gBACpBC,KAAKJ,QAAQI,GAAG;gBAChBC,YAAY;uBAAIL,QAAQK,UAAU;iBAAC;gBACnCC,QAAQN,QAAQM,MAAM;YACxB;QACF,EAAE,OAAOC,OAAO;YACdN,OAAO,CAACT,SAAS,GAAG;YACpB,8DAA8D;YAC9DgB,QAAQD,KAAK,CAAC,CAAC,UAAU,EAAEf,SAAS,iCAAiC,EAAEQ,QAAQE,KAAK,EAAE,EAAEK;QAC1F;IACF;IACA,OAAON;AACT;AAEA,6DAA6D,GAC7D,OAAO,SAASQ;IACd,OAAOrB,cAAcsB,GAAG,CAAC,CAACb,QAAUA,MAAML,QAAQ;AACpD;AAEA,oCAAoC,GACpC,OAAO,SAASmB;IACdvB,cAAcwB,MAAM,GAAG;AACzB"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-person-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 * The plugins' shares of a PERSON erasure (AGL-2981, AGL-3080).\n *\n * The person erasure removes one person from one workspace — a person the\n * workspace kept, not an account. The platform closes every site's door to\n * the address (a suppression row per site), sweeps the delivery log filed\n * under it, and keeps the audit; everything else the workspace keeps about the\n * person is a plugin's, and each plugin that keeps some registers an eraser\n * here: the record system's people and what it files beside them, a shop's\n * orders, a calendar's bookings, an audience's members, a sequence's\n * enrollments.\n *\n * ## The plugin that keeps the people goes first, and last\n *\n * Other plugins key what they keep about a person on the record system's ids\n * — an enrollment names the contact it enrolled — so the erasure has to know\n * those ids before anybody erases, and the records themselves have to outlive\n * every other eraser. The plugin that keeps people registers a RECORDS eraser\n * (`registerPluginPersonRecordsEraser`, one per workspace) with two halves:\n *\n * 1. `locate` names the person's records, read before anything is erased; the\n * ids are handed to every eraser as `contactIds`;\n * 2. every other eraser runs, in registration order;\n * 3. the records eraser's own `erase` runs LAST, while nothing else is left\n * that needs the records to find what it keeps.\n *\n * Every eraser runs AFTER the erasure has closed the door — every site's\n * suppression row is already written, so nothing a plugin sends can reach the\n * person while it runs.\n *\n * The two halves of the rule that holds everywhere else in the erasure hold\n * here too. A record ABOUT the person goes. A record that the person asked\n * not to be contacted — keyed by `personKey`, the hash every suppression list\n * is keyed by, and holding no address — is KEPT, because the promise it\n * records has to outlive the person's data; an eraser strips from it anything\n * that could identify the person again.\n *\n * ## Isolated, unless REQUIRED\n *\n * Erasers are ISOLATED: a throw is logged against its plugin, recorded as\n * `null`, and does not stop the next one. The `null` is how the erasure's\n * audit record says that plugin's share may remain, which a report of zero\n * would hide.\n *\n * Isolation is right for a courtesy and wrong for a share the erasure\n * PROMISES the person — their contact record, their leads, their name on an\n * order. A plugin keeping such a share declares `\"requiredPersonEraser\": true`\n * in `plugins.config.json`, compiled into {@link PLUGIN_REQUIRED_PERSON_ERASERS}\n * so no registration can opt out of it, and:\n *\n * - the erasure REFUSES to start while a required eraser is not registered —\n * a boot that failed to declare one must not produce an \"erased\" request\n * with the person still on file;\n * - a records eraser whose `locate` throws stops the erasure before anything\n * is erased: nobody can be handed the ids;\n * - a required eraser that throws still lets every other eraser run, and then\n * fails the erasure, which the job retries.\n *\n * A DRY RUN is handed to every eraser, which must then write nothing: it\n * counts what it would do.\n *\n * Registered from a plugin's declarations — `serverDeclarations`, or\n * `consoleServerDeclarations` for a plugin whose server code runs only in the\n * console — so the eraser is in place in a process that never loaded the\n * plugin's API surface.\n */\n\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\nimport { PLUGIN_REQUIRED_PERSON_ERASERS } from './first-party-plugins.generated'\nimport { runPluginDeclarationsRepair } from './plugin-declarations-repair'\n\nexport { PLUGIN_REQUIRED_PERSON_ERASERS }\n\n/** Who is being erased, and from which workspace: what is known before anyone erases. */\nexport interface PluginPersonErasureTarget {\n /** The workspace the person is erased from. */\n orgId: string\n /** The person's address, normalized. */\n email: string\n /**\n * `personKey(email)`: how every suppression list, and any list a plugin\n * keeps in the same way, names the person without their address.\n */\n key: string\n /** A plan, not an erasure: count, write nothing. */\n dryRun: boolean\n /** When the erasure runs, epoch ms: the one time every stamp it leaves carries. */\n atMs: number\n}\n\n/** What every eraser is handed. */\nexport interface PluginPersonErasureRequest extends PluginPersonErasureTarget {\n /**\n * The person's records, by the record system's ids, as its `locate`\n * answered before anything was erased — empty when no plugin keeps people.\n */\n contactIds: readonly string[]\n}\n\n/**\n * What an eraser reports for the erasure's audit record: counts and flags,\n * never the erased content. A `null` field is a figure the eraser could not\n * measure, which is not zero.\n */\nexport type PluginPersonErasureReport = Readonly<Record<string, number | boolean | null>>\n\nexport type PluginPersonEraser = (\n request: PluginPersonErasureRequest,\n) => Promise<PluginPersonErasureReport>\n\n/** The plugin that keeps the people: names the person's records first, erases them last. */\nexport interface PluginPersonRecordsEraser {\n /** The ids of the person's records in this workspace. Writes nothing. */\n locate(target: PluginPersonErasureTarget): Promise<readonly string[]>\n /** Its own share, run after every other eraser. */\n erase: PluginPersonEraser\n}\n\ninterface Registration {\n pluginId: string\n eraser: PluginPersonEraser\n}\n\ninterface PersonErasers {\n registrations: Registration[]\n records: { pluginId: string; eraser: PluginPersonRecordsEraser } | null\n}\n\n/**\n * One set of erasers per process, on `globalThis` (AGL-3464): the app registers its\n * plugins' erasers from `instrumentation.ts`, which Next compiles apart from\n * the route that runs the erasure, and a module-scoped list is filled in one\n * copy and read empty in the other — the AGL-3412 shape.\n * Here a required share registered in the other copy reads as missing, and\n * every erasure refuses.\n */\nconst ERASERS_KEY = Symbol.for('@aglyn/aglyn:plugin-person-erasers')\n\nconst globalScope = globalThis as typeof globalThis & {\n [ERASERS_KEY]?: PersonErasers\n}\n\nconst erasers: PersonErasers =\n globalScope[ERASERS_KEY] ?? (globalScope[ERASERS_KEY] = { registrations: [], records: null })\nconst registrations = erasers.registrations\n\nfunction ownerOf(options: { pluginId?: string } | undefined, what: string): string {\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n if (!pluginId) {\n throw new Error(\n `a plugin ${what} was registered with no owner: pass { pluginId } ` +\n 'when registering outside a plugin register fn',\n )\n }\n return pluginId\n}\n\n/**\n * Registers a plugin's person 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 registerPluginPersonEraser(\n eraser: PluginPersonEraser,\n options?: { pluginId?: string },\n): void {\n const pluginId = ownerOf(options, 'person eraser')\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 * Registers the records eraser of the plugin that keeps people. One per\n * process: the same plugin registering again replaces its own, and a second\n * plugin is refused naming both — a workspace keeps one set of people.\n */\nexport function registerPluginPersonRecordsEraser(\n eraser: PluginPersonRecordsEraser,\n options?: { pluginId?: string },\n): void {\n const pluginId = ownerOf(options, 'person records eraser')\n if (erasers.records && erasers.records.pluginId !== pluginId) {\n throw new Error(\n `the person records eraser is \"${erasers.records.pluginId}\"'s; refused one from \"${pluginId}\"`,\n )\n }\n erasers.records = { pluginId, eraser }\n}\n\n/**\n * The required erasers not registered in this process, in `required` order —\n * empty when the erasure may run. The erasure asks before it writes anything.\n */\nexport function missingRequiredPersonErasers(\n required: readonly string[] = PLUGIN_REQUIRED_PERSON_ERASERS,\n): string[] {\n const registered = new Set(registrations.map((entry) => entry.pluginId))\n if (erasers.records) registered.add(erasers.records.pluginId)\n return required.filter((pluginId) => !registered.has(pluginId))\n}\n\n/**\n * {@link missingRequiredPersonErasers}, after running the app's boot step\n * once when any is missing (AGL-3464): a boot whose declarations failed looks\n * the same from here as one that never declared the eraser, and only the\n * second is a reason to refuse. What the erasure asks before it writes\n * anything.\n */\nexport async function missingRequiredPersonErasersAfterRepair(\n required: readonly string[] = PLUGIN_REQUIRED_PERSON_ERASERS,\n): Promise<string[]> {\n if (!missingRequiredPersonErasers(required).length) return []\n await runPluginDeclarationsRepair().catch((error: unknown) => {\n console.error('[plugins] the declarations repair failed before a person erasure', error)\n })\n return missingRequiredPersonErasers(required)\n}\n\n/** What one erasure's plugins did. */\nexport interface PluginPersonErasureOutcome {\n /** The records `locate` named, handed to every eraser. */\n contactIds: string[]\n /** Each plugin's report by plugin id, or `null` for an eraser that threw. */\n reports: Record<string, PluginPersonErasureReport | null>\n}\n\n/**\n * Runs the plugins' shares of one person erasure: the records eraser's\n * `locate`, every other eraser in registration order, then the records\n * eraser's `erase`. See the module note.\n *\n * Throws in three cases, each about a share the erasure promises (`required`,\n * the compiled {@link PLUGIN_REQUIRED_PERSON_ERASERS} unless a spec passes its\n * own): before running anything, when a required eraser is not registered\n * even after the app's boot step has run once more, or the records eraser\n * cannot `locate`; and after running every eraser, when a required one\n * threw. Otherwise never.\n */\nexport async function runPluginPersonErasure(\n target: PluginPersonErasureTarget,\n required: readonly string[] = PLUGIN_REQUIRED_PERSON_ERASERS,\n): Promise<PluginPersonErasureOutcome> {\n const missing = await missingRequiredPersonErasersAfterRepair(required)\n if (missing.length) {\n throw new Error(\n `[plugins] erasing a person in org ${target.orgId} refused: ${missing.join(', ')} ` +\n 'declared a required person eraser and none is registered in this process',\n )\n }\n const base: PluginPersonErasureTarget = {\n orgId: target.orgId,\n email: target.email,\n key: target.key,\n dryRun: target.dryRun,\n atMs: target.atMs,\n }\n const records = erasers.records\n const contactIds = records ? [...(await records.eraser.locate({ ...base }))] : []\n const reports: Record<string, PluginPersonErasureReport | null> = {}\n const failedRequired: string[] = []\n const run = async (pluginId: string, eraser: PluginPersonEraser) => {\n try {\n reports[pluginId] = await eraser({ ...base, contactIds: [...contactIds] })\n } catch (error) {\n reports[pluginId] = null\n // The org, never the address: this line outlives the erasure.\n console.error(`[plugins] ${pluginId} failed to erase a person in org ${target.orgId}`, error)\n if (required.includes(pluginId)) failedRequired.push(pluginId)\n }\n }\n const owner = records\n for (const { pluginId, eraser } of [...registrations]) {\n if (pluginId === owner?.pluginId) continue\n await run(pluginId, eraser)\n }\n if (owner) await run(owner.pluginId, owner.eraser.erase)\n if (failedRequired.length) {\n throw new Error(\n `[plugins] erasing a person in org ${target.orgId} incomplete: the required eraser of ` +\n `${failedRequired.join(', ')} failed`,\n )\n }\n return { contactIds, reports }\n}\n\n/** The plugins with a person eraser, in the order they run: the records eraser last. */\nexport function listPluginPersonErasers(): string[] {\n const owner = erasers.records?.pluginId\n return [\n ...registrations.map((entry) => entry.pluginId).filter((pluginId) => pluginId !== owner),\n ...(owner ? [owner] : []),\n ]\n}\n\n/** Test seam: forget every eraser. */\nexport function resetPluginPersonErasersForTests(): void {\n registrations.length = 0\n erasers.records = null\n}\n\n/**\n * Test seam: a no-op eraser for every REQUIRED plugin that has none, so a spec\n * about the erasure's own sweeps can run it without loading a plugin (core\n * and the data layer may not import one). A spec about a plugin's share\n * registers the real one through the app's manifest instead.\n */\nexport function standInRequiredPersonErasersForTests(): void {\n const registered = new Set(registrations.map((entry) => entry.pluginId))\n if (erasers.records) registered.add(erasers.records.pluginId)\n for (const pluginId of PLUGIN_REQUIRED_PERSON_ERASERS) {\n if (!registered.has(pluginId)) {\n registrations.push({ pluginId, eraser: async () => ({ standIn: true }) })\n }\n }\n}\n"],"names":["globalScope","getRegisteringPluginId","PLUGIN_REQUIRED_PERSON_ERASERS","runPluginDeclarationsRepair","ERASERS_KEY","Symbol","for","globalThis","erasers","registrations","records","ownerOf","options","what","pluginId","trim","Error","registerPluginPersonEraser","eraser","index","findIndex","entry","push","registerPluginPersonRecordsEraser","missingRequiredPersonErasers","required","registered","Set","map","add","filter","has","missingRequiredPersonErasersAfterRepair","length","catch","error","console","runPluginPersonErasure","target","missing","orgId","join","base","email","key","dryRun","atMs","contactIds","locate","reports","failedRequired","run","includes","owner","erase","listPluginPersonErasers","resetPluginPersonErasersForTests","standInRequiredPersonErasersForTests","standIn"],"mappings":"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"}