@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
@@ -14,7 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import { type AiBuildPlan } from '../model/ai-build-plan';
17
+ import { type AiBuildPlan, type AiBuildPlanEmbed } from '../model/ai-build-plan';
18
18
  import { type AiPlanCapabilities } from '../model/ai-plan-capabilities';
19
19
  import type { AiSiteInventory } from '../model/ai-site-inventory';
20
20
  import { type AiNodeTreeContext, type AiNodeTreeResult } from './ai-node-tree';
@@ -100,8 +100,12 @@ export interface AiDoctrineTreeContext extends AiNodeTreeContext {
100
100
  } | null;
101
101
  /** Placed library assets by media id. */
102
102
  assets?: Record<string, AiAssetFacts>;
103
- /** The plan named a third-party embed the brief asked for, and its cost. */
104
- allowEmbeds?: boolean;
103
+ /**
104
+ * The third-party players the confirmed plan lists for this output
105
+ * (AGL-3433): a Video embed is admitted only where one is listed, playing
106
+ * the link the brief gave or none. Absent or empty admits none.
107
+ */
108
+ plannedEmbeds?: readonly Pick<AiBuildPlanEmbed, 'host' | 'url'>[];
105
109
  framing?: AiCopyFraming;
106
110
  /**
107
111
  * Whether the workspace keeps reusable components and saved forms
@@ -495,7 +499,8 @@ export declare function detectCutLines(repairs: readonly string[], written: Read
495
499
  * wraps one other container and adds nothing, no empty containers, no inline
496
500
  * style repeated where a token or a component prop would carry it, no font
497
501
  * beyond the theme's, images lazy below the first, video by poster rather
498
- * than autoplay, and no third-party embed the plan did not name.
502
+ * than autoplay, no third-party player the confirmed plan does not list, and
503
+ * none playing a video the brief did not give.
499
504
  */
500
505
  export declare function detectHeavyDocument(tree: AiDoctrineTree, outputKind: AiOutputKind, context?: AiDoctrineTreeContext): AiDoctrineViolation[];
501
506
  /**
@@ -733,6 +738,22 @@ export declare function detectPlanOverFreeWall(plan: AiBuildPlan, inventory: AiS
733
738
  * instead of reporting itself done.
734
739
  */
735
740
  export declare function detectPlanUnreadableRegions(plan: AiBuildPlan): AiDoctrineViolation[];
741
+ /**
742
+ * Rule 16 (plan): a third-party player is planned where it can play
743
+ * (AGL-3433). A screen of the plan or a component it creates carries at most
744
+ * its kind's budget. A template renders for every record it serves and a
745
+ * layout around every page, so neither carries one, and a form, an email or a
746
+ * dataset has nowhere to. A link the plan gives is one its host plays.
747
+ */
748
+ export declare function detectPlanEmbeds(plan: AiBuildPlan): AiDoctrineViolation[];
749
+ /**
750
+ * Rule 16 (plan, against the brief): a player is planned only where the brief
751
+ * asks for one (AGL-3433). The plan quotes the brief's words, and those words
752
+ * are found in the brief and ask for a video; a link it gives is one the brief
753
+ * gives. The plan step holds this, being the one door that reads the brief
754
+ * beside the plan.
755
+ */
756
+ export declare function aiPlanEmbedBriefViolations(plan: AiBuildPlan, brief: string): AiDoctrineViolation[];
736
757
  /**
737
758
  * Every plan rule, against the site inventory the plan was made from and,
738
759
  * where the job read them, what it may create there (AGL-3030). `null`
@@ -17,11 +17,12 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
17
17
  */ import { sanitizeAuthorHtml } from "@aglyn/aglyn/app-utils/author-html";
18
18
  import { formatBytes, measureNodeMap, nodeMapBytes } from "@aglyn/aglyn/app-utils/measure-node-map";
19
19
  import { MEDIA_CDN_VARIANT_WIDTHS, parseMediaRef } from "@aglyn/aglyn/app-utils/media-ref";
20
- import { ESTIMATED_PAGE_TRANSFER_BYTES, FREE_AI_TASTE_CREDITS_PER_MONTH } from "@aglyn/aglyn/app-utils/plan-entitlements";
20
+ import { ESTIMATED_PAGE_TRANSFER_BYTES } from "@aglyn/aglyn/app-utils/plan-entitlements";
21
+ import { FREE_AI_TASTE_CREDITS_PER_MONTH } from "../plan-entitlements.js";
21
22
  import { REUSABLE_INSTANCE_COMPONENT_ID, REUSABLE_INSTANCE_PROP_VALUES_KEY } from "@aglyn/aglyn/app-utils/reusable-component-keys";
22
23
  import { parseBreakpointSpan } from "@aglyn/shared-data-enums/breakpoint-span";
23
24
  import { renderEmailHtml } from "@aglyn/shared-util-email/email-render";
24
- import { aiPlanCreateFor, aiPlanUndeclaredRefs, isAiPlanNewRef } from "../model/ai-build-plan.js";
25
+ import { AI_BUILD_PLAN_CREATION_NOUNS, AI_BUILD_PLAN_EMBED_HOST_NAMES, AI_PLAN_ASKS_FOR_VIDEO, aiEmbedHostOf, aiEmbedVideoKey, aiPlanCreateFor, aiPlanSlugKey, aiPlanUndeclaredRefs, isAiPlanNewRef } from "../model/ai-build-plan.js";
25
26
  import { aiCreationNoun, aiPlanUncreatable, aiPlanUncreatableKind } from "../model/ai-plan-capabilities.js";
26
27
  import { AI_INSTANCE_REF_PROP, validateAiNodeTree } from "./ai-node-tree.js";
27
28
  import { AI_PAGE_SCROLL_TO_PROP, aiPageLinkTarget, aiStoredScrollTargets } from "./ai-page-links.js";
@@ -1421,7 +1422,7 @@ const GRID = 'muiGrid';
1421
1422
  {
1422
1423
  rule: 10,
1423
1424
  code: 'link-unrelated-screen',
1424
- message: `"${sent[0].label}" links the home page, which does not do what its words say. Link the screen that does, or leave the link out when the site has none.`,
1425
+ message: `"${sent[0].label}" links the home page, which does not do what its words say. Link the page that does, or leave the link out when the site has none.`,
1425
1426
  nodeIds: unique(sent.map((entry)=>entry.id))
1426
1427
  }
1427
1428
  ];
@@ -1505,7 +1506,7 @@ const GRID = 'muiGrid';
1505
1506
  violations.push({
1506
1507
  rule: 10,
1507
1508
  code: 'link-fragment',
1508
- message: `${wordsOf(first)} links "${first.value}", an anchor, and no element on a page carries an id an anchor could name, so it goes nowhere. ${context.pageSections ? `${toSection} Otherwise give it the "screenId" of a screen the site has that does what its words say, or take it out.` : 'Give it the "screenId" of a screen the site has that does what its words say, or an "href" that is a path on this site or an https: address the brief gives, or take it out.'}`,
1509
+ message: `${wordsOf(first)} links "${first.value}", an anchor, and no element on a page carries an id an anchor could name, so it goes nowhere. ${context.pageSections ? `${toSection} Otherwise give it the "screenId" of a page the site has that does what its words say, or take it out.` : 'Give it the "screenId" of a page the site has that does what its words say, or an "href" that is a path on this site or an https: address the brief gives, or take it out.'}`,
1509
1510
  nodeIds: unique(fragments.map((entry)=>entry.id))
1510
1511
  });
1511
1512
  }
@@ -1524,7 +1525,7 @@ const GRID = 'muiGrid';
1524
1525
  violations.push({
1525
1526
  rule: 10,
1526
1527
  code: 'link-without-destination',
1527
- message: `${wordsOf(first)} goes nowhere. Give it the "screenId" of a screen the site has that does what its words say, or an "href" that is a path on this site or an https: address the brief gives.${instead}`,
1528
+ message: `${wordsOf(first)} goes nowhere. Give it the "screenId" of a page the site has that does what its words say, or an "href" that is a path on this site or an https: address the brief gives.${instead}`,
1528
1529
  nodeIds: unique(dead.map((entry)=>entry.id))
1529
1530
  });
1530
1531
  }
@@ -1884,12 +1885,25 @@ const EMBED_COMPONENTS = new Set([
1884
1885
  ]);
1885
1886
  const DUPLICATE_SX_MIN_KEYS = 2;
1886
1887
  const DUPLICATE_SX_MIN_NODES = 4;
1888
+ /**
1889
+ * Whether a third-party player is one the confirmed plan lists (AGL-3433). A
1890
+ * Video embed is admitted where the plan lists a player, playing the video
1891
+ * the brief gave or none yet, which the site owner pastes. Raw HTML never is.
1892
+ */ function embedVerdict(node, planned) {
1893
+ var _node_props;
1894
+ if (node.componentId !== 'videoEmbed' || !planned.length) return 'unplanned';
1895
+ const url = typeof ((_node_props = node.props) == null ? void 0 : _node_props['url']) === 'string' ? node.props['url'].trim() : '';
1896
+ if (!url) return 'planned';
1897
+ const key = aiEmbedVideoKey(url);
1898
+ return key && planned.some((embed)=>embed.url && aiEmbedVideoKey(embed.url) === key) ? 'planned' : 'unplanned-link';
1899
+ }
1887
1900
  /**
1888
1901
  * Rule 16. The flattest tree that renders the design: no container that
1889
1902
  * wraps one other container and adds nothing, no empty containers, no inline
1890
1903
  * style repeated where a token or a component prop would carry it, no font
1891
1904
  * beyond the theme's, images lazy below the first, video by poster rather
1892
- * than autoplay, and no third-party embed the plan did not name.
1905
+ * than autoplay, no third-party player the confirmed plan does not list, and
1906
+ * none playing a video the brief did not give.
1893
1907
  */ export function detectHeavyDocument(tree, outputKind, context = {}) {
1894
1908
  const visits = walkTree(tree);
1895
1909
  const wrappers = [];
@@ -1898,6 +1912,7 @@ const DUPLICATE_SX_MIN_NODES = 4;
1898
1912
  const eager = [];
1899
1913
  const video = [];
1900
1914
  const embeds = [];
1915
+ const embedLinks = [];
1901
1916
  const sxSeen = new Map();
1902
1917
  let images = 0;
1903
1918
  for (const { id, node } of visits){
@@ -1920,7 +1935,12 @@ const DUPLICATE_SX_MIN_NODES = 4;
1920
1935
  if (node.componentId === 'video' && (((_node_props2 = node.props) == null ? void 0 : _node_props2['autoPlay']) === true || ((_node_props3 = node.props) == null ? void 0 : _node_props3['preload']) === 'auto' || typeof ((_node_props4 = node.props) == null ? void 0 : _node_props4['src']) === 'string' && node.props['src'] && !((_node_props5 = node.props) == null ? void 0 : _node_props5['poster']))) {
1921
1936
  video.push(id);
1922
1937
  }
1923
- if (EMBED_COMPONENTS.has(node.componentId) && !context.allowEmbeds) embeds.push(id);
1938
+ if (EMBED_COMPONENTS.has(node.componentId)) {
1939
+ var _context_plannedEmbeds;
1940
+ const verdict = embedVerdict(node, (_context_plannedEmbeds = context.plannedEmbeds) != null ? _context_plannedEmbeds : []);
1941
+ if (verdict === 'unplanned') embeds.push(id);
1942
+ if (verdict === 'unplanned-link') embedLinks.push(id);
1943
+ }
1924
1944
  const sx = (_node_sx1 = node.sx) != null ? _node_sx1 : {};
1925
1945
  if (Object.keys(sx).length >= DUPLICATE_SX_MIN_KEYS) {
1926
1946
  var _sxSeen_get;
@@ -1952,7 +1972,8 @@ const DUPLICATE_SX_MIN_NODES = 4;
1952
1972
  add('extra-font', fonts, "This names a font the theme does not load. Use the theme's typography.");
1953
1973
  add('eager-image', eager, 'An image below the first loads eagerly. Leave loading unset so images further down the page load when they are reached.');
1954
1974
  add('autoplay-video', video, 'A video loads its file before anyone plays it. Give it a poster, and let it play on click.');
1955
- add('third-party-embed', embeds, 'This embeds a third-party player or script, which loads its own code on every visit. Use a Video from the media library, or name the embed and its cost in the plan.');
1975
+ add('third-party-embed', embeds, "This embeds a third-party player, which loads its host's code on every visit, and the confirmed plan lists none here. Remove it; a film from the media library plays in a Video (video).");
1976
+ add('embed-link-unplanned', embedLinks, 'This player plays a video the brief did not give. Use the link the plan lists, or leave the link empty for the site owner to paste.');
1956
1977
  return violations;
1957
1978
  }
1958
1979
  /** The items a list or a row of cards repeats, as a re-ask names them. */ const ITEM_NOUNS = {
@@ -2513,7 +2534,7 @@ const FORM_SECTION_NAME = /\bform\b|\bsign[- ]?up\b|\bsubscribe\b|\bnewsletter\b
2513
2534
  violations.push({
2514
2535
  rule: 1,
2515
2536
  code: 'plan-section-across-screens',
2516
- message: `The "${entries[0].section.name}" section is planned on ${screens.size} screens. Plan it as one reusable component and place it on each.`,
2537
+ message: `The "${entries[0].section.name}" section is planned on ${screens.size} pages. Plan it as one reusable component and place it on each.`,
2517
2538
  paths: entries.map((entry)=>entry.path)
2518
2539
  });
2519
2540
  }
@@ -2638,7 +2659,7 @@ const FORM_SECTION_NAME = /\bform\b|\bsign[- ]?up\b|\bsubscribe\b|\bnewsletter\b
2638
2659
  violations.push({
2639
2660
  rule: 2,
2640
2661
  code: 'plan-screen-without-layout',
2641
- message: noLayoutToName ? 'A screen names a layout the site does not have, and this job may not create one. Leave the layout empty.' : mayCreateLayout ? "A screen names no layout the site has or the plan creates. Put every screen in the site's layout, or plan one." : 'A screen names no layout the site has, and this job may not create one. Put every screen in a layout the site has.',
2662
+ message: noLayoutToName ? 'A page names a layout the site does not have, and this job may not create one. Leave the layout empty.' : mayCreateLayout ? "A page names no layout the site has or the plan creates. Put every page in the site's layout, or plan one." : 'A page names no layout the site has, and this job may not create one. Put every page in a layout the site has.',
2642
2663
  paths: unlaid.map(({ index })=>`screens[${index}].layout`)
2643
2664
  });
2644
2665
  }
@@ -2647,7 +2668,7 @@ const FORM_SECTION_NAME = /\bform\b|\bsign[- ]?up\b|\bsubscribe\b|\bnewsletter\b
2647
2668
  violations.push({
2648
2669
  rule: 2,
2649
2670
  code: 'plan-layout-region-section',
2650
- message: 'A screen plans its own header, navigation or footer. Those live in the layout; take the section off the screen.',
2671
+ message: 'A page plans its own header, navigation or footer. Those live in the layout; take the section off the page.',
2651
2672
  paths: regions.map((entry)=>entry.path)
2652
2673
  });
2653
2674
  }
@@ -2656,7 +2677,7 @@ const FORM_SECTION_NAME = /\bform\b|\bsign[- ]?up\b|\bsubscribe\b|\bnewsletter\b
2656
2677
  violations.push({
2657
2678
  rule: 2,
2658
2679
  code: 'plan-layout-in-section',
2659
- message: "A section places a layout. A layout frames a whole screen and is never placed inside one: name it as the screen's layout, and take it out of the section's uses.",
2680
+ message: "A section places a layout. A layout frames a whole page and is never placed inside one: name it as the page's layout, and take it out of the section's uses.",
2660
2681
  paths: placed
2661
2682
  });
2662
2683
  }
@@ -2703,7 +2724,7 @@ const FORM_SECTION_NAME = /\bform\b|\bsign[- ]?up\b|\bsubscribe\b|\bnewsletter\b
2703
2724
  violations.push({
2704
2725
  rule: 4,
2705
2726
  code: 'plan-similar-pages',
2706
- message: `${indexes.length} screens share one shape. Plan one template and apply it ${indexes.length} times with each page's copy, or bind it to a collection.`,
2727
+ message: `${indexes.length} pages share one shape. Plan one template and apply it ${indexes.length} times with each page's copy, or bind it to a collection.`,
2707
2728
  paths: indexes.map((index)=>`screens[${index}].template`)
2708
2729
  });
2709
2730
  }
@@ -2712,7 +2733,7 @@ const FORM_SECTION_NAME = /\bform\b|\bsign[- ]?up\b|\bsubscribe\b|\bnewsletter\b
2712
2733
  violations.push({
2713
2734
  rule: 4,
2714
2735
  code: 'plan-template-in-section',
2715
- message: "A section places a template. A template is applied to a whole screen and is never placed inside one: take it out of the section's uses, and name it as the screen's template only when the whole screen is built from it.",
2736
+ message: "A section places a template. A template is applied to a whole page and is never placed inside one: take it out of the section's uses, and name it as the page's template only when the whole page is built from it.",
2716
2737
  paths: placed
2717
2738
  });
2718
2739
  }
@@ -2851,15 +2872,15 @@ const REUSE_BEFORE_CREATE_KINDS = new Set([
2851
2872
  const section = first.field === 'uses' ? screen.sections[first.sectionIndex] : null;
2852
2873
  const kind = section ? undeclaredPlacementKind(first.name, section, plan, kinds) : 'template';
2853
2874
  const refused = capabilities ? aiPlanUncreatableKind(plan, kind, capabilities) : null;
2854
- const where = section ? `${section.name ? `The "${section.name}" section` : 'A section'} places a creation named "${first.name}", but the plan never creates it` : `${screen.title ? `The screen "${screen.title}"` : 'A screen'} applies a template named "${first.name}", but the plan never creates it`;
2875
+ const where = section ? `${section.name ? `The "${section.name}" section` : 'A section'} places a creation named "${first.name}", but the plan never creates it` : `${screen.title ? `The page "${screen.title}"` : 'A page'} applies a template named "${first.name}", but the plan never creates it`;
2855
2876
  let message;
2856
2877
  if (!refused) {
2857
- const otherwise = section ? `place ${aiCreationNoun(kind)} the site already has by its id` : "leave the screen's template empty";
2878
+ const otherwise = section ? `place ${aiCreationNoun(kind)} the site already has by its id` : "leave the page's template empty";
2858
2879
  message = `${where}. Declare it in create as ${aiCreationNoun(kind)}, with why nothing the site has will do, or ${otherwise}.`;
2859
2880
  } else if (section) {
2860
2881
  message = `${where}, and ${refused.reason}. ${refused.instead} Take it out of the section's uses.`;
2861
2882
  } else {
2862
- message = `${where}, and ${refused.reason}. Leave the screen's template empty, or apply a template the site already has.`;
2883
+ message = `${where}, and ${refused.reason}. Leave the page's template empty, or apply a template the site already has.`;
2863
2884
  }
2864
2885
  return {
2865
2886
  rule: 7,
@@ -2951,7 +2972,7 @@ function slugKey(slug) {
2951
2972
  violations.push({
2952
2973
  rule: 10,
2953
2974
  code: 'plan-slug',
2954
- message: 'A screen has no usable address. Give each one a slug of lowercase words joined by hyphens.',
2975
+ message: 'A page has no usable address. Give each one a slug of lowercase words joined by hyphens.',
2955
2976
  paths: badSlugs
2956
2977
  });
2957
2978
  }
@@ -2959,7 +2980,7 @@ function slugKey(slug) {
2959
2980
  violations.push({
2960
2981
  rule: 10,
2961
2982
  code: 'plan-slug-taken',
2962
- message: 'A screen reuses an address the site or the plan already uses. Give each screen its own slug.',
2983
+ message: 'A page reuses an address the site or the plan already uses. Give each page its own slug.',
2963
2984
  paths: collisions
2964
2985
  });
2965
2986
  }
@@ -2967,7 +2988,7 @@ function slugKey(slug) {
2967
2988
  violations.push({
2968
2989
  rule: 10,
2969
2990
  code: 'plan-seo',
2970
- message: `A screen is missing its search title or description, or runs past ${AI_SEO_TITLE_MAX} and ${AI_SEO_DESCRIPTION_MAX} characters. Write both for every screen.`,
2991
+ message: `A page is missing its search title or description, or runs past ${AI_SEO_TITLE_MAX} and ${AI_SEO_DESCRIPTION_MAX} characters. Write both for every page.`,
2971
2992
  paths: seo
2972
2993
  });
2973
2994
  }
@@ -3014,7 +3035,7 @@ function slugKey(slug) {
3014
3035
  violations.push({
3015
3036
  rule: 15,
3016
3037
  code: 'plan-missed-duplicate',
3017
- message: `The site already has "${nearest.name}". Start the new screen from a duplicate of it, which keeps its bindings and SEO, and edit that.`,
3038
+ message: `The site already has "${nearest.name}". Start the new page from a duplicate of it, which keeps its bindings and SEO, and edit that.`,
3018
3039
  paths: [
3019
3040
  `screens[${index}].duplicateOf`
3020
3041
  ]
@@ -3155,6 +3176,141 @@ function slugKey(slug) {
3155
3176
  }
3156
3177
  ];
3157
3178
  }
3179
+ /**
3180
+ * Rule 16 (plan): a third-party player is planned where it can play
3181
+ * (AGL-3433). A screen of the plan or a component it creates carries at most
3182
+ * its kind's budget. A template renders for every record it serves and a
3183
+ * layout around every page, so neither carries one, and a form, an email or a
3184
+ * dataset has nowhere to. A link the plan gives is one its host plays.
3185
+ */ export function detectPlanEmbeds(plan) {
3186
+ var _plan_embeds;
3187
+ const nowhere = [];
3188
+ const elsewhere = [];
3189
+ const wrongHost = [];
3190
+ const placed = new Map();
3191
+ ((_plan_embeds = plan.embeds) != null ? _plan_embeds : []).forEach((embed, index)=>{
3192
+ var _placed_get;
3193
+ const path = `embeds[${index}]`;
3194
+ if (embed.url !== null && aiEmbedHostOf(embed.url) !== embed.host) wrongHost.push(`${path}.url`);
3195
+ let target = null;
3196
+ if (isAiPlanNewRef(embed.where)) {
3197
+ const creation = aiPlanCreateFor(plan, embed.where);
3198
+ if (creation && creation.kind !== 'component') {
3199
+ elsewhere.push({
3200
+ kind: creation.kind,
3201
+ path: `${path}.where`
3202
+ });
3203
+ return;
3204
+ }
3205
+ if (creation) target = {
3206
+ key: embed.where.trim().toLowerCase(),
3207
+ label: creation.name,
3208
+ kind: 'component'
3209
+ };
3210
+ } else {
3211
+ const screen = plan.screens.find((row)=>aiPlanSlugKey(row.slug) === aiPlanSlugKey(embed.where));
3212
+ if (screen) target = {
3213
+ key: aiPlanSlugKey(screen.slug),
3214
+ label: screen.slug,
3215
+ kind: 'page'
3216
+ };
3217
+ }
3218
+ if (!target) {
3219
+ nowhere.push(`${path}.where`);
3220
+ return;
3221
+ }
3222
+ const entry = (_placed_get = placed.get(target.key)) != null ? _placed_get : {
3223
+ label: target.label,
3224
+ kind: target.kind,
3225
+ paths: []
3226
+ };
3227
+ entry.paths.push(path);
3228
+ placed.set(target.key, entry);
3229
+ });
3230
+ const violations = [];
3231
+ if (nowhere.length) {
3232
+ violations.push({
3233
+ rule: 16,
3234
+ code: 'plan-embed-nowhere',
3235
+ message: 'A player is planned where the plan builds nothing. Name a screen of the plan by its slug, or new:<name> of a component it creates.',
3236
+ paths: nowhere
3237
+ });
3238
+ }
3239
+ if (elsewhere.length) {
3240
+ const nouns = [
3241
+ ...new Set(elsewhere.map((entry)=>AI_BUILD_PLAN_CREATION_NOUNS[entry.kind].noun))
3242
+ ];
3243
+ violations.push({
3244
+ rule: 16,
3245
+ code: 'plan-embed-refused',
3246
+ message: `Only a page or a component carries a third-party player, and this plan puts one on a ${nouns.join(' and a ')}. A template or a layout would load the player's code on every page it serves. Place it on a page or a component, or leave it off.`,
3247
+ paths: elsewhere.map((entry)=>entry.path)
3248
+ });
3249
+ }
3250
+ for (const entry of placed.values()){
3251
+ const limit = AI_OUTPUT_BUDGETS[entry.kind].embeds;
3252
+ if (entry.paths.length <= limit) continue;
3253
+ violations.push({
3254
+ rule: 16,
3255
+ code: 'plan-embed-over-budget',
3256
+ message: `${entry.label} is planned ${entry.paths.length} players, and a ${entry.kind} carries at most ${limit}. Keep the one the brief asks for most.`,
3257
+ paths: entry.paths
3258
+ });
3259
+ }
3260
+ if (wrongHost.length) {
3261
+ violations.push({
3262
+ rule: 16,
3263
+ code: 'plan-embed-link-host',
3264
+ message: `A planned player's link is not a video link of its host, ${Object.values(AI_BUILD_PLAN_EMBED_HOST_NAMES).join(' or ')}. Give the brief's own link for that host, or null.`,
3265
+ paths: wrongHost
3266
+ });
3267
+ }
3268
+ return violations;
3269
+ }
3270
+ /** Text as two copies of one phrase compare: lowercase, no quotes, one space. */ function phraseKey(text) {
3271
+ return text.toLowerCase().replace(/[“”"‘’'`]/g, '').replace(/\s+/g, ' ').trim();
3272
+ }
3273
+ /**
3274
+ * Rule 16 (plan, against the brief): a player is planned only where the brief
3275
+ * asks for one (AGL-3433). The plan quotes the brief's words, and those words
3276
+ * are found in the brief and ask for a video; a link it gives is one the brief
3277
+ * gives. The plan step holds this, being the one door that reads the brief
3278
+ * beside the plan.
3279
+ */ export function aiPlanEmbedBriefViolations(plan, brief) {
3280
+ var _brief_match, _plan_embeds;
3281
+ const said = phraseKey(brief);
3282
+ const given = new Set(((_brief_match = brief.match(/https:\/\/\S+/g)) != null ? _brief_match : []).map((link)=>aiEmbedVideoKey(link.replace(/[).,;!?]+$/, ''))).filter((key)=>key !== null));
3283
+ const unasked = [];
3284
+ const ungiven = [];
3285
+ ((_plan_embeds = plan.embeds) != null ? _plan_embeds : []).forEach((embed, index)=>{
3286
+ const asked = phraseKey(embed.asked);
3287
+ if (!asked || !said.includes(asked) || !AI_PLAN_ASKS_FOR_VIDEO.test(asked)) {
3288
+ unasked.push(`embeds[${index}].asked`);
3289
+ }
3290
+ if (embed.url !== null) {
3291
+ const key = aiEmbedVideoKey(embed.url);
3292
+ if (!key || !given.has(key)) ungiven.push(`embeds[${index}].url`);
3293
+ }
3294
+ });
3295
+ const violations = [];
3296
+ if (unasked.length) {
3297
+ violations.push({
3298
+ rule: 16,
3299
+ code: 'plan-embed-not-asked',
3300
+ message: 'A player is planned that the brief does not ask for: its words are not words of the brief asking for a video. Take it off the plan; a film from the media library plays in a Video.',
3301
+ paths: unasked
3302
+ });
3303
+ }
3304
+ if (ungiven.length) {
3305
+ violations.push({
3306
+ rule: 16,
3307
+ code: 'plan-embed-link-not-given',
3308
+ message: "A planned player's link is not one the brief gives. Give the brief's own link, or null for the site owner to paste.",
3309
+ paths: ungiven
3310
+ });
3311
+ }
3312
+ return violations;
3313
+ }
3158
3314
  /**
3159
3315
  * Every plan rule, against the site inventory the plan was made from and,
3160
3316
  * where the job read them, what it may create there (AGL-3030). `null`
@@ -3169,6 +3325,7 @@ function slugKey(slug) {
3169
3325
  ...detectPlanInlineForms(plan, inventory, capabilities),
3170
3326
  ...detectUntemplatedSimilarPages(plan, inventory),
3171
3327
  ...detectPlanLiteralColors(plan),
3328
+ ...detectPlanEmbeds(plan),
3172
3329
  ...detectCreateBeforeReuse(plan, inventory),
3173
3330
  ...detectPlanUncreatable(plan, capabilities),
3174
3331
  ...detectPlanUndeclaredCreations(plan, inventory, capabilities),