@aglyn/aglyn 1.0.0-beta.217 → 1.0.0-beta.219

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 (348) hide show
  1. package/package.json +14 -14
  2. package/src/lib/app-utils/actions.d.ts +25 -374
  3. package/src/lib/app-utils/actions.js +101 -561
  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/admin-audit-index.js +2 -2
  9. package/src/lib/app-utils/admin-audit-index.js.map +1 -1
  10. package/src/lib/app-utils/analytics-events.d.ts +11 -2
  11. package/src/lib/app-utils/analytics-events.js +11 -2
  12. package/src/lib/app-utils/analytics-events.js.map +1 -1
  13. package/src/lib/app-utils/analytics-provider.d.ts +182 -0
  14. package/src/lib/app-utils/analytics-provider.js +133 -0
  15. package/src/lib/app-utils/analytics-provider.js.map +1 -0
  16. package/src/lib/app-utils/api-plugins.d.ts +46 -0
  17. package/src/lib/app-utils/api-plugins.js +19 -0
  18. package/src/lib/app-utils/api-plugins.js.map +1 -1
  19. package/src/lib/app-utils/{marketplace-overrides.d.ts → artifact-overrides.d.ts} +9 -6
  20. package/src/lib/app-utils/{marketplace-overrides.js → artifact-overrides.js} +10 -7
  21. package/src/lib/app-utils/artifact-overrides.js.map +1 -0
  22. package/src/lib/app-utils/{marketplace-provenance.d.ts → artifact-provenance.d.ts} +12 -10
  23. package/src/lib/app-utils/{marketplace-provenance.js → artifact-provenance.js} +9 -7
  24. package/src/lib/app-utils/artifact-provenance.js.map +1 -0
  25. package/src/lib/app-utils/assist-credits.js +2 -1
  26. package/src/lib/app-utils/assist-credits.js.map +1 -1
  27. package/src/lib/app-utils/binding-token-catalog.d.ts +5 -13
  28. package/src/lib/app-utils/binding-token-catalog.js +17 -41
  29. package/src/lib/app-utils/binding-token-catalog.js.map +1 -1
  30. package/src/lib/app-utils/binding-tokens.js +1 -1
  31. package/src/lib/app-utils/binding-tokens.js.map +1 -1
  32. package/src/lib/app-utils/brand-support-line.d.ts +36 -0
  33. package/src/lib/app-utils/brand-support-line.js +38 -0
  34. package/src/lib/app-utils/brand-support-line.js.map +1 -0
  35. package/src/lib/app-utils/campaign-touch.d.ts +5 -5
  36. package/src/lib/app-utils/campaign-touch.js +8 -8
  37. package/src/lib/app-utils/campaign-touch.js.map +1 -1
  38. package/src/lib/app-utils/collection-entries.d.ts +2 -2
  39. package/src/lib/app-utils/collection-entries.js.map +1 -1
  40. package/src/lib/app-utils/consent-group-change.d.ts +4 -0
  41. package/src/lib/app-utils/consent-group-change.js.map +1 -1
  42. package/src/lib/app-utils/console-record-links.d.ts +10 -89
  43. package/src/lib/app-utils/console-record-links.js +3 -107
  44. package/src/lib/app-utils/console-record-links.js.map +1 -1
  45. package/src/lib/app-utils/console-routes.d.ts +16 -7
  46. package/src/lib/app-utils/console-routes.js +9 -3
  47. package/src/lib/app-utils/console-routes.js.map +1 -1
  48. package/src/lib/app-utils/contacts.d.ts +23 -1
  49. package/src/lib/app-utils/contacts.js +21 -5
  50. package/src/lib/app-utils/contacts.js.map +1 -1
  51. package/src/lib/app-utils/cross-pool-desync.d.ts +39 -0
  52. package/src/lib/app-utils/cross-pool-desync.js +47 -0
  53. package/src/lib/app-utils/cross-pool-desync.js.map +1 -0
  54. package/src/lib/app-utils/csv-upload.d.ts +29 -0
  55. package/src/lib/app-utils/csv-upload.js +29 -0
  56. package/src/lib/app-utils/csv-upload.js.map +1 -0
  57. package/src/lib/app-utils/dataset-models.d.ts +5 -5
  58. package/src/lib/app-utils/dataset-models.js +19 -2
  59. package/src/lib/app-utils/dataset-models.js.map +1 -1
  60. package/src/lib/app-utils/datasets.d.ts +65 -11
  61. package/src/lib/app-utils/datasets.js +74 -11
  62. package/src/lib/app-utils/datasets.js.map +1 -1
  63. package/src/lib/app-utils/docs-deflection.d.ts +6 -6
  64. package/src/lib/app-utils/docs-deflection.js.map +1 -1
  65. package/src/lib/app-utils/docs-help.generated.d.ts +12 -6
  66. package/src/lib/app-utils/docs-help.generated.js +21 -6
  67. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  68. package/src/lib/app-utils/docs-index.generated.d.ts +2 -2
  69. package/src/lib/app-utils/docs-index.generated.js +514 -318
  70. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  71. package/src/lib/app-utils/docs-retrieval.d.ts +3 -3
  72. package/src/lib/app-utils/docs-retrieval.js +14 -10
  73. package/src/lib/app-utils/docs-retrieval.js.map +1 -1
  74. package/src/lib/app-utils/duplicate-resource.d.ts +4 -1
  75. package/src/lib/app-utils/duplicate-resource.js +7 -4
  76. package/src/lib/app-utils/duplicate-resource.js.map +1 -1
  77. package/src/lib/app-utils/dynamic-list-rule.d.ts +1 -1
  78. package/src/lib/app-utils/dynamic-list-rule.js.map +1 -1
  79. package/src/lib/app-utils/element-ui.d.ts +1 -1
  80. package/src/lib/app-utils/element-ui.js +1 -1
  81. package/src/lib/app-utils/element-ui.js.map +1 -1
  82. package/src/lib/app-utils/email-starter-policy.js +3 -3
  83. package/src/lib/app-utils/email-starter-policy.js.map +1 -1
  84. package/src/lib/app-utils/entity-picker-context.d.ts +10 -5
  85. package/src/lib/app-utils/entity-picker-context.js.map +1 -1
  86. package/src/lib/app-utils/error-beacon.d.ts +75 -6
  87. package/src/lib/app-utils/error-beacon.js +160 -21
  88. package/src/lib/app-utils/error-beacon.js.map +1 -1
  89. package/src/lib/app-utils/expand-repeatables.d.ts +10 -10
  90. package/src/lib/app-utils/expand-repeatables.js +10 -10
  91. package/src/lib/app-utils/expand-repeatables.js.map +1 -1
  92. package/src/lib/app-utils/host-events.d.ts +82 -0
  93. package/src/lib/app-utils/host-events.js +62 -0
  94. package/src/lib/app-utils/host-events.js.map +1 -0
  95. package/src/lib/app-utils/lockdown.d.ts +33 -1
  96. package/src/lib/app-utils/lockdown.js +45 -4
  97. package/src/lib/app-utils/lockdown.js.map +1 -1
  98. package/src/lib/app-utils/margin-utilization.d.ts +4 -69
  99. package/src/lib/app-utils/margin-utilization.js +82 -69
  100. package/src/lib/app-utils/margin-utilization.js.map +1 -1
  101. package/src/lib/app-utils/node-definition-sanitizer.d.ts +16 -16
  102. package/src/lib/app-utils/node-definition-sanitizer.js +24 -20
  103. package/src/lib/app-utils/node-definition-sanitizer.js.map +1 -1
  104. package/src/lib/app-utils/node-interactions.d.ts +7 -7
  105. package/src/lib/app-utils/node-interactions.js.map +1 -1
  106. package/src/lib/app-utils/notifications.d.ts +78 -47
  107. package/src/lib/app-utils/notifications.js +63 -44
  108. package/src/lib/app-utils/notifications.js.map +1 -1
  109. package/src/lib/app-utils/payments-configured.d.ts +8 -21
  110. package/src/lib/app-utils/payments-configured.js +8 -23
  111. package/src/lib/app-utils/payments-configured.js.map +1 -1
  112. package/src/lib/app-utils/plan-entitlements.d.ts +43 -128
  113. package/src/lib/app-utils/plan-entitlements.js +145 -304
  114. package/src/lib/app-utils/plan-entitlements.js.map +1 -1
  115. package/src/lib/app-utils/platform-brand.d.ts +10 -0
  116. package/src/lib/app-utils/platform-brand.js +14 -3
  117. package/src/lib/app-utils/platform-brand.js.map +1 -1
  118. package/src/lib/app-utils/plugin-api-rate-limit.d.ts +7 -1
  119. package/src/lib/app-utils/plugin-api-rate-limit.js +11 -3
  120. package/src/lib/app-utils/plugin-api-rate-limit.js.map +1 -1
  121. package/src/lib/app-utils/plugin-bundle-checks.js +2 -2
  122. package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -1
  123. package/src/lib/app-utils/plugin-host-events.generated.d.ts +12 -0
  124. package/src/lib/app-utils/plugin-host-events.generated.js +169 -0
  125. package/src/lib/app-utils/plugin-host-events.generated.js.map +1 -0
  126. package/src/lib/app-utils/plugin-release-flags.generated.d.ts +12 -0
  127. package/src/lib/app-utils/plugin-release-flags.generated.js +106 -0
  128. package/src/lib/app-utils/plugin-release-flags.generated.js.map +1 -0
  129. package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
  130. package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
  131. package/src/lib/app-utils/release-flags.d.ts +14 -5
  132. package/src/lib/app-utils/release-flags.js +21 -125
  133. package/src/lib/app-utils/release-flags.js.map +1 -1
  134. package/src/lib/app-utils/repeat-sources.d.ts +9 -0
  135. package/src/lib/app-utils/repeat-sources.js.map +1 -1
  136. package/src/lib/app-utils/retained-refusals.d.ts +73 -0
  137. package/src/lib/app-utils/retained-refusals.js +186 -0
  138. package/src/lib/app-utils/retained-refusals.js.map +1 -0
  139. package/src/lib/app-utils/scope-tokens.d.ts +27 -0
  140. package/src/lib/app-utils/scope-tokens.js +61 -0
  141. package/src/lib/app-utils/scope-tokens.js.map +1 -1
  142. package/src/lib/app-utils/screen-link-context.js +3 -3
  143. package/src/lib/app-utils/screen-link-context.js.map +1 -1
  144. package/src/lib/app-utils/screen-route.d.ts +15 -0
  145. package/src/lib/app-utils/screen-route.js +21 -1
  146. package/src/lib/app-utils/screen-route.js.map +1 -1
  147. package/src/lib/app-utils/seo-audit.d.ts +132 -0
  148. package/src/lib/app-utils/seo-audit.js +246 -0
  149. package/src/lib/app-utils/seo-audit.js.map +1 -0
  150. package/src/lib/app-utils/seo-keywords.d.ts +48 -0
  151. package/src/lib/app-utils/seo-keywords.js +60 -0
  152. package/src/lib/app-utils/seo-keywords.js.map +1 -0
  153. package/src/lib/app-utils/seo-page-facts.d.ts +66 -0
  154. package/src/lib/app-utils/seo-page-facts.js +155 -0
  155. package/src/lib/app-utils/seo-page-facts.js.map +1 -0
  156. package/src/lib/app-utils/seo-site-scan.d.ts +126 -0
  157. package/src/lib/app-utils/seo-site-scan.js +162 -0
  158. package/src/lib/app-utils/seo-site-scan.js.map +1 -0
  159. package/src/lib/app-utils/seo-title-variables.js +1 -1
  160. package/src/lib/app-utils/seo-title-variables.js.map +1 -1
  161. package/src/lib/app-utils/server.d.ts +6 -9
  162. package/src/lib/app-utils/server.js +17 -27
  163. package/src/lib/app-utils/server.js.map +1 -1
  164. package/src/lib/app-utils/site-interactions.d.ts +501 -0
  165. package/src/lib/app-utils/site-interactions.js +554 -0
  166. package/src/lib/app-utils/site-interactions.js.map +1 -0
  167. package/src/lib/app-utils/{marketplace-theme.d.ts → site-theme.d.ts} +13 -2
  168. package/src/lib/app-utils/{marketplace-theme.js → site-theme.js} +5 -2
  169. package/src/lib/app-utils/site-theme.js.map +1 -0
  170. package/src/lib/app-utils/sitemap.d.ts +4 -3
  171. package/src/lib/app-utils/sitemap.js +9 -7
  172. package/src/lib/app-utils/sitemap.js.map +1 -1
  173. package/src/lib/app-utils/stale-build-error.d.ts +51 -0
  174. package/src/lib/app-utils/stale-build-error.js +100 -1
  175. package/src/lib/app-utils/stale-build-error.js.map +1 -1
  176. package/src/lib/app-utils/starter-templates.d.ts +20 -0
  177. package/src/lib/app-utils/starter-templates.js +49 -0
  178. package/src/lib/app-utils/starter-templates.js.map +1 -1
  179. package/src/lib/app-utils/tax-jurisdiction-figures.d.ts +118 -0
  180. package/src/lib/app-utils/tax-jurisdiction-figures.js +123 -0
  181. package/src/lib/app-utils/tax-jurisdiction-figures.js.map +1 -0
  182. package/src/lib/app-utils/theme-library.d.ts +10 -3
  183. package/src/lib/app-utils/theme-library.js +13 -4
  184. package/src/lib/app-utils/theme-library.js.map +1 -1
  185. package/src/lib/app-utils/{campaign-attribution.d.ts → utm-attribution.d.ts} +11 -6
  186. package/src/lib/app-utils/{campaign-attribution.js → utm-attribution.js} +13 -8
  187. package/src/lib/app-utils/utm-attribution.js.map +1 -0
  188. package/src/lib/app-utils/{campaign-forwarding.d.ts → utm-forwarding.d.ts} +14 -14
  189. package/src/lib/app-utils/{campaign-forwarding.js → utm-forwarding.js} +26 -26
  190. package/src/lib/app-utils/utm-forwarding.js.map +1 -0
  191. package/src/lib/app-utils/video-object.js +6 -5
  192. package/src/lib/app-utils/video-object.js.map +1 -1
  193. package/src/lib/app-utils/visitor-consent.d.ts +41 -164
  194. package/src/lib/app-utils/visitor-consent.js +66 -277
  195. package/src/lib/app-utils/visitor-consent.js.map +1 -1
  196. package/src/lib/app-utils/web-vitals-rum.d.ts +7 -7
  197. package/src/lib/app-utils/web-vitals-rum.js +31 -63
  198. package/src/lib/app-utils/web-vitals-rum.js.map +1 -1
  199. package/src/lib/app-utils/where-used.d.ts +11 -6
  200. package/src/lib/app-utils/where-used.js +3 -8
  201. package/src/lib/app-utils/where-used.js.map +1 -1
  202. package/src/lib/canvas-manager/canvas-manager.js +3 -3
  203. package/src/lib/canvas-manager/canvas-manager.js.map +1 -1
  204. package/src/lib/foundation/definitions/components.types.d.ts +1 -1
  205. package/src/lib/foundation/definitions/components.types.js +1 -1
  206. package/src/lib/foundation/definitions/components.types.js.map +1 -1
  207. package/src/lib/foundation/definitions/org-billing.types.d.ts +18 -135
  208. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
  209. package/src/lib/foundation/definitions/organization.types.d.ts +6 -3
  210. package/src/lib/foundation/definitions/organization.types.js.map +1 -1
  211. package/src/lib/foundation/definitions/platform.types.d.ts +27 -5
  212. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  213. package/src/lib/foundation/definitions/write-deny-coverage.util.d.ts +16 -0
  214. package/src/lib/foundation/definitions/write-deny-coverage.util.js +20 -0
  215. package/src/lib/foundation/definitions/write-deny-coverage.util.js.map +1 -1
  216. package/src/lib/foundation/index.d.ts +1 -1
  217. package/src/lib/foundation/index.js.map +1 -1
  218. package/src/lib/plugin-manager/besigner-documents.d.ts +96 -0
  219. package/src/lib/plugin-manager/besigner-documents.js +61 -0
  220. package/src/lib/plugin-manager/besigner-documents.js.map +1 -0
  221. package/src/lib/plugin-manager/computed-variables.d.ts +71 -0
  222. package/src/lib/plugin-manager/computed-variables.js +74 -0
  223. package/src/lib/plugin-manager/computed-variables.js.map +1 -0
  224. package/src/lib/plugin-manager/custom-fields.d.ts +29 -2
  225. package/src/lib/plugin-manager/custom-fields.js +47 -1
  226. package/src/lib/plugin-manager/custom-fields.js.map +1 -1
  227. package/src/lib/plugin-manager/feature-plugins.d.ts +292 -20
  228. package/src/lib/plugin-manager/feature-plugins.js +105 -17
  229. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  230. package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +123 -0
  231. package/src/lib/plugin-manager/first-party-plugins.generated.js +785 -14
  232. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  233. package/src/lib/plugin-manager/plugin-activity-actions.d.ts +12 -0
  234. package/src/lib/plugin-manager/plugin-activity-actions.js +25 -0
  235. package/src/lib/plugin-manager/plugin-activity-actions.js.map +1 -1
  236. package/src/lib/plugin-manager/plugin-contributions.d.ts +1 -1
  237. package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
  238. package/src/lib/plugin-manager/plugin-declarations-repair.d.ts +36 -0
  239. package/src/lib/plugin-manager/plugin-declarations-repair.js +64 -0
  240. package/src/lib/plugin-manager/plugin-declarations-repair.js.map +1 -0
  241. package/src/lib/plugin-manager/plugin-dependents.d.ts +101 -0
  242. package/src/lib/plugin-manager/plugin-dependents.js +75 -0
  243. package/src/lib/plugin-manager/plugin-dependents.js.map +1 -0
  244. package/src/lib/plugin-manager/plugin-distribution.d.ts +55 -0
  245. package/src/lib/plugin-manager/plugin-distribution.js +45 -0
  246. package/src/lib/plugin-manager/plugin-distribution.js.map +1 -0
  247. package/src/lib/plugin-manager/plugin-host-collections.d.ts +17 -0
  248. package/src/lib/plugin-manager/plugin-host-collections.js +6 -0
  249. package/src/lib/plugin-manager/plugin-host-collections.js.map +1 -1
  250. package/src/lib/plugin-manager/plugin-host-resources.d.ts +133 -0
  251. package/src/lib/plugin-manager/plugin-host-resources.js +52 -0
  252. package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -0
  253. package/src/lib/plugin-manager/plugin-org-erasure.d.ts +28 -2
  254. package/src/lib/plugin-manager/plugin-org-erasure.js +61 -2
  255. package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
  256. package/src/lib/plugin-manager/plugin-plan-entitlements.d.ts +123 -0
  257. package/src/lib/plugin-manager/plugin-plan-entitlements.js +101 -0
  258. package/src/lib/plugin-manager/plugin-plan-entitlements.js.map +1 -0
  259. package/src/lib/plugin-manager/plugin-record-index.d.ts +95 -0
  260. package/src/lib/plugin-manager/plugin-record-index.js +51 -0
  261. package/src/lib/plugin-manager/plugin-record-index.js.map +1 -0
  262. package/src/lib/plugin-manager/plugin-record-written.d.ts +58 -0
  263. package/src/lib/plugin-manager/plugin-record-written.js +42 -0
  264. package/src/lib/plugin-manager/plugin-record-written.js.map +1 -0
  265. package/src/lib/plugin-manager/plugin-revenue-sources.d.ts +213 -0
  266. package/src/lib/plugin-manager/plugin-revenue-sources.js +194 -0
  267. package/src/lib/plugin-manager/plugin-revenue-sources.js.map +1 -0
  268. package/src/lib/plugin-manager/plugin-site-bundle.d.ts +145 -0
  269. package/src/lib/plugin-manager/plugin-site-bundle.js +78 -0
  270. package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -0
  271. package/src/lib/plugin-manager/plugin-site-cache.d.ts +12 -0
  272. package/src/lib/plugin-manager/plugin-site-cache.js.map +1 -1
  273. package/src/lib/plugin-manager/plugin-site-export.d.ts +81 -0
  274. package/src/lib/plugin-manager/plugin-site-export.js +40 -0
  275. package/src/lib/plugin-manager/plugin-site-export.js.map +1 -0
  276. package/src/lib/plugin-manager/plugin-sitemap-sections.d.ts +88 -0
  277. package/src/lib/plugin-manager/plugin-sitemap-sections.js +29 -0
  278. package/src/lib/plugin-manager/plugin-sitemap-sections.js.map +1 -0
  279. package/src/lib/plugin-manager/plugin-tax-profile.d.ts +20 -4
  280. package/src/lib/plugin-manager/plugin-tax-profile.js.map +1 -1
  281. package/src/lib/plugin-manager/plugin-tax-return-sources.d.ts +255 -0
  282. package/src/lib/plugin-manager/plugin-tax-return-sources.js +191 -0
  283. package/src/lib/plugin-manager/plugin-tax-return-sources.js.map +1 -0
  284. package/src/lib/plugin-manager/plugin-template-sources.d.ts +75 -0
  285. package/src/lib/plugin-manager/plugin-template-sources.js +37 -0
  286. package/src/lib/plugin-manager/plugin-template-sources.js.map +1 -0
  287. package/src/lib/plugin-manager/plugin-usage-axes.d.ts +107 -0
  288. package/src/lib/plugin-manager/plugin-usage-axes.js +107 -0
  289. package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -0
  290. package/src/lib/plugin-manager/plugin-zones.d.ts +4 -5
  291. package/src/lib/plugin-manager/plugin-zones.js.map +1 -1
  292. package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
  293. package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
  294. package/src/lib/plugin-manager/record-captured-contact.d.ts +1 -14
  295. package/src/lib/plugin-manager/record-captured-contact.js +10 -32
  296. package/src/lib/plugin-manager/record-captured-contact.js.map +1 -1
  297. package/src/lib/plugin-manager/repeat-rows.d.ts +124 -0
  298. package/src/lib/plugin-manager/repeat-rows.js +108 -0
  299. package/src/lib/plugin-manager/repeat-rows.js.map +1 -0
  300. package/src/lib/plugin-manager/submission-record-target.d.ts +144 -0
  301. package/src/lib/plugin-manager/submission-record-target.js +125 -0
  302. package/src/lib/plugin-manager/submission-record-target.js.map +1 -0
  303. package/src/lib/plugin-manager/video-embed-provider.d.ts +108 -0
  304. package/src/lib/plugin-manager/video-embed-provider.js +165 -0
  305. package/src/lib/plugin-manager/video-embed-provider.js.map +1 -0
  306. package/src/lib/types/nodes.d.ts +2 -2
  307. package/src/lib/types/nodes.js.map +1 -1
  308. package/src/lib/app-utils/campaign-attribution.js.map +0 -1
  309. package/src/lib/app-utils/campaign-forwarding.js.map +0 -1
  310. package/src/lib/app-utils/contact-holder.d.ts +0 -68
  311. package/src/lib/app-utils/contact-holder.js +0 -129
  312. package/src/lib/app-utils/contact-holder.js.map +0 -1
  313. package/src/lib/app-utils/contact-merge.d.ts +0 -136
  314. package/src/lib/app-utils/contact-merge.js +0 -412
  315. package/src/lib/app-utils/contact-merge.js.map +0 -1
  316. package/src/lib/app-utils/crm-csv.d.ts +0 -239
  317. package/src/lib/app-utils/crm-csv.js +0 -467
  318. package/src/lib/app-utils/crm-csv.js.map +0 -1
  319. package/src/lib/app-utils/crm-digest.d.ts +0 -185
  320. package/src/lib/app-utils/crm-digest.js +0 -272
  321. package/src/lib/app-utils/crm-digest.js.map +0 -1
  322. package/src/lib/app-utils/form-dataset-binding.d.ts +0 -50
  323. package/src/lib/app-utils/form-dataset-binding.js +0 -74
  324. package/src/lib/app-utils/form-dataset-binding.js.map +0 -1
  325. package/src/lib/app-utils/list-import.d.ts +0 -158
  326. package/src/lib/app-utils/list-import.js +0 -332
  327. package/src/lib/app-utils/list-import.js.map +0 -1
  328. package/src/lib/app-utils/marketplace-listing-visibility.d.ts +0 -122
  329. package/src/lib/app-utils/marketplace-listing-visibility.js +0 -147
  330. package/src/lib/app-utils/marketplace-listing-visibility.js.map +0 -1
  331. package/src/lib/app-utils/marketplace-overrides.js.map +0 -1
  332. package/src/lib/app-utils/marketplace-provenance.js.map +0 -1
  333. package/src/lib/app-utils/marketplace-theme.js.map +0 -1
  334. package/src/lib/app-utils/marketplace-update-state.d.ts +0 -119
  335. package/src/lib/app-utils/marketplace-update-state.js +0 -132
  336. package/src/lib/app-utils/marketplace-update-state.js.map +0 -1
  337. package/src/lib/app-utils/publisher-agreement.d.ts +0 -289
  338. package/src/lib/app-utils/publisher-agreement.js +0 -321
  339. package/src/lib/app-utils/publisher-agreement.js.map +0 -1
  340. package/src/lib/app-utils/wistia-embed.d.ts +0 -104
  341. package/src/lib/app-utils/wistia-embed.js +0 -138
  342. package/src/lib/app-utils/wistia-embed.js.map +0 -1
  343. package/src/lib/app-utils/workflows.d.ts +0 -136
  344. package/src/lib/app-utils/workflows.js +0 -346
  345. package/src/lib/app-utils/workflows.js.map +0 -1
  346. package/src/lib/plugin-manager/commerce-zone-props.d.ts +0 -172
  347. package/src/lib/plugin-manager/commerce-zone-props.js +0 -31
  348. package/src/lib/plugin-manager/commerce-zone-props.js.map +0 -1
@@ -0,0 +1,133 @@
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 host collection as a KIND the platform's generic create route
19
+ * writes (AGL-3080).
20
+ *
21
+ * `/api/hosts/resources` is the only door a client creates quota-governed
22
+ * site documents through: the rules deny client `create` on each of these
23
+ * collections, and the route is the allow-list of what a client may write
24
+ * (AGL-1377), the count it is met against (AGL-473, AGL-2231) and the
25
+ * provenance it stamps (AGL-118). `duplicateResource` is its twin for a copy.
26
+ * Both used to hold every plugin's row — which collection a workflow lives
27
+ * in, which entitlement a product needs, which fields a redirect may carry —
28
+ * so the platform was the keeper of its plugins' document models, and a
29
+ * plugin could not add a kind without editing core.
30
+ *
31
+ * So the plugin that owns a collection declares the kind it is created as,
32
+ * beside the collection itself in the `hostCollections` block of
33
+ * `plugins.config.json` (a host collection's `resource`),
34
+ * and both readers ask this module.
35
+ *
36
+ * ## Compiled, and refused when absent
37
+ *
38
+ * The route is a SECURITY path: its field list is what stands between a
39
+ * client and a document the UI later presents as trustworthy. A runtime
40
+ * registry that one process had not filled would either refuse a legitimate
41
+ * create or — worse, if a reader ever fell back to an open list — store
42
+ * whatever it was sent. So these rows are compiled by
43
+ * `tools/scripts/generate-plugin-manifests.mjs`, present the moment this
44
+ * module is imported, and a kind nobody declares is REFUSED as an unknown
45
+ * resource. There is no registrar: `registerPluginHostCollections` refuses a
46
+ * declaration that carries a `resource`.
47
+ *
48
+ * The generator checks what a reader would otherwise have to trust: one owner
49
+ * per kind and none of core's own; a count on every kind (a plan `quotaKey` or
50
+ * a `platformCap`); no server-stamped field, stamp or approver field on the
51
+ * client's list; `externalDestination` only with the publishing role; and a
52
+ * copy's fields a subset of the create's.
53
+ *
54
+ * ## What core keeps
55
+ *
56
+ * The NUMBERS. A declaration names its plan counter and feature flag as keys
57
+ * (`quotaKey`, `entitlement`) and core's plan table resolves them, exactly as
58
+ * it does for its own kinds. A flat platform cap is named the same way —
59
+ * `platformCap` is the name of a constant core holds, resolved by
60
+ * {@link hostResourcePlatformCap} — so no plugin carries a number that is the
61
+ * platform's abuse ceiling. Core also keeps everything a create does for
62
+ * EVERY kind: the role gate, the lockdown verdict, the transaction that counts
63
+ * and writes, the node codec, the timestamps and `createdBy`.
64
+ *
65
+ * Reached by its own subpath, never the barrel: its readers are server routes.
66
+ */
67
+ /** A constant value a create writes: never the client's, never another stamp's. */
68
+ export type PluginHostResourceStampValue = string | number | boolean | null;
69
+ /** How a whole copy of the kind is made, when the kind can be duplicated. */
70
+ export interface PluginHostResourceDuplicate {
71
+ /** The field the name lives in; the copy's is made unique among the live siblings. */
72
+ nameField: string;
73
+ /** The source fields carried onto the copy — a subset of the create's. */
74
+ fields: readonly string[];
75
+ /** What the copy carries beyond the source's fields: a cleared trigger. */
76
+ stamps?: Readonly<Record<string, PluginHostResourceStampValue>>;
77
+ }
78
+ export interface PluginHostResourceDeclaration {
79
+ /** The `resource` a caller names, in camelCase: `product`, `workflow`. */
80
+ kind: string;
81
+ /** Plural, for quota copy: "this site can run 5 {label}". */
82
+ label: string;
83
+ /** Singular, for the site activity line: "Created {activityNoun}". */
84
+ activityNoun: string;
85
+ /** The activity target type a row filters and deep-links by; `content` when absent. */
86
+ activityType?: string;
87
+ /** The plan counter, an `OrgEntitlements` key core resolves. */
88
+ quotaKey?: string;
89
+ /** The plan feature the kind needs at all, an `OrgFeatureFlags` key. */
90
+ entitlement?: string;
91
+ /** A flat platform cap by the name of core's constant; see {@link hostResourcePlatformCap}. */
92
+ platformCap?: string;
93
+ /** Deleting stamps `deletedAt`, so the cap counts live documents only. */
94
+ softDeletes?: boolean;
95
+ /** Creating it needs the publishing role rather than the write role. */
96
+ requiresPublishRole?: boolean;
97
+ /** The keys a client may set. Anything else it sends is dropped. */
98
+ fields: readonly string[];
99
+ /** Constant values every create writes, e.g. `deletedAt: null` for a kind born live. */
100
+ stamps?: Readonly<Record<string, PluginHostResourceStampValue>>;
101
+ /**
102
+ * A destination field that may point off the platform. When the value is
103
+ * not a site path, the creating publisher's VERIFIED uid is stamped into
104
+ * `approvedByField` — the evidence a serve path trusts that somebody chose
105
+ * to send traffic away. Only with `requiresPublishRole`.
106
+ */
107
+ externalDestination?: {
108
+ field: string;
109
+ approvedByField: string;
110
+ };
111
+ /**
112
+ * The field holding the site path the document answers at the moment it
113
+ * exists. A create announces it to the site cache, because what used to
114
+ * answer there is already cached as the page.
115
+ */
116
+ livePathField?: string;
117
+ duplicate?: PluginHostResourceDuplicate;
118
+ }
119
+ /** A declaration with the plugin that made it and the collection it writes. */
120
+ export type ResolvedPluginHostResource = PluginHostResourceDeclaration & {
121
+ pluginId: string;
122
+ /** The subcollection under `hosts/{hostId}` the kind is created in. */
123
+ collection: string;
124
+ };
125
+ /** Every declared kind, in config order. */
126
+ export declare function listPluginHostResources(): ResolvedPluginHostResource[];
127
+ /** One declared kind, or `null` — which every reader treats as unknown. */
128
+ export declare function pluginHostResource(kind: unknown): ResolvedPluginHostResource | null;
129
+ /**
130
+ * The number a `platformCap` names, or `null` for a name core does not hold —
131
+ * which the create route answers by refusing, never by leaving it uncapped.
132
+ */
133
+ export declare function hostResourcePlatformCap(name: string): number | null;
@@ -0,0 +1,52 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { ACTIONS_MAX_PER_HOST, WEBHOOK_MAX_PER_HOST } from "../app-utils/actions.js";
18
+ import { PLUGIN_HOST_COLLECTIONS_DECLARED } from "./first-party-plugins.generated.js";
19
+ /** Every declared kind, in config order. */ export function listPluginHostResources() {
20
+ const out = [];
21
+ for (const row of PLUGIN_HOST_COLLECTIONS_DECLARED){
22
+ if (row.resource) {
23
+ out.push(_extends({}, row.resource, {
24
+ pluginId: row.pluginId,
25
+ collection: row.name
26
+ }));
27
+ }
28
+ }
29
+ return out;
30
+ }
31
+ /** One declared kind, or `null` — which every reader treats as unknown. */ export function pluginHostResource(kind) {
32
+ var _listPluginHostResources_find;
33
+ if (typeof kind !== 'string' || !kind) return null;
34
+ return (_listPluginHostResources_find = listPluginHostResources().find((one)=>one.kind === kind)) != null ? _listPluginHostResources_find : null;
35
+ }
36
+ /**
37
+ * The flat platform caps a declaration may name (AGL-1360, AGL-2266): a
38
+ * ceiling on documents mintable from a browser that no plan varies, so it has
39
+ * no `OrgEntitlements` key. Keyed by the constant's own name, so a search for
40
+ * the constant finds the declaration that uses it.
41
+ */ const HOST_RESOURCE_PLATFORM_CAPS = {
42
+ ACTIONS_MAX_PER_HOST,
43
+ WEBHOOK_MAX_PER_HOST
44
+ };
45
+ /**
46
+ * The number a `platformCap` names, or `null` for a name core does not hold —
47
+ * which the create route answers by refusing, never by leaving it uncapped.
48
+ */ export function hostResourcePlatformCap(name) {
49
+ return Object.prototype.hasOwnProperty.call(HOST_RESOURCE_PLATFORM_CAPS, name) ? HOST_RESOURCE_PLATFORM_CAPS[name] : null;
50
+ }
51
+
52
+ //# sourceMappingURL=plugin-host-resources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-host-resources.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 { ACTIONS_MAX_PER_HOST, WEBHOOK_MAX_PER_HOST } from '../app-utils/actions'\nimport { PLUGIN_HOST_COLLECTIONS_DECLARED } from './first-party-plugins.generated'\n\n/**\n * A plugin's host collection as a KIND the platform's generic create route\n * writes (AGL-3080).\n *\n * `/api/hosts/resources` is the only door a client creates quota-governed\n * site documents through: the rules deny client `create` on each of these\n * collections, and the route is the allow-list of what a client may write\n * (AGL-1377), the count it is met against (AGL-473, AGL-2231) and the\n * provenance it stamps (AGL-118). `duplicateResource` is its twin for a copy.\n * Both used to hold every plugin's row — which collection a workflow lives\n * in, which entitlement a product needs, which fields a redirect may carry —\n * so the platform was the keeper of its plugins' document models, and a\n * plugin could not add a kind without editing core.\n *\n * So the plugin that owns a collection declares the kind it is created as,\n * beside the collection itself in the `hostCollections` block of\n * `plugins.config.json` (a host collection's `resource`),\n * and both readers ask this module.\n *\n * ## Compiled, and refused when absent\n *\n * The route is a SECURITY path: its field list is what stands between a\n * client and a document the UI later presents as trustworthy. A runtime\n * registry that one process had not filled would either refuse a legitimate\n * create or — worse, if a reader ever fell back to an open list — store\n * whatever it was sent. So these rows are compiled by\n * `tools/scripts/generate-plugin-manifests.mjs`, present the moment this\n * module is imported, and a kind nobody declares is REFUSED as an unknown\n * resource. There is no registrar: `registerPluginHostCollections` refuses a\n * declaration that carries a `resource`.\n *\n * The generator checks what a reader would otherwise have to trust: one owner\n * per kind and none of core's own; a count on every kind (a plan `quotaKey` or\n * a `platformCap`); no server-stamped field, stamp or approver field on the\n * client's list; `externalDestination` only with the publishing role; and a\n * copy's fields a subset of the create's.\n *\n * ## What core keeps\n *\n * The NUMBERS. A declaration names its plan counter and feature flag as keys\n * (`quotaKey`, `entitlement`) and core's plan table resolves them, exactly as\n * it does for its own kinds. A flat platform cap is named the same way —\n * `platformCap` is the name of a constant core holds, resolved by\n * {@link hostResourcePlatformCap} — so no plugin carries a number that is the\n * platform's abuse ceiling. Core also keeps everything a create does for\n * EVERY kind: the role gate, the lockdown verdict, the transaction that counts\n * and writes, the node codec, the timestamps and `createdBy`.\n *\n * Reached by its own subpath, never the barrel: its readers are server routes.\n */\n\n/** A constant value a create writes: never the client's, never another stamp's. */\nexport type PluginHostResourceStampValue = string | number | boolean | null\n\n/** How a whole copy of the kind is made, when the kind can be duplicated. */\nexport interface PluginHostResourceDuplicate {\n /** The field the name lives in; the copy's is made unique among the live siblings. */\n nameField: string\n /** The source fields carried onto the copy — a subset of the create's. */\n fields: readonly string[]\n /** What the copy carries beyond the source's fields: a cleared trigger. */\n stamps?: Readonly<Record<string, PluginHostResourceStampValue>>\n}\n\nexport interface PluginHostResourceDeclaration {\n /** The `resource` a caller names, in camelCase: `product`, `workflow`. */\n kind: string\n /** Plural, for quota copy: \"this site can run 5 {label}\". */\n label: string\n /** Singular, for the site activity line: \"Created {activityNoun}\". */\n activityNoun: string\n /** The activity target type a row filters and deep-links by; `content` when absent. */\n activityType?: string\n /** The plan counter, an `OrgEntitlements` key core resolves. */\n quotaKey?: string\n /** The plan feature the kind needs at all, an `OrgFeatureFlags` key. */\n entitlement?: string\n /** A flat platform cap by the name of core's constant; see {@link hostResourcePlatformCap}. */\n platformCap?: string\n /** Deleting stamps `deletedAt`, so the cap counts live documents only. */\n softDeletes?: boolean\n /** Creating it needs the publishing role rather than the write role. */\n requiresPublishRole?: boolean\n /** The keys a client may set. Anything else it sends is dropped. */\n fields: readonly string[]\n /** Constant values every create writes, e.g. `deletedAt: null` for a kind born live. */\n stamps?: Readonly<Record<string, PluginHostResourceStampValue>>\n /**\n * A destination field that may point off the platform. When the value is\n * not a site path, the creating publisher's VERIFIED uid is stamped into\n * `approvedByField` — the evidence a serve path trusts that somebody chose\n * to send traffic away. Only with `requiresPublishRole`.\n */\n externalDestination?: { field: string; approvedByField: string }\n /**\n * The field holding the site path the document answers at the moment it\n * exists. A create announces it to the site cache, because what used to\n * answer there is already cached as the page.\n */\n livePathField?: string\n duplicate?: PluginHostResourceDuplicate\n}\n\n/** A declaration with the plugin that made it and the collection it writes. */\nexport type ResolvedPluginHostResource = PluginHostResourceDeclaration & {\n pluginId: string\n /** The subcollection under `hosts/{hostId}` the kind is created in. */\n collection: string\n}\n\n/** Every declared kind, in config order. */\nexport function listPluginHostResources(): ResolvedPluginHostResource[] {\n const out: ResolvedPluginHostResource[] = []\n for (const row of PLUGIN_HOST_COLLECTIONS_DECLARED) {\n if (row.resource) {\n out.push({ ...row.resource, pluginId: row.pluginId, collection: row.name })\n }\n }\n return out\n}\n\n/** One declared kind, or `null` — which every reader treats as unknown. */\nexport function pluginHostResource(kind: unknown): ResolvedPluginHostResource | null {\n if (typeof kind !== 'string' || !kind) return null\n return listPluginHostResources().find((one) => one.kind === kind) ?? null\n}\n\n/**\n * The flat platform caps a declaration may name (AGL-1360, AGL-2266): a\n * ceiling on documents mintable from a browser that no plan varies, so it has\n * no `OrgEntitlements` key. Keyed by the constant's own name, so a search for\n * the constant finds the declaration that uses it.\n */\nconst HOST_RESOURCE_PLATFORM_CAPS: Readonly<Record<string, number>> = {\n ACTIONS_MAX_PER_HOST,\n WEBHOOK_MAX_PER_HOST,\n}\n\n/**\n * The number a `platformCap` names, or `null` for a name core does not hold —\n * which the create route answers by refusing, never by leaving it uncapped.\n */\nexport function hostResourcePlatformCap(name: string): number | null {\n return Object.prototype.hasOwnProperty.call(HOST_RESOURCE_PLATFORM_CAPS, name)\n ? HOST_RESOURCE_PLATFORM_CAPS[name]\n : null\n}\n"],"names":["ACTIONS_MAX_PER_HOST","WEBHOOK_MAX_PER_HOST","PLUGIN_HOST_COLLECTIONS_DECLARED","listPluginHostResources","out","row","resource","push","pluginId","collection","name","pluginHostResource","kind","find","one","HOST_RESOURCE_PLATFORM_CAPS","hostResourcePlatformCap","Object","prototype","hasOwnProperty","call"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,oBAAoB,EAAEC,oBAAoB,QAAQ,0BAAsB;AACjF,SAASC,gCAAgC,QAAQ,qCAAiC;AAgHlF,0CAA0C,GAC1C,OAAO,SAASC;IACd,MAAMC,MAAoC,EAAE;IAC5C,KAAK,MAAMC,OAAOH,iCAAkC;QAClD,IAAIG,IAAIC,QAAQ,EAAE;YAChBF,IAAIG,IAAI,CAAC,aAAKF,IAAIC,QAAQ;gBAAEE,UAAUH,IAAIG,QAAQ;gBAAEC,YAAYJ,IAAIK,IAAI;;QAC1E;IACF;IACA,OAAON;AACT;AAEA,yEAAyE,GACzE,OAAO,SAASO,mBAAmBC,IAAa;QAEvCT;IADP,IAAI,OAAOS,SAAS,YAAY,CAACA,MAAM,OAAO;IAC9C,QAAOT,gCAAAA,0BAA0BU,IAAI,CAAC,CAACC,MAAQA,IAAIF,IAAI,KAAKA,iBAArDT,gCAA8D;AACvE;AAEA;;;;;CAKC,GACD,MAAMY,8BAAgE;IACpEf;IACAC;AACF;AAEA;;;CAGC,GACD,OAAO,SAASe,wBAAwBN,IAAY;IAClD,OAAOO,OAAOC,SAAS,CAACC,cAAc,CAACC,IAAI,CAACL,6BAA6BL,QACrEK,2BAA2B,CAACL,KAAK,GACjC;AACN"}
@@ -14,6 +14,20 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
+ import { PLUGIN_ORG_KEYED_COLLECTIONS, PLUGIN_REQUIRED_ORG_ERASERS } from './first-party-plugins.generated';
18
+ export { PLUGIN_ORG_KEYED_COLLECTIONS, PLUGIN_REQUIRED_ORG_ERASERS };
19
+ /**
20
+ * A plugin's top-level collection whose documents name an organization in a
21
+ * field, declared by that plugin. See the module note.
22
+ */
23
+ export interface PluginOrgKeyedCollection {
24
+ /** The plugin that declared it. */
25
+ pluginId: string;
26
+ /** The top-level collection. */
27
+ name: string;
28
+ /** The field naming the organization a document belongs to. */
29
+ orgField: string;
30
+ }
17
31
  /** What an eraser is asked to erase. */
18
32
  export interface PluginOrgErasureRequest {
19
33
  /** The workspace being erased. */
@@ -43,10 +57,22 @@ export declare function registerPluginOrgEraser(eraser: PluginOrgEraser, options
43
57
  /**
44
58
  * Runs every eraser, in registration order, and answers each plugin's
45
59
  * report by plugin id: the eraser's own report, or `null` for one that
46
- * threw. Never throws.
60
+ * threw.
61
+ *
62
+ * Throws in exactly two cases, both about a REQUIRED eraser (`required`,
63
+ * the compiled {@link PLUGIN_REQUIRED_ORG_ERASERS} unless a spec passes its
64
+ * own): before running anything, when one is not registered; and after
65
+ * running every eraser, when one threw.
47
66
  */
48
- export declare function runPluginOrgErasers(request: PluginOrgErasureRequest): Promise<Record<string, PluginOrgErasureReport | null>>;
67
+ export declare function runPluginOrgErasers(request: PluginOrgErasureRequest, required?: readonly string[]): Promise<Record<string, PluginOrgErasureReport | null>>;
49
68
  /** The plugins with a workspace eraser, in the order they run. */
50
69
  export declare function listPluginOrgErasers(): string[];
51
70
  /** Test seam: forget every eraser. */
52
71
  export declare function resetPluginOrgErasersForTests(): void;
72
+ /**
73
+ * Test seam: a no-op eraser for every REQUIRED plugin that has none, so a
74
+ * spec about the erasure's own sweeps can run it without loading a plugin
75
+ * (core and the data layer may not import one). A spec about a required
76
+ * plugin's share registers the real one through the app's manifest instead.
77
+ */
78
+ export declare function standInRequiredOrgErasersForTests(): void;
@@ -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"}