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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/package.json +15 -11
  2. package/src/lib/app-utils/agent-openapi.js +1 -1
  3. package/src/lib/app-utils/agent-openapi.js.map +1 -1
  4. package/src/lib/app-utils/analytics-events.d.ts +3 -2
  5. package/src/lib/app-utils/analytics-events.js +3 -2
  6. package/src/lib/app-utils/analytics-events.js.map +1 -1
  7. package/src/lib/app-utils/api-plugins.d.ts +30 -0
  8. package/src/lib/app-utils/api-plugins.js +24 -1
  9. package/src/lib/app-utils/api-plugins.js.map +1 -1
  10. package/src/lib/app-utils/author-html.d.ts +1 -1
  11. package/src/lib/app-utils/author-html.js.map +1 -1
  12. package/src/lib/app-utils/bandwidth-cap.d.ts +12 -1
  13. package/src/lib/app-utils/bandwidth-cap.js +17 -1
  14. package/src/lib/app-utils/bandwidth-cap.js.map +1 -1
  15. package/src/lib/app-utils/compose-reusable-components.d.ts +19 -0
  16. package/src/lib/app-utils/compose-reusable-components.js +116 -26
  17. package/src/lib/app-utils/compose-reusable-components.js.map +1 -1
  18. package/src/lib/app-utils/console-routes.d.ts +4 -0
  19. package/src/lib/app-utils/console-routes.js +3 -0
  20. package/src/lib/app-utils/console-routes.js.map +1 -1
  21. package/src/lib/app-utils/contacts.d.ts +71 -0
  22. package/src/lib/app-utils/contacts.js +55 -0
  23. package/src/lib/app-utils/contacts.js.map +1 -1
  24. package/src/lib/app-utils/contexts.d.ts +1 -0
  25. package/src/lib/app-utils/contexts.js +1 -0
  26. package/src/lib/app-utils/contexts.js.map +1 -1
  27. package/src/lib/app-utils/crm-email-templates.js +21 -4
  28. package/src/lib/app-utils/crm-email-templates.js.map +1 -1
  29. package/src/lib/app-utils/crm-kinds.d.ts +2 -0
  30. package/src/lib/app-utils/crm-kinds.js +1 -0
  31. package/src/lib/app-utils/crm-kinds.js.map +1 -1
  32. package/src/lib/app-utils/crm.d.ts +1199 -67
  33. package/src/lib/app-utils/crm.js +1951 -310
  34. package/src/lib/app-utils/crm.js.map +1 -1
  35. package/src/lib/app-utils/csv-import.d.ts +10 -38
  36. package/src/lib/app-utils/csv-import.js +17 -42
  37. package/src/lib/app-utils/csv-import.js.map +1 -1
  38. package/src/lib/app-utils/csv.d.ts +32 -11
  39. package/src/lib/app-utils/csv.js +43 -5
  40. package/src/lib/app-utils/csv.js.map +1 -1
  41. package/src/lib/app-utils/default-site.d.ts +116 -0
  42. package/src/lib/app-utils/default-site.js +1024 -0
  43. package/src/lib/app-utils/default-site.js.map +1 -0
  44. package/src/lib/app-utils/docs-help.generated.d.ts +22 -22
  45. package/src/lib/app-utils/docs-help.generated.js +44 -10
  46. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  47. package/src/lib/app-utils/docs-index.generated.js +738 -150
  48. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  49. package/src/lib/app-utils/draft-placeholders.d.ts +76 -0
  50. package/src/lib/app-utils/draft-placeholders.js +115 -0
  51. package/src/lib/app-utils/draft-placeholders.js.map +1 -0
  52. package/src/lib/app-utils/entity-picker-context.d.ts +19 -15
  53. package/src/lib/app-utils/entity-picker-context.js +4 -4
  54. package/src/lib/app-utils/entity-picker-context.js.map +1 -1
  55. package/src/lib/app-utils/expand-repeatables.d.ts +66 -10
  56. package/src/lib/app-utils/expand-repeatables.js +88 -14
  57. package/src/lib/app-utils/expand-repeatables.js.map +1 -1
  58. package/src/lib/app-utils/forms.d.ts +33 -22
  59. package/src/lib/app-utils/forms.js +47 -28
  60. package/src/lib/app-utils/forms.js.map +1 -1
  61. package/src/lib/app-utils/full-use-cost.d.ts +277 -0
  62. package/src/lib/app-utils/full-use-cost.js +421 -0
  63. package/src/lib/app-utils/full-use-cost.js.map +1 -0
  64. package/src/lib/app-utils/health-report.js +11 -0
  65. package/src/lib/app-utils/health-report.js.map +1 -1
  66. package/src/lib/app-utils/llms-txt.d.ts +20 -0
  67. package/src/lib/app-utils/llms-txt.js +7 -1
  68. package/src/lib/app-utils/llms-txt.js.map +1 -1
  69. package/src/lib/app-utils/media-asset-facts.d.ts +16 -1
  70. package/src/lib/app-utils/media-asset-facts.js +40 -13
  71. package/src/lib/app-utils/media-asset-facts.js.map +1 -1
  72. package/src/lib/app-utils/media-bandwidth.d.ts +52 -0
  73. package/src/lib/app-utils/media-bandwidth.js +94 -0
  74. package/src/lib/app-utils/media-bandwidth.js.map +1 -0
  75. package/src/lib/app-utils/media-ref.d.ts +241 -33
  76. package/src/lib/app-utils/media-ref.js +324 -43
  77. package/src/lib/app-utils/media-ref.js.map +1 -1
  78. package/src/lib/app-utils/media-src.d.ts +8 -8
  79. package/src/lib/app-utils/media-src.js +27 -11
  80. package/src/lib/app-utils/media-src.js.map +1 -1
  81. package/src/lib/app-utils/media-takedown-reach.d.ts +4 -1
  82. package/src/lib/app-utils/media-takedown-reach.js +6 -3
  83. package/src/lib/app-utils/media-takedown-reach.js.map +1 -1
  84. package/src/lib/app-utils/notifications.d.ts +1 -1
  85. package/src/lib/app-utils/notifications.js +2 -0
  86. package/src/lib/app-utils/notifications.js.map +1 -1
  87. package/src/lib/app-utils/organizations.d.ts +51 -2
  88. package/src/lib/app-utils/organizations.js +56 -3
  89. package/src/lib/app-utils/organizations.js.map +1 -1
  90. package/src/lib/app-utils/page-record-sources.d.ts +87 -0
  91. package/src/lib/app-utils/page-record-sources.js +53 -0
  92. package/src/lib/app-utils/page-record-sources.js.map +1 -0
  93. package/src/lib/app-utils/paid-media-source.d.ts +3 -7
  94. package/src/lib/app-utils/paid-media-source.js +5 -19
  95. package/src/lib/app-utils/paid-media-source.js.map +1 -1
  96. package/src/lib/app-utils/person-erasure.d.ts +5 -2
  97. package/src/lib/app-utils/person-erasure.js.map +1 -1
  98. package/src/lib/app-utils/picklists.d.ts +187 -0
  99. package/src/lib/app-utils/picklists.js +320 -0
  100. package/src/lib/app-utils/picklists.js.map +1 -0
  101. package/src/lib/app-utils/plan-entitlements.d.ts +220 -50
  102. package/src/lib/app-utils/plan-entitlements.js +309 -115
  103. package/src/lib/app-utils/plan-entitlements.js.map +1 -1
  104. package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
  105. package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
  106. package/src/lib/app-utils/release-flags.d.ts +1 -1
  107. package/src/lib/app-utils/release-flags.js +11 -0
  108. package/src/lib/app-utils/release-flags.js.map +1 -1
  109. package/src/lib/app-utils/rendered-widths.d.ts +121 -0
  110. package/src/lib/app-utils/rendered-widths.js +211 -0
  111. package/src/lib/app-utils/rendered-widths.js.map +1 -0
  112. package/src/lib/app-utils/repeat-sources.d.ts +2 -1
  113. package/src/lib/app-utils/repeat-sources.js.map +1 -1
  114. package/src/lib/app-utils/screen-route.d.ts +154 -36
  115. package/src/lib/app-utils/screen-route.js +185 -40
  116. package/src/lib/app-utils/screen-route.js.map +1 -1
  117. package/src/lib/app-utils/seo-audit.d.ts +11 -1
  118. package/src/lib/app-utils/seo-audit.js +33 -13
  119. package/src/lib/app-utils/seo-audit.js.map +1 -1
  120. package/src/lib/app-utils/seo-keywords.d.ts +11 -0
  121. package/src/lib/app-utils/seo-keywords.js +19 -5
  122. package/src/lib/app-utils/seo-keywords.js.map +1 -1
  123. package/src/lib/app-utils/seo-page-facts.d.ts +47 -4
  124. package/src/lib/app-utils/seo-page-facts.js +136 -31
  125. package/src/lib/app-utils/seo-page-facts.js.map +1 -1
  126. package/src/lib/app-utils/seo-site-scan.d.ts +42 -4
  127. package/src/lib/app-utils/seo-site-scan.js +113 -12
  128. package/src/lib/app-utils/seo-site-scan.js.map +1 -1
  129. package/src/lib/app-utils/server.d.ts +5 -4
  130. package/src/lib/app-utils/server.js +14 -5
  131. package/src/lib/app-utils/server.js.map +1 -1
  132. package/src/lib/app-utils/site-icon-set.d.ts +195 -0
  133. package/src/lib/app-utils/site-icon-set.js +351 -0
  134. package/src/lib/app-utils/site-icon-set.js.map +1 -0
  135. package/src/lib/app-utils/site-interactions.d.ts +101 -4
  136. package/src/lib/app-utils/site-interactions.js +120 -4
  137. package/src/lib/app-utils/site-interactions.js.map +1 -1
  138. package/src/lib/app-utils/starter-template-nodes.d.ts +7 -0
  139. package/src/lib/app-utils/starter-template-nodes.js +3 -0
  140. package/src/lib/app-utils/starter-template-nodes.js.map +1 -1
  141. package/src/lib/app-utils/starter-templates.d.ts +5 -19
  142. package/src/lib/app-utils/starter-templates.js +5 -49
  143. package/src/lib/app-utils/starter-templates.js.map +1 -1
  144. package/src/lib/app-utils/transfer-launcher-context.d.ts +136 -0
  145. package/src/lib/app-utils/transfer-launcher-context.js +23 -0
  146. package/src/lib/app-utils/transfer-launcher-context.js.map +1 -0
  147. package/src/lib/app-utils/transfer-resume-import.d.ts +39 -0
  148. package/src/lib/app-utils/transfer-resume-import.js +128 -0
  149. package/src/lib/app-utils/transfer-resume-import.js.map +1 -0
  150. package/src/lib/app-utils/upgrade-proposal.d.ts +66 -0
  151. package/src/lib/app-utils/upgrade-proposal.js +95 -0
  152. package/src/lib/app-utils/upgrade-proposal.js.map +1 -0
  153. package/src/lib/app-utils/utc-month.d.ts +33 -0
  154. package/src/lib/app-utils/utc-month.js +35 -0
  155. package/src/lib/app-utils/utc-month.js.map +1 -0
  156. package/src/lib/app-utils/visitor-record-ceiling.d.ts +4 -4
  157. package/src/lib/app-utils/visitor-record-ceiling.js +4 -4
  158. package/src/lib/app-utils/visitor-record-ceiling.js.map +1 -1
  159. package/src/lib/app-utils/wire-nodes.d.ts +39 -19
  160. package/src/lib/app-utils/wire-nodes.js +120 -34
  161. package/src/lib/app-utils/wire-nodes.js.map +1 -1
  162. package/src/lib/data-transfer/access.d.ts +74 -0
  163. package/src/lib/data-transfer/access.js +93 -0
  164. package/src/lib/data-transfer/access.js.map +1 -0
  165. package/src/lib/data-transfer/derive.d.ts +246 -0
  166. package/src/lib/data-transfer/derive.js +1007 -0
  167. package/src/lib/data-transfer/derive.js.map +1 -0
  168. package/src/lib/data-transfer/export-file.d.ts +46 -0
  169. package/src/lib/data-transfer/export-file.js +146 -0
  170. package/src/lib/data-transfer/export-file.js.map +1 -0
  171. package/src/lib/data-transfer/field-catalog.d.ts +134 -0
  172. package/src/lib/data-transfer/field-catalog.js +278 -0
  173. package/src/lib/data-transfer/field-catalog.js.map +1 -0
  174. package/src/lib/data-transfer/header-match.d.ts +129 -0
  175. package/src/lib/data-transfer/header-match.js +367 -0
  176. package/src/lib/data-transfer/header-match.js.map +1 -0
  177. package/src/lib/data-transfer/index.d.ts +34 -0
  178. package/src/lib/data-transfer/index.js +40 -0
  179. package/src/lib/data-transfer/index.js.map +1 -0
  180. package/src/lib/data-transfer/job.d.ts +190 -0
  181. package/src/lib/data-transfer/job.js +229 -0
  182. package/src/lib/data-transfer/job.js.map +1 -0
  183. package/src/lib/data-transfer/lookup.d.ts +45 -0
  184. package/src/lib/data-transfer/lookup.js +71 -0
  185. package/src/lib/data-transfer/lookup.js.map +1 -0
  186. package/src/lib/data-transfer/match.d.ts +99 -0
  187. package/src/lib/data-transfer/match.js +244 -0
  188. package/src/lib/data-transfer/match.js.map +1 -0
  189. package/src/lib/data-transfer/package-plan.d.ts +205 -0
  190. package/src/lib/data-transfer/package-plan.js +475 -0
  191. package/src/lib/data-transfer/package-plan.js.map +1 -0
  192. package/src/lib/data-transfer/package.d.ts +181 -0
  193. package/src/lib/data-transfer/package.js +426 -0
  194. package/src/lib/data-transfer/package.js.map +1 -0
  195. package/src/lib/data-transfer/picklist-map.d.ts +139 -0
  196. package/src/lib/data-transfer/picklist-map.js +271 -0
  197. package/src/lib/data-transfer/picklist-map.js.map +1 -0
  198. package/src/lib/data-transfer/plan.d.ts +176 -0
  199. package/src/lib/data-transfer/plan.js +451 -0
  200. package/src/lib/data-transfer/plan.js.map +1 -0
  201. package/src/lib/data-transfer/policy.d.ts +170 -0
  202. package/src/lib/data-transfer/policy.js +378 -0
  203. package/src/lib/data-transfer/policy.js.map +1 -0
  204. package/src/lib/data-transfer/resource.d.ts +211 -0
  205. package/src/lib/data-transfer/resource.js +241 -0
  206. package/src/lib/data-transfer/resource.js.map +1 -0
  207. package/src/lib/data-transfer/review.d.ts +70 -0
  208. package/src/lib/data-transfer/review.js +233 -0
  209. package/src/lib/data-transfer/review.js.map +1 -0
  210. package/src/lib/data-transfer/similarity.d.ts +40 -0
  211. package/src/lib/data-transfer/similarity.js +106 -0
  212. package/src/lib/data-transfer/similarity.js.map +1 -0
  213. package/src/lib/data-transfer/site-package.d.ts +365 -0
  214. package/src/lib/data-transfer/site-package.js +1077 -0
  215. package/src/lib/data-transfer/site-package.js.map +1 -0
  216. package/src/lib/data-transfer/source.d.ts +74 -0
  217. package/src/lib/data-transfer/source.js +293 -0
  218. package/src/lib/data-transfer/source.js.map +1 -0
  219. package/src/lib/data-transfer/transfer-api.d.ts +898 -0
  220. package/src/lib/data-transfer/transfer-api.js +107 -0
  221. package/src/lib/data-transfer/transfer-api.js.map +1 -0
  222. package/src/lib/foundation/definitions/org-billing.types.d.ts +31 -0
  223. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
  224. package/src/lib/foundation/definitions/organization.types.d.ts +42 -1
  225. package/src/lib/foundation/definitions/organization.types.js.map +1 -1
  226. package/src/lib/foundation/definitions/platform.types.d.ts +6 -4
  227. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  228. package/src/lib/foundation/index.d.ts +1 -1
  229. package/src/lib/foundation/index.js.map +1 -1
  230. package/src/lib/plugin-manager/feature-plugins.d.ts +91 -2
  231. package/src/lib/plugin-manager/feature-plugins.js +48 -3
  232. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  233. package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +50 -0
  234. package/src/lib/plugin-manager/first-party-plugins.generated.js +1129 -28
  235. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  236. package/src/lib/plugin-manager/index.d.ts +0 -1
  237. package/src/lib/plugin-manager/index.js +0 -1
  238. package/src/lib/plugin-manager/index.js.map +1 -1
  239. package/src/lib/plugin-manager/interaction-recipes.d.ts +142 -0
  240. package/src/lib/plugin-manager/interaction-recipes.js +104 -0
  241. package/src/lib/plugin-manager/interaction-recipes.js.map +1 -0
  242. package/src/lib/plugin-manager/interaction-step-checks.d.ts +66 -0
  243. package/src/lib/plugin-manager/interaction-step-checks.js +86 -0
  244. package/src/lib/plugin-manager/interaction-step-checks.js.map +1 -0
  245. package/src/lib/plugin-manager/plugin-artifact-types.d.ts +235 -0
  246. package/src/lib/plugin-manager/plugin-artifact-types.js +99 -0
  247. package/src/lib/plugin-manager/plugin-artifact-types.js.map +1 -0
  248. package/src/lib/plugin-manager/plugin-contact-capture.d.ts +8 -0
  249. package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
  250. package/src/lib/plugin-manager/plugin-conversion-credit.d.ts +216 -0
  251. package/src/lib/plugin-manager/plugin-conversion-credit.js +91 -0
  252. package/src/lib/plugin-manager/plugin-conversion-credit.js.map +1 -0
  253. package/src/lib/plugin-manager/plugin-entity-pickers.d.ts +83 -0
  254. package/src/lib/plugin-manager/plugin-entity-pickers.js +87 -0
  255. package/src/lib/plugin-manager/plugin-entity-pickers.js.map +1 -0
  256. package/src/lib/plugin-manager/plugin-host-resources.d.ts +6 -0
  257. package/src/lib/plugin-manager/plugin-host-resources.js +6 -1
  258. package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -1
  259. package/src/lib/plugin-manager/plugin-intake-gates.d.ts +75 -0
  260. package/src/lib/plugin-manager/plugin-intake-gates.js +49 -0
  261. package/src/lib/plugin-manager/plugin-intake-gates.js.map +1 -0
  262. package/src/lib/plugin-manager/plugin-jobs.d.ts +5 -5
  263. package/src/lib/plugin-manager/plugin-jobs.js +7 -7
  264. package/src/lib/plugin-manager/plugin-jobs.js.map +1 -1
  265. package/src/lib/plugin-manager/plugin-live-paths.d.ts +70 -0
  266. package/src/lib/plugin-manager/plugin-live-paths.js +66 -0
  267. package/src/lib/plugin-manager/plugin-live-paths.js.map +1 -0
  268. package/src/lib/plugin-manager/plugin-org-erasure.d.ts +3 -2
  269. package/src/lib/plugin-manager/plugin-org-erasure.js +30 -5
  270. package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
  271. package/src/lib/plugin-manager/plugin-person-erasure.d.ts +68 -9
  272. package/src/lib/plugin-manager/plugin-person-erasure.js +196 -51
  273. package/src/lib/plugin-manager/plugin-person-erasure.js.map +1 -1
  274. package/src/lib/plugin-manager/plugin-person-records.d.ts +275 -0
  275. package/src/lib/plugin-manager/plugin-person-records.js +107 -0
  276. package/src/lib/plugin-manager/plugin-person-records.js.map +1 -0
  277. package/src/lib/plugin-manager/plugin-record-counts.d.ts +76 -0
  278. package/src/lib/plugin-manager/plugin-record-counts.js +49 -0
  279. package/src/lib/plugin-manager/plugin-record-counts.js.map +1 -0
  280. package/src/lib/plugin-manager/plugin-record-email-state.d.ts +26 -0
  281. package/src/lib/plugin-manager/plugin-record-email-state.js +16 -0
  282. package/src/lib/plugin-manager/plugin-record-email-state.js.map +1 -1
  283. package/src/lib/plugin-manager/plugin-record-index.d.ts +10 -0
  284. package/src/lib/plugin-manager/plugin-record-index.js.map +1 -1
  285. package/src/lib/plugin-manager/plugin-record-lists.d.ts +86 -6
  286. package/src/lib/plugin-manager/plugin-record-lists.js +42 -5
  287. package/src/lib/plugin-manager/plugin-record-lists.js.map +1 -1
  288. package/src/lib/plugin-manager/plugin-record-origin.d.ts +79 -0
  289. package/src/lib/plugin-manager/plugin-record-origin.js +46 -0
  290. package/src/lib/plugin-manager/plugin-record-origin.js.map +1 -0
  291. package/src/lib/plugin-manager/plugin-record-pages.d.ts +37 -0
  292. package/src/lib/plugin-manager/plugin-record-pages.js +52 -0
  293. package/src/lib/plugin-manager/plugin-record-pages.js.map +1 -0
  294. package/src/lib/plugin-manager/plugin-record-timeline.d.ts +60 -0
  295. package/src/lib/plugin-manager/plugin-record-timeline.js +16 -0
  296. package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
  297. package/src/lib/plugin-manager/plugin-resource-drafts.d.ts +5 -5
  298. package/src/lib/plugin-manager/plugin-resource-drafts.js.map +1 -1
  299. package/src/lib/plugin-manager/plugin-revocations.d.ts +64 -0
  300. package/src/lib/plugin-manager/plugin-revocations.js +47 -0
  301. package/src/lib/plugin-manager/plugin-revocations.js.map +1 -0
  302. package/src/lib/plugin-manager/plugin-send-tallies.d.ts +69 -0
  303. package/src/lib/plugin-manager/plugin-send-tallies.js +45 -0
  304. package/src/lib/plugin-manager/plugin-send-tallies.js.map +1 -0
  305. package/src/lib/plugin-manager/plugin-site-bundle.d.ts +40 -0
  306. package/src/lib/plugin-manager/plugin-site-bundle.js +4 -0
  307. package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -1
  308. package/src/lib/plugin-manager/plugin-site-export.d.ts +57 -1
  309. package/src/lib/plugin-manager/plugin-site-export.js +23 -10
  310. package/src/lib/plugin-manager/plugin-site-export.js.map +1 -1
  311. package/src/lib/plugin-manager/plugin-sitemap-readers.d.ts +146 -0
  312. package/src/lib/plugin-manager/plugin-sitemap-readers.js +110 -0
  313. package/src/lib/plugin-manager/plugin-sitemap-readers.js.map +1 -0
  314. package/src/lib/plugin-manager/plugin-transfer-resources.d.ts +714 -0
  315. package/src/lib/plugin-manager/plugin-transfer-resources.js +443 -0
  316. package/src/lib/plugin-manager/plugin-transfer-resources.js.map +1 -0
  317. package/src/lib/plugin-manager/plugin-usage-axes.d.ts +54 -5
  318. package/src/lib/plugin-manager/plugin-usage-axes.js +11 -3
  319. package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -1
  320. package/src/lib/plugin-manager/plugin-user-erasure.js +9 -1
  321. package/src/lib/plugin-manager/plugin-user-erasure.js.map +1 -1
  322. package/src/lib/plugin-manager/plugin-visitor-doors.d.ts +104 -0
  323. package/src/lib/plugin-manager/plugin-visitor-doors.js +108 -0
  324. package/src/lib/plugin-manager/plugin-visitor-doors.js.map +1 -0
  325. package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
  326. package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
  327. package/src/lib/plugin-manager/repeat-rows.d.ts +5 -6
  328. package/src/lib/plugin-manager/repeat-rows.js.map +1 -1
  329. package/src/lib/app-utils/actions.d.ts +0 -310
  330. package/src/lib/app-utils/actions.js +0 -506
  331. package/src/lib/app-utils/actions.js.map +0 -1
  332. package/src/lib/app-utils/automation-placeholders.d.ts +0 -72
  333. package/src/lib/app-utils/automation-placeholders.js +0 -207
  334. package/src/lib/app-utils/automation-placeholders.js.map +0 -1
  335. package/src/lib/app-utils/dataset-models.d.ts +0 -339
  336. package/src/lib/app-utils/dataset-models.js +0 -722
  337. package/src/lib/app-utils/dataset-models.js.map +0 -1
  338. package/src/lib/app-utils/datasets.d.ts +0 -171
  339. package/src/lib/app-utils/datasets.js +0 -227
  340. package/src/lib/app-utils/datasets.js.map +0 -1
  341. package/src/lib/app-utils/form-abuse-ceiling.d.ts +0 -146
  342. package/src/lib/app-utils/form-abuse-ceiling.js +0 -158
  343. package/src/lib/app-utils/form-abuse-ceiling.js.map +0 -1
  344. package/src/lib/plugin-manager/order-fulfilment.d.ts +0 -142
  345. package/src/lib/plugin-manager/order-fulfilment.js +0 -104
  346. package/src/lib/plugin-manager/order-fulfilment.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-artifact-types.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { InstalledFrom } from '../app-utils/artifact-provenance'\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\nimport { PLUGIN_ARTIFACT_TYPES_DECLARED } from './first-party-plugins.generated'\nimport { runPluginDeclarationsRepair } from './plugin-declarations-repair'\n\n/**\n * AN INSTALLABLE ARTIFACT TYPE ANOTHER PLUGIN KEEPS (AGL-3080).\n *\n * Installed content is the platform's concept (`artifact-provenance.ts`): a\n * workspace can take a component, a theme or a dataset's schema from a\n * publisher rather than author it. The plugin that sells, installs and\n * updates those copies is one plugin; the plugin whose storage a copy lands\n * in may be another. A dataset schema is published from a dataset and\n * installs as a new one, and datasets are the data plugin's. The installer\n * may not read or write another plugin's storage, and neither plugin may\n * import the other, so this is the contract between them, and core names\n * neither.\n *\n * The owner answers four questions, and the installer keeps everything else:\n * who may act, the listing, its takedown and privacy gates, the purchase, the\n * publisher's preconditions, the provenance stamp and its base snapshot, and\n * the per-version tally.\n *\n * 1. {@link PluginArtifactOwner.snapshot}: publishing. Reads the source the\n * publisher named in their own workspace and reduces it to what may travel.\n * The installer stores what comes back as the version's content.\n * 2. {@link PluginArtifactOwner.admits}: before an install reads its listing,\n * whether this workspace may hold one at all (its plan).\n * 3. {@link PluginArtifactOwner.prepare}: installing. Checks the published\n * content can land here, makes it this workspace's, and writes NOTHING. The\n * installer records provenance over the prepared content, then calls\n * `commit` to write the copy with that stamp.\n * 4. {@link PluginArtifactOwner.locate}: updating. Finds the copy a listing\n * installed, and hands back what the three-way merge diffs together with\n * the one write that applies its result.\n *\n * ## Declared, then registered, and an absent owner refuses\n *\n * The owner DECLARES each type in `plugins.config.json` (`artifactTypes`),\n * compiled into core, and registers its answers from a declarations entry\n * (`consoleServerDeclarations`: an install runs on the console's server). A\n * registry alone would read a failed boot as \"nobody keeps this type\". The\n * three cases stay apart:\n *\n * - nothing declares the type: {@link resolveArtifactTypeOwner} answers\n * `null`, and the installer refuses the listing as unavailable here;\n * - declared and registered: the owner answers;\n * - declared and NOT registered: the app's declarations step runs again, and\n * if the owner is still missing this THROWS\n * {@link ArtifactTypeOwnerUnavailableError}.\n *\n * Every one of those is decided before the installer writes anything, so an\n * install with no owner refuses whole and never lands half: no copy, no\n * provenance, no tally.\n *\n * Server-only, and reached by its own subpath, never through a barrel.\n */\n\n/** A compiled declaration: the artifact type, and the plugin that keeps its copies. */\nexport interface ArtifactTypeDeclaration {\n pluginId: string\n type: string\n}\n\n/** A refusal, answered as the route's status and `{ error }` body. */\nexport interface ArtifactRefusal {\n ok: false\n status: number\n error: string\n}\n\n/** The workspace a copy is installed into. */\nexport interface ArtifactWorkspace {\n /** The organization the copy lands in. */\n orgId: string\n /** Its organization document: the plan, add-ons and default sharing a copy is held to. */\n org: Readonly<Record<string, unknown>>\n}\n\n/** The listing a copy is installed from, as the listing stores it. */\nexport interface ArtifactListingFacts {\n listingId: string\n displayName?: unknown\n description?: unknown\n /** The version on offer: the listing's `latestVersion`. */\n version?: unknown\n}\n\n/**\n * Where a copy came from, written onto it by its owner. The installer makes\n * both: the owner stores them and never composes either.\n */\nexport interface ArtifactInstallStamp {\n /** The provenance every installed copy carries (AGL-1015). */\n installedFrom: InstalledFrom\n /** The installer's own word for the copy's origin, with the listing and version. */\n source: {\n type: string\n listingId: string\n version: unknown\n }\n}\n\n/** Publishing: the source a publisher named, in their own workspace. */\nexport interface ArtifactSnapshotRequest {\n /** The publishing organization, whose source is read. */\n orgId: string\n /** The id of the source the publisher named. */\n sourceId: string\n}\n\n/** What a published version of the type carries. */\nexport interface ArtifactSnapshot {\n ok: true\n /** The content every install of this version receives. */\n content: unknown\n /** Facts about the content the listing carries beside it, written as they are. */\n facts?: Readonly<Record<string, string | number | boolean>>\n}\n\n/** Installing: one published version, into one workspace. */\nexport interface ArtifactInstallRequest extends ArtifactWorkspace {\n listing: ArtifactListingFacts\n /** The version's content as published (`versions/{v}.{type}`), unvalidated. */\n published: unknown\n}\n\n/** What an install landed, reported beside the installer's own fields. */\nexport interface ArtifactInstalled {\n ok: true\n report: Readonly<Record<string, unknown>>\n}\n\n/** An install the owner has checked and not yet written. */\nexport interface PreparedArtifactInstall {\n ok: true\n /**\n * The content exactly as the copy will hold it: what the installer records\n * as the base snapshot, so an update diffs this workspace's copy against\n * what really arrived rather than what the publisher sent.\n */\n content: unknown\n /**\n * Writes the copy with the installer's stamp. It may still refuse (a cap\n * that a concurrent install reached first), and a refusal writes nothing.\n */\n commit(stamp: ArtifactInstallStamp): Promise<ArtifactInstalled | ArtifactRefusal>\n}\n\n/** Updating: the copy a listing installed, asked from one site. */\nexport interface ArtifactLocateRequest {\n /** The site's organization, or `null` for a site with none. */\n orgId: string | null\n /** The site the update was asked from. */\n hostId: string\n listingId: string\n /** The version on offer, as published, unvalidated. */\n published: unknown\n}\n\n/**\n * What taking an update does beyond its content: shown on the preview, and,\n * when `destructive`, applied only once the caller has confirmed it.\n */\nexport interface ArtifactUpdateImpact {\n /** Fields added to the update's preview as they are. */\n preview: Readonly<Record<string, unknown>>\n /** The update reinterprets something the copy already holds. */\n destructive: boolean\n /** The sentence an unconfirmed destructive merge is refused with. */\n refusal: string\n}\n\n/** The installed copy, in the shape its base snapshot holds. */\nexport interface InstalledArtifactCopy {\n ok: true\n /** The copy as it is now. */\n current: unknown\n /** The version on offer, made this workspace's exactly as an install would. */\n incoming: unknown\n /** The version the copy was installed at, or `null` when it does not say. */\n installedVersion: string | null\n /** The hash of the base snapshot it was installed with, or `null`. */\n baseSha: string | null\n impact?: ArtifactUpdateImpact\n /** Writes the merged content onto the copy, with the update's stamp. */\n apply(update: { content: unknown; stamp: ArtifactInstallStamp }): Promise<void>\n}\n\n/** A plugin's answers for an installable artifact type it keeps. */\nexport interface PluginArtifactOwner {\n snapshot(request: ArtifactSnapshotRequest): Promise<ArtifactSnapshot | ArtifactRefusal>\n admits(workspace: ArtifactWorkspace): Promise<ArtifactRefusal | null>\n prepare(request: ArtifactInstallRequest): Promise<PreparedArtifactInstall | ArtifactRefusal>\n locate(request: ArtifactLocateRequest): Promise<InstalledArtifactCopy | ArtifactRefusal>\n}\n\n/** Every declared type, in config order. */\nexport function listDeclaredArtifactTypes(): readonly ArtifactTypeDeclaration[] {\n return PLUGIN_ARTIFACT_TYPES_DECLARED\n}\n\n/** The plugin declared as keeping `type`, or `null` when none is. */\nexport function declaredArtifactTypeOwner(type: string): ArtifactTypeDeclaration | null {\n return listDeclaredArtifactTypes().find((declared) => declared.type === type) ?? null\n}\n\ninterface Registered {\n pluginId: string\n owner: PluginArtifactOwner\n}\n\n/**\n * One table per process, on `globalThis` (AGL-3412): the owner registers from\n * the app's boot, which Next compiles apart from the routes that read.\n */\nconst OWNERS_KEY = Symbol.for('@aglyn/aglyn:plugin-artifact-types')\n\nconst globalScope = globalThis as typeof globalThis & {\n [OWNERS_KEY]?: Map<string, Registered>\n}\n\nconst owners: Map<string, Registered> =\n globalScope[OWNERS_KEY] ?? (globalScope[OWNERS_KEY] = new Map())\n\n/**\n * Registers a plugin's answers for a type it declared. The owner is the plugin\n * whose register fn is running, or the `pluginId` passed from a boot\n * declaration. Refused: no owner, a type nobody declared (no installer would\n * ever ask for it), and a type another plugin declared. The same plugin\n * registering again replaces its answers. Returns the unregister.\n */\nexport function registerArtifactTypeOwner(\n type: string,\n owner: PluginArtifactOwner,\n options?: { pluginId?: string },\n): () => void {\n const name = type.trim()\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n if (!pluginId) {\n throw new Error(\n `artifact type \"${name}\" registered with no owner: pass { pluginId } ` +\n 'when registering outside a plugin register fn',\n )\n }\n const declared = declaredArtifactTypeOwner(name)\n if (!declared) {\n throw new Error(\n `artifact type \"${name}\" is not declared in plugins.config.json ` +\n '(artifactTypes), so no installer would ever ask for it',\n )\n }\n if (declared.pluginId !== pluginId) {\n throw new Error(\n `artifact type \"${name}\" is declared by \"${declared.pluginId}\"; refused \"${pluginId}\"`,\n )\n }\n const entry: Registered = { pluginId, owner }\n owners.set(name, entry)\n return () => {\n if (owners.get(name) === entry) owners.delete(name)\n }\n}\n\n/** The registered owner of `type` with its plugin, or `null`. */\nexport function artifactTypeOwner(type: string): Registered | null {\n return owners.get(type) ?? null\n}\n\n/** Only for specs: forgets every registered owner. */\nexport function resetArtifactTypeOwnersForTests(): void {\n owners.clear()\n}\n\n/**\n * The declared owner's registration was not found, even after the app's\n * declarations step ran again.\n */\nexport class ArtifactTypeOwnerUnavailableError extends Error {\n constructor(readonly declared: ArtifactTypeDeclaration) {\n super(\n `artifact type \"${declared.type}\" is declared by \"${declared.pluginId}\" but ` +\n 'no owner is registered in this process: its declarations did not run. ' +\n 'Refusing to treat it as a type nobody keeps.',\n )\n this.name = 'ArtifactTypeOwnerUnavailableError'\n }\n}\n\n/**\n * The owner of `type`: `null` when no plugin declares it, the registered\n * answers when one does, and a THROWN {@link ArtifactTypeOwnerUnavailableError}\n * when the declared owner is still missing after the app's declarations step\n * ran once more.\n */\nexport async function resolveArtifactTypeOwner(\n type: string,\n): Promise<PluginArtifactOwner | null> {\n const declared = declaredArtifactTypeOwner(type)\n if (!declared) return null\n let registered = artifactTypeOwner(type)\n if (!registered) {\n try {\n await runPluginDeclarationsRepair()\n } catch (error) {\n console.error('[plugin-artifact-types] the declarations repair failed', error)\n }\n registered = artifactTypeOwner(type)\n }\n if (!registered) throw new ArtifactTypeOwnerUnavailableError(declared)\n return registered.owner\n}\n"],"names":["globalScope","getRegisteringPluginId","PLUGIN_ARTIFACT_TYPES_DECLARED","runPluginDeclarationsRepair","listDeclaredArtifactTypes","declaredArtifactTypeOwner","type","find","declared","OWNERS_KEY","Symbol","for","globalThis","owners","Map","registerArtifactTypeOwner","owner","options","name","trim","pluginId","Error","entry","set","get","delete","artifactTypeOwner","resetArtifactTypeOwnersForTests","clear","ArtifactTypeOwnerUnavailableError","resolveArtifactTypeOwner","registered","error","console"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,OAiOCA;AA9NF,SAASC,sBAAsB,QAAQ,qCAAiC;AACxE,SAASC,8BAA8B,QAAQ,qCAAiC;AAChF,SAASC,2BAA2B,QAAQ,kCAA8B;AAkM1E,0CAA0C,GAC1C,OAAO,SAASC;IACd,OAAOF;AACT;AAEA,mEAAmE,GACnE,OAAO,SAASG,0BAA0BC,IAAY;QAC7CF;IAAP,QAAOA,kCAAAA,4BAA4BG,IAAI,CAAC,CAACC,WAAaA,SAASF,IAAI,KAAKA,iBAAjEF,kCAA0E;AACnF;AAOA;;;CAGC,GACD,MAAMK,aAAaC,OAAOC,GAAG,CAAC;AAE9B,MAAMX,cAAcY;AAIpB,MAAMC,UACJb,0BAAAA,WAAW,CAACS,WAAW,YAAvBT,0BAA4BA,WAAW,CAACS,WAAW,GAAG,IAAIK;AAE5D;;;;;;CAMC,GACD,OAAO,SAASC,0BACdT,IAAY,EACZU,KAA0B,EAC1BC,OAA+B;QAGbhB,MAAAA;IADlB,MAAMiB,OAAOZ,KAAKa,IAAI;IACtB,MAAMC,WAAW,EAACnB,QAAAA,0BAAAA,oCAAAA,0BAA4BgB,2BAAAA,QAASG,QAAQ,YAA7CnB,OAAiD,IAAIkB,IAAI;IAC3E,IAAI,CAACC,UAAU;QACb,MAAM,IAAIC,MACR,CAAC,eAAe,EAAEH,KAAK,8CAA8C,CAAC,GACpE;IAEN;IACA,MAAMV,WAAWH,0BAA0Ba;IAC3C,IAAI,CAACV,UAAU;QACb,MAAM,IAAIa,MACR,CAAC,eAAe,EAAEH,KAAK,yCAAyC,CAAC,GAC/D;IAEN;IACA,IAAIV,SAASY,QAAQ,KAAKA,UAAU;QAClC,MAAM,IAAIC,MACR,CAAC,eAAe,EAAEH,KAAK,kBAAkB,EAAEV,SAASY,QAAQ,CAAC,YAAY,EAAEA,SAAS,CAAC,CAAC;IAE1F;IACA,MAAME,QAAoB;QAAEF;QAAUJ;IAAM;IAC5CH,OAAOU,GAAG,CAACL,MAAMI;IACjB,OAAO;QACL,IAAIT,OAAOW,GAAG,CAACN,UAAUI,OAAOT,OAAOY,MAAM,CAACP;IAChD;AACF;AAEA,+DAA+D,GAC/D,OAAO,SAASQ,kBAAkBpB,IAAY;QACrCO;IAAP,QAAOA,cAAAA,OAAOW,GAAG,CAAClB,iBAAXO,cAAoB;AAC7B;AAEA,oDAAoD,GACpD,OAAO,SAASc;IACdd,OAAOe,KAAK;AACd;AAEA;;;CAGC,GACD,OAAO,MAAMC,0CAA0CR;IACrD,YAAY,AAASb,QAAiC,CAAE;QACtD,KAAK,CACH,CAAC,eAAe,EAAEA,SAASF,IAAI,CAAC,kBAAkB,EAAEE,SAASY,QAAQ,CAAC,MAAM,CAAC,GAC3E,2EACA,sDAJeZ,WAAAA;QAMnB,IAAI,CAACU,IAAI,GAAG;IACd;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAeY,yBACpBxB,IAAY;IAEZ,MAAME,WAAWH,0BAA0BC;IAC3C,IAAI,CAACE,UAAU,OAAO;IACtB,IAAIuB,aAAaL,kBAAkBpB;IACnC,IAAI,CAACyB,YAAY;QACf,IAAI;YACF,MAAM5B;QACR,EAAE,OAAO6B,OAAO;YACdC,QAAQD,KAAK,CAAC,0DAA0DA;QAC1E;QACAD,aAAaL,kBAAkBpB;IACjC;IACA,IAAI,CAACyB,YAAY,MAAM,IAAIF,kCAAkCrB;IAC7D,OAAOuB,WAAWf,KAAK;AACzB"}
@@ -171,6 +171,14 @@ export interface PluginContactCaptureRequest {
171
171
  * does not blank a title somebody typed.
172
172
  */
173
173
  profile?: Readonly<Record<string, unknown>>;
174
+ /**
175
+ * Profile fields written only where the record the person lands on holds
176
+ * none (AGL-3493), keyed as {@link profile} is. For what a door learned
177
+ * in passing — the phone a booking form asked for — and must not put over
178
+ * a value somebody typed on the record or gave the business before.
179
+ * A key also present in `profile` is `profile`'s to write.
180
+ */
181
+ profileFill?: Readonly<Record<string, unknown>>;
174
182
  /**
175
183
  * Facts about THIS CAPTURE that the owner models and the platform does not
176
184
  * (AGL-3080) — which form was filled in, which page it was on, where the
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-contact-capture.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 { getRegisteringPluginId } from '../app-utils/registering-plugin'\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * A person met on a site, handed to whichever plugin keeps people (AGL-3124).\n *\n * Four silos capture the same thing and none of them is the record system: a\n * form submission, a site member signing up, an order, a booking. Each holds\n * an address, a name and the fact that something happened, and each wants the\n * workspace's person record to know. Today each one imports the writer, which\n * makes the highest-fan-in edge in the repo — thirty files in one plugin alone\n * — and makes the record system's plugin something a storefront cannot take a\n * payment without.\n *\n * So the silo stops importing and starts DECLARING: it registers the capture\n * door it owns, and hands a capture to this seam. The plugin that keeps people\n * registers the writer and does every part of the work that is a record\n * system's to do:\n *\n * - normalizing and keying the address, and deciding whether this is a new\n * person or a visit from one the workspace already holds;\n * - the audience band, the erasure rows, and every other refusal — answered,\n * never thrown, so a capture that is refused never costs the silo the order\n * or the submission it was recording;\n * - what a stage, an owner, a company and a tag mean, and what the arrival\n * of a new person sets off.\n *\n * The silo decides none of that. It reports what it saw.\n *\n * ## Why the profile is opaque\n *\n * `profile` is the owner's own field names with scalar values, not a core\n * type. A person's record shape belongs to the plugin that models it, and a\n * typed core shape would be that model spelled in the platform again — which\n * is the defect this seam exists to retire, not a smaller version of it. A key\n * the owner does not document is the owner's to ignore.\n *\n * `source` is the silo's own word for its door, and it is declared rather than\n * enumerated here for the same reason: core listing `form`, `member`, `order`\n * and `booking` would be core holding the plugin set again. A silo registers\n * its source with {@link registerPluginContactSource}, which is what lets a\n * timeline label an entry and a link point back at the thing that captured the\n * person, without the platform knowing what any of them are.\n *\n * ## One writer; many silos\n *\n * A workspace keeps one set of people, so the writer is a slot: a second\n * plugin's writer is refused naming both, and the incumbent keeps serving. A\n * SOURCE is per silo and there are many, but one source word has one owner —\n * two plugins claiming `order` would make the label and the link ambiguous at\n * the point they are read.\n *\n * ## The caller proves who is asking\n *\n * Like `plugin-record-timeline`, the registry authenticates nobody. A silo has\n * already decided, in its own terms and before it asks, that the capture is\n * the workspace's to record — that the submission was accepted, that the order\n * was paid.\n *\n * Server-side: reached by its own subpath, never through\n * `plugin-manager/index.ts`.\n */\n\n/** The person, as the capturing silo saw them. */\nexport interface PluginContactIdentity {\n /**\n * The address they gave. Raw: the owner normalizes and keys on it, and a\n * silo that normalized first would be keeping a second copy of that rule.\n */\n email: unknown\n name?: string | null\n}\n\n/** What happened, in the capturing silo's own words. */\nexport interface PluginContactInteraction {\n /** The silo's declared source word — see {@link registerPluginContactSource}. */\n source: string\n /** When it happened, epoch ms; the owner uses now when absent. */\n atMs?: number\n /**\n * The document it happened on, in the silo's terms\n * (`formSubmissions/{id}`, `orders/{id}`), so a timeline entry can point\n * back at it. Absent where the door leaves nothing to open.\n */\n refId?: string | null\n /** One line a person reads on the timeline. */\n summary?: string | null\n}\n\nexport interface PluginContactCaptureRequest {\n /**\n * Who caused the capture, when it was not the person captured (AGL-3376) —\n * a member adding someone by hand, an integration's key. A door where the\n * visitor acts for themselves (a form, a purchase) may leave it out.\n */\n actor?: {\n kind: 'member' | 'visitor' | 'apiKey' | 'platform'\n uid?: string | null\n email?: string | null\n apiKeyName?: string | null\n }\n orgId: string\n /** The site the person was met on: the brand whose form or checkout it was. */\n hostId: string\n identity: PluginContactIdentity\n interaction: PluginContactInteraction\n /**\n * An explicit marketing opt-in the capture surface carried, recorded against\n * `hostId`: consent to the MARKETING send purpose (`marketing-consent.ts`),\n * whichever plugin's mail later asks for it. Absent is not consent, and\n * filing a capture under a container is not consent either.\n */\n marketingConsent?: boolean\n /**\n * The disclosure key the capture surface rendered beside that opt-in\n * (`consentGroupDisclosureKey`, AGL-3320), passed through as the surface\n * sent it. The owner records the opt-in for the site's whole consent group\n * only when this is the group's current key, and for `hostId` alone\n * otherwise — so a silo whose surface shows no disclosure sends none.\n */\n disclosedConsentGroup?: string\n /** Tags the silo puts on this capture; the owner adds, never replaces. */\n tags?: readonly string[]\n /**\n * What the capture SURFACE is filed under — the merchant's own act — by\n * container kind (`plugin-containers.ts`): `{ [kind]: ids }`. The owner\n * files the person under the kinds it keeps. Filing is not consent.\n */\n containers?: Readonly<Record<string, readonly string[]>>\n /** Money the capture was worth, in cents, where the silo took some. */\n purchaseCents?: number\n /** The currency {@link purchaseCents} is in, lowercase, when the silo knows. */\n purchaseCurrency?: string\n /**\n * The earliest stage this capture describes, in the OWNER's words. A floor\n * and not a value: the owner fills an empty stage and advances an earlier\n * one, and never moves anybody back, so a repeat customer filling in a\n * contact form is still a customer.\n */\n lifecycleFloor?: string\n /**\n * What kind of door this is, which decides WHICH RECORD the person lands\n * on (AGL-3232) — the owner's rule is Salesforce's: one person is one\n * record, a lead until they are qualified and a contact after.\n *\n * - `lead`: a lead surface — a form whose author routes it to leads, a\n * booking request. The person is filed as a LEAD and nothing else,\n * unless the workspace already holds them as a contact, in which case\n * the capture lands on the contact and no lead is filed.\n * - `relationship`: an act that makes the person a known relationship —\n * a member account, a purchase. The person is filed as a CONTACT, and\n * an open lead the site held for them is stamped converted onto it.\n * - `touch`, the default: everything else — a form without lead routing,\n * a newsletter opt-in. The capture lands on the open lead when the site\n * holds one for the address, and on the contact otherwise.\n */\n surface?: 'lead' | 'relationship' | 'touch'\n /**\n * Profile fields the silo knows, keyed by the owner's own field names.\n * Only the keys given are written, so a silo that knows the phone number\n * does not blank a title somebody typed.\n */\n profile?: Readonly<Record<string, unknown>>\n /**\n * Facts about THIS CAPTURE that the owner models and the platform does not\n * (AGL-3080) — which form was filled in, which page it was on, where the\n * visitor arrived from.\n *\n * Opaque for the reason {@link profile} is: every one of these is a concept\n * belonging to the plugin that keeps people or to the silo that saw it, and\n * a typed core field for each would be those models spelled out in the\n * platform again. `profile` describes the PERSON and outlives the visit;\n * this describes the visit and does not.\n *\n * A key the owner does not recognize is the owner's to ignore, and a silo\n * that sends nothing here loses nothing: every field is an enrichment of a\n * capture that is already complete without it.\n */\n detail?: Readonly<Record<string, unknown>>\n}\n\n/**\n * The owner's answer. `created: false` is a visit by somebody the workspace\n * already held, which is an interaction and not a new person.\n *\n * A refusal is RETURNED. Every one of these silos has already done the thing\n * it was recording, so a throw here would cost an accepted submission or a\n * paid order for a record it could not keep.\n */\nexport type PluginContactCaptured =\n /** The capture landed on a contact — the record every capture used to make. */\n | { ok: true; record: 'contact'; contactId: string; created: boolean }\n /**\n * The capture landed on a LEAD (AGL-3232): a lead surface met somebody the\n * workspace does not hold as a contact, or a touch reached an open lead.\n * `leadId` is the person key the lead is filed under on `hostId`.\n */\n | {\n ok: true\n record: 'lead'\n leadId: string\n created: boolean\n /**\n * Whether this capture's surface was new on the lead — a new person,\n * or one first met through another door. A form counts its leads by\n * it (AGL-3330): the people it filed, not the times they came back.\n * Absent from a writer that does not say, which a caller reads as\n * `created`.\n */\n sourceAdded?: boolean\n }\n | {\n ok: false\n /**\n * `invalid-email`: nothing usable to key a person on. `band`: the\n * workspace is at the audience it may hold. `erased`: the workspace\n * erased this person and a capture must not quietly rebuild them.\n * `error`: the owner failed, and said so rather than silently.\n */\n reason: 'invalid-email' | 'band' | 'erased' | 'error'\n /** Customer-safe: a caller may show or log it as it stands. */\n error: string\n }\n\nexport interface PluginContactCaptureWriter {\n /** Records one capture; a refusal is returned, never thrown. */\n capture(\n request: PluginContactCaptureRequest,\n ): Promise<PluginContactCaptured>\n}\n\n/** A capture door a silo owns. */\nexport interface PluginContactSourceDeclaration {\n /** The silo's word for the door, as its captures carry it. */\n source: string\n /** What a timeline entry from this door says: `Form submission`. */\n label: string\n /**\n * What a link from that entry says when there is something to open —\n * `Open submission`. Absent where the door leaves nothing to open, which\n * is the honest answer for an import or a by-hand add.\n */\n openLabel?: string\n /**\n * The record kind the silo's own document is, for\n * `plugin-record-routes` to address it with. Absent where the door has no\n * record of its own to open.\n */\n recordKind?: string\n}\n\n/** A source declaration with the silo that made it. */\nexport type ResolvedPluginContactSource = PluginContactSourceDeclaration & {\n pluginId: string\n}\n\nexport const PLUGIN_CONTACT_CAPTURE =\n definePluginServiceContract<PluginContactCaptureWriter>(\n 'core.contact-capture',\n { multiple: false },\n )\n\nexport const PLUGIN_CONTACT_SOURCES =\n definePluginServiceContract<PluginContactSourceDeclaration>(\n 'core.contact-capture-sources',\n { multiple: true },\n )\n\n/**\n * Registers the plugin that keeps people. The owner is the loader's marker\n * when a register fn is running, else `options.pluginId`; with neither the\n * registration throws, and a second plugin's writer is refused naming both —\n * the incumbent keeps serving.\n */\nexport function registerPluginContactCaptureWriter(\n writer: PluginContactCaptureWriter,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_CONTACT_CAPTURE, writer, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** Declares a capture door. One source word has one owner. */\nexport function registerPluginContactSource(\n declaration: PluginContactSourceDeclaration,\n options?: { pluginId?: string },\n): void {\n const key = declaration.source?.trim() ?? ''\n if (!key) throw new Error('a contact capture source needs a source word')\n if (!declaration.label?.trim()) {\n throw new Error(`contact capture source \"${key}\" needs a label`)\n }\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n const incumbent = resolvePluginServices(PLUGIN_CONTACT_SOURCES).find(\n (entry) => entry.key === key,\n )\n if (incumbent && pluginId && incumbent.pluginId !== pluginId) {\n throw new Error(\n `contact capture source \"${key}\" is already declared by ` +\n `\"${incumbent.pluginId}\"; refused \"${pluginId}\"`,\n )\n }\n registerPluginService(\n PLUGIN_CONTACT_SOURCES,\n { ...declaration, source: key },\n {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n key,\n },\n )\n}\n\nexport interface ResolvedPluginContactCaptureWriter {\n /** The plugin that keeps people. */\n pluginId: string\n writer: PluginContactCaptureWriter\n}\n\n/** The writer with its owner, or `null` when no plugin keeps people here. */\nexport function pluginContactCaptureWriter(): ResolvedPluginContactCaptureWriter | null {\n const entry = resolvePluginServices(PLUGIN_CONTACT_CAPTURE)[0]\n return entry ? { pluginId: entry.pluginId, writer: entry.impl } : null\n}\n\n/**\n * Hands one capture to the plugin that keeps people, or answers `null` when\n * no plugin does.\n *\n * `null` and a refusal are different answers and a silo treats them\n * differently: `null` is a workspace with no record system, where there is\n * nothing to record and nothing has gone wrong; a refusal is a record system\n * that considered this person and declined.\n */\nexport async function capturePluginContact(\n request: PluginContactCaptureRequest,\n): Promise<PluginContactCaptured | null> {\n const found = pluginContactCaptureWriter()\n if (!found) return null\n return found.writer.capture(request)\n}\n\n/** Every declared capture door, with its silo, in registration order. */\nexport function listPluginContactSources(): ResolvedPluginContactSource[] {\n return resolvePluginServices(PLUGIN_CONTACT_SOURCES).map((entry) => ({\n ...entry.impl,\n pluginId: entry.pluginId,\n }))\n}\n\n/** One declared capture door by its source word, or `null`. */\nexport function pluginContactSource(\n source: string,\n): ResolvedPluginContactSource | null {\n const key = source.trim()\n const entry = resolvePluginServices(PLUGIN_CONTACT_SOURCES).find(\n (one) => one.key === key,\n )\n return entry ? { ...entry.impl, pluginId: entry.pluginId } : null\n}\n"],"names":["getRegisteringPluginId","definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_CONTACT_CAPTURE","multiple","PLUGIN_CONTACT_SOURCES","registerPluginContactCaptureWriter","writer","options","pluginId","registerPluginContactSource","declaration","key","source","trim","Error","label","incumbent","find","entry","pluginContactCaptureWriter","impl","capturePluginContact","request","found","capture","listPluginContactSources","map","pluginContactSource","one"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,sBAAsB,QAAQ,qCAAiC;AACxE,SACEC,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AA+P1B,OAAO,MAAMC,yBACXH,4BACE,wBACA;IAAEI,UAAU;AAAM,GACnB;AAEH,OAAO,MAAMC,yBACXL,4BACE,gCACA;IAAEI,UAAU;AAAK,GAClB;AAEH;;;;;CAKC,GACD,OAAO,SAASE,mCACdC,MAAkC,EAClCC,OAA+B;IAE/BP,sBAAsBE,wBAAwBI,QAAQ,aAChDC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,4DAA4D,GAC5D,OAAO,SAASC,4BACdC,WAA2C,EAC3CH,OAA+B;cAObT,OAAAA;QALNY,qBAEPA;IAFL,MAAMC,eAAMD,sBAAAA,YAAYE,MAAM,qBAAlBF,oBAAoBG,IAAI,qBAAM;IAC1C,IAAI,CAACF,KAAK,MAAM,IAAIG,MAAM;IAC1B,IAAI,GAACJ,qBAAAA,YAAYK,KAAK,qBAAjBL,mBAAmBG,IAAI,KAAI;QAC9B,MAAM,IAAIC,MAAM,CAAC,wBAAwB,EAAEH,IAAI,eAAe,CAAC;IACjE;IACA,MAAMH,WAAW,EAACV,SAAAA,0BAAAA,oCAAAA,0BAA4BS,2BAAAA,QAASC,QAAQ,YAA7CV,QAAiD,IAAIe,IAAI;IAC3E,MAAMG,YAAYf,sBAAsBG,wBAAwBa,IAAI,CAClE,CAACC,QAAUA,MAAMP,GAAG,KAAKA;IAE3B,IAAIK,aAAaR,YAAYQ,UAAUR,QAAQ,KAAKA,UAAU;QAC5D,MAAM,IAAIM,MACR,CAAC,wBAAwB,EAAEH,IAAI,yBAAyB,CAAC,GACvD,CAAC,CAAC,EAAEK,UAAUR,QAAQ,CAAC,YAAY,EAAEA,SAAS,CAAC,CAAC;IAEtD;IACAR,sBACEI,wBACA,aAAKM;QAAaE,QAAQD;QAC1B,aACMJ,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;QAC1DG;;AAGN;AAQA,2EAA2E,GAC3E,OAAO,SAASQ;IACd,MAAMD,QAAQjB,sBAAsBC,uBAAuB,CAAC,EAAE;IAC9D,OAAOgB,QAAQ;QAAEV,UAAUU,MAAMV,QAAQ;QAAEF,QAAQY,MAAME,IAAI;IAAC,IAAI;AACpE;AAEA;;;;;;;;CAQC,GACD,OAAO,eAAeC,qBACpBC,OAAoC;IAEpC,MAAMC,QAAQJ;IACd,IAAI,CAACI,OAAO,OAAO;IACnB,OAAOA,MAAMjB,MAAM,CAACkB,OAAO,CAACF;AAC9B;AAEA,uEAAuE,GACvE,OAAO,SAASG;IACd,OAAOxB,sBAAsBG,wBAAwBsB,GAAG,CAAC,CAACR,QAAW,aAChEA,MAAME,IAAI;YACbZ,UAAUU,MAAMV,QAAQ;;AAE5B;AAEA,6DAA6D,GAC7D,OAAO,SAASmB,oBACdf,MAAc;IAEd,MAAMD,MAAMC,OAAOC,IAAI;IACvB,MAAMK,QAAQjB,sBAAsBG,wBAAwBa,IAAI,CAC9D,CAACW,MAAQA,IAAIjB,GAAG,KAAKA;IAEvB,OAAOO,QAAQ,aAAKA,MAAME,IAAI;QAAEZ,UAAUU,MAAMV,QAAQ;SAAK;AAC/D"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-contact-capture.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 { getRegisteringPluginId } from '../app-utils/registering-plugin'\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * A person met on a site, handed to whichever plugin keeps people (AGL-3124).\n *\n * Four silos capture the same thing and none of them is the record system: a\n * form submission, a site member signing up, an order, a booking. Each holds\n * an address, a name and the fact that something happened, and each wants the\n * workspace's person record to know. Today each one imports the writer, which\n * makes the highest-fan-in edge in the repo — thirty files in one plugin alone\n * — and makes the record system's plugin something a storefront cannot take a\n * payment without.\n *\n * So the silo stops importing and starts DECLARING: it registers the capture\n * door it owns, and hands a capture to this seam. The plugin that keeps people\n * registers the writer and does every part of the work that is a record\n * system's to do:\n *\n * - normalizing and keying the address, and deciding whether this is a new\n * person or a visit from one the workspace already holds;\n * - the audience band, the erasure rows, and every other refusal — answered,\n * never thrown, so a capture that is refused never costs the silo the order\n * or the submission it was recording;\n * - what a stage, an owner, a company and a tag mean, and what the arrival\n * of a new person sets off.\n *\n * The silo decides none of that. It reports what it saw.\n *\n * ## Why the profile is opaque\n *\n * `profile` is the owner's own field names with scalar values, not a core\n * type. A person's record shape belongs to the plugin that models it, and a\n * typed core shape would be that model spelled in the platform again — which\n * is the defect this seam exists to retire, not a smaller version of it. A key\n * the owner does not document is the owner's to ignore.\n *\n * `source` is the silo's own word for its door, and it is declared rather than\n * enumerated here for the same reason: core listing `form`, `member`, `order`\n * and `booking` would be core holding the plugin set again. A silo registers\n * its source with {@link registerPluginContactSource}, which is what lets a\n * timeline label an entry and a link point back at the thing that captured the\n * person, without the platform knowing what any of them are.\n *\n * ## One writer; many silos\n *\n * A workspace keeps one set of people, so the writer is a slot: a second\n * plugin's writer is refused naming both, and the incumbent keeps serving. A\n * SOURCE is per silo and there are many, but one source word has one owner —\n * two plugins claiming `order` would make the label and the link ambiguous at\n * the point they are read.\n *\n * ## The caller proves who is asking\n *\n * Like `plugin-record-timeline`, the registry authenticates nobody. A silo has\n * already decided, in its own terms and before it asks, that the capture is\n * the workspace's to record — that the submission was accepted, that the order\n * was paid.\n *\n * Server-side: reached by its own subpath, never through\n * `plugin-manager/index.ts`.\n */\n\n/** The person, as the capturing silo saw them. */\nexport interface PluginContactIdentity {\n /**\n * The address they gave. Raw: the owner normalizes and keys on it, and a\n * silo that normalized first would be keeping a second copy of that rule.\n */\n email: unknown\n name?: string | null\n}\n\n/** What happened, in the capturing silo's own words. */\nexport interface PluginContactInteraction {\n /** The silo's declared source word — see {@link registerPluginContactSource}. */\n source: string\n /** When it happened, epoch ms; the owner uses now when absent. */\n atMs?: number\n /**\n * The document it happened on, in the silo's terms\n * (`formSubmissions/{id}`, `orders/{id}`), so a timeline entry can point\n * back at it. Absent where the door leaves nothing to open.\n */\n refId?: string | null\n /** One line a person reads on the timeline. */\n summary?: string | null\n}\n\nexport interface PluginContactCaptureRequest {\n /**\n * Who caused the capture, when it was not the person captured (AGL-3376) —\n * a member adding someone by hand, an integration's key. A door where the\n * visitor acts for themselves (a form, a purchase) may leave it out.\n */\n actor?: {\n kind: 'member' | 'visitor' | 'apiKey' | 'platform'\n uid?: string | null\n email?: string | null\n apiKeyName?: string | null\n }\n orgId: string\n /** The site the person was met on: the brand whose form or checkout it was. */\n hostId: string\n identity: PluginContactIdentity\n interaction: PluginContactInteraction\n /**\n * An explicit marketing opt-in the capture surface carried, recorded against\n * `hostId`: consent to the MARKETING send purpose (`marketing-consent.ts`),\n * whichever plugin's mail later asks for it. Absent is not consent, and\n * filing a capture under a container is not consent either.\n */\n marketingConsent?: boolean\n /**\n * The disclosure key the capture surface rendered beside that opt-in\n * (`consentGroupDisclosureKey`, AGL-3320), passed through as the surface\n * sent it. The owner records the opt-in for the site's whole consent group\n * only when this is the group's current key, and for `hostId` alone\n * otherwise — so a silo whose surface shows no disclosure sends none.\n */\n disclosedConsentGroup?: string\n /** Tags the silo puts on this capture; the owner adds, never replaces. */\n tags?: readonly string[]\n /**\n * What the capture SURFACE is filed under — the merchant's own act — by\n * container kind (`plugin-containers.ts`): `{ [kind]: ids }`. The owner\n * files the person under the kinds it keeps. Filing is not consent.\n */\n containers?: Readonly<Record<string, readonly string[]>>\n /** Money the capture was worth, in cents, where the silo took some. */\n purchaseCents?: number\n /** The currency {@link purchaseCents} is in, lowercase, when the silo knows. */\n purchaseCurrency?: string\n /**\n * The earliest stage this capture describes, in the OWNER's words. A floor\n * and not a value: the owner fills an empty stage and advances an earlier\n * one, and never moves anybody back, so a repeat customer filling in a\n * contact form is still a customer.\n */\n lifecycleFloor?: string\n /**\n * What kind of door this is, which decides WHICH RECORD the person lands\n * on (AGL-3232) — the owner's rule is Salesforce's: one person is one\n * record, a lead until they are qualified and a contact after.\n *\n * - `lead`: a lead surface — a form whose author routes it to leads, a\n * booking request. The person is filed as a LEAD and nothing else,\n * unless the workspace already holds them as a contact, in which case\n * the capture lands on the contact and no lead is filed.\n * - `relationship`: an act that makes the person a known relationship —\n * a member account, a purchase. The person is filed as a CONTACT, and\n * an open lead the site held for them is stamped converted onto it.\n * - `touch`, the default: everything else — a form without lead routing,\n * a newsletter opt-in. The capture lands on the open lead when the site\n * holds one for the address, and on the contact otherwise.\n */\n surface?: 'lead' | 'relationship' | 'touch'\n /**\n * Profile fields the silo knows, keyed by the owner's own field names.\n * Only the keys given are written, so a silo that knows the phone number\n * does not blank a title somebody typed.\n */\n profile?: Readonly<Record<string, unknown>>\n /**\n * Profile fields written only where the record the person lands on holds\n * none (AGL-3493), keyed as {@link profile} is. For what a door learned\n * in passing — the phone a booking form asked for — and must not put over\n * a value somebody typed on the record or gave the business before.\n * A key also present in `profile` is `profile`'s to write.\n */\n profileFill?: Readonly<Record<string, unknown>>\n /**\n * Facts about THIS CAPTURE that the owner models and the platform does not\n * (AGL-3080) — which form was filled in, which page it was on, where the\n * visitor arrived from.\n *\n * Opaque for the reason {@link profile} is: every one of these is a concept\n * belonging to the plugin that keeps people or to the silo that saw it, and\n * a typed core field for each would be those models spelled out in the\n * platform again. `profile` describes the PERSON and outlives the visit;\n * this describes the visit and does not.\n *\n * A key the owner does not recognize is the owner's to ignore, and a silo\n * that sends nothing here loses nothing: every field is an enrichment of a\n * capture that is already complete without it.\n */\n detail?: Readonly<Record<string, unknown>>\n}\n\n/**\n * The owner's answer. `created: false` is a visit by somebody the workspace\n * already held, which is an interaction and not a new person.\n *\n * A refusal is RETURNED. Every one of these silos has already done the thing\n * it was recording, so a throw here would cost an accepted submission or a\n * paid order for a record it could not keep.\n */\nexport type PluginContactCaptured =\n /** The capture landed on a contact — the record every capture used to make. */\n | { ok: true; record: 'contact'; contactId: string; created: boolean }\n /**\n * The capture landed on a LEAD (AGL-3232): a lead surface met somebody the\n * workspace does not hold as a contact, or a touch reached an open lead.\n * `leadId` is the person key the lead is filed under on `hostId`.\n */\n | {\n ok: true\n record: 'lead'\n leadId: string\n created: boolean\n /**\n * Whether this capture's surface was new on the lead — a new person,\n * or one first met through another door. A form counts its leads by\n * it (AGL-3330): the people it filed, not the times they came back.\n * Absent from a writer that does not say, which a caller reads as\n * `created`.\n */\n sourceAdded?: boolean\n }\n | {\n ok: false\n /**\n * `invalid-email`: nothing usable to key a person on. `band`: the\n * workspace is at the audience it may hold. `erased`: the workspace\n * erased this person and a capture must not quietly rebuild them.\n * `error`: the owner failed, and said so rather than silently.\n */\n reason: 'invalid-email' | 'band' | 'erased' | 'error'\n /** Customer-safe: a caller may show or log it as it stands. */\n error: string\n }\n\nexport interface PluginContactCaptureWriter {\n /** Records one capture; a refusal is returned, never thrown. */\n capture(\n request: PluginContactCaptureRequest,\n ): Promise<PluginContactCaptured>\n}\n\n/** A capture door a silo owns. */\nexport interface PluginContactSourceDeclaration {\n /** The silo's word for the door, as its captures carry it. */\n source: string\n /** What a timeline entry from this door says: `Form submission`. */\n label: string\n /**\n * What a link from that entry says when there is something to open —\n * `Open submission`. Absent where the door leaves nothing to open, which\n * is the honest answer for an import or a by-hand add.\n */\n openLabel?: string\n /**\n * The record kind the silo's own document is, for\n * `plugin-record-routes` to address it with. Absent where the door has no\n * record of its own to open.\n */\n recordKind?: string\n}\n\n/** A source declaration with the silo that made it. */\nexport type ResolvedPluginContactSource = PluginContactSourceDeclaration & {\n pluginId: string\n}\n\nexport const PLUGIN_CONTACT_CAPTURE =\n definePluginServiceContract<PluginContactCaptureWriter>(\n 'core.contact-capture',\n { multiple: false },\n )\n\nexport const PLUGIN_CONTACT_SOURCES =\n definePluginServiceContract<PluginContactSourceDeclaration>(\n 'core.contact-capture-sources',\n { multiple: true },\n )\n\n/**\n * Registers the plugin that keeps people. The owner is the loader's marker\n * when a register fn is running, else `options.pluginId`; with neither the\n * registration throws, and a second plugin's writer is refused naming both —\n * the incumbent keeps serving.\n */\nexport function registerPluginContactCaptureWriter(\n writer: PluginContactCaptureWriter,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_CONTACT_CAPTURE, writer, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** Declares a capture door. One source word has one owner. */\nexport function registerPluginContactSource(\n declaration: PluginContactSourceDeclaration,\n options?: { pluginId?: string },\n): void {\n const key = declaration.source?.trim() ?? ''\n if (!key) throw new Error('a contact capture source needs a source word')\n if (!declaration.label?.trim()) {\n throw new Error(`contact capture source \"${key}\" needs a label`)\n }\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n const incumbent = resolvePluginServices(PLUGIN_CONTACT_SOURCES).find(\n (entry) => entry.key === key,\n )\n if (incumbent && pluginId && incumbent.pluginId !== pluginId) {\n throw new Error(\n `contact capture source \"${key}\" is already declared by ` +\n `\"${incumbent.pluginId}\"; refused \"${pluginId}\"`,\n )\n }\n registerPluginService(\n PLUGIN_CONTACT_SOURCES,\n { ...declaration, source: key },\n {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n key,\n },\n )\n}\n\nexport interface ResolvedPluginContactCaptureWriter {\n /** The plugin that keeps people. */\n pluginId: string\n writer: PluginContactCaptureWriter\n}\n\n/** The writer with its owner, or `null` when no plugin keeps people here. */\nexport function pluginContactCaptureWriter(): ResolvedPluginContactCaptureWriter | null {\n const entry = resolvePluginServices(PLUGIN_CONTACT_CAPTURE)[0]\n return entry ? { pluginId: entry.pluginId, writer: entry.impl } : null\n}\n\n/**\n * Hands one capture to the plugin that keeps people, or answers `null` when\n * no plugin does.\n *\n * `null` and a refusal are different answers and a silo treats them\n * differently: `null` is a workspace with no record system, where there is\n * nothing to record and nothing has gone wrong; a refusal is a record system\n * that considered this person and declined.\n */\nexport async function capturePluginContact(\n request: PluginContactCaptureRequest,\n): Promise<PluginContactCaptured | null> {\n const found = pluginContactCaptureWriter()\n if (!found) return null\n return found.writer.capture(request)\n}\n\n/** Every declared capture door, with its silo, in registration order. */\nexport function listPluginContactSources(): ResolvedPluginContactSource[] {\n return resolvePluginServices(PLUGIN_CONTACT_SOURCES).map((entry) => ({\n ...entry.impl,\n pluginId: entry.pluginId,\n }))\n}\n\n/** One declared capture door by its source word, or `null`. */\nexport function pluginContactSource(\n source: string,\n): ResolvedPluginContactSource | null {\n const key = source.trim()\n const entry = resolvePluginServices(PLUGIN_CONTACT_SOURCES).find(\n (one) => one.key === key,\n )\n return entry ? { ...entry.impl, pluginId: entry.pluginId } : null\n}\n"],"names":["getRegisteringPluginId","definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_CONTACT_CAPTURE","multiple","PLUGIN_CONTACT_SOURCES","registerPluginContactCaptureWriter","writer","options","pluginId","registerPluginContactSource","declaration","key","source","trim","Error","label","incumbent","find","entry","pluginContactCaptureWriter","impl","capturePluginContact","request","found","capture","listPluginContactSources","map","pluginContactSource","one"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,sBAAsB,QAAQ,qCAAiC;AACxE,SACEC,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AAuQ1B,OAAO,MAAMC,yBACXH,4BACE,wBACA;IAAEI,UAAU;AAAM,GACnB;AAEH,OAAO,MAAMC,yBACXL,4BACE,gCACA;IAAEI,UAAU;AAAK,GAClB;AAEH;;;;;CAKC,GACD,OAAO,SAASE,mCACdC,MAAkC,EAClCC,OAA+B;IAE/BP,sBAAsBE,wBAAwBI,QAAQ,aAChDC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,4DAA4D,GAC5D,OAAO,SAASC,4BACdC,WAA2C,EAC3CH,OAA+B;cAObT,OAAAA;QALNY,qBAEPA;IAFL,MAAMC,eAAMD,sBAAAA,YAAYE,MAAM,qBAAlBF,oBAAoBG,IAAI,qBAAM;IAC1C,IAAI,CAACF,KAAK,MAAM,IAAIG,MAAM;IAC1B,IAAI,GAACJ,qBAAAA,YAAYK,KAAK,qBAAjBL,mBAAmBG,IAAI,KAAI;QAC9B,MAAM,IAAIC,MAAM,CAAC,wBAAwB,EAAEH,IAAI,eAAe,CAAC;IACjE;IACA,MAAMH,WAAW,EAACV,SAAAA,0BAAAA,oCAAAA,0BAA4BS,2BAAAA,QAASC,QAAQ,YAA7CV,QAAiD,IAAIe,IAAI;IAC3E,MAAMG,YAAYf,sBAAsBG,wBAAwBa,IAAI,CAClE,CAACC,QAAUA,MAAMP,GAAG,KAAKA;IAE3B,IAAIK,aAAaR,YAAYQ,UAAUR,QAAQ,KAAKA,UAAU;QAC5D,MAAM,IAAIM,MACR,CAAC,wBAAwB,EAAEH,IAAI,yBAAyB,CAAC,GACvD,CAAC,CAAC,EAAEK,UAAUR,QAAQ,CAAC,YAAY,EAAEA,SAAS,CAAC,CAAC;IAEtD;IACAR,sBACEI,wBACA,aAAKM;QAAaE,QAAQD;QAC1B,aACMJ,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;QAC1DG;;AAGN;AAQA,2EAA2E,GAC3E,OAAO,SAASQ;IACd,MAAMD,QAAQjB,sBAAsBC,uBAAuB,CAAC,EAAE;IAC9D,OAAOgB,QAAQ;QAAEV,UAAUU,MAAMV,QAAQ;QAAEF,QAAQY,MAAME,IAAI;IAAC,IAAI;AACpE;AAEA;;;;;;;;CAQC,GACD,OAAO,eAAeC,qBACpBC,OAAoC;IAEpC,MAAMC,QAAQJ;IACd,IAAI,CAACI,OAAO,OAAO;IACnB,OAAOA,MAAMjB,MAAM,CAACkB,OAAO,CAACF;AAC9B;AAEA,uEAAuE,GACvE,OAAO,SAASG;IACd,OAAOxB,sBAAsBG,wBAAwBsB,GAAG,CAAC,CAACR,QAAW,aAChEA,MAAME,IAAI;YACbZ,UAAUU,MAAMV,QAAQ;;AAE5B;AAEA,6DAA6D,GAC7D,OAAO,SAASmB,oBACdf,MAAc;IAEd,MAAMD,MAAMC,OAAOC,IAAI;IACvB,MAAMK,QAAQjB,sBAAsBG,wBAAwBa,IAAI,CAC9D,CAACW,MAAQA,IAAIjB,GAAG,KAAKA;IAEvB,OAAOO,QAAQ,aAAKA,MAAME,IAAI;QAAEZ,UAAUU,MAAMV,QAAQ;SAAK;AAC/D"}
@@ -0,0 +1,216 @@
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
+ * Credit for an outcome, handed to whichever plugin keeps track of where
19
+ * visitors came from (AGL-3080).
20
+ *
21
+ * Many doors produce an outcome somebody may be credited with: a form
22
+ * submission, a booking, a member sign-up, a newsletter opt-in, the contact
23
+ * and the lead the record system files for them, an order and its refund,
24
+ * and what a plugin's own outbound mail led to. None of those doors is the
25
+ * plugin that decides WHICH arrival earns the credit — the window, the
26
+ * last-touch rule, the channels compared, the rollups a report reads — and
27
+ * each of them used to import that plugin's join from the core, which is
28
+ * how a marketing model came to live in the platform's data layer.
29
+ *
30
+ * So a door stops importing and starts ASKING. The plugin that credits
31
+ * outcomes registers one creditor here; every door hands it what it saw.
32
+ *
33
+ * - {@link resolveConversionTouch}, once per request, at the door: where
34
+ * the visitor arrived from, from what their browser carried and the
35
+ * address they gave. The answer is OPAQUE to the door — it is the
36
+ * creditor's own shape, handed back unread to every writer beneath the
37
+ * door, so one visitor action pays for one lookup and its records cannot
38
+ * disagree about the arrival. A door that hands it to the person capture
39
+ * puts it under {@link CONVERSION_TOUCH_DETAIL} in the capture's
40
+ * `detail`, which the plugin that keeps people passes on as it found it.
41
+ * - {@link creditConversion}: one identify moment (`kind` is the door's own
42
+ * word — a form, a lead, a contact, a booking), credited to that touch or
43
+ * to a click a plugin's own mail recorded.
44
+ * - {@link creditOrderConversion} and {@link reverseOrderConversion}: money
45
+ * an order brought in, and money a refund or a chargeback took back.
46
+ * - {@link recordConversionClick}: a person followed a link in mail a
47
+ * plugin sent them, which a later outcome of theirs may be credited to.
48
+ * - {@link creditConversionOutcome}: what a plugin's own record produced —
49
+ * an enrollment enrolled, sent, replied — counted under every container
50
+ * that record is filed under (`plugin-containers.ts`).
51
+ * - {@link eraseConversionCredits}: everything the creditor holds about a
52
+ * person, by the key every suppression list names them by, on every site.
53
+ * The platform's own address erasure calls it beside the delivery log.
54
+ *
55
+ * ## No creditor is an answer
56
+ *
57
+ * A workspace whose plugins credit nothing has nothing to credit, and every
58
+ * door's outcome is complete without it. So each call answers its empty
59
+ * value — `null`, `false`, `0` — and the door goes on. A boot whose
60
+ * declarations failed looks the same from here, so the first call that finds
61
+ * nobody runs the app's boot step once (`plugin-declarations-repair.ts`) and
62
+ * asks again, as a person capture does.
63
+ *
64
+ * ## Never throws
65
+ *
66
+ * Every door has already done the thing being credited — the submission is
67
+ * stored, the money has moved. A lost credit understates a report; a thrown
68
+ * one loses a lead or a checkout. A creditor that throws anyway is logged
69
+ * and answered as empty.
70
+ *
71
+ * ## The caller proves who is asking
72
+ *
73
+ * The registry authenticates nobody. A door has decided, in its own terms and
74
+ * before it asks, that the outcome happened on that site.
75
+ *
76
+ * Server-side: reached by its own subpath, never through
77
+ * `plugin-manager/index.ts`.
78
+ */
79
+ /**
80
+ * Where a visitor arrived from, as the creditor resolved it. Opaque: no door
81
+ * reads it, and every door hands it back exactly as it was given.
82
+ */
83
+ export type PluginConversionTouch = Readonly<Record<string, unknown>>;
84
+ /**
85
+ * The key a door files a resolved touch under in a person capture's
86
+ * `detail` (`plugin-contact-capture.ts`), so the plugin that keeps people
87
+ * credits the contact or the lead it files to the same arrival.
88
+ */
89
+ export declare const CONVERSION_TOUCH_DETAIL = "conversionTouch";
90
+ /** What a door knows when it asks where its visitor came from. */
91
+ export interface PluginConversionTouchRequest {
92
+ hostId: string;
93
+ /**
94
+ * What the visitor's browser carried for the purpose, as the request
95
+ * delivered it. Untrusted: the creditor re-parses it.
96
+ */
97
+ wire?: unknown;
98
+ /** The address the visitor identified with, raw, when they gave one. */
99
+ email?: unknown;
100
+ /** When they identified themselves. Defaults to now. */
101
+ atMs?: number;
102
+ }
103
+ /**
104
+ * A link in mail a plugin sent, followed by the person it was sent to.
105
+ *
106
+ * `creditTo` is what the mail was sent under, in the creditor's terms: the
107
+ * send, or the container the sending record is filed under. `via` is the
108
+ * sending plugin's own facts about the message (its sequence, its
109
+ * enrollment), passed through for the creditor's record and unread here.
110
+ */
111
+ export interface PluginConversionClick {
112
+ hostId: string;
113
+ creditTo: string;
114
+ /** When the link was followed, epoch ms. */
115
+ atMs: number;
116
+ via?: Readonly<Record<string, string>>;
117
+ }
118
+ /** One identify moment, credited to how the visitor arrived. */
119
+ export interface PluginConversionRequest {
120
+ hostId: string;
121
+ /** The door's word for the moment: `form`, `lead`, `contact`, `booking`. */
122
+ kind: string;
123
+ /** The record the moment produced, in the door's terms. */
124
+ refId: string;
125
+ /** The touch {@link resolveConversionTouch} answered; `null` credits nobody. */
126
+ touch?: PluginConversionTouch | null;
127
+ /** A click the door's own mail recorded, credited in place of a touch. */
128
+ click?: PluginConversionClick | null;
129
+ /** When the visitor became identifiable. Defaults to now. */
130
+ convertedAtMs?: number;
131
+ }
132
+ /** Money an order brought in, credited to the buyer's last arrival. */
133
+ export interface PluginOrderConversionRequest {
134
+ hostId: string;
135
+ /** The order, booking or invoice the money came in on. */
136
+ orderId: string;
137
+ /** The buyer as the sale recorded them, raw. */
138
+ email: unknown;
139
+ /** Gross minor units the buyer was charged. */
140
+ amountCents: number;
141
+ /** Lowercase currency code, when the door knows one. */
142
+ currency?: string;
143
+ /** When the order was placed. Defaults to now. */
144
+ orderedAtMs?: number;
145
+ }
146
+ /** Money taken back off an order: a refund, or a lost dispute. */
147
+ export interface PluginOrderReversalRequest {
148
+ hostId: string;
149
+ orderId: string;
150
+ /** Minor units reversed by THIS attempt, never the order total. */
151
+ amountCents: number;
152
+ /** True only for the write that moved the order into fully refunded. */
153
+ closedTheOrder: boolean;
154
+ kind?: 'refund' | 'chargeback';
155
+ }
156
+ /** What a plugin's own record produced, counted under its containers. */
157
+ export interface PluginConversionOutcomeRequest {
158
+ /** The site the record acts for; the creditor resolves the org from it. */
159
+ hostId: string;
160
+ /** The org, when the caller holds it. */
161
+ orgId?: string | null;
162
+ /** The containers the record is filed under (`plugin-containers.ts`). */
163
+ containerIds: readonly string[];
164
+ /** The sending plugin's word for the outcome: `enrolled`, `replied`. */
165
+ outcome: string;
166
+ atMs?: number;
167
+ }
168
+ export interface PluginConversionCreditor {
169
+ resolveTouch(request: PluginConversionTouchRequest): Promise<PluginConversionTouch | null>;
170
+ /** Answers whether a credit was written; once per (`kind`, `refId`). */
171
+ creditConversion(request: PluginConversionRequest): Promise<boolean>;
172
+ creditOrder(request: PluginOrderConversionRequest): Promise<boolean>;
173
+ reverseOrder(request: PluginOrderReversalRequest): Promise<boolean>;
174
+ /** Answers whether the person's last click on that site moved forward. */
175
+ recordClick(click: PluginConversionClick & {
176
+ email: unknown;
177
+ }): Promise<boolean>;
178
+ /** Answers how many containers were credited. */
179
+ creditOutcome(request: PluginConversionOutcomeRequest): Promise<number>;
180
+ /**
181
+ * Erases what the creditor holds about one person, on every site, by
182
+ * `personKey`/`emailSuppressionKey` — the same hash. Answers how many
183
+ * records went.
184
+ */
185
+ erasePerson(key: string): Promise<number>;
186
+ }
187
+ export declare const PLUGIN_CONVERSION_CREDIT: import("./plugin-services").PluginServiceContract<PluginConversionCreditor>;
188
+ /**
189
+ * Registers the plugin that credits outcomes. The owner is the loader's
190
+ * marker when a register fn is running, else `options.pluginId`; a second
191
+ * plugin's creditor is refused naming both, and the incumbent keeps serving.
192
+ */
193
+ export declare function registerPluginConversionCreditor(creditor: PluginConversionCreditor, options?: {
194
+ pluginId?: string;
195
+ }): void;
196
+ /** The creditor with its owner, or `null` when no plugin credits outcomes here. */
197
+ export declare function pluginConversionCreditor(): {
198
+ pluginId: string;
199
+ creditor: PluginConversionCreditor;
200
+ } | null;
201
+ /** Where the visitor arrived from, or `null`: direct, or nobody credits. */
202
+ export declare function resolveConversionTouch(request: PluginConversionTouchRequest): Promise<PluginConversionTouch | null>;
203
+ /** Credits one identify moment. A request with no touch and no click credits nobody. */
204
+ export declare function creditConversion(request: PluginConversionRequest): Promise<boolean>;
205
+ /** Credits an order's money to the buyer's last arrival. */
206
+ export declare function creditOrderConversion(request: PluginOrderConversionRequest): Promise<boolean>;
207
+ /** Takes money back off an order's credit. */
208
+ export declare function reverseOrderConversion(request: PluginOrderReversalRequest): Promise<boolean>;
209
+ /** Records that a person followed a link in mail a plugin sent them. */
210
+ export declare function recordConversionClick(click: PluginConversionClick & {
211
+ email: unknown;
212
+ }): Promise<boolean>;
213
+ /** Counts what a plugin's own record produced under each of its containers. */
214
+ export declare function creditConversionOutcome(request: PluginConversionOutcomeRequest): Promise<number>;
215
+ /** Erases what the creditor holds about one person, by key. */
216
+ export declare function eraseConversionCredits(key: string): Promise<number>;
@@ -0,0 +1,91 @@
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 { definePluginServiceContract, registerPluginService, resolvePluginServices } from "./plugin-services.js";
18
+ import { runPluginDeclarationsRepair } from "./plugin-declarations-repair.js";
19
+ /**
20
+ * The key a door files a resolved touch under in a person capture's
21
+ * `detail` (`plugin-contact-capture.ts`), so the plugin that keeps people
22
+ * credits the contact or the lead it files to the same arrival.
23
+ */ export const CONVERSION_TOUCH_DETAIL = 'conversionTouch';
24
+ export const PLUGIN_CONVERSION_CREDIT = definePluginServiceContract('core.conversion-credit', {
25
+ multiple: false
26
+ });
27
+ /**
28
+ * Registers the plugin that credits outcomes. The owner is the loader's
29
+ * marker when a register fn is running, else `options.pluginId`; a second
30
+ * plugin's creditor is refused naming both, and the incumbent keeps serving.
31
+ */ export function registerPluginConversionCreditor(creditor, options) {
32
+ registerPluginService(PLUGIN_CONVERSION_CREDIT, creditor, _extends({}, (options == null ? void 0 : options.pluginId) ? {
33
+ pluginId: options.pluginId
34
+ } : {}));
35
+ }
36
+ /** The creditor with its owner, or `null` when no plugin credits outcomes here. */ export function pluginConversionCreditor() {
37
+ const entry = resolvePluginServices(PLUGIN_CONVERSION_CREDIT)[0];
38
+ return entry ? {
39
+ pluginId: entry.pluginId,
40
+ creditor: entry.impl
41
+ } : null;
42
+ }
43
+ /**
44
+ * The creditor, running the app's boot step once when nobody answers. The
45
+ * step memoizes its own promise, so this is one attempt per process.
46
+ */ async function creditor() {
47
+ var _ref;
48
+ var _pluginConversionCreditor;
49
+ const found = pluginConversionCreditor();
50
+ if (found) return found.creditor;
51
+ try {
52
+ await runPluginDeclarationsRepair();
53
+ } catch (error) {
54
+ console.error('[conversion-credit] plugin declarations failed', error);
55
+ }
56
+ return (_ref = (_pluginConversionCreditor = pluginConversionCreditor()) == null ? void 0 : _pluginConversionCreditor.creditor) != null ? _ref : null;
57
+ }
58
+ /** One call, with the empty answer for nobody and for a throw. */ async function ask(what, empty, call) {
59
+ try {
60
+ const found = await creditor();
61
+ return found ? await call(found) : empty;
62
+ } catch (error) {
63
+ console.error(`[conversion-credit] ${what} failed`, error);
64
+ return empty;
65
+ }
66
+ }
67
+ /** Where the visitor arrived from, or `null`: direct, or nobody credits. */ export function resolveConversionTouch(request) {
68
+ return ask('resolveTouch', null, (found)=>found.resolveTouch(request));
69
+ }
70
+ /** Credits one identify moment. A request with no touch and no click credits nobody. */ export function creditConversion(request) {
71
+ if (!request.touch && !request.click) return Promise.resolve(false);
72
+ return ask('creditConversion', false, (found)=>found.creditConversion(request));
73
+ }
74
+ /** Credits an order's money to the buyer's last arrival. */ export function creditOrderConversion(request) {
75
+ return ask('creditOrder', false, (found)=>found.creditOrder(request));
76
+ }
77
+ /** Takes money back off an order's credit. */ export function reverseOrderConversion(request) {
78
+ return ask('reverseOrder', false, (found)=>found.reverseOrder(request));
79
+ }
80
+ /** Records that a person followed a link in mail a plugin sent them. */ export function recordConversionClick(click) {
81
+ return ask('recordClick', false, (found)=>found.recordClick(click));
82
+ }
83
+ /** Counts what a plugin's own record produced under each of its containers. */ export function creditConversionOutcome(request) {
84
+ return ask('creditOutcome', 0, (found)=>found.creditOutcome(request));
85
+ }
86
+ /** Erases what the creditor holds about one person, by key. */ export function eraseConversionCredits(key) {
87
+ if (!key) return Promise.resolve(0);
88
+ return ask('erasePerson', 0, (found)=>found.erasePerson(key));
89
+ }
90
+
91
+ //# sourceMappingURL=plugin-conversion-credit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-conversion-credit.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 {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\nimport { runPluginDeclarationsRepair } from './plugin-declarations-repair'\n\n/**\n * Credit for an outcome, handed to whichever plugin keeps track of where\n * visitors came from (AGL-3080).\n *\n * Many doors produce an outcome somebody may be credited with: a form\n * submission, a booking, a member sign-up, a newsletter opt-in, the contact\n * and the lead the record system files for them, an order and its refund,\n * and what a plugin's own outbound mail led to. None of those doors is the\n * plugin that decides WHICH arrival earns the credit — the window, the\n * last-touch rule, the channels compared, the rollups a report reads — and\n * each of them used to import that plugin's join from the core, which is\n * how a marketing model came to live in the platform's data layer.\n *\n * So a door stops importing and starts ASKING. The plugin that credits\n * outcomes registers one creditor here; every door hands it what it saw.\n *\n * - {@link resolveConversionTouch}, once per request, at the door: where\n * the visitor arrived from, from what their browser carried and the\n * address they gave. The answer is OPAQUE to the door — it is the\n * creditor's own shape, handed back unread to every writer beneath the\n * door, so one visitor action pays for one lookup and its records cannot\n * disagree about the arrival. A door that hands it to the person capture\n * puts it under {@link CONVERSION_TOUCH_DETAIL} in the capture's\n * `detail`, which the plugin that keeps people passes on as it found it.\n * - {@link creditConversion}: one identify moment (`kind` is the door's own\n * word — a form, a lead, a contact, a booking), credited to that touch or\n * to a click a plugin's own mail recorded.\n * - {@link creditOrderConversion} and {@link reverseOrderConversion}: money\n * an order brought in, and money a refund or a chargeback took back.\n * - {@link recordConversionClick}: a person followed a link in mail a\n * plugin sent them, which a later outcome of theirs may be credited to.\n * - {@link creditConversionOutcome}: what a plugin's own record produced —\n * an enrollment enrolled, sent, replied — counted under every container\n * that record is filed under (`plugin-containers.ts`).\n * - {@link eraseConversionCredits}: everything the creditor holds about a\n * person, by the key every suppression list names them by, on every site.\n * The platform's own address erasure calls it beside the delivery log.\n *\n * ## No creditor is an answer\n *\n * A workspace whose plugins credit nothing has nothing to credit, and every\n * door's outcome is complete without it. So each call answers its empty\n * value — `null`, `false`, `0` — and the door goes on. A boot whose\n * declarations failed looks the same from here, so the first call that finds\n * nobody runs the app's boot step once (`plugin-declarations-repair.ts`) and\n * asks again, as a person capture does.\n *\n * ## Never throws\n *\n * Every door has already done the thing being credited — the submission is\n * stored, the money has moved. A lost credit understates a report; a thrown\n * one loses a lead or a checkout. A creditor that throws anyway is logged\n * and answered as empty.\n *\n * ## The caller proves who is asking\n *\n * The registry authenticates nobody. A door has decided, in its own terms and\n * before it asks, that the outcome happened on that site.\n *\n * Server-side: reached by its own subpath, never through\n * `plugin-manager/index.ts`.\n */\n\n/**\n * Where a visitor arrived from, as the creditor resolved it. Opaque: no door\n * reads it, and every door hands it back exactly as it was given.\n */\nexport type PluginConversionTouch = Readonly<Record<string, unknown>>\n\n/**\n * The key a door files a resolved touch under in a person capture's\n * `detail` (`plugin-contact-capture.ts`), so the plugin that keeps people\n * credits the contact or the lead it files to the same arrival.\n */\nexport const CONVERSION_TOUCH_DETAIL = 'conversionTouch'\n\n/** What a door knows when it asks where its visitor came from. */\nexport interface PluginConversionTouchRequest {\n hostId: string\n /**\n * What the visitor's browser carried for the purpose, as the request\n * delivered it. Untrusted: the creditor re-parses it.\n */\n wire?: unknown\n /** The address the visitor identified with, raw, when they gave one. */\n email?: unknown\n /** When they identified themselves. Defaults to now. */\n atMs?: number\n}\n\n/**\n * A link in mail a plugin sent, followed by the person it was sent to.\n *\n * `creditTo` is what the mail was sent under, in the creditor's terms: the\n * send, or the container the sending record is filed under. `via` is the\n * sending plugin's own facts about the message (its sequence, its\n * enrollment), passed through for the creditor's record and unread here.\n */\nexport interface PluginConversionClick {\n hostId: string\n creditTo: string\n /** When the link was followed, epoch ms. */\n atMs: number\n via?: Readonly<Record<string, string>>\n}\n\n/** One identify moment, credited to how the visitor arrived. */\nexport interface PluginConversionRequest {\n hostId: string\n /** The door's word for the moment: `form`, `lead`, `contact`, `booking`. */\n kind: string\n /** The record the moment produced, in the door's terms. */\n refId: string\n /** The touch {@link resolveConversionTouch} answered; `null` credits nobody. */\n touch?: PluginConversionTouch | null\n /** A click the door's own mail recorded, credited in place of a touch. */\n click?: PluginConversionClick | null\n /** When the visitor became identifiable. Defaults to now. */\n convertedAtMs?: number\n}\n\n/** Money an order brought in, credited to the buyer's last arrival. */\nexport interface PluginOrderConversionRequest {\n hostId: string\n /** The order, booking or invoice the money came in on. */\n orderId: string\n /** The buyer as the sale recorded them, raw. */\n email: unknown\n /** Gross minor units the buyer was charged. */\n amountCents: number\n /** Lowercase currency code, when the door knows one. */\n currency?: string\n /** When the order was placed. Defaults to now. */\n orderedAtMs?: number\n}\n\n/** Money taken back off an order: a refund, or a lost dispute. */\nexport interface PluginOrderReversalRequest {\n hostId: string\n orderId: string\n /** Minor units reversed by THIS attempt, never the order total. */\n amountCents: number\n /** True only for the write that moved the order into fully refunded. */\n closedTheOrder: boolean\n kind?: 'refund' | 'chargeback'\n}\n\n/** What a plugin's own record produced, counted under its containers. */\nexport interface PluginConversionOutcomeRequest {\n /** The site the record acts for; the creditor resolves the org from it. */\n hostId: string\n /** The org, when the caller holds it. */\n orgId?: string | null\n /** The containers the record is filed under (`plugin-containers.ts`). */\n containerIds: readonly string[]\n /** The sending plugin's word for the outcome: `enrolled`, `replied`. */\n outcome: string\n atMs?: number\n}\n\nexport interface PluginConversionCreditor {\n resolveTouch(request: PluginConversionTouchRequest): Promise<PluginConversionTouch | null>\n /** Answers whether a credit was written; once per (`kind`, `refId`). */\n creditConversion(request: PluginConversionRequest): Promise<boolean>\n creditOrder(request: PluginOrderConversionRequest): Promise<boolean>\n reverseOrder(request: PluginOrderReversalRequest): Promise<boolean>\n /** Answers whether the person's last click on that site moved forward. */\n recordClick(click: PluginConversionClick & { email: unknown }): Promise<boolean>\n /** Answers how many containers were credited. */\n creditOutcome(request: PluginConversionOutcomeRequest): Promise<number>\n /**\n * Erases what the creditor holds about one person, on every site, by\n * `personKey`/`emailSuppressionKey` — the same hash. Answers how many\n * records went.\n */\n erasePerson(key: string): Promise<number>\n}\n\nexport const PLUGIN_CONVERSION_CREDIT = definePluginServiceContract<PluginConversionCreditor>(\n 'core.conversion-credit',\n { multiple: false },\n)\n\n/**\n * Registers the plugin that credits outcomes. The owner is the loader's\n * marker when a register fn is running, else `options.pluginId`; a second\n * plugin's creditor is refused naming both, and the incumbent keeps serving.\n */\nexport function registerPluginConversionCreditor(\n creditor: PluginConversionCreditor,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_CONVERSION_CREDIT, creditor, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The creditor with its owner, or `null` when no plugin credits outcomes here. */\nexport function pluginConversionCreditor(): { pluginId: string; creditor: PluginConversionCreditor } | null {\n const entry = resolvePluginServices(PLUGIN_CONVERSION_CREDIT)[0]\n return entry ? { pluginId: entry.pluginId, creditor: entry.impl } : null\n}\n\n/**\n * The creditor, running the app's boot step once when nobody answers. The\n * step memoizes its own promise, so this is one attempt per process.\n */\nasync function creditor(): Promise<PluginConversionCreditor | null> {\n const found = pluginConversionCreditor()\n if (found) return found.creditor\n try {\n await runPluginDeclarationsRepair()\n } catch (error) {\n console.error('[conversion-credit] plugin declarations failed', error)\n }\n return pluginConversionCreditor()?.creditor ?? null\n}\n\n/** One call, with the empty answer for nobody and for a throw. */\nasync function ask<T>(\n what: string,\n empty: T,\n call: (found: PluginConversionCreditor) => Promise<T>,\n): Promise<T> {\n try {\n const found = await creditor()\n return found ? await call(found) : empty\n } catch (error) {\n console.error(`[conversion-credit] ${what} failed`, error)\n return empty\n }\n}\n\n/** Where the visitor arrived from, or `null`: direct, or nobody credits. */\nexport function resolveConversionTouch(\n request: PluginConversionTouchRequest,\n): Promise<PluginConversionTouch | null> {\n return ask('resolveTouch', null, (found) => found.resolveTouch(request))\n}\n\n/** Credits one identify moment. A request with no touch and no click credits nobody. */\nexport function creditConversion(request: PluginConversionRequest): Promise<boolean> {\n if (!request.touch && !request.click) return Promise.resolve(false)\n return ask('creditConversion', false, (found) => found.creditConversion(request))\n}\n\n/** Credits an order's money to the buyer's last arrival. */\nexport function creditOrderConversion(request: PluginOrderConversionRequest): Promise<boolean> {\n return ask('creditOrder', false, (found) => found.creditOrder(request))\n}\n\n/** Takes money back off an order's credit. */\nexport function reverseOrderConversion(request: PluginOrderReversalRequest): Promise<boolean> {\n return ask('reverseOrder', false, (found) => found.reverseOrder(request))\n}\n\n/** Records that a person followed a link in mail a plugin sent them. */\nexport function recordConversionClick(\n click: PluginConversionClick & { email: unknown },\n): Promise<boolean> {\n return ask('recordClick', false, (found) => found.recordClick(click))\n}\n\n/** Counts what a plugin's own record produced under each of its containers. */\nexport function creditConversionOutcome(request: PluginConversionOutcomeRequest): Promise<number> {\n return ask('creditOutcome', 0, (found) => found.creditOutcome(request))\n}\n\n/** Erases what the creditor holds about one person, by key. */\nexport function eraseConversionCredits(key: string): Promise<number> {\n if (!key) return Promise.resolve(0)\n return ask('erasePerson', 0, (found) => found.erasePerson(key))\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","runPluginDeclarationsRepair","CONVERSION_TOUCH_DETAIL","PLUGIN_CONVERSION_CREDIT","multiple","registerPluginConversionCreditor","creditor","options","pluginId","pluginConversionCreditor","entry","impl","found","error","console","ask","what","empty","call","resolveConversionTouch","request","resolveTouch","creditConversion","touch","click","Promise","resolve","creditOrderConversion","creditOrder","reverseOrderConversion","reverseOrder","recordConversionClick","recordClick","creditConversionOutcome","creditOutcome","eraseConversionCredits","key","erasePerson"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AAC1B,SAASC,2BAA2B,QAAQ,kCAA8B;AAuE1E;;;;CAIC,GACD,OAAO,MAAMC,0BAA0B,kBAAiB;AAwGxD,OAAO,MAAMC,2BAA2BL,4BACtC,0BACA;IAAEM,UAAU;AAAM,GACnB;AAED;;;;CAIC,GACD,OAAO,SAASC,iCACdC,QAAkC,EAClCC,OAA+B;IAE/BR,sBAAsBI,0BAA0BG,UAAU,aACpDC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,iFAAiF,GACjF,OAAO,SAASC;IACd,MAAMC,QAAQV,sBAAsBG,yBAAyB,CAAC,EAAE;IAChE,OAAOO,QAAQ;QAAEF,UAAUE,MAAMF,QAAQ;QAAEF,UAAUI,MAAMC,IAAI;IAAC,IAAI;AACtE;AAEA;;;CAGC,GACD,eAAeL;;QAQNG;IAPP,MAAMG,QAAQH;IACd,IAAIG,OAAO,OAAOA,MAAMN,QAAQ;IAChC,IAAI;QACF,MAAML;IACR,EAAE,OAAOY,OAAO;QACdC,QAAQD,KAAK,CAAC,kDAAkDA;IAClE;IACA,gBAAOJ,4BAAAA,+CAAAA,0BAA4BH,QAAQ,mBAAI;AACjD;AAEA,gEAAgE,GAChE,eAAeS,IACbC,IAAY,EACZC,KAAQ,EACRC,IAAqD;IAErD,IAAI;QACF,MAAMN,QAAQ,MAAMN;QACpB,OAAOM,QAAQ,MAAMM,KAAKN,SAASK;IACrC,EAAE,OAAOJ,OAAO;QACdC,QAAQD,KAAK,CAAC,CAAC,oBAAoB,EAAEG,KAAK,OAAO,CAAC,EAAEH;QACpD,OAAOI;IACT;AACF;AAEA,0EAA0E,GAC1E,OAAO,SAASE,uBACdC,OAAqC;IAErC,OAAOL,IAAI,gBAAgB,MAAM,CAACH,QAAUA,MAAMS,YAAY,CAACD;AACjE;AAEA,sFAAsF,GACtF,OAAO,SAASE,iBAAiBF,OAAgC;IAC/D,IAAI,CAACA,QAAQG,KAAK,IAAI,CAACH,QAAQI,KAAK,EAAE,OAAOC,QAAQC,OAAO,CAAC;IAC7D,OAAOX,IAAI,oBAAoB,OAAO,CAACH,QAAUA,MAAMU,gBAAgB,CAACF;AAC1E;AAEA,0DAA0D,GAC1D,OAAO,SAASO,sBAAsBP,OAAqC;IACzE,OAAOL,IAAI,eAAe,OAAO,CAACH,QAAUA,MAAMgB,WAAW,CAACR;AAChE;AAEA,4CAA4C,GAC5C,OAAO,SAASS,uBAAuBT,OAAmC;IACxE,OAAOL,IAAI,gBAAgB,OAAO,CAACH,QAAUA,MAAMkB,YAAY,CAACV;AAClE;AAEA,sEAAsE,GACtE,OAAO,SAASW,sBACdP,KAAiD;IAEjD,OAAOT,IAAI,eAAe,OAAO,CAACH,QAAUA,MAAMoB,WAAW,CAACR;AAChE;AAEA,6EAA6E,GAC7E,OAAO,SAASS,wBAAwBb,OAAuC;IAC7E,OAAOL,IAAI,iBAAiB,GAAG,CAACH,QAAUA,MAAMsB,aAAa,CAACd;AAChE;AAEA,6DAA6D,GAC7D,OAAO,SAASe,uBAAuBC,GAAW;IAChD,IAAI,CAACA,KAAK,OAAOX,QAAQC,OAAO,CAAC;IACjC,OAAOX,IAAI,eAAe,GAAG,CAACH,QAAUA,MAAMyB,WAAW,CAACD;AAC5D"}
@@ -0,0 +1,83 @@
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
+ /** One equality clause a kind's documents are narrowed by, on the server. */
18
+ export interface PluginEntityPickerClause {
19
+ field: string;
20
+ equals: string | number | boolean;
21
+ }
22
+ export interface PluginEntityPickerDeclaration {
23
+ /**
24
+ * The kind's key in the picker context — `options[kind]`, `status[kind]`,
25
+ * `resolved[kind]` — and in a repeat source's `entityKind`. Unique.
26
+ */
27
+ kind: string;
28
+ /** The attribute type (`FieldComponentType`) whose picker lists this kind. */
29
+ attribute: string;
30
+ /**
31
+ * Where the kind's documents hang: `host` — the site's own
32
+ * `hosts/<hostId>/<collection>`; `orgData` — the organization's data, read
33
+ * where the site's data scope resolves and narrowed to what the site may
34
+ * use.
35
+ */
36
+ scope: 'host' | 'orgData';
37
+ /** The collection's name under the scope's root. */
38
+ collection: string;
39
+ /** The field holding a document's name; `name` is read when it is empty. */
40
+ nameField: string;
41
+ /** Equality clauses applied to the browse AND the search, on the server. */
42
+ where?: readonly PluginEntityPickerClause[];
43
+ /**
44
+ * The documents carry the name-search keys (`nameTokens`, `nameLower`),
45
+ * so a typed query reaches past the browse window.
46
+ */
47
+ searchable?: boolean;
48
+ /**
49
+ * An attribute type whose options are the FIELDS of a chosen entity of
50
+ * this kind — a form field's "Maps to schema field" — with `fieldsFrom`,
51
+ * the record kind whose console list source shares each entity's `fields`
52
+ * as `{ id, name }` (`plugin-record-lists`).
53
+ */
54
+ fieldsAttribute?: string;
55
+ fieldsFrom?: string;
56
+ /** The words a picker says: "No (plural) yet — add one on (page)". */
57
+ singular: string;
58
+ plural: string;
59
+ page: string;
60
+ }
61
+ /** A declaration with the plugin that made it. */
62
+ export type ResolvedPluginEntityPicker = PluginEntityPickerDeclaration & {
63
+ pluginId: string;
64
+ };
65
+ /**
66
+ * The load point a plugin whose console list source shares an entity's
67
+ * fields declares in its `console.slots` (`fieldsFrom`), so the console's
68
+ * picker provider loads it: the declaration is compiled, the fields are read
69
+ * through the source at run time.
70
+ */
71
+ export declare const ENTITY_PICKERS_LOAD_POINT = "entityPickers";
72
+ /** Every declared picker kind, in the order the plugins are configured. */
73
+ export declare function pluginEntityPickers(): readonly ResolvedPluginEntityPicker[];
74
+ /** The declared kind with this key, or `null`. */
75
+ export declare function pluginEntityPicker(kind: string): ResolvedPluginEntityPicker | null;
76
+ /** The kind an attribute type's picker lists, or `null` when it lists none. */
77
+ export declare function entityPickerForAttribute(attribute: string | undefined): ResolvedPluginEntityPicker | null;
78
+ /**
79
+ * The kind whose entity FIELDS an attribute type offers, or `null` — the
80
+ * picker needs that kind's list to resolve the entity its fields are read
81
+ * from, without being a picker of the kind itself.
82
+ */
83
+ export declare function entityFieldsPickerForAttribute(attribute: string | undefined): ResolvedPluginEntityPicker | null;