@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
@@ -17,8 +17,9 @@
17
17
  // serves it — so it may import neither entry barrel, for the reason
18
18
  // `usage-metering.ts` states at its own head. The specific modules underneath
19
19
  // are safe in both.
20
- import { assistCreditsFromUsd } from "./assist-credits.js";
21
20
  import { hasAiAddon, INFRA_COGS_PER_SITE_USD, MARGIN_SCOPE_NOTE, netMarginRating, PLAN_PRICING, orgListPriceMonthlyUsd, orgMonthlyCogsUsd, orgMonthlyRevenueUsd, orgNetMonthlyRevenueUsd, orgSiteCount, pageViewsFromBandwidthGb, resolveOrgEntitlements, resolveEffectivePlan } from "./plan-entitlements.js";
21
+ import { planQuotaOf } from "../plugin-manager/plugin-plan-entitlements.js";
22
+ import { declaredMeterReading, pluginUsageBands } from "../plugin-manager/plugin-usage-axes.js";
22
23
  export { MARGIN_SCOPE_NOTE };
23
24
  /**
24
25
  * WHAT SHARE OF THE BANDS THEY BOUGHT DO CUSTOMERS ACTUALLY SPEND?
@@ -71,47 +72,68 @@ export { MARGIN_SCOPE_NOTE };
71
72
  * `tier-margin-floor.spec.ts` costs it unexpanded. Following either of those
72
73
  * and not the name is what keeps this table agreeing with both.
73
74
  */ /**
74
- * A meter with an included band, keyed by the ROLLUP field it is measured by.
75
- *
76
- * Every meter `orgMonthlyCogsUsd` prices, plus `hosts` and `assistCredits`.
77
- *
78
- * Assist is measured in CREDITS rather than in the dollars the rollup stores.
79
- * `assistCostUsd` is our provider bill and `assistCreditsPerMonth` is the band
80
- * the plan sells, so the two are only comparable through
81
- * `assistCreditsFromUsd` — the one conversion between the units, and the same
82
- * one the customer-facing meter uses.
83
- *
84
- * It is also the band most worth watching: Assist is the only line item on the
85
- * platform whose unit cost is real money paid to a third party, and the only
86
- * one that can clear the $2/site floor on its own.
87
- */ export const UTILIZATION_BANDS = [
88
- 'hosts',
89
- 'storageGb',
90
- 'pageViews',
91
- 'formSubmissions',
92
- 'dataStorageMb',
93
- 'apiRequests',
94
- 'contactsCount',
95
- 'emailSends',
96
- 'assistCredits',
97
- 'workflowRuns',
98
- 'actionRuns'
75
+ * The platform's own bands, at the positions the plugins' declared bands
76
+ * interleave with (`order`, a multiple of 10 here) — every meter
77
+ * `orgMonthlyCogsUsd` prices that is the platform's, plus `hosts`.
78
+ */ const CORE_BANDS = [
79
+ {
80
+ id: 'hosts',
81
+ label: 'Sites',
82
+ order: 10
83
+ },
84
+ {
85
+ id: 'storageGb',
86
+ label: 'Media storage',
87
+ order: 20
88
+ },
89
+ {
90
+ id: 'pageViews',
91
+ label: 'Bandwidth (page views)',
92
+ order: 30
93
+ },
94
+ {
95
+ id: 'dataStorageMb',
96
+ label: 'Dataset storage',
97
+ order: 50
98
+ },
99
+ {
100
+ id: 'apiRequests',
101
+ label: 'API requests',
102
+ order: 60
103
+ },
104
+ {
105
+ id: 'emailSends',
106
+ label: 'Email sends',
107
+ order: 80
108
+ }
99
109
  ];
100
- /** Column headings, so the page and the aggregate name a band identically. */ export const UTILIZATION_BAND_LABELS = {
101
- hosts: 'Sites',
102
- storageGb: 'Media storage',
103
- pageViews: 'Bandwidth (page views)',
104
- formSubmissions: 'Form submissions',
105
- dataStorageMb: 'Dataset storage',
106
- apiRequests: 'API requests',
107
- // The band under the rollup's `contactsCount` key widened to contacts,
108
- // companies and deals (AGL-2611); the column says what it measures.
109
- contactsCount: 'CRM records',
110
- emailSends: 'Email sends',
111
- assistCredits: 'Assist credits',
112
- workflowRuns: 'Workflow runs',
113
- actionRuns: 'Action runs'
114
- };
110
+ /**
111
+ * Every band the table reads, in column order: the platform's, and each one a
112
+ * plugin declares beside the meter it contributes (`plugin-usage-axes.ts`) —
113
+ * the AI plugin's credits, the CRM's records, the forms plugin's
114
+ * submissions, the workflows plugin's runs.
115
+ *
116
+ * A plugin band sold in a unit OF cost is measured in that unit rather than
117
+ * in the dollars the rollup stores: the AI plugin's credits are the dollars
118
+ * over its declared unit cost, rounded up, which is the one conversion the
119
+ * customer's own meter uses. It is also the band most worth watching —
120
+ * provider spend is the only line item on the platform whose unit cost is
121
+ * real money paid to a third party, and the only one that can clear the
122
+ * $2/site floor on its own.
123
+ */ const BANDS = [
124
+ ...CORE_BANDS,
125
+ ...pluginUsageBands().map((declared)=>({
126
+ id: declared.id,
127
+ label: declared.label,
128
+ order: declared.order,
129
+ declared
130
+ }))
131
+ ].sort((a, b)=>a.order - b.order);
132
+ /** Every band, keyed by what it measures, in column order. */ export const UTILIZATION_BANDS = BANDS.map((band)=>band.id);
133
+ /** Column headings, so the page and the aggregate name a band identically. */ export const UTILIZATION_BAND_LABELS = Object.fromEntries(BANDS.map((band)=>[
134
+ band.id,
135
+ band.label
136
+ ]));
115
137
  const finite = (value)=>{
116
138
  const parsed = Number(value != null ? value : 0);
117
139
  return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
@@ -128,12 +150,11 @@ const finite = (value)=>{
128
150
  */ export function orgIncludedBands(org) {
129
151
  const entitlements = resolveOrgEntitlements(org);
130
152
  const hostLimit = Math.max(1, entitlements.hostLimit);
131
- return {
153
+ const included = {
132
154
  hosts: entitlements.hostLimit,
133
155
  // Per host, expanded by the host limit — `meteredIncludedAllowance`
134
- // expands these two and nothing else.
156
+ // expands the per-host bands and nothing else.
135
157
  storageGb: hostLimit * entitlements.storagePerHostMb / 1024,
136
- formSubmissions: hostLimit * entitlements.formSubmissionsPerMonth,
137
158
  // Bandwidth IS the page-view band, expressed in the unit customers buy.
138
159
  pageViews: pageViewsFromBandwidthGb(entitlements.bandwidthGb),
139
160
  // Org-wide, in the unit the rollup stores. `dataStorageMbPerOrg` is
@@ -141,18 +162,15 @@ const finite = (value)=>{
141
162
  // `orgMonthlyCogsUsd` and must not happen twice.
142
163
  dataStorageMb: entitlements.dataStorageMbPerOrg,
143
164
  apiRequests: entitlements.apiRequestsPerMonth,
144
- // Org-wide despite the name — see the module note.
145
- contactsCount: entitlements.contactsPerHost,
146
- emailSends: entitlements.emailSendsPerMonth,
147
- // Never `UNLIMITED` on any plan, deliberately — Enterprise carries a
148
- // finite default. The band is a third-party liability rather than capacity
149
- // the platform already owns, so an uncapped one would be an uncapped bill.
150
- assistCredits: entitlements.assistCreditsPerMonth,
151
- // Two bands, one rate: the cost model prices both counters at `perRun`,
152
- // and the utilization is read against the band each builder sells.
153
- workflowRuns: entitlements.workflowRunsPerMonth,
154
- actionRuns: entitlements.actionRunsPerMonth
165
+ emailSends: entitlements.emailSendsPerMonth
155
166
  };
167
+ // A plugin's band reads the entitlement its declaration names — absent is
168
+ // nothing included — expanded by the host limit where it is per site.
169
+ for (const band of pluginUsageBands()){
170
+ const figure = planQuotaOf(entitlements, band.entitlement);
171
+ included[band.id] = band.perHost ? hostLimit * figure : figure;
172
+ }
173
+ return included;
156
174
  }
157
175
  /** One band's reading. The whole of the `UNLIMITED` and zero-band rule. */ export function bandUtilization(band, used, included) {
158
176
  const usedSafe = Number.isFinite(used) && used > 0 ? used : 0;
@@ -190,26 +208,21 @@ const finite = (value)=>{
190
208
  * site added since the cron last ran is a site the org has. The rollup's own
191
209
  * `hostCount` is a snapshot of the same thing at cron time.
192
210
  */ function bandUsage(org, rollup) {
193
- var _ref;
194
- return {
211
+ const used = {
195
212
  hosts: orgSiteCount(org),
196
213
  storageGb: finite(rollup == null ? void 0 : rollup.storageGb),
197
214
  pageViews: finite(rollup == null ? void 0 : rollup.pageViews),
198
- formSubmissions: finite(rollup == null ? void 0 : rollup.formSubmissions),
199
215
  dataStorageMb: finite(rollup == null ? void 0 : rollup.dataStorageMb),
200
216
  apiRequests: finite(rollup == null ? void 0 : rollup.apiRequests),
201
- // The records band's figure (AGL-2611), falling back to contacts alone
202
- // only on a row written before the band was widened — the same rule
203
- // `orgMonthlyCogsUsd` applies, so utilization and cost read one number.
204
- contactsCount: finite((_ref = rollup == null ? void 0 : rollup.crmRecordsCount) != null ? _ref : rollup == null ? void 0 : rollup.contactsCount),
205
- emailSends: finite(rollup == null ? void 0 : rollup.emailSends),
206
- // Dollars on the rollup, credits on the band. `assistCreditsFromUsd` is
207
- // the ONE conversion between them, so this figure and the one the customer
208
- // sees on their own billing page cannot disagree about the same month.
209
- assistCredits: assistCreditsFromUsd(finite(rollup == null ? void 0 : rollup.assistCostUsd)),
210
- workflowRuns: finite(rollup == null ? void 0 : rollup.workflowRuns),
211
- actionRuns: finite(rollup == null ? void 0 : rollup.actionRuns)
217
+ emailSends: finite(rollup == null ? void 0 : rollup.emailSends)
212
218
  };
219
+ for (const band of pluginUsageBands()){
220
+ const reading = declaredMeterReading(rollup, band);
221
+ // Dollars on the rollup, a unit of cost on the band: rounded UP, so spend
222
+ // already made never reads as less of the band.
223
+ used[band.id] = band.unitCostUsd && reading > 0 ? Math.ceil(reading / band.unitCostUsd) : reading;
224
+ }
225
+ return used;
213
226
  }
214
227
  /**
215
228
  * One org's realised utilization, cost and margin.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/margin-utilization.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// Imported from BOTH graphs — the staff page (client) and the App Route that\n// serves it — so it may import neither entry barrel, for the reason\n// `usage-metering.ts` states at its own head. The specific modules underneath\n// are safe in both.\nimport type { AglynOrgBilling, OrgPlan } from '../foundation'\nimport { assistCreditsFromUsd } from './assist-credits'\nimport {\n hasAiAddon,\n INFRA_COGS_PER_SITE_USD,\n MARGIN_SCOPE_NOTE,\n netMarginRating,\n PLAN_PRICING,\n orgListPriceMonthlyUsd,\n orgMonthlyCogsUsd,\n orgMonthlyRevenueUsd,\n orgNetMonthlyRevenueUsd,\n orgSiteCount,\n pageViewsFromBandwidthGb,\n resolveOrgEntitlements,\n resolveEffectivePlan,\n type DiscountMarginRating,\n type OrgCogsResult,\n type OrgUsageRollupInput,\n} from './plan-entitlements'\n\nexport { MARGIN_SCOPE_NOTE }\n\n/**\n * WHAT SHARE OF THE BANDS THEY BOUGHT DO CUSTOMERS ACTUALLY SPEND?\n *\n * `tier-margin-floor.spec.ts` proves what every tier costs at 100% of every\n * band, and pins the margin at 3 / 25 / 50 / 100%. Which of those four columns\n * the business is actually operating in has never been measured — and the\n * spread between them is the whole question, because on Pro the same price\n * list yields 89.3% at the 3% column and 7.1% at the 100% one.\n *\n * Everything needed to answer it was already recorded and already priced. The\n * monthly rollup carries the meters, `ORG_COGS_UNIT_RATES_USD` carries their\n * unit costs, and `orgMonthlyCogsUsd` already turns one into the other. What\n * did not exist was the DIVISION: usage over the band the plan sold, per org\n * and across the fleet.\n *\n * This module is that division, and nothing else. It runs no query, holds no\n * rate of its own and re-derives no cost — every dollar here comes back out of\n * `orgMonthlyCogsUsd`, so a surface built on it cannot come to disagree with\n * the discount guardrail about what an org costs.\n *\n * ## The three answers a band can give, and why two of them are not numbers\n *\n * A percentage needs a denominator, and two of the bands in `PLAN_ENTITLEMENTS`\n * do not supply one:\n *\n * * `UNLIMITED` — `Number.POSITIVE_INFINITY`. Enterprise carries it on\n * contacts, API requests and dataset storage. `used / Infinity` is 0, which\n * would render the platform's largest customers as its lightest users; and\n * a model that clamps to 100% instead would render them as its heaviest.\n * Both are inventions. The band is UNCAPPED, the fraction is `null`, and no\n * aggregate may average it in either direction.\n * * `0` — Free carries it on email sends, API requests and dataset storage.\n * `0 / 0` is `NaN` and `n / 0` is `Infinity`. Neither is a utilization. The\n * band admits nothing, so there is no allowance to be a fraction of, and an\n * org that spent the meter anyway did so with no included band behind it —\n * which is a cost signal in its own right, reported as\n * `usageWithNoAllowance` rather than folded into a percentile.\n *\n * The two are kept DISTINCT from each other and from `0%`. An uncapped band\n * and an exhausted one are opposite facts, and the recurring defect in this\n * repo's cost work is exactly that an absent figure reads as a zero one.\n *\n * ## What the bands are\n *\n * `meteredIncludedAllowance` sizes three of them and is the shape followed\n * here: the two PER-HOST bands are expanded by `hostLimit`, the rest are\n * org-wide as stored. `contactsPerHost` is org-wide DESPITE ITS NAME —\n * `checkContactQuota` compares it against an org-wide headcount, and\n * `tier-margin-floor.spec.ts` costs it unexpanded. Following either of those\n * and not the name is what keeps this table agreeing with both.\n */\n\n/**\n * A meter with an included band, keyed by the ROLLUP field it is measured by.\n *\n * Every meter `orgMonthlyCogsUsd` prices, plus `hosts` and `assistCredits`.\n *\n * Assist is measured in CREDITS rather than in the dollars the rollup stores.\n * `assistCostUsd` is our provider bill and `assistCreditsPerMonth` is the band\n * the plan sells, so the two are only comparable through\n * `assistCreditsFromUsd` — the one conversion between the units, and the same\n * one the customer-facing meter uses.\n *\n * It is also the band most worth watching: Assist is the only line item on the\n * platform whose unit cost is real money paid to a third party, and the only\n * one that can clear the $2/site floor on its own.\n */\nexport const UTILIZATION_BANDS = [\n 'hosts',\n 'storageGb',\n 'pageViews',\n 'formSubmissions',\n 'dataStorageMb',\n 'apiRequests',\n 'contactsCount',\n 'emailSends',\n 'assistCredits',\n 'workflowRuns',\n 'actionRuns',\n] as const\n\nexport type UtilizationBand = (typeof UTILIZATION_BANDS)[number]\n\n/** Column headings, so the page and the aggregate name a band identically. */\nexport const UTILIZATION_BAND_LABELS: Record<UtilizationBand, string> = {\n hosts: 'Sites',\n storageGb: 'Media storage',\n pageViews: 'Bandwidth (page views)',\n formSubmissions: 'Form submissions',\n dataStorageMb: 'Dataset storage',\n apiRequests: 'API requests',\n // The band under the rollup's `contactsCount` key widened to contacts,\n // companies and deals (AGL-2611); the column says what it measures.\n contactsCount: 'CRM records',\n emailSends: 'Email sends',\n assistCredits: 'Assist credits',\n workflowRuns: 'Workflow runs',\n actionRuns: 'Action runs',\n}\n\n/**\n * Why a band has no percentage, or that it has one.\n *\n * `uncapped` and `noAllowance` are the two non-numbers described at the head\n * of this module. They are separate values rather than one `null` because the\n * remedies are opposite: an uncapped band is a pricing exposure, an exhausted\n * one is an upgrade conversation.\n */\nexport type BandState = 'measured' | 'uncapped' | 'noAllowance'\n\nexport interface BandUtilization {\n band: UtilizationBand\n /** The org-wide included allowance. `Infinity` when uncapped, 0 when none. */\n included: number\n /** What the rollup recorded. Always a finite, non-negative number. */\n used: number\n state: BandState\n /**\n * Share of the band consumed, as a fraction of 1.\n *\n * `null` for every state but `measured`, and callers must render it as\n * neither 0 nor 1. It is NOT clamped: an org past its included band reads\n * above 1, which is the number worth seeing.\n */\n fraction: number | null\n}\n\n/** One org's realised position: what it consumed, what it cost, what it paid. */\nexport interface OrgMarginRow {\n orgId: string\n name: string | null\n plan: OrgPlan\n /** The rollup month these figures describe, or null when none has run. */\n month: string | null\n bands: Record<UtilizationBand, BandUtilization>\n /**\n * The shared cost model's answer — the same function and the same fields\n * `/api/admin/org-discount` underwrites against.\n */\n cogs: OrgCogsResult\n /** Pre-discount sticker price. */\n listPriceUsd: number\n /** List price net of any per-org discount. */\n mrrUsd: number\n /** MRR net of Stripe's processing fee — the base the margin is taken on. */\n netRevenueUsd: number\n /**\n * The AI add-on's share of `listPriceUsd` (AGL-2930) — already INSIDE\n * `listPriceUsd`, `mrrUsd` and `netRevenueUsd`, because\n * `orgListPriceMonthlyUsd` folds every purchased add-on. Named here so the\n * surface can show that the assist band's cost has revenue against it\n * rather than reading as pure margin drag; adding it again would count it\n * twice. 0 on a custom-priced deal, whose quote already folds the add-on\n * into one negotiated figure.\n */\n aiAddonRevenueUsd: number\n /**\n * `(net revenue − COGS) / net revenue`, the same arithmetic\n * `checkDiscountMargin` rates a discount on. See `MARGIN_SCOPE_NOTE`: it is\n * a contribution margin, not a profit.\n *\n * `null` when the org bills nothing. A free or comped org has no revenue to\n * take a fraction of, and reporting it as 0% or −100% would put a customer\n * who was never meant to be profitable at the top of a list whose whole\n * purpose is finding the one who was.\n */\n marginPct: number | null\n rating: DiscountMarginRating | null\n}\n\n/**\n * A rollup as this surface reads it: every field `orgMonthlyCogsUsd` prices.\n *\n * The two run counters used to be the surface's own extension of that shape\n * — recorded and banded but unpriced. Since 2026-09-07 the cost model prices\n * them at `perRun`, so the shape is the model's and nothing here is extra.\n */\nexport type UtilizationRollup = OrgUsageRollupInput\n\nconst finite = (value: unknown): number => {\n const parsed = Number(value ?? 0)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 0\n}\n\n/**\n * The org-wide included band for every metered axis, on this org's plan and\n * with this org's overrides and add-ons applied.\n *\n * `resolveOrgEntitlements` rather than `PLAN_ENTITLEMENTS[plan]`: a staff\n * entitlement override and a purchased host add-on both change what the\n * customer actually bought, and measuring consumption against the catalogue\n * row instead of the resolved one is the same class of error as measuring it\n * against a constant.\n */\nexport function orgIncludedBands(\n org: Partial<AglynOrgBilling> | null | undefined,\n): Record<UtilizationBand, number> {\n const entitlements = resolveOrgEntitlements(org)\n const hostLimit = Math.max(1, entitlements.hostLimit)\n return {\n hosts: entitlements.hostLimit,\n // Per host, expanded by the host limit — `meteredIncludedAllowance`\n // expands these two and nothing else.\n storageGb: (hostLimit * entitlements.storagePerHostMb) / 1024,\n formSubmissions: hostLimit * entitlements.formSubmissionsPerMonth,\n // Bandwidth IS the page-view band, expressed in the unit customers buy.\n pageViews: pageViewsFromBandwidthGb(entitlements.bandwidthGb),\n // Org-wide, in the unit the rollup stores. `dataStorageMbPerOrg` is\n // megabytes and so is `dataStorageMb`; the GB conversion happens inside\n // `orgMonthlyCogsUsd` and must not happen twice.\n dataStorageMb: entitlements.dataStorageMbPerOrg,\n apiRequests: entitlements.apiRequestsPerMonth,\n // Org-wide despite the name — see the module note.\n contactsCount: entitlements.contactsPerHost,\n emailSends: entitlements.emailSendsPerMonth,\n // Never `UNLIMITED` on any plan, deliberately — Enterprise carries a\n // finite default. The band is a third-party liability rather than capacity\n // the platform already owns, so an uncapped one would be an uncapped bill.\n assistCredits: entitlements.assistCreditsPerMonth,\n // Two bands, one rate: the cost model prices both counters at `perRun`,\n // and the utilization is read against the band each builder sells.\n workflowRuns: entitlements.workflowRunsPerMonth,\n actionRuns: entitlements.actionRunsPerMonth,\n }\n}\n\n/** One band's reading. The whole of the `UNLIMITED` and zero-band rule. */\nexport function bandUtilization(\n band: UtilizationBand,\n used: number,\n included: number,\n): BandUtilization {\n const usedSafe = Number.isFinite(used) && used > 0 ? used : 0\n if (!Number.isFinite(included)) {\n return { band, included: Number.POSITIVE_INFINITY, used: usedSafe, state: 'uncapped', fraction: null }\n }\n if (!(included > 0)) {\n return { band, included: 0, used: usedSafe, state: 'noAllowance', fraction: null }\n }\n return { band, included, used: usedSafe, state: 'measured', fraction: usedSafe / included }\n}\n\n/**\n * What the rollup recorded, per band.\n *\n * `hosts` comes from the ORG rather than the rollup: `orgSiteCount` is what\n * the cost floor is charged on and what `checkDiscountMargin` counts, and a\n * site added since the cron last ran is a site the org has. The rollup's own\n * `hostCount` is a snapshot of the same thing at cron time.\n */\nfunction bandUsage(\n org: Partial<AglynOrgBilling> | null | undefined,\n rollup: UtilizationRollup | null | undefined,\n): Record<UtilizationBand, number> {\n return {\n hosts: orgSiteCount(org),\n storageGb: finite(rollup?.storageGb),\n pageViews: finite(rollup?.pageViews),\n formSubmissions: finite(rollup?.formSubmissions),\n dataStorageMb: finite(rollup?.dataStorageMb),\n apiRequests: finite(rollup?.apiRequests),\n // The records band's figure (AGL-2611), falling back to contacts alone\n // only on a row written before the band was widened — the same rule\n // `orgMonthlyCogsUsd` applies, so utilization and cost read one number.\n contactsCount: finite(rollup?.crmRecordsCount ?? rollup?.contactsCount),\n emailSends: finite(rollup?.emailSends),\n // Dollars on the rollup, credits on the band. `assistCreditsFromUsd` is\n // the ONE conversion between them, so this figure and the one the customer\n // sees on their own billing page cannot disagree about the same month.\n assistCredits: assistCreditsFromUsd(finite(rollup?.assistCostUsd)),\n workflowRuns: finite(rollup?.workflowRuns),\n actionRuns: finite(rollup?.actionRuns),\n }\n}\n\nexport interface OrgMarginInput {\n orgId: string\n name?: string | null\n /** The merged `{...orgDoc, ...billingDoc}` every revenue helper reads. */\n org: Partial<AglynOrgBilling> | null | undefined\n rollup: UtilizationRollup | null | undefined\n month?: string | null\n}\n\n/**\n * One org's realised utilization, cost and margin.\n *\n * The cost is `orgMonthlyCogsUsd`'s, unmodified, floor included — the same\n * `max(measured, $2 × sites)` the guardrail applies. On production today that\n * floor is what every org's figure is, which is worth reading off the `basis`\n * field rather than assuming the meters are driving anything yet.\n */\nexport function orgMarginRow(input: OrgMarginInput): OrgMarginRow {\n const { org, rollup } = input\n const included = orgIncludedBands(org)\n const used = bandUsage(org, rollup)\n const bands = Object.fromEntries(\n UTILIZATION_BANDS.map((band) => [band, bandUtilization(band, used[band], included[band])]),\n ) as Record<UtilizationBand, BandUtilization>\n\n const cogs = orgMonthlyCogsUsd(rollup, orgSiteCount(org))\n const listPriceUsd = orgListPriceMonthlyUsd(org)\n const mrrUsd = orgMonthlyRevenueUsd(org)\n const netRevenueUsd = orgNetMonthlyRevenueUsd(org)\n // Only when the org bills something at all — `listPriceUsd` is 0 for a\n // comped or lapsed org whatever its `seatAddons` say, and the add-on's\n // share of nothing is nothing. `hasAiAddon` already drops a dead\n // subscription's add-on; the list-price check catches the comped one.\n const plan = resolveEffectivePlan(org)\n const customPriced =\n typeof org?.subscription?.customMonthlyUsd === 'number' &&\n org.subscription.customMonthlyUsd > 0\n const aiAddonRevenueUsd =\n listPriceUsd > 0 && !customPriced && hasAiAddon(org)\n ? finite(PLAN_PRICING[plan]?.aiAddonMonthlyUsd)\n : 0\n // A margin needs revenue to be a fraction OF. Free and comped orgs have\n // none, and inventing one for them fills the top of a worst-first list with\n // customers who were never billed.\n const marginPct =\n netRevenueUsd > 0\n ? Math.round(((netRevenueUsd - cogs.cogsUsd) / netRevenueUsd) * 10000) / 10000\n : null\n\n return {\n orgId: input.orgId,\n name: input.name ?? null,\n plan,\n month: input.month ?? null,\n bands,\n cogs,\n listPriceUsd,\n mrrUsd,\n netRevenueUsd,\n aiAddonRevenueUsd,\n marginPct,\n rating: marginPct === null ? null : netMarginRating(marginPct),\n }\n}\n\n/**\n * Nearest-rank percentile over a sample sorted ascending.\n *\n * `null` for an empty sample. A median of nothing is not zero, and the fleet\n * view exists to answer a question that has no answer yet for most bands.\n */\nexport function percentile(sorted: readonly number[], p: number): number | null {\n if (!sorted.length) return null\n const rank = Math.ceil(p * sorted.length)\n return sorted[Math.min(sorted.length - 1, Math.max(0, rank - 1))]\n}\n\nexport interface BandDistribution {\n band: UtilizationBand\n /**\n * Orgs a percentage EXISTS for — a finite, positive band. Every percentile\n * below is over this sample and no other.\n */\n counted: number\n /** Excluded because the band is `UNLIMITED`. Not 0%, not 100%: no answer. */\n excludedUncapped: number\n /** Excluded because the plan includes none of this meter. */\n excludedNoAllowance: number\n /**\n * Orgs with no included allowance that spent the meter anyway — cost with no\n * band behind it. Excluded from the percentiles, reported on its own.\n */\n usageWithNoAllowance: number\n p50: number | null\n p25: number | null\n p75: number | null\n p90: number | null\n max: number | null\n /** Orgs at or past 100% of the band, out of `counted`. */\n overBand: number\n}\n\nexport interface FleetUtilization {\n /** Orgs folded in — every row handed to it, whatever its plan. */\n orgs: number\n /** Of those, how many had a usage rollup at all. */\n withRollup: number\n distributions: BandDistribution[]\n /** Median contribution margin across orgs that bill something. */\n medianMarginPct: number | null\n /** Orgs billing something whose margin sits under `NET_MARGIN_FLOOR_PCT`. */\n orgsUnderFloor: number\n /** Orgs billing something whose COGS exceeds their net revenue. */\n orgsUnderwater: number\n /** Total measured COGS and net revenue across the fold, in USD. */\n totalCogsUsd: number\n totalNetRevenueUsd: number\n /** Of `totalNetRevenueUsd`'s gross, how much the AI add-on brought in. */\n totalAiAddonRevenueUsd: number\n}\n\n/**\n * The fleet answer: the median and spread of utilization per band.\n *\n * This is the figure every margin projection has been assuming. It is folded\n * over whatever rows the caller read — see the route's truncation disclosure,\n * because a distribution over a partial fleet is a distribution over a partial\n * fleet and must say so.\n *\n * An org WITHOUT a rollup still contributes its `hosts` band (the org doc\n * knows its own sites) and is excluded from the other seven by having no\n * measurement, not by being scored at zero. `withRollup` is what says how much\n * of the fold is real.\n */\nexport function fleetUtilization(rows: readonly OrgMarginRow[]): FleetUtilization {\n const distributions = UTILIZATION_BANDS.map<BandDistribution>((band) => {\n const sample: number[] = []\n let excludedUncapped = 0\n let excludedNoAllowance = 0\n let usageWithNoAllowance = 0\n for (const row of rows) {\n const reading = row.bands[band]\n if (!reading) continue\n // A band the cron has never measured contributes nothing rather than a\n // zero: `hosts` is known without a rollup, the seven meters are not.\n if (band !== 'hosts' && row.month === null) continue\n if (reading.state === 'uncapped') {\n excludedUncapped += 1\n continue\n }\n if (reading.state === 'noAllowance') {\n excludedNoAllowance += 1\n if (reading.used > 0) usageWithNoAllowance += 1\n continue\n }\n sample.push(reading.fraction as number)\n }\n sample.sort((a, b) => a - b)\n return {\n band,\n counted: sample.length,\n excludedUncapped,\n excludedNoAllowance,\n usageWithNoAllowance,\n p50: percentile(sample, 0.5),\n p25: percentile(sample, 0.25),\n p75: percentile(sample, 0.75),\n p90: percentile(sample, 0.9),\n max: sample.length ? sample[sample.length - 1] : null,\n overBand: sample.filter((fraction) => fraction >= 1).length,\n }\n })\n\n const margins = rows\n .map((row) => row.marginPct)\n .filter((margin): margin is number => margin !== null)\n .sort((a, b) => a - b)\n\n return {\n orgs: rows.length,\n withRollup: rows.filter((row) => row.month !== null).length,\n distributions,\n medianMarginPct: percentile(margins, 0.5),\n orgsUnderFloor: margins.filter((margin) => netMarginRating(margin) !== 'ok').length,\n orgsUnderwater: margins.filter((margin) => margin < 0).length,\n totalCogsUsd: Math.round(rows.reduce((sum, row) => sum + row.cogs.cogsUsd, 0) * 100) / 100,\n totalNetRevenueUsd:\n Math.round(rows.reduce((sum, row) => sum + row.netRevenueUsd, 0) * 100) / 100,\n totalAiAddonRevenueUsd:\n Math.round(\n rows.reduce((sum, row) => sum + (row.aiAddonRevenueUsd ?? 0), 0) * 100,\n ) / 100,\n }\n}\n\n/**\n * Worst margin first — the ordering the surface exists for.\n *\n * An org with no revenue has no margin and sorts to the END regardless. It\n * cannot be the unprofitable customer this list is looking for, and putting\n * the free tier at the top would bury the one org that is.\n */\nexport function byWorstMargin(a: OrgMarginRow, b: OrgMarginRow): number {\n if (a.marginPct === null && b.marginPct === null) return b.cogs.cogsUsd - a.cogs.cogsUsd\n if (a.marginPct === null) return 1\n if (b.marginPct === null) return -1\n return a.marginPct - b.marginPct || b.cogs.cogsUsd - a.cogs.cogsUsd\n}\n\n/** Re-exported so the page states the floor it is colouring against. */\nexport { INFRA_COGS_PER_SITE_USD }\n"],"names":["assistCreditsFromUsd","hasAiAddon","INFRA_COGS_PER_SITE_USD","MARGIN_SCOPE_NOTE","netMarginRating","PLAN_PRICING","orgListPriceMonthlyUsd","orgMonthlyCogsUsd","orgMonthlyRevenueUsd","orgNetMonthlyRevenueUsd","orgSiteCount","pageViewsFromBandwidthGb","resolveOrgEntitlements","resolveEffectivePlan","UTILIZATION_BANDS","UTILIZATION_BAND_LABELS","hosts","storageGb","pageViews","formSubmissions","dataStorageMb","apiRequests","contactsCount","emailSends","assistCredits","workflowRuns","actionRuns","finite","value","parsed","Number","isFinite","orgIncludedBands","org","entitlements","hostLimit","Math","max","storagePerHostMb","formSubmissionsPerMonth","bandwidthGb","dataStorageMbPerOrg","apiRequestsPerMonth","contactsPerHost","emailSendsPerMonth","assistCreditsPerMonth","workflowRunsPerMonth","actionRunsPerMonth","bandUtilization","band","used","included","usedSafe","POSITIVE_INFINITY","state","fraction","bandUsage","rollup","crmRecordsCount","assistCostUsd","orgMarginRow","input","bands","Object","fromEntries","map","cogs","listPriceUsd","mrrUsd","netRevenueUsd","plan","customPriced","subscription","customMonthlyUsd","aiAddonRevenueUsd","aiAddonMonthlyUsd","marginPct","round","cogsUsd","orgId","name","month","rating","percentile","sorted","p","length","rank","ceil","min","fleetUtilization","rows","distributions","sample","excludedUncapped","excludedNoAllowance","usageWithNoAllowance","row","reading","push","sort","a","b","counted","p50","p25","p75","p90","overBand","filter","margins","margin","orgs","withRollup","medianMarginPct","orgsUnderFloor","orgsUnderwater","totalCogsUsd","reduce","sum","totalNetRevenueUsd","totalAiAddonRevenueUsd","byWorstMargin"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/margin-utilization.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// Imported from BOTH graphs — the staff page (client) and the App Route that\n// serves it — so it may import neither entry barrel, for the reason\n// `usage-metering.ts` states at its own head. The specific modules underneath\n// are safe in both.\nimport type { AglynOrgBilling, OrgPlan } from '../foundation'\nimport {\n hasAiAddon,\n INFRA_COGS_PER_SITE_USD,\n MARGIN_SCOPE_NOTE,\n netMarginRating,\n PLAN_PRICING,\n orgListPriceMonthlyUsd,\n orgMonthlyCogsUsd,\n orgMonthlyRevenueUsd,\n orgNetMonthlyRevenueUsd,\n orgSiteCount,\n pageViewsFromBandwidthGb,\n resolveOrgEntitlements,\n resolveEffectivePlan,\n type DiscountMarginRating,\n type OrgCogsResult,\n type OrgUsageRollupInput,\n} from './plan-entitlements'\nimport { planQuotaOf } from '../plugin-manager/plugin-plan-entitlements'\nimport {\n declaredMeterReading,\n pluginUsageBands,\n type ResolvedPluginUsageBand,\n} from '../plugin-manager/plugin-usage-axes'\n\nexport { MARGIN_SCOPE_NOTE }\n\n/**\n * WHAT SHARE OF THE BANDS THEY BOUGHT DO CUSTOMERS ACTUALLY SPEND?\n *\n * `tier-margin-floor.spec.ts` proves what every tier costs at 100% of every\n * band, and pins the margin at 3 / 25 / 50 / 100%. Which of those four columns\n * the business is actually operating in has never been measured — and the\n * spread between them is the whole question, because on Pro the same price\n * list yields 89.3% at the 3% column and 7.1% at the 100% one.\n *\n * Everything needed to answer it was already recorded and already priced. The\n * monthly rollup carries the meters, `ORG_COGS_UNIT_RATES_USD` carries their\n * unit costs, and `orgMonthlyCogsUsd` already turns one into the other. What\n * did not exist was the DIVISION: usage over the band the plan sold, per org\n * and across the fleet.\n *\n * This module is that division, and nothing else. It runs no query, holds no\n * rate of its own and re-derives no cost — every dollar here comes back out of\n * `orgMonthlyCogsUsd`, so a surface built on it cannot come to disagree with\n * the discount guardrail about what an org costs.\n *\n * ## The three answers a band can give, and why two of them are not numbers\n *\n * A percentage needs a denominator, and two of the bands in `PLAN_ENTITLEMENTS`\n * do not supply one:\n *\n * * `UNLIMITED` — `Number.POSITIVE_INFINITY`. Enterprise carries it on\n * contacts, API requests and dataset storage. `used / Infinity` is 0, which\n * would render the platform's largest customers as its lightest users; and\n * a model that clamps to 100% instead would render them as its heaviest.\n * Both are inventions. The band is UNCAPPED, the fraction is `null`, and no\n * aggregate may average it in either direction.\n * * `0` — Free carries it on email sends, API requests and dataset storage.\n * `0 / 0` is `NaN` and `n / 0` is `Infinity`. Neither is a utilization. The\n * band admits nothing, so there is no allowance to be a fraction of, and an\n * org that spent the meter anyway did so with no included band behind it —\n * which is a cost signal in its own right, reported as\n * `usageWithNoAllowance` rather than folded into a percentile.\n *\n * The two are kept DISTINCT from each other and from `0%`. An uncapped band\n * and an exhausted one are opposite facts, and the recurring defect in this\n * repo's cost work is exactly that an absent figure reads as a zero one.\n *\n * ## What the bands are\n *\n * `meteredIncludedAllowance` sizes three of them and is the shape followed\n * here: the two PER-HOST bands are expanded by `hostLimit`, the rest are\n * org-wide as stored. `contactsPerHost` is org-wide DESPITE ITS NAME —\n * `checkContactQuota` compares it against an org-wide headcount, and\n * `tier-margin-floor.spec.ts` costs it unexpanded. Following either of those\n * and not the name is what keeps this table agreeing with both.\n */\n\n/**\n * The platform's own bands, at the positions the plugins' declared bands\n * interleave with (`order`, a multiple of 10 here) — every meter\n * `orgMonthlyCogsUsd` prices that is the platform's, plus `hosts`.\n */\nconst CORE_BANDS: ReadonlyArray<{ id: string; label: string; order: number }> = [\n { id: 'hosts', label: 'Sites', order: 10 },\n { id: 'storageGb', label: 'Media storage', order: 20 },\n { id: 'pageViews', label: 'Bandwidth (page views)', order: 30 },\n { id: 'dataStorageMb', label: 'Dataset storage', order: 50 },\n { id: 'apiRequests', label: 'API requests', order: 60 },\n { id: 'emailSends', label: 'Email sends', order: 80 },\n]\n\n/**\n * Every band the table reads, in column order: the platform's, and each one a\n * plugin declares beside the meter it contributes (`plugin-usage-axes.ts`) —\n * the AI plugin's credits, the CRM's records, the forms plugin's\n * submissions, the workflows plugin's runs.\n *\n * A plugin band sold in a unit OF cost is measured in that unit rather than\n * in the dollars the rollup stores: the AI plugin's credits are the dollars\n * over its declared unit cost, rounded up, which is the one conversion the\n * customer's own meter uses. It is also the band most worth watching —\n * provider spend is the only line item on the platform whose unit cost is\n * real money paid to a third party, and the only one that can clear the\n * $2/site floor on its own.\n */\nconst BANDS: ReadonlyArray<{\n id: string\n label: string\n order: number\n declared?: ResolvedPluginUsageBand\n}> = [\n ...CORE_BANDS,\n ...pluginUsageBands().map((declared) => ({\n id: declared.id,\n label: declared.label,\n order: declared.order,\n declared,\n })),\n].sort((a, b) => a.order - b.order)\n\n/** Every band, keyed by what it measures, in column order. */\nexport const UTILIZATION_BANDS: readonly string[] = BANDS.map((band) => band.id)\n\nexport type UtilizationBand = string\n\n/** Column headings, so the page and the aggregate name a band identically. */\nexport const UTILIZATION_BAND_LABELS: Readonly<Record<UtilizationBand, string>> =\n Object.fromEntries(BANDS.map((band) => [band.id, band.label]))\n\n/**\n * Why a band has no percentage, or that it has one.\n *\n * `uncapped` and `noAllowance` are the two non-numbers described at the head\n * of this module. They are separate values rather than one `null` because the\n * remedies are opposite: an uncapped band is a pricing exposure, an exhausted\n * one is an upgrade conversation.\n */\nexport type BandState = 'measured' | 'uncapped' | 'noAllowance'\n\nexport interface BandUtilization {\n band: UtilizationBand\n /** The org-wide included allowance. `Infinity` when uncapped, 0 when none. */\n included: number\n /** What the rollup recorded. Always a finite, non-negative number. */\n used: number\n state: BandState\n /**\n * Share of the band consumed, as a fraction of 1.\n *\n * `null` for every state but `measured`, and callers must render it as\n * neither 0 nor 1. It is NOT clamped: an org past its included band reads\n * above 1, which is the number worth seeing.\n */\n fraction: number | null\n}\n\n/** One org's realised position: what it consumed, what it cost, what it paid. */\nexport interface OrgMarginRow {\n orgId: string\n name: string | null\n plan: OrgPlan\n /** The rollup month these figures describe, or null when none has run. */\n month: string | null\n bands: Record<UtilizationBand, BandUtilization>\n /**\n * The shared cost model's answer — the same function and the same fields\n * `/api/admin/org-discount` underwrites against.\n */\n cogs: OrgCogsResult\n /** Pre-discount sticker price. */\n listPriceUsd: number\n /** List price net of any per-org discount. */\n mrrUsd: number\n /** MRR net of Stripe's processing fee — the base the margin is taken on. */\n netRevenueUsd: number\n /**\n * The AI add-on's share of `listPriceUsd` (AGL-2930) — already INSIDE\n * `listPriceUsd`, `mrrUsd` and `netRevenueUsd`, because\n * `orgListPriceMonthlyUsd` folds every purchased add-on. Named here so the\n * surface can show that the assist band's cost has revenue against it\n * rather than reading as pure margin drag; adding it again would count it\n * twice. 0 on a custom-priced deal, whose quote already folds the add-on\n * into one negotiated figure.\n */\n aiAddonRevenueUsd: number\n /**\n * `(net revenue − COGS) / net revenue`, the same arithmetic\n * `checkDiscountMargin` rates a discount on. See `MARGIN_SCOPE_NOTE`: it is\n * a contribution margin, not a profit.\n *\n * `null` when the org bills nothing. A free or comped org has no revenue to\n * take a fraction of, and reporting it as 0% or −100% would put a customer\n * who was never meant to be profitable at the top of a list whose whole\n * purpose is finding the one who was.\n */\n marginPct: number | null\n rating: DiscountMarginRating | null\n}\n\n/**\n * A rollup as this surface reads it: every field `orgMonthlyCogsUsd` prices.\n *\n * The two run counters used to be the surface's own extension of that shape\n * — recorded and banded but unpriced. Since 2026-09-07 the cost model prices\n * them at `perRun`, so the shape is the model's and nothing here is extra.\n */\nexport type UtilizationRollup = OrgUsageRollupInput\n\nconst finite = (value: unknown): number => {\n const parsed = Number(value ?? 0)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 0\n}\n\n/**\n * The org-wide included band for every metered axis, on this org's plan and\n * with this org's overrides and add-ons applied.\n *\n * `resolveOrgEntitlements` rather than `PLAN_ENTITLEMENTS[plan]`: a staff\n * entitlement override and a purchased host add-on both change what the\n * customer actually bought, and measuring consumption against the catalogue\n * row instead of the resolved one is the same class of error as measuring it\n * against a constant.\n */\nexport function orgIncludedBands(\n org: Partial<AglynOrgBilling> | null | undefined,\n): Record<UtilizationBand, number> {\n const entitlements = resolveOrgEntitlements(org)\n const hostLimit = Math.max(1, entitlements.hostLimit)\n const included: Record<UtilizationBand, number> = {\n hosts: entitlements.hostLimit,\n // Per host, expanded by the host limit — `meteredIncludedAllowance`\n // expands the per-host bands and nothing else.\n storageGb: (hostLimit * entitlements.storagePerHostMb) / 1024,\n // Bandwidth IS the page-view band, expressed in the unit customers buy.\n pageViews: pageViewsFromBandwidthGb(entitlements.bandwidthGb),\n // Org-wide, in the unit the rollup stores. `dataStorageMbPerOrg` is\n // megabytes and so is `dataStorageMb`; the GB conversion happens inside\n // `orgMonthlyCogsUsd` and must not happen twice.\n dataStorageMb: entitlements.dataStorageMbPerOrg,\n apiRequests: entitlements.apiRequestsPerMonth,\n emailSends: entitlements.emailSendsPerMonth,\n }\n // A plugin's band reads the entitlement its declaration names — absent is\n // nothing included — expanded by the host limit where it is per site.\n for (const band of pluginUsageBands()) {\n const figure = planQuotaOf(entitlements, band.entitlement)\n included[band.id] = band.perHost ? hostLimit * figure : figure\n }\n return included\n}\n\n/** One band's reading. The whole of the `UNLIMITED` and zero-band rule. */\nexport function bandUtilization(\n band: UtilizationBand,\n used: number,\n included: number,\n): BandUtilization {\n const usedSafe = Number.isFinite(used) && used > 0 ? used : 0\n if (!Number.isFinite(included)) {\n return { band, included: Number.POSITIVE_INFINITY, used: usedSafe, state: 'uncapped', fraction: null }\n }\n if (!(included > 0)) {\n return { band, included: 0, used: usedSafe, state: 'noAllowance', fraction: null }\n }\n return { band, included, used: usedSafe, state: 'measured', fraction: usedSafe / included }\n}\n\n/**\n * What the rollup recorded, per band.\n *\n * `hosts` comes from the ORG rather than the rollup: `orgSiteCount` is what\n * the cost floor is charged on and what `checkDiscountMargin` counts, and a\n * site added since the cron last ran is a site the org has. The rollup's own\n * `hostCount` is a snapshot of the same thing at cron time.\n */\nfunction bandUsage(\n org: Partial<AglynOrgBilling> | null | undefined,\n rollup: UtilizationRollup | null | undefined,\n): Record<UtilizationBand, number> {\n const used: Record<UtilizationBand, number> = {\n hosts: orgSiteCount(org),\n storageGb: finite(rollup?.storageGb),\n pageViews: finite(rollup?.pageViews),\n dataStorageMb: finite(rollup?.dataStorageMb),\n apiRequests: finite(rollup?.apiRequests),\n emailSends: finite(rollup?.emailSends),\n }\n for (const band of pluginUsageBands()) {\n const reading = declaredMeterReading(rollup, band)\n // Dollars on the rollup, a unit of cost on the band: rounded UP, so spend\n // already made never reads as less of the band.\n used[band.id] =\n band.unitCostUsd && reading > 0\n ? Math.ceil(reading / band.unitCostUsd)\n : reading\n }\n return used\n}\n\nexport interface OrgMarginInput {\n orgId: string\n name?: string | null\n /** The merged `{...orgDoc, ...billingDoc}` every revenue helper reads. */\n org: Partial<AglynOrgBilling> | null | undefined\n rollup: UtilizationRollup | null | undefined\n month?: string | null\n}\n\n/**\n * One org's realised utilization, cost and margin.\n *\n * The cost is `orgMonthlyCogsUsd`'s, unmodified, floor included — the same\n * `max(measured, $2 × sites)` the guardrail applies. On production today that\n * floor is what every org's figure is, which is worth reading off the `basis`\n * field rather than assuming the meters are driving anything yet.\n */\nexport function orgMarginRow(input: OrgMarginInput): OrgMarginRow {\n const { org, rollup } = input\n const included = orgIncludedBands(org)\n const used = bandUsage(org, rollup)\n const bands = Object.fromEntries(\n UTILIZATION_BANDS.map((band) => [band, bandUtilization(band, used[band], included[band])]),\n ) as Record<UtilizationBand, BandUtilization>\n\n const cogs = orgMonthlyCogsUsd(rollup, orgSiteCount(org))\n const listPriceUsd = orgListPriceMonthlyUsd(org)\n const mrrUsd = orgMonthlyRevenueUsd(org)\n const netRevenueUsd = orgNetMonthlyRevenueUsd(org)\n // Only when the org bills something at all — `listPriceUsd` is 0 for a\n // comped or lapsed org whatever its `seatAddons` say, and the add-on's\n // share of nothing is nothing. `hasAiAddon` already drops a dead\n // subscription's add-on; the list-price check catches the comped one.\n const plan = resolveEffectivePlan(org)\n const customPriced =\n typeof org?.subscription?.customMonthlyUsd === 'number' &&\n org.subscription.customMonthlyUsd > 0\n const aiAddonRevenueUsd =\n listPriceUsd > 0 && !customPriced && hasAiAddon(org)\n ? finite(PLAN_PRICING[plan]?.aiAddonMonthlyUsd)\n : 0\n // A margin needs revenue to be a fraction OF. Free and comped orgs have\n // none, and inventing one for them fills the top of a worst-first list with\n // customers who were never billed.\n const marginPct =\n netRevenueUsd > 0\n ? Math.round(((netRevenueUsd - cogs.cogsUsd) / netRevenueUsd) * 10000) / 10000\n : null\n\n return {\n orgId: input.orgId,\n name: input.name ?? null,\n plan,\n month: input.month ?? null,\n bands,\n cogs,\n listPriceUsd,\n mrrUsd,\n netRevenueUsd,\n aiAddonRevenueUsd,\n marginPct,\n rating: marginPct === null ? null : netMarginRating(marginPct),\n }\n}\n\n/**\n * Nearest-rank percentile over a sample sorted ascending.\n *\n * `null` for an empty sample. A median of nothing is not zero, and the fleet\n * view exists to answer a question that has no answer yet for most bands.\n */\nexport function percentile(sorted: readonly number[], p: number): number | null {\n if (!sorted.length) return null\n const rank = Math.ceil(p * sorted.length)\n return sorted[Math.min(sorted.length - 1, Math.max(0, rank - 1))]\n}\n\nexport interface BandDistribution {\n band: UtilizationBand\n /**\n * Orgs a percentage EXISTS for — a finite, positive band. Every percentile\n * below is over this sample and no other.\n */\n counted: number\n /** Excluded because the band is `UNLIMITED`. Not 0%, not 100%: no answer. */\n excludedUncapped: number\n /** Excluded because the plan includes none of this meter. */\n excludedNoAllowance: number\n /**\n * Orgs with no included allowance that spent the meter anyway — cost with no\n * band behind it. Excluded from the percentiles, reported on its own.\n */\n usageWithNoAllowance: number\n p50: number | null\n p25: number | null\n p75: number | null\n p90: number | null\n max: number | null\n /** Orgs at or past 100% of the band, out of `counted`. */\n overBand: number\n}\n\nexport interface FleetUtilization {\n /** Orgs folded in — every row handed to it, whatever its plan. */\n orgs: number\n /** Of those, how many had a usage rollup at all. */\n withRollup: number\n distributions: BandDistribution[]\n /** Median contribution margin across orgs that bill something. */\n medianMarginPct: number | null\n /** Orgs billing something whose margin sits under `NET_MARGIN_FLOOR_PCT`. */\n orgsUnderFloor: number\n /** Orgs billing something whose COGS exceeds their net revenue. */\n orgsUnderwater: number\n /** Total measured COGS and net revenue across the fold, in USD. */\n totalCogsUsd: number\n totalNetRevenueUsd: number\n /** Of `totalNetRevenueUsd`'s gross, how much the AI add-on brought in. */\n totalAiAddonRevenueUsd: number\n}\n\n/**\n * The fleet answer: the median and spread of utilization per band.\n *\n * This is the figure every margin projection has been assuming. It is folded\n * over whatever rows the caller read — see the route's truncation disclosure,\n * because a distribution over a partial fleet is a distribution over a partial\n * fleet and must say so.\n *\n * An org WITHOUT a rollup still contributes its `hosts` band (the org doc\n * knows its own sites) and is excluded from the other seven by having no\n * measurement, not by being scored at zero. `withRollup` is what says how much\n * of the fold is real.\n */\nexport function fleetUtilization(rows: readonly OrgMarginRow[]): FleetUtilization {\n const distributions = UTILIZATION_BANDS.map<BandDistribution>((band) => {\n const sample: number[] = []\n let excludedUncapped = 0\n let excludedNoAllowance = 0\n let usageWithNoAllowance = 0\n for (const row of rows) {\n const reading = row.bands[band]\n if (!reading) continue\n // A band the cron has never measured contributes nothing rather than a\n // zero: `hosts` is known without a rollup, the seven meters are not.\n if (band !== 'hosts' && row.month === null) continue\n if (reading.state === 'uncapped') {\n excludedUncapped += 1\n continue\n }\n if (reading.state === 'noAllowance') {\n excludedNoAllowance += 1\n if (reading.used > 0) usageWithNoAllowance += 1\n continue\n }\n sample.push(reading.fraction as number)\n }\n sample.sort((a, b) => a - b)\n return {\n band,\n counted: sample.length,\n excludedUncapped,\n excludedNoAllowance,\n usageWithNoAllowance,\n p50: percentile(sample, 0.5),\n p25: percentile(sample, 0.25),\n p75: percentile(sample, 0.75),\n p90: percentile(sample, 0.9),\n max: sample.length ? sample[sample.length - 1] : null,\n overBand: sample.filter((fraction) => fraction >= 1).length,\n }\n })\n\n const margins = rows\n .map((row) => row.marginPct)\n .filter((margin): margin is number => margin !== null)\n .sort((a, b) => a - b)\n\n return {\n orgs: rows.length,\n withRollup: rows.filter((row) => row.month !== null).length,\n distributions,\n medianMarginPct: percentile(margins, 0.5),\n orgsUnderFloor: margins.filter((margin) => netMarginRating(margin) !== 'ok').length,\n orgsUnderwater: margins.filter((margin) => margin < 0).length,\n totalCogsUsd: Math.round(rows.reduce((sum, row) => sum + row.cogs.cogsUsd, 0) * 100) / 100,\n totalNetRevenueUsd:\n Math.round(rows.reduce((sum, row) => sum + row.netRevenueUsd, 0) * 100) / 100,\n totalAiAddonRevenueUsd:\n Math.round(\n rows.reduce((sum, row) => sum + (row.aiAddonRevenueUsd ?? 0), 0) * 100,\n ) / 100,\n }\n}\n\n/**\n * Worst margin first — the ordering the surface exists for.\n *\n * An org with no revenue has no margin and sorts to the END regardless. It\n * cannot be the unprofitable customer this list is looking for, and putting\n * the free tier at the top would bury the one org that is.\n */\nexport function byWorstMargin(a: OrgMarginRow, b: OrgMarginRow): number {\n if (a.marginPct === null && b.marginPct === null) return b.cogs.cogsUsd - a.cogs.cogsUsd\n if (a.marginPct === null) return 1\n if (b.marginPct === null) return -1\n return a.marginPct - b.marginPct || b.cogs.cogsUsd - a.cogs.cogsUsd\n}\n\n/** Re-exported so the page states the floor it is colouring against. */\nexport { INFRA_COGS_PER_SITE_USD }\n"],"names":["hasAiAddon","INFRA_COGS_PER_SITE_USD","MARGIN_SCOPE_NOTE","netMarginRating","PLAN_PRICING","orgListPriceMonthlyUsd","orgMonthlyCogsUsd","orgMonthlyRevenueUsd","orgNetMonthlyRevenueUsd","orgSiteCount","pageViewsFromBandwidthGb","resolveOrgEntitlements","resolveEffectivePlan","planQuotaOf","declaredMeterReading","pluginUsageBands","CORE_BANDS","id","label","order","BANDS","map","declared","sort","a","b","UTILIZATION_BANDS","band","UTILIZATION_BAND_LABELS","Object","fromEntries","finite","value","parsed","Number","isFinite","orgIncludedBands","org","entitlements","hostLimit","Math","max","included","hosts","storageGb","storagePerHostMb","pageViews","bandwidthGb","dataStorageMb","dataStorageMbPerOrg","apiRequests","apiRequestsPerMonth","emailSends","emailSendsPerMonth","figure","entitlement","perHost","bandUtilization","used","usedSafe","POSITIVE_INFINITY","state","fraction","bandUsage","rollup","reading","unitCostUsd","ceil","orgMarginRow","input","bands","cogs","listPriceUsd","mrrUsd","netRevenueUsd","plan","customPriced","subscription","customMonthlyUsd","aiAddonRevenueUsd","aiAddonMonthlyUsd","marginPct","round","cogsUsd","orgId","name","month","rating","percentile","sorted","p","length","rank","min","fleetUtilization","rows","distributions","sample","excludedUncapped","excludedNoAllowance","usageWithNoAllowance","row","push","counted","p50","p25","p75","p90","overBand","filter","margins","margin","orgs","withRollup","medianMarginPct","orgsUnderFloor","orgsUnderwater","totalCogsUsd","reduce","sum","totalNetRevenueUsd","totalAiAddonRevenueUsd","byWorstMargin"],"mappings":"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"}
@@ -17,7 +17,7 @@
17
17
  import { SAFE_SRC_PATTERN } from './node-url-policy';
18
18
  import { SAFE_HREF_PATTERN } from './screen-link-value';
19
19
  /**
20
- * Component ids publishable to the marketplace. Mirrors the persisted ids in
20
+ * Component ids a portable tree may hold. Mirrors the persisted ids in
21
21
  * plugins-mui (plugin.spec.ts) minus `reusableInstance` — nested
22
22
  * instances would smuggle references to another tenant's private
23
23
  * definitions — and minus `layoutSlot`, which is layout chrome. Keep sorted.
@@ -35,7 +35,7 @@ import { SAFE_HREF_PATTERN } from './screen-link-value';
35
35
  * so the whole category was unpublishable. `blocks-publishable.spec.ts` walks
36
36
  * the presets and fails if this list falls behind them again.
37
37
  */
38
- export declare const MARKETPLACE_COMPONENT_ID_ALLOWLIST: readonly string[];
38
+ export declare const PORTABLE_COMPONENT_ID_ALLOWLIST: readonly string[];
39
39
  /**
40
40
  * Email block ids publishable as an `emailTemplate` (AGL-657).
41
41
  *
@@ -50,7 +50,7 @@ export declare const MARKETPLACE_COMPONENT_ID_ALLOWLIST: readonly string[];
50
50
  * else's domain is a phishing and tracking-pixel vector that no amount of
51
51
  * render-time sanitization makes reviewable. Keep sorted.
52
52
  */
53
- export declare const MARKETPLACE_EMAIL_COMPONENT_ID_ALLOWLIST: readonly string[];
53
+ export declare const PORTABLE_EMAIL_COMPONENT_ID_ALLOWLIST: readonly string[];
54
54
  /**
55
55
  * Email block ids publishable as an `emailStarter` — the transactional list
56
56
  * minus `emailRichtext`.
@@ -74,20 +74,20 @@ export declare const MARKETPLACE_EMAIL_COMPONENT_ID_ALLOWLIST: readonly string[]
74
74
  * paragraph, a link, a button — the remaining blocks already express as a
75
75
  * structured tree we render ourselves.
76
76
  */
77
- export declare const MARKETPLACE_EMAIL_STARTER_COMPONENT_ID_ALLOWLIST: readonly string[];
77
+ export declare const PORTABLE_EMAIL_STARTER_COMPONENT_ID_ALLOWLIST: readonly string[];
78
78
  /** Serialized definition size cap (Firestore doc limit is 1 MiB). */
79
- export declare const MARKETPLACE_DEFINITION_MAX_BYTES: number;
79
+ export declare const PORTABLE_DEFINITION_MAX_BYTES: number;
80
80
  /**
81
81
  * The address rules a published node's `href` and `src` meet — the linking
82
82
  * elements' navigable protocols, and https, inline or site-relative images
83
83
  * (see `node-url-policy`).
84
84
  *
85
- * Exported under these names for the marketplace's property sanitizer, which
86
- * holds a published property's Link default to the same rule as a published
87
- * node's `href`, and an Image default to the `src` rule.
85
+ * Exported under these names for a property sanitizer (the marketplace's is
86
+ * one), which holds a portable property's Link default to the same rule as a
87
+ * portable node's `href`, and an Image default to the `src` rule.
88
88
  */
89
- export { SAFE_HREF_PATTERN as MARKETPLACE_SAFE_HREF, SAFE_SRC_PATTERN as MARKETPLACE_SAFE_SRC, };
90
- export type MarketplaceDefinitionNodes = Record<string, {
89
+ export { SAFE_HREF_PATTERN as PORTABLE_SAFE_HREF, SAFE_SRC_PATTERN as PORTABLE_SAFE_SRC, };
90
+ export type PortableDefinitionNodes = Record<string, {
91
91
  $id: string;
92
92
  componentId: string;
93
93
  pluginId?: string;
@@ -110,7 +110,7 @@ export type MarketplaceDefinitionNodes = Record<string, {
110
110
  * `src`) are hardened HERE as well, because publishing hands them to a
111
111
  * different org's render tree.
112
112
  */
113
- export declare function sanitizeMarketplaceDefinition(definition: {
113
+ export declare function sanitizePortableDefinition(definition: {
114
114
  rootId: string;
115
115
  nodes: Record<string, any>;
116
116
  }, options?: {
@@ -143,22 +143,22 @@ export declare function sanitizeMarketplaceDefinition(definition: {
143
143
  }): {
144
144
  ok: true;
145
145
  rootId: string;
146
- nodes: MarketplaceDefinitionNodes;
146
+ nodes: PortableDefinitionNodes;
147
147
  } | {
148
148
  ok: false;
149
149
  error: string;
150
150
  };
151
151
  /**
152
- * Converts a sanitized marketplace/AI definition (normalized map) into the
152
+ * Converts a sanitized portable definition (normalized map) into the
153
153
  * nested node shape `canvas.addNodeFromPreset` grafts — ids regenerate on
154
154
  * insert, so collisions with existing canvas nodes are impossible
155
155
  * (AGL-169). A seen-set guards malformed self-referencing trees.
156
156
  */
157
- export declare function marketplaceDefinitionToNested(rootId: string, nodes: MarketplaceDefinitionNodes): Record<string, unknown> | null;
157
+ export declare function portableDefinitionToNested(rootId: string, nodes: PortableDefinitionNodes): Record<string, unknown> | null;
158
158
  /**
159
- * Field types a published dataset schema may declare (AGL-657). Mirrors
159
+ * Field types a portable dataset schema may declare (AGL-657). Mirrors
160
160
  * `DATASET_FIELD_TYPES` in core; duplicated rather than imported to keep this
161
161
  * module dependency-free (it is imported by API routes and client components
162
162
  * alike), and asserted against the source of truth in the spec.
163
163
  */
164
- export declare const MARKETPLACE_DATASET_FIELD_TYPES: readonly string[];
164
+ export declare const PORTABLE_DATASET_FIELD_TYPES: readonly string[];
@@ -15,17 +15,21 @@
15
15
  * limitations under the License.
16
16
  */ /**
17
17
  * The node-definition sanitizer and the component allowlists (AGL-2939):
18
- * what a node tree must satisfy before it lands on a canvas that did not
19
- * author it — a marketplace install, and a tree a model generated. Core
20
- * rather than the marketplace plugin, because two plugins pass their trees
21
- * through it and a plugin may not import another plugin's model; the
18
+ * what a PORTABLE node tree must satisfy — one that lands on a canvas that did
19
+ * not author it. Today that is a tree installed from another workspace and a
20
+ * tree a model generated, and the rule is the same for both: whatever reaches
21
+ * the canvas renders in a site whose author never saw it.
22
+ *
23
+ * The platform's rule, so named for what it guards rather than for who asks
24
+ * (AGL-3080): any plugin that brings a tree in from elsewhere passes it
25
+ * through here, and a plugin may not import another plugin's model. The
22
26
  * marketplace re-exports it under the names its publishers use.
23
27
  */ import { _ as _extends } from "@swc/helpers/_/_extends";
24
28
  import { matchComponentPropToken } from "./compose-reusable-components.js";
25
29
  import { isSafeNodeUrl, SAFE_SRC_PATTERN } from "./node-url-policy.js";
26
30
  import { SAFE_HREF_PATTERN } from "./screen-link-value.js";
27
31
  /**
28
- * Component ids publishable to the marketplace. Mirrors the persisted ids in
32
+ * Component ids a portable tree may hold. Mirrors the persisted ids in
29
33
  * plugins-mui (plugin.spec.ts) minus `reusableInstance` — nested
30
34
  * instances would smuggle references to another tenant's private
31
35
  * definitions — and minus `layoutSlot`, which is layout chrome. Keep sorted.
@@ -42,7 +46,7 @@ import { SAFE_HREF_PATTERN } from "./screen-link-value.js";
42
46
  * came to be missing (AGL-1033): every Sections & Blocks preset composes it,
43
47
  * so the whole category was unpublishable. `blocks-publishable.spec.ts` walks
44
48
  * the presets and fails if this list falls behind them again.
45
- */ export const MARKETPLACE_COMPONENT_ID_ALLOWLIST = [
49
+ */ export const PORTABLE_COMPONENT_ID_ALLOWLIST = [
46
50
  'form',
47
51
  'formField',
48
52
  'image',
@@ -76,7 +80,7 @@ import { SAFE_HREF_PATTERN } from "./screen-link-value.js";
76
80
  * scripts, so this isn't XSS — it's that arbitrary markup sent from someone
77
81
  * else's domain is a phishing and tracking-pixel vector that no amount of
78
82
  * render-time sanitization makes reviewable. Keep sorted.
79
- */ export const MARKETPLACE_EMAIL_COMPONENT_ID_ALLOWLIST = [
83
+ */ export const PORTABLE_EMAIL_COMPONENT_ID_ALLOWLIST = [
80
84
  'emailButton',
81
85
  'emailDivider',
82
86
  'emailImage',
@@ -108,8 +112,8 @@ import { SAFE_HREF_PATTERN } from "./screen-link-value.js";
108
112
  * re-making, and everything rich text can express in an email — a heading, a
109
113
  * paragraph, a link, a button — the remaining blocks already express as a
110
114
  * structured tree we render ourselves.
111
- */ export const MARKETPLACE_EMAIL_STARTER_COMPONENT_ID_ALLOWLIST = MARKETPLACE_EMAIL_COMPONENT_ID_ALLOWLIST.filter((componentId)=>componentId !== 'emailRichtext');
112
- /** Serialized definition size cap (Firestore doc limit is 1 MiB). */ export const MARKETPLACE_DEFINITION_MAX_BYTES = 200 * 1024;
115
+ */ export const PORTABLE_EMAIL_STARTER_COMPONENT_ID_ALLOWLIST = PORTABLE_EMAIL_COMPONENT_ID_ALLOWLIST.filter((componentId)=>componentId !== 'emailRichtext');
116
+ /** Serialized definition size cap (Firestore doc limit is 1 MiB). */ export const PORTABLE_DEFINITION_MAX_BYTES = 200 * 1024;
113
117
  const KEPT_NODE_KEYS = [
114
118
  '$id',
115
119
  'componentId',
@@ -123,10 +127,10 @@ const KEPT_NODE_KEYS = [
123
127
  * elements' navigable protocols, and https, inline or site-relative images
124
128
  * (see `node-url-policy`).
125
129
  *
126
- * Exported under these names for the marketplace's property sanitizer, which
127
- * holds a published property's Link default to the same rule as a published
128
- * node's `href`, and an Image default to the `src` rule.
129
- */ export { SAFE_HREF_PATTERN as MARKETPLACE_SAFE_HREF, SAFE_SRC_PATTERN as MARKETPLACE_SAFE_SRC, };
130
+ * Exported under these names for a property sanitizer (the marketplace's is
131
+ * one), which holds a portable property's Link default to the same rule as a
132
+ * portable node's `href`, and an Image default to the `src` rule.
133
+ */ export { SAFE_HREF_PATTERN as PORTABLE_SAFE_HREF, SAFE_SRC_PATTERN as PORTABLE_SAFE_SRC, };
130
134
  /**
131
135
  * The property kind whose value may fill each address prop through a binding
132
136
  * (AGL-2933): a Link feeds an `href`, an Image a `src`. Each kind's default is
@@ -238,11 +242,11 @@ const KEPT_NODE_KEYS = [
238
242
  * that are spread onto the DOM (`dangerouslySetInnerHTML`, `on*`, `href`,
239
243
  * `src`) are hardened HERE as well, because publishing hands them to a
240
244
  * different org's render tree.
241
- */ export function sanitizeMarketplaceDefinition(definition, options) {
245
+ */ export function sanitizePortableDefinition(definition, options) {
242
246
  var _ref;
243
247
  var _options_extraComponentIds;
244
248
  const { rootId, nodes } = definition;
245
- const base = (_ref = options == null ? void 0 : options.componentIds) != null ? _ref : MARKETPLACE_COMPONENT_ID_ALLOWLIST;
249
+ const base = (_ref = options == null ? void 0 : options.componentIds) != null ? _ref : PORTABLE_COMPONENT_ID_ALLOWLIST;
246
250
  const allowed = (options == null ? void 0 : (_options_extraComponentIds = options.extraComponentIds) == null ? void 0 : _options_extraComponentIds.length) ? [
247
251
  ...base,
248
252
  ...options.extraComponentIds
@@ -328,7 +332,7 @@ const KEPT_NODE_KEYS = [
328
332
  error: 'Definition is not serializable'
329
333
  };
330
334
  }
331
- if (serialized.length > MARKETPLACE_DEFINITION_MAX_BYTES) {
335
+ if (serialized.length > PORTABLE_DEFINITION_MAX_BYTES) {
332
336
  return {
333
337
  ok: false,
334
338
  error: 'Definition is too large to publish'
@@ -356,11 +360,11 @@ const KEPT_NODE_KEYS = [
356
360
  };
357
361
  }
358
362
  /**
359
- * Converts a sanitized marketplace/AI definition (normalized map) into the
363
+ * Converts a sanitized portable definition (normalized map) into the
360
364
  * nested node shape `canvas.addNodeFromPreset` grafts — ids regenerate on
361
365
  * insert, so collisions with existing canvas nodes are impossible
362
366
  * (AGL-169). A seen-set guards malformed self-referencing trees.
363
- */ export function marketplaceDefinitionToNested(rootId, nodes) {
367
+ */ export function portableDefinitionToNested(rootId, nodes) {
364
368
  const seen = new Set();
365
369
  const build = (id)=>{
366
370
  var _node_nodes;
@@ -380,11 +384,11 @@ const KEPT_NODE_KEYS = [
380
384
  return build(rootId);
381
385
  }
382
386
  /**
383
- * Field types a published dataset schema may declare (AGL-657). Mirrors
387
+ * Field types a portable dataset schema may declare (AGL-657). Mirrors
384
388
  * `DATASET_FIELD_TYPES` in core; duplicated rather than imported to keep this
385
389
  * module dependency-free (it is imported by API routes and client components
386
390
  * alike), and asserted against the source of truth in the spec.
387
- */ export const MARKETPLACE_DATASET_FIELD_TYPES = [
391
+ */ export const PORTABLE_DATASET_FIELD_TYPES = [
388
392
  'bool',
389
393
  'bytes',
390
394
  'coordinates',