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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/README.md +2 -2
  2. package/package.json +18 -18
  3. package/src/index.d.ts +1 -0
  4. package/src/index.js.map +1 -1
  5. package/src/lib/activity/ai-activity-actions.js +8 -1
  6. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  7. package/src/lib/billing/ai-month-meter.d.ts +51 -0
  8. package/src/lib/billing/ai-month-meter.js +89 -0
  9. package/src/lib/billing/ai-month-meter.js.map +1 -0
  10. package/src/lib/billing/ai-overage-close.d.ts +11 -33
  11. package/src/lib/billing/ai-overage-close.js +4 -3
  12. package/src/lib/billing/ai-overage-close.js.map +1 -1
  13. package/src/lib/billing/ai-overage-cutover.d.ts +28 -12
  14. package/src/lib/billing/ai-overage-cutover.js +1 -28
  15. package/src/lib/billing/ai-overage-cutover.js.map +1 -1
  16. package/src/lib/billing/ai-overage-ledger.js +1 -1
  17. package/src/lib/billing/ai-overage-ledger.js.map +1 -1
  18. package/src/lib/billing/ai-overage-standing.js +1 -1
  19. package/src/lib/billing/ai-overage-standing.js.map +1 -1
  20. package/src/lib/components/ai-allotment-editor.component.js +1 -1
  21. package/src/lib/components/ai-allotment-editor.component.js.map +1 -1
  22. package/src/lib/components/ai-assist-provider.component.js +5 -4
  23. package/src/lib/components/ai-assist-provider.component.js.map +1 -1
  24. package/src/lib/components/ai-automation-zones.d.ts +63 -0
  25. package/src/lib/components/ai-automation-zones.js +31 -0
  26. package/src/lib/components/ai-automation-zones.js.map +1 -0
  27. package/src/lib/components/ai-brief-dialog.component.js +3 -3
  28. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  29. package/src/lib/components/ai-describe-automation.component.d.ts +1 -1
  30. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  31. package/src/lib/components/ai-describe-button.component.d.ts +6 -3
  32. package/src/lib/components/ai-describe-button.component.js +4 -1
  33. package/src/lib/components/ai-describe-button.component.js.map +1 -1
  34. package/src/lib/components/ai-experiment-cards.component.js +1 -1
  35. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  36. package/src/lib/components/ai-explain-automation.component.d.ts +1 -1
  37. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  38. package/src/lib/components/ai-job-plan.component.d.ts +8 -0
  39. package/src/lib/components/ai-job-plan.component.js +21 -4
  40. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  41. package/src/lib/components/ai-permissions-on-host.component.d.ts +12 -2
  42. package/src/lib/components/ai-permissions-on-host.component.js +12 -3
  43. package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
  44. package/src/lib/components/ai-product-copy-card.component.d.ts +1 -1
  45. package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
  46. package/src/lib/components/ai-product-import-option.component.d.ts +1 -1
  47. package/src/lib/components/ai-product-import-option.component.js.map +1 -1
  48. package/src/lib/components/ai-product-zones.d.ts +124 -0
  49. package/src/lib/components/ai-product-zones.js +32 -0
  50. package/src/lib/components/ai-product-zones.js.map +1 -0
  51. package/src/lib/components/ai-products-hub-card.component.d.ts +1 -1
  52. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  53. package/src/lib/components/ai-products-proposals.component.d.ts +1 -1
  54. package/src/lib/components/ai-products-proposals.component.js.map +1 -1
  55. package/src/lib/components/ai-seo-audit-card.component.js +35 -92
  56. package/src/lib/components/ai-seo-audit-card.component.js.map +1 -1
  57. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  58. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  59. package/src/lib/components/assist-jobs-drawer.component.js +21 -15
  60. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  61. package/src/lib/components/assist-panel.component.js +2 -2
  62. package/src/lib/components/assist-panel.component.js.map +1 -1
  63. package/src/lib/components/assist-signals-page.component.js +2 -2
  64. package/src/lib/components/assist-signals-page.component.js.map +1 -1
  65. package/src/lib/components/billing-ai-allotments.component.js +1 -1
  66. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  67. package/src/lib/components/billing-assist-overage-card.component.js +1 -1
  68. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  69. package/src/lib/constants.d.ts +5 -0
  70. package/src/lib/constants.js +4 -0
  71. package/src/lib/constants.js.map +1 -1
  72. package/src/lib/declarations.js +2 -0
  73. package/src/lib/declarations.js.map +1 -1
  74. package/src/lib/declarations.server.js +24 -10
  75. package/src/lib/declarations.server.js.map +1 -1
  76. package/src/lib/insights/ai-dataset-catalog.d.ts +49 -0
  77. package/src/lib/insights/ai-dataset-catalog.js +57 -0
  78. package/src/lib/insights/ai-dataset-catalog.js.map +1 -0
  79. package/src/lib/insights/ai-figure-readers.d.ts +6 -39
  80. package/src/lib/insights/ai-figure-readers.js +2 -391
  81. package/src/lib/insights/ai-figure-readers.js.map +1 -1
  82. package/src/lib/insights/ai-insight-digest.d.ts +10 -1
  83. package/src/lib/insights/ai-insight-digest.js +16 -4
  84. package/src/lib/insights/ai-insight-digest.js.map +1 -1
  85. package/src/lib/insights/ai-insight-readers.d.ts +1 -1
  86. package/src/lib/insights/ai-insight-readers.js.map +1 -1
  87. package/src/lib/jobs/ai-email-bindings.d.ts +28 -21
  88. package/src/lib/jobs/ai-email-bindings.js +75 -42
  89. package/src/lib/jobs/ai-email-bindings.js.map +1 -1
  90. package/src/lib/jobs/ai-job-campaign-step.d.ts +0 -2
  91. package/src/lib/jobs/ai-job-campaign-step.js +5 -10
  92. package/src/lib/jobs/ai-job-campaign-step.js.map +1 -1
  93. package/src/lib/jobs/ai-job-component-step.d.ts +6 -1
  94. package/src/lib/jobs/ai-job-component-step.js +32 -9
  95. package/src/lib/jobs/ai-job-component-step.js.map +1 -1
  96. package/src/lib/jobs/ai-job-draft-ids.js +11 -0
  97. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  98. package/src/lib/jobs/ai-job-drafts.js +1 -1
  99. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  100. package/src/lib/jobs/ai-job-email-step.d.ts +5 -3
  101. package/src/lib/jobs/ai-job-email-step.js +11 -4
  102. package/src/lib/jobs/ai-job-email-step.js.map +1 -1
  103. package/src/lib/jobs/ai-job-experiment-step.d.ts +1 -1
  104. package/src/lib/jobs/ai-job-experiment-step.js +1 -1
  105. package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
  106. package/src/lib/jobs/ai-job-form-step.d.ts +17 -1
  107. package/src/lib/jobs/ai-job-form-step.js +60 -8
  108. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  109. package/src/lib/jobs/ai-job-generation.d.ts +22 -1
  110. package/src/lib/jobs/ai-job-generation.js +63 -3
  111. package/src/lib/jobs/ai-job-generation.js.map +1 -1
  112. package/src/lib/jobs/ai-job-insight-step.js +1 -1
  113. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  114. package/src/lib/jobs/ai-job-layout-step.d.ts +15 -4
  115. package/src/lib/jobs/ai-job-layout-step.js +46 -13
  116. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  117. package/src/lib/jobs/ai-job-page-sections.d.ts +4 -2
  118. package/src/lib/jobs/ai-job-page-sections.js +7 -4
  119. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  120. package/src/lib/jobs/ai-job-page-step.js +7 -2
  121. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  122. package/src/lib/jobs/ai-job-plan-conformance.d.ts +5 -0
  123. package/src/lib/jobs/ai-job-plan-conformance.js +40 -1
  124. package/src/lib/jobs/ai-job-plan-conformance.js.map +1 -1
  125. package/src/lib/jobs/ai-job-plan-creations.d.ts +80 -0
  126. package/src/lib/jobs/ai-job-plan-creations.js +199 -0
  127. package/src/lib/jobs/ai-job-plan-creations.js.map +1 -0
  128. package/src/lib/jobs/ai-job-plan-step.d.ts +11 -2
  129. package/src/lib/jobs/ai-job-plan-step.js +49 -23
  130. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  131. package/src/lib/jobs/ai-job-plugin-drafts.d.ts +14 -6
  132. package/src/lib/jobs/ai-job-plugin-drafts.js +12 -4
  133. package/src/lib/jobs/ai-job-plugin-drafts.js.map +1 -1
  134. package/src/lib/jobs/ai-job-products-step.d.ts +7 -0
  135. package/src/lib/jobs/ai-job-products-step.js +54 -32
  136. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  137. package/src/lib/jobs/ai-job-seo-step.d.ts +3 -24
  138. package/src/lib/jobs/ai-job-seo-step.js +35 -108
  139. package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
  140. package/src/lib/jobs/ai-job-site-step.d.ts +2 -0
  141. package/src/lib/jobs/ai-job-site-step.js +6 -6
  142. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  143. package/src/lib/jobs/ai-job-template-step.d.ts +9 -6
  144. package/src/lib/jobs/ai-job-template-step.js +73 -15
  145. package/src/lib/jobs/ai-job-template-step.js.map +1 -1
  146. package/src/lib/jobs/ai-job-theme-step.d.ts +1 -1
  147. package/src/lib/jobs/ai-job-theme-step.js +1 -1
  148. package/src/lib/jobs/ai-job-theme-step.js.map +1 -1
  149. package/src/lib/jobs/ai-job-workflow-step.js +17 -9
  150. package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
  151. package/src/lib/jobs/ai-jobs.js +1 -1
  152. package/src/lib/jobs/ai-jobs.js.map +1 -1
  153. package/src/lib/jobs/ai-workflow-records.d.ts +20 -11
  154. package/src/lib/jobs/ai-workflow-records.js +80 -43
  155. package/src/lib/jobs/ai-workflow-records.js.map +1 -1
  156. package/src/lib/model/ai-allotments.d.ts +27 -6
  157. package/src/lib/model/ai-allotments.js +54 -14
  158. package/src/lib/model/ai-allotments.js.map +1 -1
  159. package/src/lib/model/ai-automation-outline.d.ts +21 -2
  160. package/src/lib/model/ai-automation-outline.js.map +1 -1
  161. package/src/lib/model/ai-build-plan.d.ts +57 -0
  162. package/src/lib/model/ai-build-plan.js +116 -7
  163. package/src/lib/model/ai-build-plan.js.map +1 -1
  164. package/src/lib/model/ai-job-creations.d.ts +55 -0
  165. package/src/lib/model/ai-job-creations.js +85 -0
  166. package/src/lib/model/ai-job-creations.js.map +1 -0
  167. package/src/lib/model/ai-page-job.d.ts +3 -2
  168. package/src/lib/model/ai-page-job.js +4 -3
  169. package/src/lib/model/ai-page-job.js.map +1 -1
  170. package/src/lib/model/ai-seo.d.ts +11 -51
  171. package/src/lib/model/ai-seo.js +0 -17
  172. package/src/lib/model/ai-seo.js.map +1 -1
  173. package/src/lib/model/ai-site-inventory.js +1 -1
  174. package/src/lib/model/ai-site-inventory.js.map +1 -1
  175. package/src/lib/model/ai-site-job.d.ts +4 -2
  176. package/src/lib/model/ai-site-job.js +8 -4
  177. package/src/lib/model/ai-site-job.js.map +1 -1
  178. package/src/lib/model/ai-template-subjects.d.ts +10 -0
  179. package/src/lib/model/ai-template-subjects.js +16 -2
  180. package/src/lib/model/ai-template-subjects.js.map +1 -1
  181. package/src/lib/model/ai-theme-proposal.d.ts +1 -1
  182. package/src/lib/model/ai-theme-proposal.js +1 -1
  183. package/src/lib/model/ai-theme-proposal.js.map +1 -1
  184. package/src/lib/model/ai-tokens.js +1 -1
  185. package/src/lib/model/ai-tokens.js.map +1 -1
  186. package/src/lib/model/ai-usage-by-user.js +1 -1
  187. package/src/lib/model/ai-usage-by-user.js.map +1 -1
  188. package/src/lib/model/ai-workflow-job.d.ts +1 -3
  189. package/src/lib/model/ai-workflow-job.js +1 -2
  190. package/src/lib/model/ai-workflow-job.js.map +1 -1
  191. package/src/lib/plan-entitlements.d.ts +149 -0
  192. package/src/lib/plan-entitlements.js +168 -0
  193. package/src/lib/plan-entitlements.js.map +1 -0
  194. package/src/lib/plugin.js +1 -1
  195. package/src/lib/plugin.js.map +1 -1
  196. package/src/lib/providers/catalog.js +1 -1
  197. package/src/lib/providers/catalog.js.map +1 -1
  198. package/src/lib/providers/model-choice.js +1 -1
  199. package/src/lib/providers/model-choice.js.map +1 -1
  200. package/src/lib/runtime/ai-device-audit.d.ts +1 -1
  201. package/src/lib/runtime/ai-device-audit.js +1 -1
  202. package/src/lib/runtime/ai-device-audit.js.map +1 -1
  203. package/src/lib/runtime/ai-doctrine-validators.d.ts +25 -4
  204. package/src/lib/runtime/ai-doctrine-validators.js +178 -21
  205. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  206. package/src/lib/runtime/ai-doctrine.js +5 -5
  207. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  208. package/src/lib/runtime/ai-eval-live.js +30 -15
  209. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  210. package/src/lib/runtime/ai-eval.d.ts +9 -2
  211. package/src/lib/runtime/ai-eval.js +29 -2
  212. package/src/lib/runtime/ai-eval.js.map +1 -1
  213. package/src/lib/runtime/ai-gate.js +1 -1
  214. package/src/lib/runtime/ai-gate.js.map +1 -1
  215. package/src/lib/runtime/ai-node-tree.js +3 -3
  216. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  217. package/src/lib/runtime/ai-palette.d.ts +7 -1
  218. package/src/lib/runtime/ai-palette.generated.d.ts +1 -1
  219. package/src/lib/runtime/ai-palette.generated.js +16 -13
  220. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  221. package/src/lib/runtime/ai-palette.js +7 -1
  222. package/src/lib/runtime/ai-palette.js.map +1 -1
  223. package/src/lib/runtime/seo-audit.d.ts +13 -63
  224. package/src/lib/runtime/seo-audit.js +22 -207
  225. package/src/lib/runtime/seo-audit.js.map +1 -1
  226. package/src/lib/runtime/site-inventory.js +29 -23
  227. package/src/lib/runtime/site-inventory.js.map +1 -1
  228. package/src/lib/server/ai-admin-org.js +1 -1
  229. package/src/lib/server/ai-admin-org.js.map +1 -1
  230. package/src/lib/server/ai-admin-orgs-spend.js +1 -1
  231. package/src/lib/server/ai-admin-orgs-spend.js.map +1 -1
  232. package/src/lib/server/ai-admin-signals.js +1 -1
  233. package/src/lib/server/ai-admin-signals.js.map +1 -1
  234. package/src/lib/server/ai-allotments.js +1 -1
  235. package/src/lib/server/ai-allotments.js.map +1 -1
  236. package/src/lib/server/ai-assist-prompts.js +3 -3
  237. package/src/lib/server/ai-assist-prompts.js.map +1 -1
  238. package/src/lib/server/ai-assist.js +2 -2
  239. package/src/lib/server/ai-assist.js.map +1 -1
  240. package/src/lib/server/assist-chat.d.ts +1 -1
  241. package/src/lib/server/assist-chat.js +13 -12
  242. package/src/lib/server/assist-chat.js.map +1 -1
  243. package/src/lib/server/assist-view-context.js +23 -23
  244. package/src/lib/server/assist-view-context.js.map +1 -1
  245. package/src/lib/server/billing-credits.d.ts +5 -0
  246. package/src/lib/server/billing-credits.js +8 -2
  247. package/src/lib/server/billing-credits.js.map +1 -1
  248. package/src/lib/server/billing-overage.js +2 -2
  249. package/src/lib/server/billing-overage.js.map +1 -1
  250. package/src/lib/server.d.ts +1 -0
  251. package/src/lib/server.js.map +1 -1
  252. package/src/lib/testing/stand-in-automation-index.d.ts +20 -0
  253. package/src/lib/testing/stand-in-automation-index.js +102 -0
  254. package/src/lib/testing/stand-in-automation-index.js.map +1 -0
  255. package/src/lib/testing/stand-in-dataset-index.d.ts +20 -0
  256. package/src/lib/testing/stand-in-dataset-index.js +80 -0
  257. package/src/lib/testing/stand-in-dataset-index.js.map +1 -0
  258. package/src/lib/testing/stand-in-list-send-time.d.ts +4 -0
  259. package/src/lib/testing/stand-in-list-send-time.js +77 -0
  260. package/src/lib/testing/stand-in-list-send-time.js.map +1 -0
  261. package/src/lib/testing/stand-in-pipeline-index.d.ts +32 -0
  262. package/src/lib/testing/stand-in-pipeline-index.js +59 -0
  263. package/src/lib/testing/stand-in-pipeline-index.js.map +1 -0
  264. package/src/lib/testing/stand-in-product-index.d.ts +20 -0
  265. package/src/lib/testing/stand-in-product-index.js +105 -0
  266. package/src/lib/testing/stand-in-product-index.js.map +1 -0
  267. package/src/lib/tools/ai-component-tool.js +2 -2
  268. package/src/lib/tools/ai-component-tool.js.map +1 -1
  269. package/src/lib/tools/ai-duplicate-resource-tool.js +1 -1
  270. package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
  271. package/src/lib/tools/ai-seo-tool.d.ts +6 -18
  272. package/src/lib/tools/ai-seo-tool.js +7 -38
  273. package/src/lib/tools/ai-seo-tool.js.map +1 -1
  274. package/src/lib/tools/ai-workflow-tool.d.ts +1 -1
  275. package/src/lib/tools/ai-workflow-tool.js +1 -1
  276. package/src/lib/tools/ai-workflow-tool.js.map +1 -1
  277. package/src/lib/usage/ai-allotment-alerts.d.ts +10 -7
  278. package/src/lib/usage/ai-allotment-alerts.js +39 -15
  279. package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
  280. package/src/lib/usage/ai-quota-checks.d.ts +37 -0
  281. package/src/lib/usage/ai-quota-checks.js +69 -0
  282. package/src/lib/usage/ai-quota-checks.js.map +1 -0
  283. package/src/lib/usage/ai-usage-alerts.js +22 -10
  284. package/src/lib/usage/ai-usage-alerts.js.map +1 -1
  285. package/src/lib/usage/ai-usage-by-user.js +1 -1
  286. package/src/lib/usage/ai-usage-by-user.js.map +1 -1
  287. package/src/lib/usage/assist-ceiling.d.ts +2 -2
  288. package/src/lib/usage/assist-ceiling.js +4 -4
  289. package/src/lib/usage/assist-ceiling.js.map +1 -1
  290. package/src/lib/usage/assist-credits.d.ts +494 -0
  291. package/src/lib/usage/assist-credits.js +524 -0
  292. package/src/lib/usage/assist-credits.js.map +1 -0
  293. package/src/lib/usage/assist-free-taste.d.ts +1 -1
  294. package/src/lib/usage/assist-free-taste.js +3 -2
  295. package/src/lib/usage/assist-free-taste.js.map +1 -1
  296. package/src/lib/usage/assist-signal-mining.js +1 -1
  297. package/src/lib/usage/assist-signal-mining.js.map +1 -1
  298. package/src/lib/usage/assist-spend-guards.d.ts +153 -0
  299. package/src/lib/usage/assist-spend-guards.js +182 -0
  300. package/src/lib/usage/assist-spend-guards.js.map +1 -0
  301. package/src/lib/usage/assist-usage.d.ts +1 -1
  302. package/src/lib/usage/assist-usage.js +1 -1
  303. package/src/lib/usage/assist-usage.js.map +1 -1
  304. package/src/lib/usage/staff-org-ai.js +1 -1
  305. package/src/lib/usage/staff-org-ai.js.map +1 -1
  306. package/src/lib/usage-axes.d.ts +49 -0
  307. package/src/lib/usage-axes.js +108 -0
  308. package/src/lib/usage-axes.js.map +1 -0
  309. package/src/lib/runtime/seo-page-facts.d.ts +0 -61
  310. package/src/lib/runtime/seo-page-facts.js +0 -150
  311. package/src/lib/runtime/seo-page-facts.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/billing-overage.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 { pluginRequestFromWeb } from '@aglyn/aglyn/server'\nimport {\n ASSIST_HARD_CAP_CONTROL_LABEL,\n ASSIST_OVERAGE_CAP_CONTROL_LABEL,\n ASSIST_OVERAGE_CAP_MAX_USD,\n ASSIST_OVERAGE_CAP_MIN_USD,\n resolveAssistCreditBudget,\n resolveAssistHardCap,\n resolveAssistOverageCapUsd,\n resolveAssistOverageRateUsdPer1k,\n} from '@aglyn/aglyn/app-utils/assist-credits'\nimport {\n emailUnverifiedResponse,\n firebaseAdmin,\n isImpersonationSession,\n memberHasOrgPermission,\n resolveOrgMembership,\n} from '@aglyn/tenant-data-admin'\nimport { logAiOverageControl } from '../activity/ai-activity'\nimport { assistMonthOverage } from '@aglyn/aglyn/app-utils/assist-credits'\nimport { assistUsageMonth } from '../usage/assist-usage'\nimport { aiOverageBillsByInvoice } from '../billing/ai-overage-cutover'\nimport { aiOverageNextChargeUsd } from '../billing/ai-overage-gate'\nimport {\n AI_BILLING_STANDING_DOC,\n AI_BILLING_SUBCOLLECTION,\n readAiOverageMonthLedger,\n} from '../billing/ai-overage-ledger'\nimport {\n AI_OVERAGE_THRESHOLD_USD,\n aiOverageCardOnFile,\n aiOverageEffectiveCeilingUsd,\n aiOverageStandingCeilingUsd,\n aiOverageStep,\n readAiOverageStanding,\n} from '../billing/ai-overage-standing'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { invalidIdTokenResponse } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport { addAdminAudit } from '@aglyn/tenant-data-admin/server/admin-audit-write'\n\n// lockdown-423: exempt — self-serve billing surface, same posture as\n// billing/storage-overage beside it. AGL-1501 keeps billing-locked sessions\n// alive precisely so members can reach Billing, and this is the control that\n// stops an org accruing assist charges, so a 423 would trap it accruing them.\n\n/**\n * Read or set the org's own assist controls: the hard-cap switch (AGL-2653)\n * and the dollar ceiling on overage (AGL-2898).\n *\n * Credits past the plan's assist band are SOLD by default, at the rate\n * `resolveAssistOverageRateUsdPer1k` answers — the plan's listed\n * `extraAssistCreditsUsdPer1k`, or the add-on's rate on a plan that carries\n * its band only with the add-on. This route holds the two\n * controls a customer has over that, and nothing else — it is not a consent\n * surface, and there is nothing here an org must do before assist works.\n *\n * `get` → whether the switch is on, the ceiling if any, the band,\n * the rate the overage bills at, and whether the plan sells\n * overage at all\n * `setHardCap` → store `hardCap: true | false`\n * `setCap` → store `capUsd: number`, or `null` to clear the ceiling\n *\n * `billing.manage`-gated, because both controls bound spend in both\n * directions: lifting either is what lets the org be invoiced further past\n * its band, and setting either is what stops a workspace's assistant.\n * Admin-SDK-only by construction — `assistOverage` is denied to every client\n * in the rules, so a member cannot lift their own ceiling or lower somebody\n * else's.\n *\n * TURNING THE SWITCH OFF AND CLEARING THE CEILING ARE ALWAYS AVAILABLE, on\n * any plan and at any usage level: an org that wants the sale back on must\n * not have to argue with a precondition. Turning the switch on, or setting a\n * ceiling, is refused, 409, on a plan that sells no overage — there the band\n * is already the wall (`assistBandRefuses`), and storing a control that\n * changes nothing would read as protection the org does not have.\n */\n/**\n * What the overage card needs to say about Aglyn's own limit (AGL-3011).\n *\n * Every figure is `null` before the cutover month. That is not a missing\n * value: it is the honest answer while overage still bills on the monthly\n * invoice, and rendering a ceiling and a \"next charge\" figure that nothing\n * enforces would be a promise the platform is not yet keeping.\n */\nasync function readAiOverageStandingForCard(\n orgRef: FirebaseFirestore.DocumentReference,\n org: Parameters<typeof aiOverageEffectiveCeilingUsd>[0],\n): Promise<{\n aglynCeilingUsd: number | null\n overageStep: number | null\n overageThresholdUsd: number | null\n overageChargedUsd: number | null\n overageChargeCount: number | null\n overageNextChargeUsd: number | null\n overagePaused: string | null\n overageCardOnFile: boolean | null\n overageBillsByInvoice: boolean\n}> {\n const now = new Date()\n const month = assistUsageMonth(now)\n const empty = {\n aglynCeilingUsd: null,\n overageStep: null,\n overageThresholdUsd: null,\n overageChargedUsd: null,\n overageChargeCount: null,\n overageNextChargeUsd: null,\n overagePaused: null,\n overageCardOnFile: null,\n overageBillsByInvoice: false,\n }\n if (!aiOverageBillsByInvoice(month)) return empty\n const [standingSnapshot, usageSnapshot] = await Promise.all([\n orgRef.collection(AI_BILLING_SUBCOLLECTION).doc(AI_BILLING_STANDING_DOC).get(),\n orgRef.collection('assistUsage').doc(month).get(),\n ])\n const standing = readAiOverageStanding(\n standingSnapshot.exists ? (standingSnapshot.data() ?? null) : null,\n )\n const ledger = readAiOverageMonthLedger(\n usageSnapshot.exists ? (usageSnapshot.data() ?? null) : null,\n )\n const priced = assistMonthOverage(org, Number(usageSnapshot.get('estCostUsd') ?? 0))\n return {\n aglynCeilingUsd: aiOverageStandingCeilingUsd(standing, now),\n overageStep: aiOverageStep(standing),\n overageThresholdUsd: AI_OVERAGE_THRESHOLD_USD,\n overageChargedUsd: ledger.paidUsd,\n overageChargeCount: ledger.charges,\n overageNextChargeUsd: aiOverageNextChargeUsd(\n priced.overageMonthlyUsd,\n ledger.invoicedUsd,\n ),\n overagePaused: standing.pause?.reason ?? null,\n overageCardOnFile: aiOverageCardOnFile(standing) ?? null,\n overageBillsByInvoice: true,\n }\n}\n\nasync function handler(request: Request): Promise<Response> {\n const { method, body, headers: rawHeaders } = await pluginRequestFromWeb(request)\n const headers = rawHeaders as Partial<Record<string, string>>\n if (method !== 'POST') {\n return Response.json({ error: 'Method not allowed' }, { status: 405 })\n }\n const authorization = headers.authorization ?? ''\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return Response.json({ error: 'Unauthenticated' }, { status: 401 })\n const orgId = String(body?.orgId ?? '')\n const action = String(body?.action ?? '')\n if (!orgId || !['get', 'setHardCap', 'setCap'].includes(action)) {\n return Response.json({ error: 'Bad request' }, { status: 400 })\n }\n\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n if (!decoded.email_verified && !isImpersonationSession(decoded)) {\n return emailUnverifiedResponse()\n }\n const isStaff = decoded['staff'] === true\n const actor = await resolveOrgMembership(decoded.uid, orgId)\n if (\n !isStaff &&\n !(await memberHasOrgPermission(orgId, actor?.member, 'billing.manage'))\n ) {\n return Response.json({ error: 'billing.manage required' }, { status: 403 })\n }\n\n const orgRef = firebaseAdmin.app().firestore().collection('orgs').doc(orgId)\n const orgSnapshot = await orgRef.get()\n if (!orgSnapshot.exists) {\n return Response.json({ error: 'Unknown organization' }, { status: 404 })\n }\n const org = (orgSnapshot.data() ?? {}) as Record<string, unknown>\n const hardCap = resolveAssistHardCap(org as never)\n const capUsd = resolveAssistOverageCapUsd(org as never)\n const bandCredits = resolveAssistCreditBudget(org as never)\n // The rate the card quotes must be the rate the rollup bills, so it is\n // read through the one resolver every reader of the rate goes through.\n // That mattered acutely under AGL-3014, when Starter's rate lived off\n // `PLAN_PRICING` and reading the table directly quoted $0.00 on an org\n // that was really being invoiced. AGL-3203 put the rate on Starter's own\n // row and deleted the exception, so the table and the resolver now agree\n // — and this still reads the resolver, because a comp is priced at\n // nothing and only `resolvePlanPricing` knows that.\n const overageRateUsdPer1k = resolveAssistOverageRateUsdPer1k(org as never)\n const sellsOverage = bandCredits !== null && overageRateUsdPer1k !== null\n\n if (action === 'get') {\n // AGLYN'S OWN LIMIT BESIDE THE WORKSPACE'S (AGL-3011). The card shows\n // both because the LOWER of the two binds, and a card that showed only\n // the figure the workspace set would explain neither the refusal it\n // gets at Aglyn's nor the charge it sees before its own is reached.\n //\n // `null` for every figure while the platform still bills overage on\n // the monthly invoice, so the card keeps saying what is true today\n // rather than describing a mechanism that is not running.\n const overage = await readAiOverageStandingForCard(orgRef, org as never)\n return Response.json(\n {\n hardCap,\n capUsd,\n bandCredits,\n overageRateUsdPer1k,\n sellsOverage,\n ...overage,\n label: ASSIST_HARD_CAP_CONTROL_LABEL,\n capLabel: ASSIST_OVERAGE_CAP_CONTROL_LABEL,\n minCapUsd: ASSIST_OVERAGE_CAP_MIN_USD,\n maxCapUsd: ASSIST_OVERAGE_CAP_MAX_USD,\n },\n { status: 200 },\n )\n }\n\n if (action === 'setCap') {\n const raw = body?.capUsd\n // `null` clears; anything else must be a real number in a real range.\n // A string \"25\" is refused rather than coerced, the way the switch\n // refuses a string \"false\": the resolver reads the field strictly, so\n // a value that reached the document as a string would be no ceiling\n // at all while the card said one was set.\n const clearing = raw === null\n if (\n !clearing &&\n (typeof raw !== 'number' ||\n !Number.isFinite(raw) ||\n raw < ASSIST_OVERAGE_CAP_MIN_USD ||\n raw > ASSIST_OVERAGE_CAP_MAX_USD)\n ) {\n return Response.json(\n {\n error:\n `Set a monthly AI overage ceiling between ` +\n `$${ASSIST_OVERAGE_CAP_MIN_USD} and ` +\n `$${ASSIST_OVERAGE_CAP_MAX_USD.toLocaleString('en-US')}, ` +\n `or clear it.`,\n code: 'invalid_cap',\n },\n { status: 400 },\n )\n }\n if (!clearing && !sellsOverage) {\n return Response.json(\n {\n error:\n bandCredits === null\n ? 'Your plan includes no AI assist credits, so there is no ' +\n 'overage to cap. Upgrade in Billing to add AI assist.'\n : 'Your plan already stops AI assist at its included band — ' +\n 'it sells no credits past it, so there is no overage to cap.',\n code: 'not_sold',\n },\n { status: 409 },\n )\n }\n const requestedCap = clearing ? null : (raw as number)\n await orgRef.set(\n {\n assistOverage: {\n capUsd: requestedCap,\n capSetAt: FieldValue.serverTimestamp(),\n capSetBy: decoded.uid,\n },\n },\n { merge: true },\n )\n await addAdminAudit(firebaseAdmin.app().firestore(), {\n actorUid: decoded.uid,\n actorEmail: decoded.email ?? null,\n action: 'billing.assistOverage.setCap',\n target: `orgs/${orgId}`,\n before: { capUsd },\n after: { capUsd: requestedCap },\n at: FieldValue.serverTimestamp(),\n })\n .catch(() => undefined)\n // The customer's own feed (AGL-2929): `adminAudit` is staff-only, and\n // the workspace should see who set its ceiling. Nothing when the value\n // did not move.\n await logAiOverageControl(\n orgId,\n { uid: decoded.uid, email: decoded.email ?? null },\n { control: 'cap', before: capUsd, after: requestedCap },\n )\n return Response.json({ ok: true, capUsd: requestedCap }, { status: 200 })\n }\n\n // Strictly a boolean, the same way the resolver reads it strictly: a\n // string \"false\" arriving here would otherwise be stored as `true`.\n if (typeof body?.hardCap !== 'boolean') {\n return Response.json(\n { error: 'hardCap must be true or false', code: 'invalid_value' },\n { status: 400 },\n )\n }\n const requested = body.hardCap as boolean\n\n if (requested && !sellsOverage) {\n return Response.json(\n {\n error:\n bandCredits === null\n ? 'Your plan includes no AI assist credits, so there is no band ' +\n 'to stop at. Upgrade in Billing to add AI assist.'\n : 'Your plan already stops AI assist at its included band — ' +\n 'it sells no credits past it, so there is nothing to switch off.',\n code: 'not_sold',\n },\n { status: 409 },\n )\n }\n\n await orgRef.set(\n {\n assistOverage: {\n hardCap: requested,\n hardCapSetAt: FieldValue.serverTimestamp(),\n hardCapSetBy: decoded.uid,\n },\n },\n { merge: true },\n )\n await addAdminAudit(firebaseAdmin.app().firestore(), {\n actorUid: decoded.uid,\n actorEmail: decoded.email ?? null,\n action: 'billing.assistOverage.setHardCap',\n target: `orgs/${orgId}`,\n before: { hardCap },\n after: { hardCap: requested },\n at: FieldValue.serverTimestamp(),\n })\n .catch(() => undefined)\n await logAiOverageControl(\n orgId,\n { uid: decoded.uid, email: decoded.email ?? null },\n { control: 'hardCap', before: hardCap, after: requested },\n )\n\n return Response.json({ ok: true, hardCap: requested }, { status: 200 })\n } catch (error) {\n // A refused credential is a 401, not a fault of ours (AGL-1993). Null\n // for anything else, so a real failure keeps the answer below.\n const unauthenticated = invalidIdTokenResponse(error)\n if (unauthenticated) return unauthenticated\n console.error(error)\n return Response.json(\n { error: 'Assist overage update failed' },\n { status: 500 },\n )\n }\n}\n\nexport const dynamic = 'force-dynamic'\nexport { handler as POST }\n"],"names":["pluginRequestFromWeb","ASSIST_HARD_CAP_CONTROL_LABEL","ASSIST_OVERAGE_CAP_CONTROL_LABEL","ASSIST_OVERAGE_CAP_MAX_USD","ASSIST_OVERAGE_CAP_MIN_USD","resolveAssistCreditBudget","resolveAssistHardCap","resolveAssistOverageCapUsd","resolveAssistOverageRateUsdPer1k","emailUnverifiedResponse","firebaseAdmin","isImpersonationSession","memberHasOrgPermission","resolveOrgMembership","logAiOverageControl","assistMonthOverage","assistUsageMonth","aiOverageBillsByInvoice","aiOverageNextChargeUsd","AI_BILLING_STANDING_DOC","AI_BILLING_SUBCOLLECTION","readAiOverageMonthLedger","AI_OVERAGE_THRESHOLD_USD","aiOverageCardOnFile","aiOverageStandingCeilingUsd","aiOverageStep","readAiOverageStanding","FieldValue","invalidIdTokenResponse","addAdminAudit","readAiOverageStandingForCard","orgRef","org","standingSnapshot","usageSnapshot","standing","now","Date","month","empty","aglynCeilingUsd","overageStep","overageThresholdUsd","overageChargedUsd","overageChargeCount","overageNextChargeUsd","overagePaused","overageCardOnFile","overageBillsByInvoice","Promise","all","collection","doc","get","exists","data","ledger","priced","Number","paidUsd","charges","overageMonthlyUsd","invoicedUsd","pause","reason","handler","request","headers","method","body","rawHeaders","Response","json","error","status","authorization","idToken","startsWith","slice","length","undefined","orgId","String","action","includes","orgSnapshot","decoded","app","auth","verifyIdToken","email_verified","isStaff","actor","uid","member","firestore","hardCap","capUsd","bandCredits","overageRateUsdPer1k","sellsOverage","overage","label","capLabel","minCapUsd","maxCapUsd","raw","clearing","isFinite","toLocaleString","code","requestedCap","set","assistOverage","capSetAt","serverTimestamp","capSetBy","merge","actorUid","actorEmail","email","target","before","after","at","catch","control","ok","requested","hardCapSetAt","hardCapSetBy","unauthenticated","console","dynamic","POST"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/billing-overage.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 { pluginRequestFromWeb } from '@aglyn/aglyn/server'\nimport {\n ASSIST_HARD_CAP_CONTROL_LABEL,\n ASSIST_OVERAGE_CAP_CONTROL_LABEL,\n ASSIST_OVERAGE_CAP_MAX_USD,\n ASSIST_OVERAGE_CAP_MIN_USD,\n resolveAssistCreditBudget,\n resolveAssistHardCap,\n resolveAssistOverageCapUsd,\n resolveAssistOverageRateUsdPer1k,\n} from '../usage/assist-credits'\nimport {\n emailUnverifiedResponse,\n firebaseAdmin,\n isImpersonationSession,\n memberHasOrgPermission,\n resolveOrgMembership,\n} from '@aglyn/tenant-data-admin'\nimport { logAiOverageControl } from '../activity/ai-activity'\nimport { assistMonthOverage } from '../usage/assist-credits'\nimport { assistUsageMonth } from '../usage/assist-usage'\nimport { aiOverageBillsByInvoice } from '../billing/ai-overage-cutover'\nimport { aiOverageNextChargeUsd } from '../billing/ai-overage-gate'\nimport {\n AI_BILLING_STANDING_DOC,\n AI_BILLING_SUBCOLLECTION,\n readAiOverageMonthLedger,\n} from '../billing/ai-overage-ledger'\nimport {\n AI_OVERAGE_THRESHOLD_USD,\n aiOverageCardOnFile,\n aiOverageEffectiveCeilingUsd,\n aiOverageStandingCeilingUsd,\n aiOverageStep,\n readAiOverageStanding,\n} from '../billing/ai-overage-standing'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { invalidIdTokenResponse } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport { addAdminAudit } from '@aglyn/tenant-data-admin/server/admin-audit-write'\n\n// lockdown-423: exempt — self-serve billing surface, same posture as\n// billing/storage-overage beside it. AGL-1501 keeps billing-locked sessions\n// alive precisely so members can reach Billing, and this is the control that\n// stops an org accruing assist charges, so a 423 would trap it accruing them.\n\n/**\n * Read or set the org's own assist controls: the hard-cap switch (AGL-2653)\n * and the dollar ceiling on overage (AGL-2898).\n *\n * Credits past the plan's assist band are SOLD by default, at the rate\n * `resolveAssistOverageRateUsdPer1k` answers — the plan's listed\n * `extraAssistCreditsUsdPer1k`, or the add-on's rate on a plan that carries\n * its band only with the add-on. This route holds the two\n * controls a customer has over that, and nothing else — it is not a consent\n * surface, and there is nothing here an org must do before assist works.\n *\n * `get` → whether the switch is on, the ceiling if any, the band,\n * the rate the overage bills at, and whether the plan sells\n * overage at all\n * `setHardCap` → store `hardCap: true | false`\n * `setCap` → store `capUsd: number`, or `null` to clear the ceiling\n *\n * `billing.manage`-gated, because both controls bound spend in both\n * directions: lifting either is what lets the org be invoiced further past\n * its band, and setting either is what stops a workspace's assistant.\n * Admin-SDK-only by construction — `assistOverage` is denied to every client\n * in the rules, so a member cannot lift their own ceiling or lower somebody\n * else's.\n *\n * TURNING THE SWITCH OFF AND CLEARING THE CEILING ARE ALWAYS AVAILABLE, on\n * any plan and at any usage level: an org that wants the sale back on must\n * not have to argue with a precondition. Turning the switch on, or setting a\n * ceiling, is refused, 409, on a plan that sells no overage — there the band\n * is already the wall (`assistBandRefuses`), and storing a control that\n * changes nothing would read as protection the org does not have.\n */\n/**\n * What the overage card needs to say about Aglyn's own limit (AGL-3011).\n *\n * Every figure is `null` before the cutover month. That is not a missing\n * value: it is the honest answer while overage still bills on the monthly\n * invoice, and rendering a ceiling and a \"next charge\" figure that nothing\n * enforces would be a promise the platform is not yet keeping.\n */\nasync function readAiOverageStandingForCard(\n orgRef: FirebaseFirestore.DocumentReference,\n org: Parameters<typeof aiOverageEffectiveCeilingUsd>[0],\n): Promise<{\n aglynCeilingUsd: number | null\n overageStep: number | null\n overageThresholdUsd: number | null\n overageChargedUsd: number | null\n overageChargeCount: number | null\n overageNextChargeUsd: number | null\n overagePaused: string | null\n overageCardOnFile: boolean | null\n overageBillsByInvoice: boolean\n}> {\n const now = new Date()\n const month = assistUsageMonth(now)\n const empty = {\n aglynCeilingUsd: null,\n overageStep: null,\n overageThresholdUsd: null,\n overageChargedUsd: null,\n overageChargeCount: null,\n overageNextChargeUsd: null,\n overagePaused: null,\n overageCardOnFile: null,\n overageBillsByInvoice: false,\n }\n if (!aiOverageBillsByInvoice(month)) return empty\n const [standingSnapshot, usageSnapshot] = await Promise.all([\n orgRef.collection(AI_BILLING_SUBCOLLECTION).doc(AI_BILLING_STANDING_DOC).get(),\n orgRef.collection('assistUsage').doc(month).get(),\n ])\n const standing = readAiOverageStanding(\n standingSnapshot.exists ? (standingSnapshot.data() ?? null) : null,\n )\n const ledger = readAiOverageMonthLedger(\n usageSnapshot.exists ? (usageSnapshot.data() ?? null) : null,\n )\n const priced = assistMonthOverage(org, Number(usageSnapshot.get('estCostUsd') ?? 0))\n return {\n aglynCeilingUsd: aiOverageStandingCeilingUsd(standing, now),\n overageStep: aiOverageStep(standing),\n overageThresholdUsd: AI_OVERAGE_THRESHOLD_USD,\n overageChargedUsd: ledger.paidUsd,\n overageChargeCount: ledger.charges,\n overageNextChargeUsd: aiOverageNextChargeUsd(\n priced.overageMonthlyUsd,\n ledger.invoicedUsd,\n ),\n overagePaused: standing.pause?.reason ?? null,\n overageCardOnFile: aiOverageCardOnFile(standing) ?? null,\n overageBillsByInvoice: true,\n }\n}\n\nasync function handler(request: Request): Promise<Response> {\n const { method, body, headers: rawHeaders } = await pluginRequestFromWeb(request)\n const headers = rawHeaders as Partial<Record<string, string>>\n if (method !== 'POST') {\n return Response.json({ error: 'Method not allowed' }, { status: 405 })\n }\n const authorization = headers.authorization ?? ''\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return Response.json({ error: 'Unauthenticated' }, { status: 401 })\n const orgId = String(body?.orgId ?? '')\n const action = String(body?.action ?? '')\n if (!orgId || !['get', 'setHardCap', 'setCap'].includes(action)) {\n return Response.json({ error: 'Bad request' }, { status: 400 })\n }\n\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n if (!decoded.email_verified && !isImpersonationSession(decoded)) {\n return emailUnverifiedResponse()\n }\n const isStaff = decoded['staff'] === true\n const actor = await resolveOrgMembership(decoded.uid, orgId)\n if (\n !isStaff &&\n !(await memberHasOrgPermission(orgId, actor?.member, 'billing.manage'))\n ) {\n return Response.json({ error: 'billing.manage required' }, { status: 403 })\n }\n\n const orgRef = firebaseAdmin.app().firestore().collection('orgs').doc(orgId)\n const orgSnapshot = await orgRef.get()\n if (!orgSnapshot.exists) {\n return Response.json({ error: 'Unknown organization' }, { status: 404 })\n }\n const org = (orgSnapshot.data() ?? {}) as Record<string, unknown>\n const hardCap = resolveAssistHardCap(org as never)\n const capUsd = resolveAssistOverageCapUsd(org as never)\n const bandCredits = resolveAssistCreditBudget(org as never)\n // The rate the card quotes must be the rate the rollup bills, so it is\n // read through the one resolver every reader of the rate goes through.\n // That mattered acutely under AGL-3014, when Starter's rate lived off\n // `PLAN_PRICING` and reading the table directly quoted $0.00 on an org\n // that was really being invoiced. AGL-3203 put the rate on Starter's own\n // row and deleted the exception, so the table and the resolver now agree\n // — and this still reads the resolver, because a comp is priced at\n // nothing and only `resolvePlanPricing` knows that.\n const overageRateUsdPer1k = resolveAssistOverageRateUsdPer1k(org as never)\n const sellsOverage = bandCredits !== null && overageRateUsdPer1k !== null\n\n if (action === 'get') {\n // AGLYN'S OWN LIMIT BESIDE THE WORKSPACE'S (AGL-3011). The card shows\n // both because the LOWER of the two binds, and a card that showed only\n // the figure the workspace set would explain neither the refusal it\n // gets at Aglyn's nor the charge it sees before its own is reached.\n //\n // `null` for every figure while the platform still bills overage on\n // the monthly invoice, so the card keeps saying what is true today\n // rather than describing a mechanism that is not running.\n const overage = await readAiOverageStandingForCard(orgRef, org as never)\n return Response.json(\n {\n hardCap,\n capUsd,\n bandCredits,\n overageRateUsdPer1k,\n sellsOverage,\n ...overage,\n label: ASSIST_HARD_CAP_CONTROL_LABEL,\n capLabel: ASSIST_OVERAGE_CAP_CONTROL_LABEL,\n minCapUsd: ASSIST_OVERAGE_CAP_MIN_USD,\n maxCapUsd: ASSIST_OVERAGE_CAP_MAX_USD,\n },\n { status: 200 },\n )\n }\n\n if (action === 'setCap') {\n const raw = body?.capUsd\n // `null` clears; anything else must be a real number in a real range.\n // A string \"25\" is refused rather than coerced, the way the switch\n // refuses a string \"false\": the resolver reads the field strictly, so\n // a value that reached the document as a string would be no ceiling\n // at all while the card said one was set.\n const clearing = raw === null\n if (\n !clearing &&\n (typeof raw !== 'number' ||\n !Number.isFinite(raw) ||\n raw < ASSIST_OVERAGE_CAP_MIN_USD ||\n raw > ASSIST_OVERAGE_CAP_MAX_USD)\n ) {\n return Response.json(\n {\n error:\n `Set a monthly AI overage ceiling between ` +\n `$${ASSIST_OVERAGE_CAP_MIN_USD} and ` +\n `$${ASSIST_OVERAGE_CAP_MAX_USD.toLocaleString('en-US')}, ` +\n `or clear it.`,\n code: 'invalid_cap',\n },\n { status: 400 },\n )\n }\n if (!clearing && !sellsOverage) {\n return Response.json(\n {\n error:\n bandCredits === null\n ? 'Your plan includes no AI assist credits, so there is no ' +\n 'overage to cap. Upgrade in Billing to add AI assist.'\n : 'Your plan already stops AI assist at its included band — ' +\n 'it sells no credits past it, so there is no overage to cap.',\n code: 'not_sold',\n },\n { status: 409 },\n )\n }\n const requestedCap = clearing ? null : (raw as number)\n await orgRef.set(\n {\n assistOverage: {\n capUsd: requestedCap,\n capSetAt: FieldValue.serverTimestamp(),\n capSetBy: decoded.uid,\n },\n },\n { merge: true },\n )\n await addAdminAudit(firebaseAdmin.app().firestore(), {\n actorUid: decoded.uid,\n actorEmail: decoded.email ?? null,\n action: 'billing.assistOverage.setCap',\n target: `orgs/${orgId}`,\n before: { capUsd },\n after: { capUsd: requestedCap },\n at: FieldValue.serverTimestamp(),\n })\n .catch(() => undefined)\n // The customer's own feed (AGL-2929): `adminAudit` is staff-only, and\n // the workspace should see who set its ceiling. Nothing when the value\n // did not move.\n await logAiOverageControl(\n orgId,\n { uid: decoded.uid, email: decoded.email ?? null },\n { control: 'cap', before: capUsd, after: requestedCap },\n )\n return Response.json({ ok: true, capUsd: requestedCap }, { status: 200 })\n }\n\n // Strictly a boolean, the same way the resolver reads it strictly: a\n // string \"false\" arriving here would otherwise be stored as `true`.\n if (typeof body?.hardCap !== 'boolean') {\n return Response.json(\n { error: 'hardCap must be true or false', code: 'invalid_value' },\n { status: 400 },\n )\n }\n const requested = body.hardCap as boolean\n\n if (requested && !sellsOverage) {\n return Response.json(\n {\n error:\n bandCredits === null\n ? 'Your plan includes no AI assist credits, so there is no band ' +\n 'to stop at. Upgrade in Billing to add AI assist.'\n : 'Your plan already stops AI assist at its included band — ' +\n 'it sells no credits past it, so there is nothing to switch off.',\n code: 'not_sold',\n },\n { status: 409 },\n )\n }\n\n await orgRef.set(\n {\n assistOverage: {\n hardCap: requested,\n hardCapSetAt: FieldValue.serverTimestamp(),\n hardCapSetBy: decoded.uid,\n },\n },\n { merge: true },\n )\n await addAdminAudit(firebaseAdmin.app().firestore(), {\n actorUid: decoded.uid,\n actorEmail: decoded.email ?? null,\n action: 'billing.assistOverage.setHardCap',\n target: `orgs/${orgId}`,\n before: { hardCap },\n after: { hardCap: requested },\n at: FieldValue.serverTimestamp(),\n })\n .catch(() => undefined)\n await logAiOverageControl(\n orgId,\n { uid: decoded.uid, email: decoded.email ?? null },\n { control: 'hardCap', before: hardCap, after: requested },\n )\n\n return Response.json({ ok: true, hardCap: requested }, { status: 200 })\n } catch (error) {\n // A refused credential is a 401, not a fault of ours (AGL-1993). Null\n // for anything else, so a real failure keeps the answer below.\n const unauthenticated = invalidIdTokenResponse(error)\n if (unauthenticated) return unauthenticated\n console.error(error)\n return Response.json(\n { error: 'Assist overage update failed' },\n { status: 500 },\n )\n }\n}\n\nexport const dynamic = 'force-dynamic'\nexport { handler as POST }\n"],"names":["pluginRequestFromWeb","ASSIST_HARD_CAP_CONTROL_LABEL","ASSIST_OVERAGE_CAP_CONTROL_LABEL","ASSIST_OVERAGE_CAP_MAX_USD","ASSIST_OVERAGE_CAP_MIN_USD","resolveAssistCreditBudget","resolveAssistHardCap","resolveAssistOverageCapUsd","resolveAssistOverageRateUsdPer1k","emailUnverifiedResponse","firebaseAdmin","isImpersonationSession","memberHasOrgPermission","resolveOrgMembership","logAiOverageControl","assistMonthOverage","assistUsageMonth","aiOverageBillsByInvoice","aiOverageNextChargeUsd","AI_BILLING_STANDING_DOC","AI_BILLING_SUBCOLLECTION","readAiOverageMonthLedger","AI_OVERAGE_THRESHOLD_USD","aiOverageCardOnFile","aiOverageStandingCeilingUsd","aiOverageStep","readAiOverageStanding","FieldValue","invalidIdTokenResponse","addAdminAudit","readAiOverageStandingForCard","orgRef","org","standingSnapshot","usageSnapshot","standing","now","Date","month","empty","aglynCeilingUsd","overageStep","overageThresholdUsd","overageChargedUsd","overageChargeCount","overageNextChargeUsd","overagePaused","overageCardOnFile","overageBillsByInvoice","Promise","all","collection","doc","get","exists","data","ledger","priced","Number","paidUsd","charges","overageMonthlyUsd","invoicedUsd","pause","reason","handler","request","headers","method","body","rawHeaders","Response","json","error","status","authorization","idToken","startsWith","slice","length","undefined","orgId","String","action","includes","orgSnapshot","decoded","app","auth","verifyIdToken","email_verified","isStaff","actor","uid","member","firestore","hardCap","capUsd","bandCredits","overageRateUsdPer1k","sellsOverage","overage","label","capLabel","minCapUsd","maxCapUsd","raw","clearing","isFinite","toLocaleString","code","requestedCap","set","assistOverage","capSetAt","serverTimestamp","capSetBy","merge","actorUid","actorEmail","email","target","before","after","at","catch","control","ok","requested","hardCapSetAt","hardCapSetBy","unauthenticated","console","dynamic","POST"],"mappings":"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"}
@@ -30,3 +30,4 @@ export * from './runtime/ai-runtime';
30
30
  * `AI_JOBS_BEAT_PATH` gives.
31
31
  */
32
32
  export declare function registerAiConsoleApi(): void;
33
+ export type { aiPlanEntitlements } from './plan-entitlements';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/server.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 { registerPluginApiRoute } from '@aglyn/aglyn/server'\nimport { registerAiDeclarations } from './declarations'\nimport { AI_JOBS_BEAT_PATH } from './jobs/ai-jobs-beat'\nimport { registerAiJobPlan } from './jobs/ai-job-plan-step'\nimport { registerAiJobsPause } from './jobs/ai-jobs-pause'\nimport { registerAiComponentJob } from './jobs/ai-job-component-step'\nimport { registerAiCrmJob } from './jobs/ai-job-crm-step'\nimport { registerAiLayoutJob } from './jobs/ai-job-layout-step'\nimport { registerAiTemplateJob } from './jobs/ai-job-template-step'\nimport { registerAiExperimentJob } from './jobs/ai-job-experiment-step'\nimport { registerAiFormJob } from './jobs/ai-job-form-step'\nimport { registerAiPageJob } from './jobs/ai-job-page-step'\nimport { registerAiProductsJob } from './jobs/ai-job-products-step'\nimport { registerAiEmailJob } from './jobs/ai-job-email-step'\nimport { registerAiCampaignJob } from './jobs/ai-job-campaign-step'\nimport { registerAiSiteJob } from './jobs/ai-job-site-step'\nimport { registerAiWorkflowJob } from './jobs/ai-job-workflow-step'\nimport { registerAiInsightJob } from './jobs/ai-job-insight-step'\nimport { registerAiFigureReaders } from './insights/ai-figure-readers'\nimport { registerAiTextGenerator } from './server/plugin-text-generation'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { ensureFirstPartyAiProviders } from './providers/registry'\nimport { aiAssistHandler } from './server/ai-assist'\nimport { GET as aiCrmAnswer } from './server/ai-crm-answer'\nimport { POST as runAiJobsBeat } from './server/ai-jobs-beat-route'\nimport { POST as cancelAiJob } from './server/ai-jobs-cancel'\nimport { GET as aiJobEvents } from './server/ai-jobs-events-route'\nimport { POST as createAiSiteBatch } from './server/ai-jobs-batch'\nimport { GET as listAiJobs, POST as createAiJob } from './server/ai-jobs-route'\nimport { POST as resumeAiJob } from './server/ai-jobs-resume'\nimport { POST as aiGenerateComponent } from './server/ai-generate-component'\nimport { POST as applyAiSeoAudit } from './server/ai-seo-apply'\nimport { POST as assistChat } from './server/assist-chat'\nimport { POST as assistEditApplied } from './server/assist-edit-applied'\nimport { POST as assistFeedback } from './server/assist-feedback'\nimport { PATCH as aiHostPermissions } from './server/ai-host-permissions'\nimport { GET as aiAdminOrg } from './server/ai-admin-org'\nimport { GET as aiAdminOrgsSpend } from './server/ai-admin-orgs-spend'\nimport { GET as aiAdminSignals } from './server/ai-admin-signals'\nimport { GET as aiAdminUser } from './server/ai-admin-user'\nimport { POST as aiAdminOverage } from './server/ai-admin-overage'\nimport { GET as aiUsage } from './server/ai-usage'\nimport { GET as aiAllotments } from './server/ai-allotments'\nimport { GET as aiInsightAnswer } from './server/ai-insight-answer'\nimport { POST as runAiInsightsDigest } from './server/ai-insight-digest-route'\nimport { AI_INSIGHTS_DIGEST_PATH } from './insights/ai-insight-digest'\nimport { GET as aiModels } from './server/ai-models'\nimport { GET as billingCredits } from './server/billing-credits'\nimport { POST as billingOverage } from './server/billing-overage'\n\nexport * from './providers/contract'\nexport * from './providers/catalog'\nexport * from './providers/registry'\nexport * from './providers/routing'\nexport * from './runtime/ai-runtime'\n\n/**\n * The two first-party adapters, registered once per process. A marketplace\n * plugin registers its own against `AI_PROVIDER_CONTRACT` from its own\n * entry and never touches this.\n */\nconst registerFirstPartyProviders = ensureFirstPartyAiProviders\n\n/**\n * Every job kind this plugin runs beyond the three the machine registers\n * itself (AGL-3025). Each is a CALL, never an import made for what the\n * module does as it loads: this package declares only this file\n * effect-ful, so a bundler honoring `sideEffects` deletes an import whose\n * exports go unused — and a step registered that way ran in every spec and\n * in neither app. `registrations-survive-a-bundler.spec.ts` holds this.\n *\n * The console surface is the only caller (AGL-3026): its doors run a job's\n * first step inline, and its beat runs every step after it. The tenant app\n * loads no server surface of this plugin at all, so nothing that serves a\n * published site can reach a provider.\n */\nfunction registerAiJobKinds(): void {\n // The workspace AI pause the machine asks before it claims any step, so a\n // paused workspace's queued jobs spend nothing (AGL-3037).\n registerAiJobsPause()\n // The plan step every planned kind runs first (AGL-2935).\n registerAiJobPlan()\n // Components, layouts and templates (AGL-2908, AGL-2909).\n registerAiComponentJob()\n registerAiLayoutJob()\n registerAiTemplateJob()\n // Forms (AGL-2913).\n registerAiExperimentJob()\n registerAiFormJob()\n // Pages, with the least time one pass needs (AGL-2907).\n registerAiPageJob()\n // Email designs and campaigns, whose drafts the email and marketing\n // plugins write on the resource-draft seam (AGL-2912).\n registerAiEmailJob()\n registerAiCampaignJob()\n // Automations drafted from a description, and explained, whose drafts the\n // workflows plugin writes on the resource-draft seam (AGL-2919).\n registerAiWorkflowJob()\n // Product copy, and a store's products, categories and discounts from a\n // brief, as proposals the commerce plugin's surfaces apply (AGL-2916).\n registerAiProductsJob()\n // The site scaffold, which builds a whole site through the steps above\n // (AGL-2911).\n registerAiSiteJob()\n // Insights (AGL-2915): a question about a site's figures, answered from\n // tables the figure readers return, and the weekly digest. The readers for\n // the platform's own records — page views, forms, datasets — are this\n // plugin's; every other plugin registers its own from its console surface.\n registerAiFigureReaders(() => firebaseAdmin.app().firestore())\n registerAiInsightJob()\n // CRM by AI (AGL-2917): a record's summary and next step, an email draft\n // and an import's column matches, read through the CRM's facts readers.\n registerAiCrmJob()\n}\n\n/**\n * The console-side API (AGL-2939): every AI door, under the `ai` and\n * `assist` prefixes the plugin owns. The URLs the panel and the besigner\n * call are unchanged — `/api/assist/chat`, `/api/ai/assist`,\n * `/api/ai/jobs` — because the dispatcher serves them from the registry\n * exactly where the named routes used to; the two billing doors and the\n * per-member usage read (`ai/usage`) live under the plugin's own prefix.\n * The jobs beat is the one path outside them, for the reason\n * `AI_JOBS_BEAT_PATH` gives.\n */\nexport function registerAiConsoleApi(): void {\n registerAiDeclarations()\n registerFirstPartyProviders()\n registerAiJobKinds()\n // This plugin as the workspace's text generator on the core's seam\n // (AGL-3324): another plugin drafts a paragraph through it under the\n // generation doors' own rules, metered as a text job is.\n registerAiTextGenerator()\n registerPluginApiRoute('assist/chat', { web: assistChat })\n registerPluginApiRoute('assist/feedback', { web: assistFeedback })\n // The applied-edit record (AGL-2906): counts of a proposal the author\n // applied in their editor, written to the site's activity log.\n registerPluginApiRoute('assist/edit-applied', { web: assistEditApplied })\n // A collaborator's AI toggles on one site (AGL-2927), set from the site's\n // collaborators card through this plugin's column there.\n registerPluginApiRoute('ai/host-permissions', { web: aiHostPermissions })\n registerPluginApiRoute('ai/assist', aiAssistHandler)\n registerPluginApiRoute('ai/jobs', {\n web: (request) => (request.method === 'GET' ? listAiJobs(request) : createAiJob(request)),\n })\n // The agency batch (AGL-2911): one brief, one `site` job per named site,\n // under one batch id. Before the `:jobId` routes, which it is not one of.\n registerPluginApiRoute('ai/jobs/batch', { web: createAiSiteBatch })\n registerPluginApiRoute('ai/jobs/:jobId/cancel', {\n web: (request, context) =>\n cancelAiJob(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n registerPluginApiRoute('ai/jobs/:jobId/resume', {\n web: (request, context) =>\n resumeAiJob(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // An insight job's answer (AGL-2915), which is kept off the job document\n // and served only to the member who asked, or for a digest to the site's\n // members.\n registerPluginApiRoute('ai/insights/:jobId', {\n web: (request, context) =>\n aiInsightAnswer(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // A CRM job's answer (AGL-2917), which is kept off the job document and\n // served only to a member the CRM still lets read the record.\n registerPluginApiRoute('ai/crm/:jobId', {\n web: (request, context) =>\n aiCrmAnswer(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n registerPluginApiRoute('ai/jobs/:jobId/events', {\n web: (request, context) =>\n aiJobEvents(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // The jobs beat (AGL-3026): every step the doors leave queued, run every\n // minute by the console's scheduler on the cron secret, on the one surface\n // that holds the provider's key.\n registerPluginApiRoute(AI_JOBS_BEAT_PATH, { web: runAiJobsBeat })\n // The weekly insights (AGL-2915): Monday's digests made for the people who\n // asked for them, and delivered once the beat has written them. Beside the\n // beat under `/api/admin/`, where the console's scheduled sweeps live.\n registerPluginApiRoute(AI_INSIGHTS_DIGEST_PATH, { web: runAiInsightsDigest })\n // A site SEO audit's \"Apply all\" (AGL-2910): content fixes as new\n // unpublished versions, listing values staged for their SEO cards.\n registerPluginApiRoute('ai/seo/apply', { web: applyAiSeoAudit })\n // Save the selection as a reusable component, with AI (AGL-2908): the\n // second entry point of the component job, from the besigner's Attributes\n // panel. It proposes, and the person's Apply writes.\n registerPluginApiRoute('ai/generate/component', { web: aiGenerateComponent })\n registerPluginApiRoute('ai/billing/credits', { web: billingCredits })\n registerPluginApiRoute('ai/billing/overage', { web: billingOverage })\n registerPluginApiRoute('ai/usage', { web: aiUsage })\n // The allotments a manager sets, and the options the model switch lists\n // (AGL-2942). One handler answers the allotments' GET and POST.\n registerPluginApiRoute('ai/allotments', { web: aiAllotments })\n registerPluginApiRoute('ai/models', { web: aiModels })\n // The staff doors (AGL-2928, AGL-2930, AGL-2252): one org's AI in full,\n // one account's usage across its workspaces, and the fleet's Assist\n // signals behind the Assist signal staff page.\n registerPluginApiRoute('ai/admin/org', { web: aiAdminOrg })\n registerPluginApiRoute('ai/admin/orgs-spend', { web: aiAdminOrgsSpend })\n registerPluginApiRoute('ai/admin/user', { web: aiAdminUser })\n registerPluginApiRoute('ai/admin/signals', { web: aiAdminSignals })\n // One workspace's overage standing and the four acts staff have over it\n // (AGL-3011): the ceiling override, lifting a pause — the only way a\n // dispute pause comes off — and resetting the step. Every act audited.\n registerPluginApiRoute('ai/admin/overage', { web: aiAdminOverage })\n}\n"],"names":["registerPluginApiRoute","registerAiDeclarations","AI_JOBS_BEAT_PATH","registerAiJobPlan","registerAiJobsPause","registerAiComponentJob","registerAiCrmJob","registerAiLayoutJob","registerAiTemplateJob","registerAiExperimentJob","registerAiFormJob","registerAiPageJob","registerAiProductsJob","registerAiEmailJob","registerAiCampaignJob","registerAiSiteJob","registerAiWorkflowJob","registerAiInsightJob","registerAiFigureReaders","registerAiTextGenerator","firebaseAdmin","ensureFirstPartyAiProviders","aiAssistHandler","GET","aiCrmAnswer","POST","runAiJobsBeat","cancelAiJob","aiJobEvents","createAiSiteBatch","listAiJobs","createAiJob","resumeAiJob","aiGenerateComponent","applyAiSeoAudit","assistChat","assistEditApplied","assistFeedback","PATCH","aiHostPermissions","aiAdminOrg","aiAdminOrgsSpend","aiAdminSignals","aiAdminUser","aiAdminOverage","aiUsage","aiAllotments","aiInsightAnswer","runAiInsightsDigest","AI_INSIGHTS_DIGEST_PATH","aiModels","billingCredits","billingOverage","registerFirstPartyProviders","registerAiJobKinds","app","firestore","registerAiConsoleApi","web","request","method","context","params","Promise","resolve","jobId","String"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/server.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 { registerPluginApiRoute } from '@aglyn/aglyn/server'\nimport { registerAiDeclarations } from './declarations'\nimport { AI_JOBS_BEAT_PATH } from './jobs/ai-jobs-beat'\nimport { registerAiJobPlan } from './jobs/ai-job-plan-step'\nimport { registerAiJobsPause } from './jobs/ai-jobs-pause'\nimport { registerAiComponentJob } from './jobs/ai-job-component-step'\nimport { registerAiCrmJob } from './jobs/ai-job-crm-step'\nimport { registerAiLayoutJob } from './jobs/ai-job-layout-step'\nimport { registerAiTemplateJob } from './jobs/ai-job-template-step'\nimport { registerAiExperimentJob } from './jobs/ai-job-experiment-step'\nimport { registerAiFormJob } from './jobs/ai-job-form-step'\nimport { registerAiPageJob } from './jobs/ai-job-page-step'\nimport { registerAiProductsJob } from './jobs/ai-job-products-step'\nimport { registerAiEmailJob } from './jobs/ai-job-email-step'\nimport { registerAiCampaignJob } from './jobs/ai-job-campaign-step'\nimport { registerAiSiteJob } from './jobs/ai-job-site-step'\nimport { registerAiWorkflowJob } from './jobs/ai-job-workflow-step'\nimport { registerAiInsightJob } from './jobs/ai-job-insight-step'\nimport { registerAiFigureReaders } from './insights/ai-figure-readers'\nimport { registerAiTextGenerator } from './server/plugin-text-generation'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { ensureFirstPartyAiProviders } from './providers/registry'\nimport { aiAssistHandler } from './server/ai-assist'\nimport { GET as aiCrmAnswer } from './server/ai-crm-answer'\nimport { POST as runAiJobsBeat } from './server/ai-jobs-beat-route'\nimport { POST as cancelAiJob } from './server/ai-jobs-cancel'\nimport { GET as aiJobEvents } from './server/ai-jobs-events-route'\nimport { POST as createAiSiteBatch } from './server/ai-jobs-batch'\nimport { GET as listAiJobs, POST as createAiJob } from './server/ai-jobs-route'\nimport { POST as resumeAiJob } from './server/ai-jobs-resume'\nimport { POST as aiGenerateComponent } from './server/ai-generate-component'\nimport { POST as applyAiSeoAudit } from './server/ai-seo-apply'\nimport { POST as assistChat } from './server/assist-chat'\nimport { POST as assistEditApplied } from './server/assist-edit-applied'\nimport { POST as assistFeedback } from './server/assist-feedback'\nimport { PATCH as aiHostPermissions } from './server/ai-host-permissions'\nimport { GET as aiAdminOrg } from './server/ai-admin-org'\nimport { GET as aiAdminOrgsSpend } from './server/ai-admin-orgs-spend'\nimport { GET as aiAdminSignals } from './server/ai-admin-signals'\nimport { GET as aiAdminUser } from './server/ai-admin-user'\nimport { POST as aiAdminOverage } from './server/ai-admin-overage'\nimport { GET as aiUsage } from './server/ai-usage'\nimport { GET as aiAllotments } from './server/ai-allotments'\nimport { GET as aiInsightAnswer } from './server/ai-insight-answer'\nimport { POST as runAiInsightsDigest } from './server/ai-insight-digest-route'\nimport { AI_INSIGHTS_DIGEST_PATH } from './insights/ai-insight-digest'\nimport { GET as aiModels } from './server/ai-models'\nimport { GET as billingCredits } from './server/billing-credits'\nimport { POST as billingOverage } from './server/billing-overage'\n\nexport * from './providers/contract'\nexport * from './providers/catalog'\nexport * from './providers/registry'\nexport * from './providers/routing'\nexport * from './runtime/ai-runtime'\n\n/**\n * The two first-party adapters, registered once per process. A marketplace\n * plugin registers its own against `AI_PROVIDER_CONTRACT` from its own\n * entry and never touches this.\n */\nconst registerFirstPartyProviders = ensureFirstPartyAiProviders\n\n/**\n * Every job kind this plugin runs beyond the three the machine registers\n * itself (AGL-3025). Each is a CALL, never an import made for what the\n * module does as it loads: this package declares only this file\n * effect-ful, so a bundler honoring `sideEffects` deletes an import whose\n * exports go unused — and a step registered that way ran in every spec and\n * in neither app. `registrations-survive-a-bundler.spec.ts` holds this.\n *\n * The console surface is the only caller (AGL-3026): its doors run a job's\n * first step inline, and its beat runs every step after it. The tenant app\n * loads no server surface of this plugin at all, so nothing that serves a\n * published site can reach a provider.\n */\nfunction registerAiJobKinds(): void {\n // The workspace AI pause the machine asks before it claims any step, so a\n // paused workspace's queued jobs spend nothing (AGL-3037).\n registerAiJobsPause()\n // The plan step every planned kind runs first (AGL-2935).\n registerAiJobPlan()\n // Components, layouts and templates (AGL-2908, AGL-2909).\n registerAiComponentJob()\n registerAiLayoutJob()\n registerAiTemplateJob()\n // Forms (AGL-2913).\n registerAiExperimentJob()\n registerAiFormJob()\n // Pages, with the least time one pass needs (AGL-2907).\n registerAiPageJob()\n // Email designs and campaigns, whose drafts the email and marketing\n // plugins write on the resource-draft seam (AGL-2912).\n registerAiEmailJob()\n registerAiCampaignJob()\n // Automations drafted from a description, and explained, whose drafts the\n // workflows plugin writes on the resource-draft seam (AGL-2919).\n registerAiWorkflowJob()\n // Product copy, and a store's products, categories and discounts from a\n // brief, as proposals the commerce plugin's surfaces apply (AGL-2916).\n registerAiProductsJob()\n // The site scaffold, which builds a whole site through the steps above\n // (AGL-2911).\n registerAiSiteJob()\n // Insights (AGL-2915): a question about a site's figures, answered from\n // tables the figure readers return, and the weekly digest. The readers for\n // the platform's own records — page views, forms, datasets — are this\n // plugin's; every other plugin registers its own from its console surface.\n registerAiFigureReaders(() => firebaseAdmin.app().firestore())\n registerAiInsightJob()\n // CRM by AI (AGL-2917): a record's summary and next step, an email draft\n // and an import's column matches, read through the CRM's facts readers.\n registerAiCrmJob()\n}\n\n/**\n * The console-side API (AGL-2939): every AI door, under the `ai` and\n * `assist` prefixes the plugin owns. The URLs the panel and the besigner\n * call are unchanged — `/api/assist/chat`, `/api/ai/assist`,\n * `/api/ai/jobs` — because the dispatcher serves them from the registry\n * exactly where the named routes used to; the two billing doors and the\n * per-member usage read (`ai/usage`) live under the plugin's own prefix.\n * The jobs beat is the one path outside them, for the reason\n * `AI_JOBS_BEAT_PATH` gives.\n */\nexport function registerAiConsoleApi(): void {\n registerAiDeclarations()\n registerFirstPartyProviders()\n registerAiJobKinds()\n // This plugin as the workspace's text generator on the core's seam\n // (AGL-3324): another plugin drafts a paragraph through it under the\n // generation doors' own rules, metered as a text job is.\n registerAiTextGenerator()\n registerPluginApiRoute('assist/chat', { web: assistChat })\n registerPluginApiRoute('assist/feedback', { web: assistFeedback })\n // The applied-edit record (AGL-2906): counts of a proposal the author\n // applied in their editor, written to the site's activity log.\n registerPluginApiRoute('assist/edit-applied', { web: assistEditApplied })\n // A collaborator's AI toggles on one site (AGL-2927), set from the site's\n // collaborators card through this plugin's column there.\n registerPluginApiRoute('ai/host-permissions', { web: aiHostPermissions })\n registerPluginApiRoute('ai/assist', aiAssistHandler)\n registerPluginApiRoute('ai/jobs', {\n web: (request) => (request.method === 'GET' ? listAiJobs(request) : createAiJob(request)),\n })\n // The agency batch (AGL-2911): one brief, one `site` job per named site,\n // under one batch id. Before the `:jobId` routes, which it is not one of.\n registerPluginApiRoute('ai/jobs/batch', { web: createAiSiteBatch })\n registerPluginApiRoute('ai/jobs/:jobId/cancel', {\n web: (request, context) =>\n cancelAiJob(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n registerPluginApiRoute('ai/jobs/:jobId/resume', {\n web: (request, context) =>\n resumeAiJob(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // An insight job's answer (AGL-2915), which is kept off the job document\n // and served only to the member who asked, or for a digest to the site's\n // members.\n registerPluginApiRoute('ai/insights/:jobId', {\n web: (request, context) =>\n aiInsightAnswer(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // A CRM job's answer (AGL-2917), which is kept off the job document and\n // served only to a member the CRM still lets read the record.\n registerPluginApiRoute('ai/crm/:jobId', {\n web: (request, context) =>\n aiCrmAnswer(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n registerPluginApiRoute('ai/jobs/:jobId/events', {\n web: (request, context) =>\n aiJobEvents(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // The jobs beat (AGL-3026): every step the doors leave queued, run every\n // minute by the console's scheduler on the cron secret, on the one surface\n // that holds the provider's key.\n registerPluginApiRoute(AI_JOBS_BEAT_PATH, { web: runAiJobsBeat })\n // The weekly insights (AGL-2915): Monday's digests made for the people who\n // asked for them, and delivered once the beat has written them. Beside the\n // beat under `/api/admin/`, where the console's scheduled sweeps live.\n registerPluginApiRoute(AI_INSIGHTS_DIGEST_PATH, { web: runAiInsightsDigest })\n // A site SEO audit's \"Apply all\" (AGL-2910): content fixes as new\n // unpublished versions, listing values staged for their SEO cards.\n registerPluginApiRoute('ai/seo/apply', { web: applyAiSeoAudit })\n // Save the selection as a reusable component, with AI (AGL-2908): the\n // second entry point of the component job, from the besigner's Attributes\n // panel. It proposes, and the person's Apply writes.\n registerPluginApiRoute('ai/generate/component', { web: aiGenerateComponent })\n registerPluginApiRoute('ai/billing/credits', { web: billingCredits })\n registerPluginApiRoute('ai/billing/overage', { web: billingOverage })\n registerPluginApiRoute('ai/usage', { web: aiUsage })\n // The allotments a manager sets, and the options the model switch lists\n // (AGL-2942). One handler answers the allotments' GET and POST.\n registerPluginApiRoute('ai/allotments', { web: aiAllotments })\n registerPluginApiRoute('ai/models', { web: aiModels })\n // The staff doors (AGL-2928, AGL-2930, AGL-2252): one org's AI in full,\n // one account's usage across its workspaces, and the fleet's Assist\n // signals behind the Assist signal staff page.\n registerPluginApiRoute('ai/admin/org', { web: aiAdminOrg })\n registerPluginApiRoute('ai/admin/orgs-spend', { web: aiAdminOrgsSpend })\n registerPluginApiRoute('ai/admin/user', { web: aiAdminUser })\n registerPluginApiRoute('ai/admin/signals', { web: aiAdminSignals })\n // One workspace's overage standing and the four acts staff have over it\n // (AGL-3011): the ceiling override, lifting a pause — the only way a\n // dispute pause comes off — and resetting the step. Every act audited.\n registerPluginApiRoute('ai/admin/overage', { web: aiAdminOverage })\n}\n\n// Type-only (AGL-3080): the plugin's entitlement keys, declared by module\n// augmentation, for every program that loads this entry point.\nexport type { aiPlanEntitlements } from './plan-entitlements'\n"],"names":["registerPluginApiRoute","registerAiDeclarations","AI_JOBS_BEAT_PATH","registerAiJobPlan","registerAiJobsPause","registerAiComponentJob","registerAiCrmJob","registerAiLayoutJob","registerAiTemplateJob","registerAiExperimentJob","registerAiFormJob","registerAiPageJob","registerAiProductsJob","registerAiEmailJob","registerAiCampaignJob","registerAiSiteJob","registerAiWorkflowJob","registerAiInsightJob","registerAiFigureReaders","registerAiTextGenerator","firebaseAdmin","ensureFirstPartyAiProviders","aiAssistHandler","GET","aiCrmAnswer","POST","runAiJobsBeat","cancelAiJob","aiJobEvents","createAiSiteBatch","listAiJobs","createAiJob","resumeAiJob","aiGenerateComponent","applyAiSeoAudit","assistChat","assistEditApplied","assistFeedback","PATCH","aiHostPermissions","aiAdminOrg","aiAdminOrgsSpend","aiAdminSignals","aiAdminUser","aiAdminOverage","aiUsage","aiAllotments","aiInsightAnswer","runAiInsightsDigest","AI_INSIGHTS_DIGEST_PATH","aiModels","billingCredits","billingOverage","registerFirstPartyProviders","registerAiJobKinds","app","firestore","registerAiConsoleApi","web","request","method","context","params","Promise","resolve","jobId","String"],"mappings":"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"}
@@ -0,0 +1,20 @@
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
+ /** Registers all three, reading through `firestore`. */
18
+ export declare function standInAutomationIndexes(firestore: FirebaseFirestore.Firestore): void;
19
+ /** Forgets all three, so the next spec starts with no plugin keeping automations. */
20
+ export declare function removeStandInAutomationIndexes(): void;
@@ -0,0 +1,102 @@
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
+ */ import { registerPluginRecordIndex } from "@aglyn/aglyn/plugin-manager/plugin-record-index";
17
+ import { unregisterPluginServices } from "@aglyn/aglyn/plugin-manager/plugin-services";
18
+ /**
19
+ * The workflow, webhook and action indexes the plugin that keeps a site's
20
+ * automations publishes (AGL-3080), stood in over a spec's own Firestore
21
+ * double — this plugin may not load the workflows plugin that registers the
22
+ * real ones. It answers in the shape the workflows plugin's
23
+ * `automation-record-index.ts` documents: live records only, an unnamed
24
+ * workflow or webhook left out, an unnamed action named by its id, and a
25
+ * webhook's direction and switch but never its URL or secret. What the
26
+ * workflows plugin itself answers is held in its own spec.
27
+ */ const OWNER = 'workflows';
28
+ const COLLECTION = {
29
+ workflow: 'workflows',
30
+ webhook: 'webhooks',
31
+ action: 'actions'
32
+ };
33
+ const str = (value)=>typeof value === 'string' ? value.trim() : '';
34
+ function record(kind, id, data) {
35
+ var _data_trigger;
36
+ if (!data || data['deletedAt'] != null) return null;
37
+ const name = str(data['name']);
38
+ const steps = Array.isArray(data['steps']) ? data['steps'] : [];
39
+ if (kind === 'action') {
40
+ var _data_trigger1;
41
+ return {
42
+ id,
43
+ name: name || id,
44
+ facts: {
45
+ trigger: (_data_trigger1 = data['trigger']) != null ? _data_trigger1 : null,
46
+ steps,
47
+ enabled: data['enabled'] !== false
48
+ }
49
+ };
50
+ }
51
+ if (!name) return null;
52
+ if (kind === 'webhook') {
53
+ return {
54
+ id,
55
+ name,
56
+ facts: {
57
+ direction: data['direction'],
58
+ enabled: data['enabled'] !== false
59
+ }
60
+ };
61
+ }
62
+ return {
63
+ id,
64
+ name,
65
+ facts: {
66
+ steps,
67
+ returnValue: str(data['returnValue']) || null,
68
+ trigger: (_data_trigger = data['trigger']) != null ? _data_trigger : null
69
+ }
70
+ };
71
+ }
72
+ /** Registers all three, reading through `firestore`. */ export function standInAutomationIndexes(firestore) {
73
+ for (const kind of Object.keys(COLLECTION)){
74
+ const site = (hostId)=>firestore.collection('hosts').doc(hostId).collection(COLLECTION[kind]);
75
+ registerPluginRecordIndex(kind, {
76
+ async list ({ hostId, limit }) {
77
+ if (!hostId) return {
78
+ records: [],
79
+ truncated: false
80
+ };
81
+ const snapshot = await site(hostId).limit(limit + 1).get();
82
+ const records = snapshot.docs.map((doc)=>record(kind, doc.id, doc.data())).filter((one)=>one !== null);
83
+ return {
84
+ records: records.slice(0, limit),
85
+ truncated: snapshot.docs.length > limit
86
+ };
87
+ },
88
+ async get ({ hostId, id }) {
89
+ if (!hostId) return null;
90
+ const snapshot = await site(hostId).doc(id).get();
91
+ return snapshot.exists ? record(kind, snapshot.id, snapshot.data()) : null;
92
+ }
93
+ }, {
94
+ pluginId: OWNER
95
+ });
96
+ }
97
+ }
98
+ /** Forgets all three, so the next spec starts with no plugin keeping automations. */ export function removeStandInAutomationIndexes() {
99
+ unregisterPluginServices(OWNER);
100
+ }
101
+
102
+ //# sourceMappingURL=stand-in-automation-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/testing/stand-in-automation-index.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 registerPluginRecordIndex,\n type PluginIndexedRecord,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { unregisterPluginServices } from '@aglyn/aglyn/plugin-manager/plugin-services'\n\n/**\n * The workflow, webhook and action indexes the plugin that keeps a site's\n * automations publishes (AGL-3080), stood in over a spec's own Firestore\n * double — this plugin may not load the workflows plugin that registers the\n * real ones. It answers in the shape the workflows plugin's\n * `automation-record-index.ts` documents: live records only, an unnamed\n * workflow or webhook left out, an unnamed action named by its id, and a\n * webhook's direction and switch but never its URL or secret. What the\n * workflows plugin itself answers is held in its own spec.\n */\n\nconst OWNER = 'workflows'\n\ntype Kind = 'workflow' | 'webhook' | 'action'\n\nconst COLLECTION: Record<Kind, string> = { workflow: 'workflows', webhook: 'webhooks', action: 'actions' }\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\nfunction record(kind: Kind, id: string, data: Record<string, unknown> | undefined): PluginIndexedRecord | null {\n if (!data || data['deletedAt'] != null) return null\n const name = str(data['name'])\n const steps = Array.isArray(data['steps']) ? data['steps'] : []\n if (kind === 'action') {\n return { id, name: name || id, facts: { trigger: data['trigger'] ?? null, steps, enabled: data['enabled'] !== false } }\n }\n if (!name) return null\n if (kind === 'webhook') {\n return { id, name, facts: { direction: data['direction'], enabled: data['enabled'] !== false } }\n }\n return { id, name, facts: { steps, returnValue: str(data['returnValue']) || null, trigger: data['trigger'] ?? null } }\n}\n\n/** Registers all three, reading through `firestore`. */\nexport function standInAutomationIndexes(firestore: FirebaseFirestore.Firestore): void {\n for (const kind of Object.keys(COLLECTION) as Kind[]) {\n const site = (hostId: string) => firestore.collection('hosts').doc(hostId).collection(COLLECTION[kind])\n registerPluginRecordIndex(\n kind,\n {\n async list({ hostId, limit }) {\n if (!hostId) return { records: [], truncated: false }\n const snapshot = await site(hostId).limit(limit + 1).get()\n const records = snapshot.docs\n .map((doc) => record(kind, doc.id, doc.data()))\n .filter((one): one is PluginIndexedRecord => one !== null)\n return { records: records.slice(0, limit), truncated: snapshot.docs.length > limit }\n },\n async get({ hostId, id }) {\n if (!hostId) return null\n const snapshot = await site(hostId).doc(id).get()\n return snapshot.exists ? record(kind, snapshot.id, snapshot.data()) : null\n },\n },\n { pluginId: OWNER },\n )\n }\n}\n\n/** Forgets all three, so the next spec starts with no plugin keeping automations. */\nexport function removeStandInAutomationIndexes(): void {\n unregisterPluginServices(OWNER)\n}\n"],"names":["registerPluginRecordIndex","unregisterPluginServices","OWNER","COLLECTION","workflow","webhook","action","str","value","trim","record","kind","id","data","name","steps","Array","isArray","facts","trigger","enabled","direction","returnValue","standInAutomationIndexes","firestore","Object","keys","site","hostId","collection","doc","list","limit","records","truncated","snapshot","get","docs","map","filter","one","slice","length","exists","pluginId","removeStandInAutomationIndexes"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,yBAAyB,QAEpB,kDAAiD;AACxD,SAASC,wBAAwB,QAAQ,8CAA6C;AAEtF;;;;;;;;;CASC,GAED,MAAMC,QAAQ;AAId,MAAMC,aAAmC;IAAEC,UAAU;IAAaC,SAAS;IAAYC,QAAQ;AAAU;AAEzG,MAAMC,MAAM,CAACC,QAA4B,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;AAEpF,SAASC,OAAOC,IAAU,EAAEC,EAAU,EAAEC,IAAyC;QAWYA;IAV3F,IAAI,CAACA,QAAQA,IAAI,CAAC,YAAY,IAAI,MAAM,OAAO;IAC/C,MAAMC,OAAOP,IAAIM,IAAI,CAAC,OAAO;IAC7B,MAAME,QAAQC,MAAMC,OAAO,CAACJ,IAAI,CAAC,QAAQ,IAAIA,IAAI,CAAC,QAAQ,GAAG,EAAE;IAC/D,IAAIF,SAAS,UAAU;YAC4BE;QAAjD,OAAO;YAAED;YAAIE,MAAMA,QAAQF;YAAIM,OAAO;gBAAEC,OAAO,GAAEN,iBAAAA,IAAI,CAAC,UAAU,YAAfA,iBAAmB;gBAAME;gBAAOK,SAASP,IAAI,CAAC,UAAU,KAAK;YAAM;QAAE;IACxH;IACA,IAAI,CAACC,MAAM,OAAO;IAClB,IAAIH,SAAS,WAAW;QACtB,OAAO;YAAEC;YAAIE;YAAMI,OAAO;gBAAEG,WAAWR,IAAI,CAAC,YAAY;gBAAEO,SAASP,IAAI,CAAC,UAAU,KAAK;YAAM;QAAE;IACjG;IACA,OAAO;QAAED;QAAIE;QAAMI,OAAO;YAAEH;YAAOO,aAAaf,IAAIM,IAAI,CAAC,cAAc,KAAK;YAAMM,OAAO,GAAEN,gBAAAA,IAAI,CAAC,UAAU,YAAfA,gBAAmB;QAAK;IAAE;AACvH;AAEA,sDAAsD,GACtD,OAAO,SAASU,yBAAyBC,SAAsC;IAC7E,KAAK,MAAMb,QAAQc,OAAOC,IAAI,CAACvB,YAAuB;QACpD,MAAMwB,OAAO,CAACC,SAAmBJ,UAAUK,UAAU,CAAC,SAASC,GAAG,CAACF,QAAQC,UAAU,CAAC1B,UAAU,CAACQ,KAAK;QACtGX,0BACEW,MACA;YACE,MAAMoB,MAAK,EAAEH,MAAM,EAAEI,KAAK,EAAE;gBAC1B,IAAI,CAACJ,QAAQ,OAAO;oBAAEK,SAAS,EAAE;oBAAEC,WAAW;gBAAM;gBACpD,MAAMC,WAAW,MAAMR,KAAKC,QAAQI,KAAK,CAACA,QAAQ,GAAGI,GAAG;gBACxD,MAAMH,UAAUE,SAASE,IAAI,CAC1BC,GAAG,CAAC,CAACR,MAAQpB,OAAOC,MAAMmB,IAAIlB,EAAE,EAAEkB,IAAIjB,IAAI,KAC1C0B,MAAM,CAAC,CAACC,MAAoCA,QAAQ;gBACvD,OAAO;oBAAEP,SAASA,QAAQQ,KAAK,CAAC,GAAGT;oBAAQE,WAAWC,SAASE,IAAI,CAACK,MAAM,GAAGV;gBAAM;YACrF;YACA,MAAMI,KAAI,EAAER,MAAM,EAAEhB,EAAE,EAAE;gBACtB,IAAI,CAACgB,QAAQ,OAAO;gBACpB,MAAMO,WAAW,MAAMR,KAAKC,QAAQE,GAAG,CAAClB,IAAIwB,GAAG;gBAC/C,OAAOD,SAASQ,MAAM,GAAGjC,OAAOC,MAAMwB,SAASvB,EAAE,EAAEuB,SAAStB,IAAI,MAAM;YACxE;QACF,GACA;YAAE+B,UAAU1C;QAAM;IAEtB;AACF;AAEA,mFAAmF,GACnF,OAAO,SAAS2C;IACd5C,yBAAyBC;AAC3B"}
@@ -0,0 +1,20 @@
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
+ /** Registers it, reading through `firestore`. */
18
+ export declare function standInDatasetIndex(firestore: FirebaseFirestore.Firestore): void;
19
+ /** Forgets it, so the next spec starts with nothing keeping datasets. */
20
+ export declare function removeStandInDatasetIndex(): void;
@@ -0,0 +1,80 @@
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
+ */ import { effectiveDatasetModel } from "@aglyn/aglyn/app-utils/dataset-models";
17
+ import { datasetDisplayName } from "@aglyn/aglyn/app-utils/datasets";
18
+ import { visibleToHost } from "@aglyn/aglyn/app-utils/scope-tokens";
19
+ import { registerPluginRecordIndex } from "@aglyn/aglyn/plugin-manager/plugin-record-index";
20
+ import { unregisterPluginServices } from "@aglyn/aglyn/plugin-manager/plugin-services";
21
+ /**
22
+ * The `dataset` index the plugin that keeps the workspace's datasets
23
+ * publishes (AGL-3080), stood in over a spec's own Firestore double — this
24
+ * plugin may not load the data plugin that registers the real one. It answers
25
+ * in the shape the data plugin's `dataset-record-index.ts` documents: live
26
+ * datasets only, narrowed to a site to those shared with it, each with its
27
+ * fields (`{ id, name, type }`, in the dataset page's order) and its
28
+ * `visibleTo`. What the data plugin itself answers is held in its own spec.
29
+ */ const OWNER = 'data';
30
+ const str = (value)=>typeof value === 'string' ? value.trim() : '';
31
+ function record(id, data) {
32
+ if (!data || data['deletedAt'] != null) return null;
33
+ const model = effectiveDatasetModel(data);
34
+ return {
35
+ id,
36
+ name: datasetDisplayName(data) || id,
37
+ facts: {
38
+ fields: model.order.map((fieldId)=>{
39
+ var _ref;
40
+ var _model_fields_fieldId, _model_fields_fieldId1;
41
+ return {
42
+ id: fieldId,
43
+ name: str((_model_fields_fieldId = model.fields[fieldId]) == null ? void 0 : _model_fields_fieldId.name) || fieldId,
44
+ type: String((_ref = (_model_fields_fieldId1 = model.fields[fieldId]) == null ? void 0 : _model_fields_fieldId1.type) != null ? _ref : 'text')
45
+ };
46
+ }),
47
+ visibleTo: Array.isArray(data['visibleTo']) ? data['visibleTo'] : []
48
+ }
49
+ };
50
+ }
51
+ /** Registers it, reading through `firestore`. */ export function standInDatasetIndex(firestore) {
52
+ const datasets = (orgId)=>firestore.collection('orgs').doc(orgId).collection('datasets');
53
+ const shared = (one, hostId)=>one && (!hostId || visibleToHost(one.facts['visibleTo'], hostId)) ? one : null;
54
+ registerPluginRecordIndex('dataset', {
55
+ async list ({ orgId, hostId, limit }) {
56
+ if (!orgId) return {
57
+ records: [],
58
+ truncated: false
59
+ };
60
+ const snapshot = await datasets(orgId).get();
61
+ const records = snapshot.docs.map((doc)=>shared(record(doc.id, doc.data()), hostId)).filter((one)=>one !== null);
62
+ return {
63
+ records: records.slice(0, limit),
64
+ truncated: records.length > limit
65
+ };
66
+ },
67
+ async get ({ orgId, hostId, id }) {
68
+ if (!orgId) return null;
69
+ const snapshot = await datasets(orgId).doc(id).get();
70
+ return snapshot.exists ? shared(record(snapshot.id, snapshot.data()), hostId) : null;
71
+ }
72
+ }, {
73
+ pluginId: OWNER
74
+ });
75
+ }
76
+ /** Forgets it, so the next spec starts with nothing keeping datasets. */ export function removeStandInDatasetIndex() {
77
+ unregisterPluginServices(OWNER);
78
+ }
79
+
80
+ //# sourceMappingURL=stand-in-dataset-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/testing/stand-in-dataset-index.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 { effectiveDatasetModel, type DatasetModel } from '@aglyn/aglyn/app-utils/dataset-models'\nimport { datasetDisplayName } from '@aglyn/aglyn/app-utils/datasets'\nimport { visibleToHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport {\n registerPluginRecordIndex,\n type PluginIndexedRecord,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { unregisterPluginServices } from '@aglyn/aglyn/plugin-manager/plugin-services'\n\n/**\n * The `dataset` index the plugin that keeps the workspace's datasets\n * publishes (AGL-3080), stood in over a spec's own Firestore double — this\n * plugin may not load the data plugin that registers the real one. It answers\n * in the shape the data plugin's `dataset-record-index.ts` documents: live\n * datasets only, narrowed to a site to those shared with it, each with its\n * fields (`{ id, name, type }`, in the dataset page's order) and its\n * `visibleTo`. What the data plugin itself answers is held in its own spec.\n */\n\nconst OWNER = 'data'\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\nfunction record(id: string, data: Record<string, unknown> | undefined): PluginIndexedRecord | null {\n if (!data || data['deletedAt'] != null) return null\n const model = effectiveDatasetModel(data as { model?: DatasetModel; fields?: string[] })\n return {\n id,\n name: datasetDisplayName(data) || id,\n facts: {\n fields: model.order.map((fieldId) => ({\n id: fieldId,\n name: str(model.fields[fieldId]?.name) || fieldId,\n type: String(model.fields[fieldId]?.type ?? 'text'),\n })),\n visibleTo: Array.isArray(data['visibleTo']) ? data['visibleTo'] : [],\n },\n }\n}\n\n/** Registers it, reading through `firestore`. */\nexport function standInDatasetIndex(firestore: FirebaseFirestore.Firestore): void {\n const datasets = (orgId: string) => firestore.collection('orgs').doc(orgId).collection('datasets')\n const shared = (one: PluginIndexedRecord | null, hostId: string | null | undefined) =>\n one && (!hostId || visibleToHost(one.facts['visibleTo'] as string[], hostId)) ? one : null\n registerPluginRecordIndex(\n 'dataset',\n {\n async list({ orgId, hostId, limit }) {\n if (!orgId) return { records: [], truncated: false }\n const snapshot = await datasets(orgId).get()\n const records = snapshot.docs\n .map((doc) => shared(record(doc.id, doc.data()), hostId))\n .filter((one): one is PluginIndexedRecord => one !== null)\n return { records: records.slice(0, limit), truncated: records.length > limit }\n },\n async get({ orgId, hostId, id }) {\n if (!orgId) return null\n const snapshot = await datasets(orgId).doc(id).get()\n return snapshot.exists ? shared(record(snapshot.id, snapshot.data()), hostId) : null\n },\n },\n { pluginId: OWNER },\n )\n}\n\n/** Forgets it, so the next spec starts with nothing keeping datasets. */\nexport function removeStandInDatasetIndex(): void {\n unregisterPluginServices(OWNER)\n}\n"],"names":["effectiveDatasetModel","datasetDisplayName","visibleToHost","registerPluginRecordIndex","unregisterPluginServices","OWNER","str","value","trim","record","id","data","model","name","facts","fields","order","map","fieldId","type","String","visibleTo","Array","isArray","standInDatasetIndex","firestore","datasets","orgId","collection","doc","shared","one","hostId","list","limit","records","truncated","snapshot","get","docs","filter","slice","length","exists","pluginId","removeStandInDatasetIndex"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,qBAAqB,QAA2B,wCAAuC;AAChG,SAASC,kBAAkB,QAAQ,kCAAiC;AACpE,SAASC,aAAa,QAAQ,sCAAqC;AACnE,SACEC,yBAAyB,QAEpB,kDAAiD;AACxD,SAASC,wBAAwB,QAAQ,8CAA6C;AAEtF;;;;;;;;CAQC,GAED,MAAMC,QAAQ;AAEd,MAAMC,MAAM,CAACC,QAA4B,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;AAEpF,SAASC,OAAOC,EAAU,EAAEC,IAAyC;IACnE,IAAI,CAACA,QAAQA,IAAI,CAAC,YAAY,IAAI,MAAM,OAAO;IAC/C,MAAMC,QAAQZ,sBAAsBW;IACpC,OAAO;QACLD;QACAG,MAAMZ,mBAAmBU,SAASD;QAClCI,OAAO;YACLC,QAAQH,MAAMI,KAAK,CAACC,GAAG,CAAC,CAACC;;oBAEbN,uBACGA;uBAHuB;oBACpCF,IAAIQ;oBACJL,MAAMP,KAAIM,wBAAAA,MAAMG,MAAM,CAACG,QAAQ,qBAArBN,sBAAuBC,IAAI,KAAKK;oBAC1CC,MAAMC,gBAAOR,yBAAAA,MAAMG,MAAM,CAACG,QAAQ,qBAArBN,uBAAuBO,IAAI,mBAAI;gBAC9C;;YACAE,WAAWC,MAAMC,OAAO,CAACZ,IAAI,CAAC,YAAY,IAAIA,IAAI,CAAC,YAAY,GAAG,EAAE;QACtE;IACF;AACF;AAEA,+CAA+C,GAC/C,OAAO,SAASa,oBAAoBC,SAAsC;IACxE,MAAMC,WAAW,CAACC,QAAkBF,UAAUG,UAAU,CAAC,QAAQC,GAAG,CAACF,OAAOC,UAAU,CAAC;IACvF,MAAME,SAAS,CAACC,KAAiCC,SAC/CD,OAAQ,CAAA,CAACC,UAAU9B,cAAc6B,IAAIjB,KAAK,CAAC,YAAY,EAAckB,OAAM,IAAKD,MAAM;IACxF5B,0BACE,WACA;QACE,MAAM8B,MAAK,EAAEN,KAAK,EAAEK,MAAM,EAAEE,KAAK,EAAE;YACjC,IAAI,CAACP,OAAO,OAAO;gBAAEQ,SAAS,EAAE;gBAAEC,WAAW;YAAM;YACnD,MAAMC,WAAW,MAAMX,SAASC,OAAOW,GAAG;YAC1C,MAAMH,UAAUE,SAASE,IAAI,CAC1BtB,GAAG,CAAC,CAACY,MAAQC,OAAOrB,OAAOoB,IAAInB,EAAE,EAAEmB,IAAIlB,IAAI,KAAKqB,SAChDQ,MAAM,CAAC,CAACT,MAAoCA,QAAQ;YACvD,OAAO;gBAAEI,SAASA,QAAQM,KAAK,CAAC,GAAGP;gBAAQE,WAAWD,QAAQO,MAAM,GAAGR;YAAM;QAC/E;QACA,MAAMI,KAAI,EAAEX,KAAK,EAAEK,MAAM,EAAEtB,EAAE,EAAE;YAC7B,IAAI,CAACiB,OAAO,OAAO;YACnB,MAAMU,WAAW,MAAMX,SAASC,OAAOE,GAAG,CAACnB,IAAI4B,GAAG;YAClD,OAAOD,SAASM,MAAM,GAAGb,OAAOrB,OAAO4B,SAAS3B,EAAE,EAAE2B,SAAS1B,IAAI,KAAKqB,UAAU;QAClF;IACF,GACA;QAAEY,UAAUvC;IAAM;AAEtB;AAEA,uEAAuE,GACvE,OAAO,SAASwC;IACdzC,yBAAyBC;AAC3B"}
@@ -0,0 +1,4 @@
1
+ /** Registers the reader, reading through `firestore`. */
2
+ export declare function standInListSendTime(firestore: FirebaseFirestore.Firestore): void;
3
+ /** Forgets it, so the next spec starts with nothing keeping sends. */
4
+ export declare function removeStandInListSendTime(): void;
@@ -0,0 +1,77 @@
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
+ */ import { registerPluginRecordFactsReader } from "@aglyn/aglyn/plugin-manager/plugin-record-facts";
17
+ import { unregisterPluginServices } from "@aglyn/aglyn/plugin-manager/plugin-services";
18
+ import { AI_LIST_SEND_TIME_RESOURCE } from "../jobs/ai-email-bindings.js";
19
+ /**
20
+ * A list's send time, as the plugin that keeps a site's sends answers it
21
+ * (AGL-3080), stood in over a spec's own Firestore double — this plugin may
22
+ * not load the marketing plugin that registers the real reader. It answers in
23
+ * the shape that reader documents, `{ label, measured }`, from the sends to
24
+ * the list AS the site asked about. Which slot the real rule picks, and why,
25
+ * is held in the marketing plugin's own spec; this answers the slot of the
26
+ * first measurable send, with three of them the least it suggests from.
27
+ */ const OWNER = 'stand-in-send-history';
28
+ const WEEKDAYS = [
29
+ 'Sunday',
30
+ 'Monday',
31
+ 'Tuesday',
32
+ 'Wednesday',
33
+ 'Thursday',
34
+ 'Friday',
35
+ 'Saturday'
36
+ ];
37
+ const millisOf = (value)=>value instanceof Date ? value.getTime() : typeof value === 'number' ? value : 0;
38
+ /** Registers the reader, reading through `firestore`. */ export function standInListSendTime(firestore) {
39
+ registerPluginRecordFactsReader(AI_LIST_SEND_TIME_RESOURCE, {
40
+ async read ({ orgId, hostId, id }) {
41
+ if (!hostId) return {
42
+ ok: false,
43
+ status: 400,
44
+ error: 'A send time is read for one site.'
45
+ };
46
+ const snapshot = await firestore.collection('orgs').doc(orgId).collection('campaigns').where('hostId', '==', hostId).where('listId', '==', id).where('status', '==', 'sent').get();
47
+ const measured = snapshot.docs.filter((doc)=>{
48
+ var _doc_get;
49
+ return Number((_doc_get = doc.get('stats.delivered')) != null ? _doc_get : 0) >= 20;
50
+ });
51
+ if (measured.length < 3) return {
52
+ ok: true,
53
+ facts: {
54
+ label: null,
55
+ measured: 0
56
+ }
57
+ };
58
+ const at = new Date(millisOf(measured[0].get('sentAt')));
59
+ const hour = at.getUTCHours();
60
+ const label = `${WEEKDAYS[at.getUTCDay()]}s around ${hour % 12 || 12} ${hour < 12 ? 'AM' : 'PM'} (UTC)`;
61
+ return {
62
+ ok: true,
63
+ facts: {
64
+ label,
65
+ measured: measured.length
66
+ }
67
+ };
68
+ }
69
+ }, {
70
+ pluginId: OWNER
71
+ });
72
+ }
73
+ /** Forgets it, so the next spec starts with nothing keeping sends. */ export function removeStandInListSendTime() {
74
+ unregisterPluginServices(OWNER);
75
+ }
76
+
77
+ //# sourceMappingURL=stand-in-list-send-time.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/testing/stand-in-list-send-time.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 */\nimport { registerPluginRecordFactsReader } from '@aglyn/aglyn/plugin-manager/plugin-record-facts'\nimport { unregisterPluginServices } from '@aglyn/aglyn/plugin-manager/plugin-services'\nimport { AI_LIST_SEND_TIME_RESOURCE } from '../jobs/ai-email-bindings'\n\n/**\n * A list's send time, as the plugin that keeps a site's sends answers it\n * (AGL-3080), stood in over a spec's own Firestore double — this plugin may\n * not load the marketing plugin that registers the real reader. It answers in\n * the shape that reader documents, `{ label, measured }`, from the sends to\n * the list AS the site asked about. Which slot the real rule picks, and why,\n * is held in the marketing plugin's own spec; this answers the slot of the\n * first measurable send, with three of them the least it suggests from.\n */\n\nconst OWNER = 'stand-in-send-history'\n\nconst WEEKDAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']\n\nconst millisOf = (value: unknown): number =>\n value instanceof Date ? value.getTime() : typeof value === 'number' ? value : 0\n\n/** Registers the reader, reading through `firestore`. */\nexport function standInListSendTime(firestore: FirebaseFirestore.Firestore): void {\n registerPluginRecordFactsReader(\n AI_LIST_SEND_TIME_RESOURCE,\n {\n async read({ orgId, hostId, id }) {\n if (!hostId) return { ok: false, status: 400, error: 'A send time is read for one site.' }\n const snapshot = await firestore\n .collection('orgs')\n .doc(orgId)\n .collection('campaigns')\n .where('hostId', '==', hostId)\n .where('listId', '==', id)\n .where('status', '==', 'sent')\n .get()\n const measured = snapshot.docs.filter((doc) => Number(doc.get('stats.delivered') ?? 0) >= 20)\n if (measured.length < 3) return { ok: true, facts: { label: null, measured: 0 } }\n const at = new Date(millisOf(measured[0].get('sentAt')))\n const hour = at.getUTCHours()\n const label = `${WEEKDAYS[at.getUTCDay()]}s around ${hour % 12 || 12} ${hour < 12 ? 'AM' : 'PM'} (UTC)`\n return { ok: true, facts: { label, measured: measured.length } }\n },\n },\n { pluginId: OWNER },\n )\n}\n\n/** Forgets it, so the next spec starts with nothing keeping sends. */\nexport function removeStandInListSendTime(): void {\n unregisterPluginServices(OWNER)\n}\n"],"names":["registerPluginRecordFactsReader","unregisterPluginServices","AI_LIST_SEND_TIME_RESOURCE","OWNER","WEEKDAYS","millisOf","value","Date","getTime","standInListSendTime","firestore","read","orgId","hostId","id","ok","status","error","snapshot","collection","doc","where","get","measured","docs","filter","Number","length","facts","label","at","hour","getUTCHours","getUTCDay","pluginId","removeStandInListSendTime"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD,SAASA,+BAA+B,QAAQ,kDAAiD;AACjG,SAASC,wBAAwB,QAAQ,8CAA6C;AACtF,SAASC,0BAA0B,QAAQ,+BAA2B;AAEtE;;;;;;;;CAQC,GAED,MAAMC,QAAQ;AAEd,MAAMC,WAAW;IAAC;IAAU;IAAU;IAAW;IAAa;IAAY;IAAU;CAAW;AAE/F,MAAMC,WAAW,CAACC,QAChBA,iBAAiBC,OAAOD,MAAME,OAAO,KAAK,OAAOF,UAAU,WAAWA,QAAQ;AAEhF,uDAAuD,GACvD,OAAO,SAASG,oBAAoBC,SAAsC;IACxEV,gCACEE,4BACA;QACE,MAAMS,MAAK,EAAEC,KAAK,EAAEC,MAAM,EAAEC,EAAE,EAAE;YAC9B,IAAI,CAACD,QAAQ,OAAO;gBAAEE,IAAI;gBAAOC,QAAQ;gBAAKC,OAAO;YAAoC;YACzF,MAAMC,WAAW,MAAMR,UACpBS,UAAU,CAAC,QACXC,GAAG,CAACR,OACJO,UAAU,CAAC,aACXE,KAAK,CAAC,UAAU,MAAMR,QACtBQ,KAAK,CAAC,UAAU,MAAMP,IACtBO,KAAK,CAAC,UAAU,MAAM,QACtBC,GAAG;YACN,MAAMC,WAAWL,SAASM,IAAI,CAACC,MAAM,CAAC,CAACL;oBAAeA;uBAAPM,QAAON,WAAAA,IAAIE,GAAG,CAAC,8BAARF,WAA8B,MAAM;;YAC1F,IAAIG,SAASI,MAAM,GAAG,GAAG,OAAO;gBAAEZ,IAAI;gBAAMa,OAAO;oBAAEC,OAAO;oBAAMN,UAAU;gBAAE;YAAE;YAChF,MAAMO,KAAK,IAAIvB,KAAKF,SAASkB,QAAQ,CAAC,EAAE,CAACD,GAAG,CAAC;YAC7C,MAAMS,OAAOD,GAAGE,WAAW;YAC3B,MAAMH,QAAQ,GAAGzB,QAAQ,CAAC0B,GAAGG,SAAS,GAAG,CAAC,SAAS,EAAEF,OAAO,MAAM,GAAG,CAAC,EAAEA,OAAO,KAAK,OAAO,KAAK,MAAM,CAAC;YACvG,OAAO;gBAAEhB,IAAI;gBAAMa,OAAO;oBAAEC;oBAAON,UAAUA,SAASI,MAAM;gBAAC;YAAE;QACjE;IACF,GACA;QAAEO,UAAU/B;IAAM;AAEtB;AAEA,oEAAoE,GACpE,OAAO,SAASgC;IACdlC,yBAAyBE;AAC3B"}
@@ -0,0 +1,32 @@
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
+ import { type PluginRecordIndexScope } from '@aglyn/aglyn/plugin-manager/plugin-record-index';
18
+ export interface StoodInPipelineIndex {
19
+ /** Every `list` and `get` asked, in order. */
20
+ asked: Array<PluginRecordIndexScope & {
21
+ limit?: number;
22
+ id?: string;
23
+ }>;
24
+ }
25
+ /** Registers the index, answering `pipelines` to every site of every org. */
26
+ export declare function standInPipelineIndex(pipelines: ReadonlyArray<{
27
+ id: string;
28
+ name: string;
29
+ stages: ReadonlyArray<Record<string, unknown>>;
30
+ }>): StoodInPipelineIndex;
31
+ /** Forgets it, so the next spec starts with no plugin keeping pipelines. */
32
+ export declare function removeStandInPipelineIndex(): void;