@aglyn/plugins-ai 1.0.0-beta.219 → 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 (181) hide show
  1. package/package.json +18 -18
  2. package/src/lib/billing/ai-month-meter.d.ts +51 -0
  3. package/src/lib/billing/ai-month-meter.js +89 -0
  4. package/src/lib/billing/ai-month-meter.js.map +1 -0
  5. package/src/lib/billing/ai-overage-close.d.ts +11 -33
  6. package/src/lib/billing/ai-overage-close.js +4 -3
  7. package/src/lib/billing/ai-overage-close.js.map +1 -1
  8. package/src/lib/billing/ai-overage-cutover.d.ts +28 -12
  9. package/src/lib/billing/ai-overage-cutover.js +1 -28
  10. package/src/lib/billing/ai-overage-cutover.js.map +1 -1
  11. package/src/lib/billing/ai-overage-ledger.js +1 -1
  12. package/src/lib/billing/ai-overage-ledger.js.map +1 -1
  13. package/src/lib/billing/ai-overage-standing.js +1 -1
  14. package/src/lib/billing/ai-overage-standing.js.map +1 -1
  15. package/src/lib/components/ai-allotment-editor.component.js +1 -1
  16. package/src/lib/components/ai-allotment-editor.component.js.map +1 -1
  17. package/src/lib/components/ai-assist-provider.component.js +3 -2
  18. package/src/lib/components/ai-assist-provider.component.js.map +1 -1
  19. package/src/lib/components/ai-automation-zones.d.ts +63 -0
  20. package/src/lib/components/ai-automation-zones.js +31 -0
  21. package/src/lib/components/ai-automation-zones.js.map +1 -0
  22. package/src/lib/components/ai-describe-automation.component.d.ts +1 -1
  23. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  24. package/src/lib/components/ai-describe-button.component.d.ts +6 -3
  25. package/src/lib/components/ai-describe-button.component.js +4 -1
  26. package/src/lib/components/ai-describe-button.component.js.map +1 -1
  27. package/src/lib/components/ai-explain-automation.component.d.ts +1 -1
  28. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  29. package/src/lib/components/billing-ai-allotments.component.js +1 -1
  30. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  31. package/src/lib/components/billing-assist-overage-card.component.js +1 -1
  32. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  33. package/src/lib/constants.d.ts +5 -0
  34. package/src/lib/constants.js +4 -0
  35. package/src/lib/constants.js.map +1 -1
  36. package/src/lib/declarations.js +2 -0
  37. package/src/lib/declarations.js.map +1 -1
  38. package/src/lib/declarations.server.js +24 -10
  39. package/src/lib/declarations.server.js.map +1 -1
  40. package/src/lib/insights/ai-dataset-catalog.d.ts +49 -0
  41. package/src/lib/insights/ai-dataset-catalog.js +57 -0
  42. package/src/lib/insights/ai-dataset-catalog.js.map +1 -0
  43. package/src/lib/insights/ai-figure-readers.d.ts +6 -39
  44. package/src/lib/insights/ai-figure-readers.js +2 -391
  45. package/src/lib/insights/ai-figure-readers.js.map +1 -1
  46. package/src/lib/insights/ai-insight-digest.d.ts +10 -1
  47. package/src/lib/insights/ai-insight-digest.js +16 -4
  48. package/src/lib/insights/ai-insight-digest.js.map +1 -1
  49. package/src/lib/insights/ai-insight-readers.d.ts +1 -1
  50. package/src/lib/insights/ai-insight-readers.js.map +1 -1
  51. package/src/lib/jobs/ai-job-component-step.d.ts +6 -1
  52. package/src/lib/jobs/ai-job-component-step.js +23 -6
  53. package/src/lib/jobs/ai-job-component-step.js.map +1 -1
  54. package/src/lib/jobs/ai-job-draft-ids.js +11 -0
  55. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  56. package/src/lib/jobs/ai-job-email-step.d.ts +5 -1
  57. package/src/lib/jobs/ai-job-email-step.js +10 -1
  58. package/src/lib/jobs/ai-job-email-step.js.map +1 -1
  59. package/src/lib/jobs/ai-job-form-step.d.ts +17 -1
  60. package/src/lib/jobs/ai-job-form-step.js +60 -8
  61. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  62. package/src/lib/jobs/ai-job-generation.d.ts +21 -0
  63. package/src/lib/jobs/ai-job-generation.js +53 -1
  64. package/src/lib/jobs/ai-job-generation.js.map +1 -1
  65. package/src/lib/jobs/ai-job-insight-step.js +1 -1
  66. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  67. package/src/lib/jobs/ai-job-layout-step.d.ts +15 -4
  68. package/src/lib/jobs/ai-job-layout-step.js +45 -12
  69. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  70. package/src/lib/jobs/ai-job-plan-conformance.d.ts +5 -0
  71. package/src/lib/jobs/ai-job-plan-conformance.js +7 -1
  72. package/src/lib/jobs/ai-job-plan-conformance.js.map +1 -1
  73. package/src/lib/jobs/ai-job-plan-creations.d.ts +80 -0
  74. package/src/lib/jobs/ai-job-plan-creations.js +199 -0
  75. package/src/lib/jobs/ai-job-plan-creations.js.map +1 -0
  76. package/src/lib/jobs/ai-job-plan-step.d.ts +8 -2
  77. package/src/lib/jobs/ai-job-plan-step.js +20 -6
  78. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  79. package/src/lib/jobs/ai-job-site-step.d.ts +2 -0
  80. package/src/lib/jobs/ai-job-site-step.js +6 -6
  81. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  82. package/src/lib/jobs/ai-job-template-step.d.ts +9 -6
  83. package/src/lib/jobs/ai-job-template-step.js +72 -15
  84. package/src/lib/jobs/ai-job-template-step.js.map +1 -1
  85. package/src/lib/jobs/ai-jobs.js +1 -1
  86. package/src/lib/jobs/ai-jobs.js.map +1 -1
  87. package/src/lib/jobs/ai-workflow-records.d.ts +5 -4
  88. package/src/lib/jobs/ai-workflow-records.js +26 -22
  89. package/src/lib/jobs/ai-workflow-records.js.map +1 -1
  90. package/src/lib/model/ai-allotments.d.ts +16 -3
  91. package/src/lib/model/ai-allotments.js +34 -11
  92. package/src/lib/model/ai-allotments.js.map +1 -1
  93. package/src/lib/model/ai-job-creations.d.ts +55 -0
  94. package/src/lib/model/ai-job-creations.js +85 -0
  95. package/src/lib/model/ai-job-creations.js.map +1 -0
  96. package/src/lib/model/ai-page-job.d.ts +3 -2
  97. package/src/lib/model/ai-page-job.js +4 -3
  98. package/src/lib/model/ai-page-job.js.map +1 -1
  99. package/src/lib/model/ai-site-job.d.ts +4 -2
  100. package/src/lib/model/ai-site-job.js +8 -4
  101. package/src/lib/model/ai-site-job.js.map +1 -1
  102. package/src/lib/model/ai-template-subjects.d.ts +10 -0
  103. package/src/lib/model/ai-template-subjects.js +14 -0
  104. package/src/lib/model/ai-template-subjects.js.map +1 -1
  105. package/src/lib/model/ai-tokens.js +1 -1
  106. package/src/lib/model/ai-tokens.js.map +1 -1
  107. package/src/lib/model/ai-usage-by-user.js +1 -1
  108. package/src/lib/model/ai-usage-by-user.js.map +1 -1
  109. package/src/lib/providers/catalog.js +1 -1
  110. package/src/lib/providers/catalog.js.map +1 -1
  111. package/src/lib/providers/model-choice.js +1 -1
  112. package/src/lib/providers/model-choice.js.map +1 -1
  113. package/src/lib/runtime/ai-device-audit.d.ts +1 -1
  114. package/src/lib/runtime/ai-device-audit.js +1 -1
  115. package/src/lib/runtime/ai-device-audit.js.map +1 -1
  116. package/src/lib/runtime/ai-eval-live.js +1 -1
  117. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  118. package/src/lib/runtime/ai-eval.d.ts +1 -1
  119. package/src/lib/runtime/ai-eval.js.map +1 -1
  120. package/src/lib/runtime/ai-gate.js +1 -1
  121. package/src/lib/runtime/ai-gate.js.map +1 -1
  122. package/src/lib/runtime/ai-palette.d.ts +1 -1
  123. package/src/lib/runtime/ai-palette.generated.d.ts +1 -1
  124. package/src/lib/runtime/ai-palette.generated.js +1 -1
  125. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  126. package/src/lib/runtime/ai-palette.js.map +1 -1
  127. package/src/lib/runtime/site-inventory.js +28 -22
  128. package/src/lib/runtime/site-inventory.js.map +1 -1
  129. package/src/lib/server/ai-admin-org.js +1 -1
  130. package/src/lib/server/ai-admin-org.js.map +1 -1
  131. package/src/lib/server/ai-admin-orgs-spend.js +1 -1
  132. package/src/lib/server/ai-admin-orgs-spend.js.map +1 -1
  133. package/src/lib/server/ai-admin-signals.js +1 -1
  134. package/src/lib/server/ai-admin-signals.js.map +1 -1
  135. package/src/lib/server/ai-allotments.js +1 -1
  136. package/src/lib/server/ai-allotments.js.map +1 -1
  137. package/src/lib/server/ai-assist.js +2 -2
  138. package/src/lib/server/ai-assist.js.map +1 -1
  139. package/src/lib/server/assist-chat.d.ts +1 -1
  140. package/src/lib/server/assist-chat.js +2 -2
  141. package/src/lib/server/assist-chat.js.map +1 -1
  142. package/src/lib/server/billing-credits.d.ts +5 -0
  143. package/src/lib/server/billing-credits.js +8 -2
  144. package/src/lib/server/billing-credits.js.map +1 -1
  145. package/src/lib/server/billing-overage.js +2 -2
  146. package/src/lib/server/billing-overage.js.map +1 -1
  147. package/src/lib/testing/stand-in-dataset-index.d.ts +20 -0
  148. package/src/lib/testing/stand-in-dataset-index.js +80 -0
  149. package/src/lib/testing/stand-in-dataset-index.js.map +1 -0
  150. package/src/lib/usage/ai-allotment-alerts.d.ts +10 -7
  151. package/src/lib/usage/ai-allotment-alerts.js +31 -14
  152. package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
  153. package/src/lib/usage/ai-quota-checks.d.ts +37 -0
  154. package/src/lib/usage/ai-quota-checks.js +69 -0
  155. package/src/lib/usage/ai-quota-checks.js.map +1 -0
  156. package/src/lib/usage/ai-usage-alerts.js +22 -10
  157. package/src/lib/usage/ai-usage-alerts.js.map +1 -1
  158. package/src/lib/usage/ai-usage-by-user.js +1 -1
  159. package/src/lib/usage/ai-usage-by-user.js.map +1 -1
  160. package/src/lib/usage/assist-ceiling.d.ts +2 -2
  161. package/src/lib/usage/assist-ceiling.js +4 -4
  162. package/src/lib/usage/assist-ceiling.js.map +1 -1
  163. package/src/lib/usage/assist-credits.d.ts +494 -0
  164. package/src/lib/usage/assist-credits.js +524 -0
  165. package/src/lib/usage/assist-credits.js.map +1 -0
  166. package/src/lib/usage/assist-free-taste.d.ts +1 -1
  167. package/src/lib/usage/assist-free-taste.js +1 -1
  168. package/src/lib/usage/assist-free-taste.js.map +1 -1
  169. package/src/lib/usage/assist-signal-mining.js +1 -1
  170. package/src/lib/usage/assist-signal-mining.js.map +1 -1
  171. package/src/lib/usage/assist-spend-guards.d.ts +153 -0
  172. package/src/lib/usage/assist-spend-guards.js +182 -0
  173. package/src/lib/usage/assist-spend-guards.js.map +1 -0
  174. package/src/lib/usage/assist-usage.d.ts +1 -1
  175. package/src/lib/usage/assist-usage.js +1 -1
  176. package/src/lib/usage/assist-usage.js.map +1 -1
  177. package/src/lib/usage/staff-org-ai.js +1 -1
  178. package/src/lib/usage/staff-org-ai.js.map +1 -1
  179. package/src/lib/usage-axes.d.ts +10 -0
  180. package/src/lib/usage-axes.js +40 -2
  181. package/src/lib/usage-axes.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-plan-conformance.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiBuildPlanScreen, AiBuildPlanSection } from '../model/ai-build-plan'\nimport type { AiJobPlan } from '../model/ai-jobs.types'\nimport {\n AI_LAYOUT_REGION_NAMES,\n aiBindsRepeatedItems,\n aiLayoutRegionOf,\n aiRepeatedItemCount,\n aiTreeLayoutRegions,\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineTree,\n type AiDoctrineViolation,\n type AiLayoutRegion,\n} from '../runtime/ai-doctrine-validators'\nimport { aiBindingTokensIn, type AiTemplateSubjectDefinition } from '../model/ai-template-subjects'\nimport { aiPlanCreation } from './ai-job-generation'\n\n/**\n * The build held to the plan the member confirmed (AGL-3024).\n *\n * The doctrine validators ask whether an artifact was built the way a careful\n * author builds one. They do not ask whether it is the artifact the plan\n * PROMISED, and a build that quietly delivers less still reports `Done` —\n * which is the dangerous part, because `Done` is what tells the customer they\n * have no reason to re-read it. Measured on beta.139: a plan promising four\n * practice areas built three cards; a plan promising a layout with a sidebar\n * built a verbatim copy of the base layout, the word \"sidebar\" nowhere in it.\n *\n * ── Structured commitments, never prose ──────────────────────────────────\n *\n * A plan's rationale (`why`) is prose a model wrote, and matching English\n * against a node tree after the fact would be a second guess dressed as a\n * check. Only the plan's STRUCTURE is read here, and only the two shapes that\n * are already numbers or names in `ai-build-plan.ts`:\n *\n * - **A count**: `screens[].sections[].items`, which the plan already\n * carries and the section prompt already states as \"It shows N items\".\n * Nothing read it back.\n * - **A region**: `create[].fields` on a layout, which is that kind's own\n * field for the regions it has (`AI_LAYOUT_REGIONS`). A layout has no\n * properties, so the planner is asked there for the one thing about a\n * layout worth promising and worth checking — a checkable assertion the\n * planner emits beside its prose, rather than English parsed out of it.\n * - **A token**: `create[].fields` on a template, from the subject's own\n * closed catalog (`AI_TEMPLATE_SUBJECT_DEFINITIONS[subject].tokens`), which\n * is the template's analogue of a layout's regions — the one thing about a\n * template that differs per record, and therefore the one worth promising.\n *\n * The third was added after a measured miss (AGL-3024, 2026-09-21). A brief\n * asked for an attorney's bar admissions; the plan's `why` promised \"a\n * bar-admissions block\"; the built template had no such thing and reported\n * `Done`. Nothing had gone wrong in the build: `author` has six tokens —\n * name, bio, image, jobTitle, worksFor, url — and no catalog anywhere fills a\n * bar admission, so the promise was unkeepable when it was written. Read as a\n * token list the promise cannot even be MADE, which is a better place to stop\n * than after the document is paid for.\n *\n * ── It fails closed ──────────────────────────────────────────────────────\n *\n * Every function here answers \"the plan is kept\" only when it could look. A\n * layout the step never read is not a layout whose regions are present; the\n * layout door turns that into a stop for review rather than a `Done`. What it\n * will not do is guess: a count it cannot settle from the document — items a\n * collection fills at render — is not reported as short.\n *\n * The findings are ordinary `AiDoctrineViolation`s, so they ride the\n * mechanism every other rule rides: `runValidatedGeneration` re-asks the\n * model once with them, and a second answer that still breaks one stops the\n * job for a person with `aiDoctrineReview`. They carry `rule: null` on\n * purpose. The seventeen rules are how a document is BUILT; keeping the plan\n * is not one of them, and numbering these as rule 7 would print \"Rule 7\n * (Reuse before creating)\" over a finding about a count.\n */\n\n/** A count below this is not a repeat, so a plan promising one promises nothing to count. */\nexport const AI_PLAN_ITEMS_MIN = 2\n\n/**\n * The regions the confirmed plan's layout creation names, and the words it\n * named that no region answers to. A plan with no layout creation names none.\n */\nexport function aiPlannedLayoutRegions(plan: AiJobPlan | null): {\n regions: AiLayoutRegion[]\n unreadable: string[]\n} {\n const regions = new Set<AiLayoutRegion>()\n const unreadable: string[] = []\n for (const field of aiPlanCreation(plan, 'layout')?.fields ?? []) {\n const region = aiLayoutRegionOf(field)\n if (region) regions.add(region)\n else unreadable.push(field)\n }\n return { regions: [...regions], unreadable }\n}\n\n/**\n * The plan's promised regions against a built layout: one finding naming\n * every region the plan lists that the layout does not carry, and one naming\n * a word the vocabulary could not read, which is a promise this check cannot\n * settle either way.\n */\nexport function aiPlanRegionViolations(\n plan: AiJobPlan | null,\n tree: AiDoctrineTree,\n): AiDoctrineViolation[] {\n const { regions, unreadable } = aiPlannedLayoutRegions(plan)\n const built = aiTreeLayoutRegions(tree)\n const violations: AiDoctrineViolation[] = []\n const missing = regions.filter((region) => !built.has(region))\n if (missing.length) {\n const one = missing.length === 1\n violations.push({\n rule: null,\n code: 'plan-region-missing',\n message: `The confirmed plan gives this layout ${listed(\n missing,\n )}, and the layout has no ${one ? 'such region' : 'such regions'}. Build ${\n one ? 'it' : 'each of them'\n }: a sidebar is a Section whose element is aside, a header a Section whose element is header or an App Bar, a nav a Section whose element is nav, a footer a Section whose element is footer, and the main region the Layout Slot.`,\n })\n }\n if (unreadable.length) {\n violations.push({\n rule: null,\n code: 'plan-region-unreadable',\n message: `The confirmed plan gives this layout ${listed(\n unreadable,\n )}, which is no region this platform builds, so nothing can check the layout has ${\n unreadable.length === 1 ? 'it' : 'them'\n }. A layout's regions are ${AI_LAYOUT_REGION_NAMES.join(', ')}.`,\n })\n }\n return violations\n}\n\n/**\n * The plan's promised item count against a built section. Silent where there\n * is nothing to settle: a section promising fewer than `AI_PLAN_ITEMS_MIN`\n * promises no repeat, and one whose items a collection fills draws them from\n * data at render, where the document cannot be counted and rule 8 asked for\n * exactly that. Silent, too, when the section shows MORE than it promised:\n * the count is read generously (`aiRepeatedItemCount`), so a number above the\n * promise is as likely to be the reading as the section.\n */\nexport function aiPlanItemCountViolations(\n section: Pick<AiBuildPlanSection, 'name' | 'items'>,\n tree: AiDoctrineTree,\n): AiDoctrineViolation[] {\n if (section.items < AI_PLAN_ITEMS_MIN) return []\n if (aiBindsRepeatedItems(tree)) return []\n const shown = aiRepeatedItemCount(tree)\n if (shown >= section.items) return []\n return [\n {\n rule: null,\n code: 'plan-items-short',\n message: `The confirmed plan says the \"${section.name}\" section shows ${section.items} items, and this section shows ${shown}. Build all ${section.items}.`,\n },\n ]\n}\n\n/**\n * The counts the plan's sections promised against a page a COPY produced\n * (AGL-3024), read over the whole page because a copy has no section of the\n * plan's in it to read one at a time.\n *\n * A copied screen carries the SOURCE's nodes under the source's ids: nothing\n * maps \"the practice areas section\" onto a subtree of it, so the per-section\n * reading `aiPlanItemCountViolations` does at a generation pass has nothing\n * to stand on here. What can still be settled is the weaker claim, and it is\n * the one that catches the measured shape: a plan promising a section of four\n * against a copy in which NO group of four repeated things exists anywhere is\n * a promise the copy did not keep, whichever section was meant to keep it.\n *\n * Weaker on purpose, and so quieter: a copy holding six of something else\n * reads as six and is let through. A screen carries no app bar and no footer\n * — those are its layout's — so what is counted is the page's own content.\n */\nexport function aiPlanCopiedPageViolations(\n screen: Pick<AiBuildPlanScreen, 'sections'>,\n tree: AiDoctrineTree,\n): AiDoctrineViolation[] {\n const promised = screen.sections\n .filter((section) => section.items >= AI_PLAN_ITEMS_MIN)\n .sort((a, b) => b.items - a.items)[0]\n if (!promised) return []\n if (aiBindsRepeatedItems(tree)) return []\n const shown = aiRepeatedItemCount(tree)\n if (shown >= promised.items) return []\n return [\n {\n rule: null,\n code: 'plan-items-short',\n message: `The confirmed plan says the \"${promised.name}\" section shows ${promised.items} items, and this page is a copy that shows at most ${shown} of anything. Build the ${promised.items}.`,\n },\n ]\n}\n\n/** A token as the catalog spells it, for matching what a plan wrote against it. */\nfunction tokenKey(value: string): string {\n return value.replace(/[{}\\s]/g, '').toLowerCase()\n}\n\n/** Every binding token a built tree holds, spelled as the catalog spells them. */\nexport function aiTreeBoundTokens(tree: AiDoctrineTree): Set<string> {\n const bound = new Set<string>()\n const nodes = tree.nodes as unknown as Record<string, AiDoctrineNode>\n const strings = (value: unknown, out: string[]): string[] => {\n if (typeof value === 'string') out.push(value)\n else if (Array.isArray(value)) for (const inner of value) strings(inner, out)\n else if (value && typeof value === 'object') {\n for (const inner of Object.values(value as Record<string, unknown>)) strings(inner, out)\n }\n return out\n }\n for (const { node } of walkTree({ rootId: tree.rootId, nodes })) {\n for (const token of strings(node.props, []).flatMap(aiBindingTokensIn)) {\n bound.add(tokenKey(token))\n }\n }\n return bound\n}\n\n/**\n * The subject tokens the confirmed plan's template creation names, and the\n * words it named that the subject's catalog does not answer to. A plan with\n * no template creation names none.\n */\nexport function aiPlannedTemplateTokens(\n plan: AiJobPlan | null,\n definition: AiTemplateSubjectDefinition,\n): { tokens: string[]; unreadable: string[] } {\n const catalog = new Map(definition.tokens.map((entry) => [tokenKey(entry.token), entry.token]))\n const tokens = new Set<string>()\n const unreadable: string[] = []\n for (const field of aiPlanCreation(plan, 'template')?.fields ?? []) {\n const token = catalog.get(tokenKey(field))\n if (token) tokens.add(token)\n else unreadable.push(field)\n }\n return { tokens: [...tokens], unreadable }\n}\n\n/**\n * The plan's promised tokens against a built template: one finding naming\n * every token the plan lists that the template does not bind, and one naming\n * a word the subject's catalog could not read — which is a promise this check\n * cannot settle either way, and, more usefully, one the page could never have\n * filled.\n */\nexport function aiPlanTemplateTokenViolations(\n plan: AiJobPlan | null,\n definition: AiTemplateSubjectDefinition,\n tree: AiDoctrineTree,\n): AiDoctrineViolation[] {\n const { tokens, unreadable } = aiPlannedTemplateTokens(plan, definition)\n const violations: AiDoctrineViolation[] = []\n if (tokens.length) {\n const bound = aiTreeBoundTokens(tree)\n const missing = tokens.filter((token) => !bound.has(tokenKey(token)))\n if (missing.length) {\n const one = missing.length === 1\n violations.push({\n rule: null,\n code: 'plan-token-missing',\n message: `The confirmed plan says this template shows ${listed(missing)}, and it binds ${\n one ? 'no such token' : 'no such tokens'\n }. Put ${one ? 'it' : 'each of them'} on the page, so every ${\n definition.noun.replace(/^an? /, '')\n } shows their own.`,\n })\n }\n }\n // A template renders for every record it serves, so a video it plays that\n // the member never confirmed loads on every page of the collection (AGL-3433).\n const promised = new Set(tokens.map(tokenKey))\n const videos = new Set(\n definition.tokens\n .map((entry) => tokenKey(entry.token))\n .filter((token) => token.endsWith('video') && !promised.has(token)),\n )\n const playing = new Map<string, string[]>()\n const nodes = tree.nodes as unknown as Record<string, AiDoctrineNode>\n for (const { id, node } of walkTree({ rootId: tree.rootId, nodes })) {\n for (const token of aiBindingTokensIn(JSON.stringify(node.props ?? {}))) {\n if (videos.has(tokenKey(token))) playing.set(token, [...(playing.get(token) ?? []), id])\n }\n }\n if (playing.size) {\n violations.push({\n rule: 16,\n code: 'plan-video-unasked',\n message: `This template plays ${listed([...playing.keys()])}, which the confirmed plan does not list, so every ${definition.noun.replace(\n /^an? /,\n '',\n )} page would load a player nobody asked for. Remove it.`,\n nodeIds: [...new Set([...playing.values()].flat())],\n })\n }\n if (unreadable.length) {\n violations.push({\n rule: null,\n code: 'plan-token-unreadable',\n message: `The confirmed plan says this template shows ${listed(\n unreadable,\n )}, which ${definition.noun}'s page does not fill, so nothing can check the template shows ${\n unreadable.length === 1 ? 'it' : 'them'\n }. Promise only what the page fills: ${definition.tokens\n .map((entry) => entry.token)\n .join(', ')}.`,\n })\n }\n return violations\n}\n\n/** `\"a\"`, `\"a\" and \"b\"`, `\"a\", \"b\" and \"c\"`. */\nfunction listed(values: readonly string[]): string {\n const quoted = values.map((value) => `\"${value}\"`)\n return quoted.length <= 1\n ? quoted.join('')\n : `${quoted.slice(0, -1).join(', ')} and ${quoted[quoted.length - 1]}`\n}\n"],"names":["AI_LAYOUT_REGION_NAMES","aiBindsRepeatedItems","aiLayoutRegionOf","aiRepeatedItemCount","aiTreeLayoutRegions","walkTree","aiBindingTokensIn","aiPlanCreation","AI_PLAN_ITEMS_MIN","aiPlannedLayoutRegions","plan","regions","Set","unreadable","field","fields","region","add","push","aiPlanRegionViolations","tree","built","violations","missing","filter","has","length","one","rule","code","message","listed","join","aiPlanItemCountViolations","section","items","shown","name","aiPlanCopiedPageViolations","screen","promised","sections","sort","a","b","tokenKey","value","replace","toLowerCase","aiTreeBoundTokens","bound","nodes","strings","out","Array","isArray","inner","Object","values","node","rootId","token","props","flatMap","aiPlannedTemplateTokens","definition","catalog","Map","tokens","map","entry","get","aiPlanTemplateTokenViolations","noun","videos","endsWith","playing","id","JSON","stringify","set","size","keys","nodeIds","flat","quoted","slice"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-plan-conformance.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiBuildPlanScreen, AiBuildPlanSection } from '../model/ai-build-plan'\nimport type { AiJobPlan } from '../model/ai-jobs.types'\nimport {\n AI_LAYOUT_REGION_NAMES,\n aiBindsRepeatedItems,\n aiLayoutRegionOf,\n aiRepeatedItemCount,\n aiTreeLayoutRegions,\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineTree,\n type AiDoctrineViolation,\n type AiLayoutRegion,\n} from '../runtime/ai-doctrine-validators'\nimport {\n aiBindingTokensIn,\n isAiUnreadToken,\n type AiTemplateSubjectDefinition,\n} from '../model/ai-template-subjects'\nimport { aiPlanCreation } from './ai-job-generation'\n\n/**\n * The build held to the plan the member confirmed (AGL-3024).\n *\n * The doctrine validators ask whether an artifact was built the way a careful\n * author builds one. They do not ask whether it is the artifact the plan\n * PROMISED, and a build that quietly delivers less still reports `Done` —\n * which is the dangerous part, because `Done` is what tells the customer they\n * have no reason to re-read it. Measured on beta.139: a plan promising four\n * practice areas built three cards; a plan promising a layout with a sidebar\n * built a verbatim copy of the base layout, the word \"sidebar\" nowhere in it.\n *\n * ── Structured commitments, never prose ──────────────────────────────────\n *\n * A plan's rationale (`why`) is prose a model wrote, and matching English\n * against a node tree after the fact would be a second guess dressed as a\n * check. Only the plan's STRUCTURE is read here, and only the two shapes that\n * are already numbers or names in `ai-build-plan.ts`:\n *\n * - **A count**: `screens[].sections[].items`, which the plan already\n * carries and the section prompt already states as \"It shows N items\".\n * Nothing read it back.\n * - **A region**: `create[].fields` on a layout, which is that kind's own\n * field for the regions it has (`AI_LAYOUT_REGIONS`). A layout has no\n * properties, so the planner is asked there for the one thing about a\n * layout worth promising and worth checking — a checkable assertion the\n * planner emits beside its prose, rather than English parsed out of it.\n * - **A token**: `create[].fields` on a template, from the subject's own\n * closed catalog (`AI_TEMPLATE_SUBJECT_DEFINITIONS[subject].tokens`), which\n * is the template's analogue of a layout's regions — the one thing about a\n * template that differs per record, and therefore the one worth promising.\n *\n * The third was added after a measured miss (AGL-3024, 2026-09-21). A brief\n * asked for an attorney's bar admissions; the plan's `why` promised \"a\n * bar-admissions block\"; the built template had no such thing and reported\n * `Done`. Nothing had gone wrong in the build: `author` has six tokens —\n * name, bio, image, jobTitle, worksFor, url — and no catalog anywhere fills a\n * bar admission, so the promise was unkeepable when it was written. Read as a\n * token list the promise cannot even be MADE, which is a better place to stop\n * than after the document is paid for.\n *\n * ── It fails closed ──────────────────────────────────────────────────────\n *\n * Every function here answers \"the plan is kept\" only when it could look. A\n * layout the step never read is not a layout whose regions are present; the\n * layout door turns that into a stop for review rather than a `Done`. What it\n * will not do is guess: a count it cannot settle from the document — items a\n * collection fills at render — is not reported as short.\n *\n * The findings are ordinary `AiDoctrineViolation`s, so they ride the\n * mechanism every other rule rides: `runValidatedGeneration` re-asks the\n * model once with them, and a second answer that still breaks one stops the\n * job for a person with `aiDoctrineReview`. They carry `rule: null` on\n * purpose. The seventeen rules are how a document is BUILT; keeping the plan\n * is not one of them, and numbering these as rule 7 would print \"Rule 7\n * (Reuse before creating)\" over a finding about a count.\n */\n\n/** A count below this is not a repeat, so a plan promising one promises nothing to count. */\nexport const AI_PLAN_ITEMS_MIN = 2\n\n/**\n * The regions the confirmed plan's layout creation names, and the words it\n * named that no region answers to. A plan with no layout creation names none.\n */\nexport function aiPlannedLayoutRegions(plan: AiJobPlan | null): {\n regions: AiLayoutRegion[]\n unreadable: string[]\n} {\n const regions = new Set<AiLayoutRegion>()\n const unreadable: string[] = []\n for (const field of aiPlanCreation(plan, 'layout')?.fields ?? []) {\n const region = aiLayoutRegionOf(field)\n if (region) regions.add(region)\n else unreadable.push(field)\n }\n return { regions: [...regions], unreadable }\n}\n\n/**\n * The plan's promised regions against a built layout: one finding naming\n * every region the plan lists that the layout does not carry, and one naming\n * a word the vocabulary could not read, which is a promise this check cannot\n * settle either way.\n */\nexport function aiPlanRegionViolations(\n plan: AiJobPlan | null,\n tree: AiDoctrineTree,\n): AiDoctrineViolation[] {\n const { regions, unreadable } = aiPlannedLayoutRegions(plan)\n const built = aiTreeLayoutRegions(tree)\n const violations: AiDoctrineViolation[] = []\n const missing = regions.filter((region) => !built.has(region))\n if (missing.length) {\n const one = missing.length === 1\n violations.push({\n rule: null,\n code: 'plan-region-missing',\n message: `The confirmed plan gives this layout ${listed(\n missing,\n )}, and the layout has no ${one ? 'such region' : 'such regions'}. Build ${\n one ? 'it' : 'each of them'\n }: a sidebar is a Section whose element is aside, a header a Section whose element is header or an App Bar, a nav a Section whose element is nav, a footer a Section whose element is footer, and the main region the Layout Slot.`,\n })\n }\n if (unreadable.length) {\n violations.push({\n rule: null,\n code: 'plan-region-unreadable',\n message: `The confirmed plan gives this layout ${listed(\n unreadable,\n )}, which is no region this platform builds, so nothing can check the layout has ${\n unreadable.length === 1 ? 'it' : 'them'\n }. A layout's regions are ${AI_LAYOUT_REGION_NAMES.join(', ')}.`,\n })\n }\n return violations\n}\n\n/**\n * The plan's promised item count against a built section. Silent where there\n * is nothing to settle: a section promising fewer than `AI_PLAN_ITEMS_MIN`\n * promises no repeat, and one whose items a collection fills draws them from\n * data at render, where the document cannot be counted and rule 8 asked for\n * exactly that. Silent, too, when the section shows MORE than it promised:\n * the count is read generously (`aiRepeatedItemCount`), so a number above the\n * promise is as likely to be the reading as the section.\n */\nexport function aiPlanItemCountViolations(\n section: Pick<AiBuildPlanSection, 'name' | 'items'>,\n tree: AiDoctrineTree,\n): AiDoctrineViolation[] {\n if (section.items < AI_PLAN_ITEMS_MIN) return []\n if (aiBindsRepeatedItems(tree)) return []\n const shown = aiRepeatedItemCount(tree)\n if (shown >= section.items) return []\n return [\n {\n rule: null,\n code: 'plan-items-short',\n message: `The confirmed plan says the \"${section.name}\" section shows ${section.items} items, and this section shows ${shown}. Build all ${section.items}.`,\n },\n ]\n}\n\n/**\n * The counts the plan's sections promised against a page a COPY produced\n * (AGL-3024), read over the whole page because a copy has no section of the\n * plan's in it to read one at a time.\n *\n * A copied screen carries the SOURCE's nodes under the source's ids: nothing\n * maps \"the practice areas section\" onto a subtree of it, so the per-section\n * reading `aiPlanItemCountViolations` does at a generation pass has nothing\n * to stand on here. What can still be settled is the weaker claim, and it is\n * the one that catches the measured shape: a plan promising a section of four\n * against a copy in which NO group of four repeated things exists anywhere is\n * a promise the copy did not keep, whichever section was meant to keep it.\n *\n * Weaker on purpose, and so quieter: a copy holding six of something else\n * reads as six and is let through. A screen carries no app bar and no footer\n * — those are its layout's — so what is counted is the page's own content.\n */\nexport function aiPlanCopiedPageViolations(\n screen: Pick<AiBuildPlanScreen, 'sections'>,\n tree: AiDoctrineTree,\n): AiDoctrineViolation[] {\n const promised = screen.sections\n .filter((section) => section.items >= AI_PLAN_ITEMS_MIN)\n .sort((a, b) => b.items - a.items)[0]\n if (!promised) return []\n if (aiBindsRepeatedItems(tree)) return []\n const shown = aiRepeatedItemCount(tree)\n if (shown >= promised.items) return []\n return [\n {\n rule: null,\n code: 'plan-items-short',\n message: `The confirmed plan says the \"${promised.name}\" section shows ${promised.items} items, and this page is a copy that shows at most ${shown} of anything. Build the ${promised.items}.`,\n },\n ]\n}\n\n/** A token as the catalog spells it, for matching what a plan wrote against it. */\nfunction tokenKey(value: string): string {\n return value.replace(/[{}\\s]/g, '').toLowerCase()\n}\n\n/** Every binding token a built tree holds, spelled as the catalog spells them. */\nexport function aiTreeBoundTokens(tree: AiDoctrineTree): Set<string> {\n const bound = new Set<string>()\n const nodes = tree.nodes as unknown as Record<string, AiDoctrineNode>\n const strings = (value: unknown, out: string[]): string[] => {\n if (typeof value === 'string') out.push(value)\n else if (Array.isArray(value)) for (const inner of value) strings(inner, out)\n else if (value && typeof value === 'object') {\n for (const inner of Object.values(value as Record<string, unknown>)) strings(inner, out)\n }\n return out\n }\n for (const { node } of walkTree({ rootId: tree.rootId, nodes })) {\n for (const token of strings(node.props, []).flatMap(aiBindingTokensIn)) {\n bound.add(tokenKey(token))\n }\n }\n return bound\n}\n\n/**\n * The subject tokens the confirmed plan's template creation names, and the\n * words it named that the subject's catalog does not answer to. A plan with\n * no template creation names none.\n *\n * A slug or the publish timestamp the plan names is neither (AGL-3143 §16):\n * the page fills it, and nothing a reader sees could keep a promise to show\n * it. Held to one, a live build printed \"entry slug: {{entry.slug}} —\n * published {{entry.publishedAt}}\" on every article to be let through.\n */\nexport function aiPlannedTemplateTokens(\n plan: AiJobPlan | null,\n definition: AiTemplateSubjectDefinition,\n): { tokens: string[]; unreadable: string[] } {\n const catalog = new Map(definition.tokens.map((entry) => [tokenKey(entry.token), entry.token]))\n const tokens = new Set<string>()\n const unreadable: string[] = []\n for (const field of aiPlanCreation(plan, 'template')?.fields ?? []) {\n const token = catalog.get(tokenKey(field))\n if (token && isAiUnreadToken(token)) continue\n if (token) tokens.add(token)\n else unreadable.push(field)\n }\n return { tokens: [...tokens], unreadable }\n}\n\n/**\n * The plan's promised tokens against a built template: one finding naming\n * every token the plan lists that the template does not bind, and one naming\n * a word the subject's catalog could not read — which is a promise this check\n * cannot settle either way, and, more usefully, one the page could never have\n * filled.\n */\nexport function aiPlanTemplateTokenViolations(\n plan: AiJobPlan | null,\n definition: AiTemplateSubjectDefinition,\n tree: AiDoctrineTree,\n): AiDoctrineViolation[] {\n const { tokens, unreadable } = aiPlannedTemplateTokens(plan, definition)\n const violations: AiDoctrineViolation[] = []\n if (tokens.length) {\n const bound = aiTreeBoundTokens(tree)\n const missing = tokens.filter((token) => !bound.has(tokenKey(token)))\n if (missing.length) {\n const one = missing.length === 1\n violations.push({\n rule: null,\n code: 'plan-token-missing',\n message: `The confirmed plan says this template shows ${listed(missing)}, and it binds ${\n one ? 'no such token' : 'no such tokens'\n }. Put ${one ? 'it' : 'each of them'} on the page, so every ${\n definition.noun.replace(/^an? /, '')\n } shows their own.`,\n })\n }\n }\n // A template renders for every record it serves, so a video it plays that\n // the member never confirmed loads on every page of the collection (AGL-3433).\n const promised = new Set(tokens.map(tokenKey))\n const videos = new Set(\n definition.tokens\n .map((entry) => tokenKey(entry.token))\n .filter((token) => token.endsWith('video') && !promised.has(token)),\n )\n const playing = new Map<string, string[]>()\n const nodes = tree.nodes as unknown as Record<string, AiDoctrineNode>\n for (const { id, node } of walkTree({ rootId: tree.rootId, nodes })) {\n for (const token of aiBindingTokensIn(JSON.stringify(node.props ?? {}))) {\n if (videos.has(tokenKey(token))) playing.set(token, [...(playing.get(token) ?? []), id])\n }\n }\n if (playing.size) {\n violations.push({\n rule: 16,\n code: 'plan-video-unasked',\n message: `This template plays ${listed([...playing.keys()])}, which the confirmed plan does not list, so every ${definition.noun.replace(\n /^an? /,\n '',\n )} page would load a player nobody asked for. Remove it.`,\n nodeIds: [...new Set([...playing.values()].flat())],\n })\n }\n if (unreadable.length) {\n violations.push({\n rule: null,\n code: 'plan-token-unreadable',\n message: `The confirmed plan says this template shows ${listed(\n unreadable,\n )}, which ${definition.noun}'s page does not fill, so nothing can check the template shows ${\n unreadable.length === 1 ? 'it' : 'them'\n }. Promise only what the page fills: ${definition.tokens\n .map((entry) => entry.token)\n .join(', ')}.`,\n })\n }\n return violations\n}\n\n/** `\"a\"`, `\"a\" and \"b\"`, `\"a\", \"b\" and \"c\"`. */\nfunction listed(values: readonly string[]): string {\n const quoted = values.map((value) => `\"${value}\"`)\n return quoted.length <= 1\n ? quoted.join('')\n : `${quoted.slice(0, -1).join(', ')} and ${quoted[quoted.length - 1]}`\n}\n"],"names":["AI_LAYOUT_REGION_NAMES","aiBindsRepeatedItems","aiLayoutRegionOf","aiRepeatedItemCount","aiTreeLayoutRegions","walkTree","aiBindingTokensIn","isAiUnreadToken","aiPlanCreation","AI_PLAN_ITEMS_MIN","aiPlannedLayoutRegions","plan","regions","Set","unreadable","field","fields","region","add","push","aiPlanRegionViolations","tree","built","violations","missing","filter","has","length","one","rule","code","message","listed","join","aiPlanItemCountViolations","section","items","shown","name","aiPlanCopiedPageViolations","screen","promised","sections","sort","a","b","tokenKey","value","replace","toLowerCase","aiTreeBoundTokens","bound","nodes","strings","out","Array","isArray","inner","Object","values","node","rootId","token","props","flatMap","aiPlannedTemplateTokens","definition","catalog","Map","tokens","map","entry","get","aiPlanTemplateTokenViolations","noun","videos","endsWith","playing","id","JSON","stringify","set","size","keys","nodeIds","flat","quoted","slice"],"mappings":"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"}
@@ -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
+ */
17
+ import type { AiJob, AiJobKind, AiJobPlan } from '../model/ai-jobs.types';
18
+ import type { AiStepKind } from '../providers/catalog';
19
+ import type { AiJobAdmissionContext, AiJobAdmissionRefusal } from './ai-job-admission';
20
+ import { readAiPlanCapabilities } from './ai-job-drafts';
21
+ import { type AiSiteUnit } from './ai-job-site-step';
22
+ import type { AiJobStepRunner } from './ai-job-text-step';
23
+ import { aiJobStepRunnerFor } from './ai-jobs';
24
+ /**
25
+ * A template, layout or component job builds the rest of its plan before its
26
+ * own record (AGL-3143 §15), through the machinery a page job builds its
27
+ * creations with (AGL-3031): one creation a pass, handed to the step that
28
+ * builds that kind under a job derived from this one, where the job stands
29
+ * read from its outputs. Once every creation is built, the kind's own step
30
+ * runs on the plan with each `new:<name>` resolved to the record that was
31
+ * built and listed among what the plan reuses — so the template's reuse check
32
+ * holds it to placing the component its plan created, exactly as it holds it
33
+ * to one the site always had.
34
+ *
35
+ * What each kind builds is `AI_JOB_CREATE_KINDS`; a form or an email design
36
+ * places nothing another job builds, so its plan is only held to that list.
37
+ */
38
+ /** A creation the plan names that this deployment has no step to build. */
39
+ export declare const AI_JOB_CREATION_UNAVAILABLE_COPY = "This needs something that cannot be built here yet. Describe it again.";
40
+ /** A creation that finished without reporting what it built; the job's own record cannot place it. */
41
+ export declare const AI_JOB_CREATION_EMPTY_COPY = "Part of this could not be built. Describe it again.";
42
+ /**
43
+ * The units the creations a job of `kind` builds before its own record are
44
+ * built as, in build order, each addressed by its place in the plan so a unit
45
+ * run again finds its own draft.
46
+ */
47
+ export declare function aiJobCreationUnits(kind: AiJobKind, plan: Pick<AiJobPlan, 'create'>): AiSiteUnit[];
48
+ /**
49
+ * The job the kind's own step builds its record under once every creation is
50
+ * built: the plan's own creation alone, from a copy resolved to what was
51
+ * built, and every built record reused, with the reason the plan gave for it.
52
+ */
53
+ export declare function aiJobWithBuiltCreations(job: AiJob, kind: AiJobKind, units: readonly AiSiteUnit[]): AiJob;
54
+ export interface AiJobPlanCreationsDeps {
55
+ /** The runners a plan's creations are built by; the registry's otherwise. */
56
+ runnerFor?: typeof aiJobStepRunnerFor;
57
+ }
58
+ /**
59
+ * The kind's own runner, building the plan's other creations first: one a
60
+ * pass while any is unbuilt, then the kind's own record on the plan they
61
+ * resolve. A plan with nothing else to build runs the kind's own step as it
62
+ * always ran.
63
+ */
64
+ export declare function aiBuildingPlanCreations(kind: AiJobKind, step: AiStepKind, runner: AiJobStepRunner, deps?: AiJobPlanCreationsDeps): AiJobStepRunner;
65
+ /**
66
+ * The least time this job's next pass needs (AGL-3035): a pass that builds a
67
+ * creation needs what the step that builds it registers for the job derived
68
+ * for it; the kind's own pass needs what the kind registered.
69
+ */
70
+ export declare function aiPlanCreationsRunMinimumMs(kind: AiJobKind): (job: AiJob) => number;
71
+ /**
72
+ * Why a job of `kind` cannot build the creations its plan names, as the door
73
+ * that confirms the plan answers; `null` when it can, or when there is no plan
74
+ * yet. A creation the kind does not build, one this deployment has no step
75
+ * for, and one the workspace may not make on this site now are each refused
76
+ * before a credit is spent, the way a page job's door refuses them.
77
+ */
78
+ export declare function aiJobPlanCreationsRefusal(kind: AiJobKind, context: Pick<AiJobAdmissionContext, 'firestore' | 'org' | 'plan'>, hostId: string, deps?: AiJobPlanCreationsDeps & {
79
+ readCapabilities?: typeof readAiPlanCapabilities;
80
+ }): Promise<AiJobAdmissionRefusal | null>;
@@ -0,0 +1,199 @@
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 { _ as _extends } from "@swc/helpers/_/_extends";
17
+ import { AI_BUILD_PLAN_CREATION_NOUNS } from "../model/ai-build-plan.js";
18
+ import { AI_JOB_CREATE_KINDS, AI_JOB_CREATE_NOUNS, aiJobOtherCreations } from "../model/ai-job-creations.js";
19
+ import { aiPageCreationRefusal } from "../model/ai-page-job.js";
20
+ import { aiPlanCapabilitiesForJob, aiPlanUncreatable } from "../model/ai-plan-capabilities.js";
21
+ import { aiModelForStep } from "../providers/routing.js";
22
+ import { readAiPlanCapabilities } from "./ai-job-drafts.js";
23
+ import { aiConfirmedPlan, aiUnspentOutcome } from "./ai-job-generation.js";
24
+ import { aiCreationUnit, aiRunJobUnit, aiSiteBuiltRefs, aiSitePendingUnits, aiSiteResolvedRef, aiSiteUnitJob } from "./ai-job-site-step.js";
25
+ import { aiJobStepRunMinimumMs, aiJobStepRunnerFor } from "./ai-jobs.js";
26
+ /**
27
+ * A template, layout or component job builds the rest of its plan before its
28
+ * own record (AGL-3143 §15), through the machinery a page job builds its
29
+ * creations with (AGL-3031): one creation a pass, handed to the step that
30
+ * builds that kind under a job derived from this one, where the job stands
31
+ * read from its outputs. Once every creation is built, the kind's own step
32
+ * runs on the plan with each `new:<name>` resolved to the record that was
33
+ * built and listed among what the plan reuses — so the template's reuse check
34
+ * holds it to placing the component its plan created, exactly as it holds it
35
+ * to one the site always had.
36
+ *
37
+ * What each kind builds is `AI_JOB_CREATE_KINDS`; a form or an email design
38
+ * places nothing another job builds, so its plan is only held to that list.
39
+ */ /** A creation the plan names that this deployment has no step to build. */ export const AI_JOB_CREATION_UNAVAILABLE_COPY = 'This needs something that cannot be built here yet. Describe it again.';
40
+ /** A creation that finished without reporting what it built; the job's own record cannot place it. */ export const AI_JOB_CREATION_EMPTY_COPY = 'Part of this could not be built. Describe it again.';
41
+ /** The order a page job builds creations in: a form before the component that may place it. */ const UNIT_ORDER = [
42
+ 'form',
43
+ 'component'
44
+ ];
45
+ /**
46
+ * The units the creations a job of `kind` builds before its own record are
47
+ * built as, in build order, each addressed by its place in the plan so a unit
48
+ * run again finds its own draft.
49
+ */ export function aiJobCreationUnits(kind, plan) {
50
+ const others = aiJobOtherCreations(kind, plan);
51
+ return UNIT_ORDER.flatMap((order)=>others.flatMap(({ creation, index })=>{
52
+ if (creation.kind !== order) return [];
53
+ const unit = aiCreationUnit(creation, `c${index}`);
54
+ return unit ? [
55
+ unit
56
+ ] : [];
57
+ }));
58
+ }
59
+ /**
60
+ * The job a unit is derived from. A unit places none of the components the
61
+ * plan reuses: those are the job's own record's to place, and a card told to
62
+ * place them would be refused for leaving them out.
63
+ */ function forUnits(job, plan) {
64
+ return _extends({}, job, {
65
+ plan: _extends({}, plan, {
66
+ reuse: plan.reuse.filter((entry)=>entry.kind !== 'component')
67
+ })
68
+ });
69
+ }
70
+ /**
71
+ * The job the kind's own step builds its record under once every creation is
72
+ * built: the plan's own creation alone, from a copy resolved to what was
73
+ * built, and every built record reused, with the reason the plan gave for it.
74
+ */ export function aiJobWithBuiltCreations(job, kind, units) {
75
+ var _job_outputs, _plan_labels;
76
+ const plan = job.plan;
77
+ const built = aiSiteBuiltRefs(units, (_job_outputs = job.outputs) != null ? _job_outputs : []);
78
+ const labels = _extends({}, (_plan_labels = plan.labels) != null ? _plan_labels : {});
79
+ const reuse = [
80
+ ...plan.reuse
81
+ ];
82
+ for (const unit of units){
83
+ const record = unit.creation ? built.get(unit.creation.name.toLowerCase()) : undefined;
84
+ if (!record || !unit.creation) continue;
85
+ labels[record.id] = record.label;
86
+ reuse.push({
87
+ kind: record.kind,
88
+ id: record.id,
89
+ purpose: `made for this plan: ${unit.creation.why}`
90
+ });
91
+ }
92
+ const own = plan.create.find((entry)=>entry.kind === kind);
93
+ return _extends({}, job, {
94
+ plan: _extends({}, plan, {
95
+ labels,
96
+ reuse,
97
+ create: own ? [
98
+ _extends({}, own, {
99
+ duplicateOf: aiSiteResolvedRef(own.duplicateOf, built)
100
+ })
101
+ ] : []
102
+ })
103
+ });
104
+ }
105
+ /**
106
+ * The kind's own runner, building the plan's other creations first: one a
107
+ * pass while any is unbuilt, then the kind's own record on the plan they
108
+ * resolve. A plan with nothing else to build runs the kind's own step as it
109
+ * always ran.
110
+ */ export function aiBuildingPlanCreations(kind, step, runner, deps = {}) {
111
+ var _deps_runnerFor;
112
+ const runnerFor = (_deps_runnerFor = deps.runnerFor) != null ? _deps_runnerFor : aiJobStepRunnerFor;
113
+ return async (context)=>{
114
+ var _job_outputs;
115
+ const { job } = context;
116
+ const plan = aiConfirmedPlan(job);
117
+ const units = plan ? aiJobCreationUnits(kind, plan) : [];
118
+ if (!plan || !units.length) return runner(context);
119
+ const [unit] = aiSitePendingUnits(units, (_job_outputs = job.outputs) != null ? _job_outputs : []);
120
+ if (!unit) return runner(_extends({}, context, {
121
+ job: aiJobWithBuiltCreations(job, kind, units)
122
+ }));
123
+ const unitRunner = runnerFor(unit.jobKind);
124
+ if (!unitRunner) {
125
+ var _ref;
126
+ const model = (_ref = context.modelFor == null ? void 0 : context.modelFor.call(context, step)) != null ? _ref : aiModelForStep(step);
127
+ return _extends({}, aiUnspentOutcome(model), {
128
+ failure: AI_JOB_CREATION_UNAVAILABLE_COPY
129
+ });
130
+ }
131
+ const pass = await aiRunJobUnit(_extends({}, context, {
132
+ job: forUnits(job, plan)
133
+ }), {
134
+ unit,
135
+ units,
136
+ runner: unitRunner,
137
+ emptyCopy: AI_JOB_CREATION_EMPTY_COPY
138
+ });
139
+ // A built creation is followed by the next, or by the job's own record.
140
+ return pass.built ? _extends({}, pass.outcome, {
141
+ continue: true
142
+ }) : pass.outcome;
143
+ };
144
+ }
145
+ /**
146
+ * The least time this job's next pass needs (AGL-3035): a pass that builds a
147
+ * creation needs what the step that builds it registers for the job derived
148
+ * for it; the kind's own pass needs what the kind registered.
149
+ */ export function aiPlanCreationsRunMinimumMs(kind) {
150
+ return (job)=>{
151
+ var _job_outputs;
152
+ const plan = aiConfirmedPlan(job);
153
+ if (!plan) return 0;
154
+ const units = aiJobCreationUnits(kind, plan);
155
+ const outputs = (_job_outputs = job.outputs) != null ? _job_outputs : [];
156
+ const [unit] = aiSitePendingUnits(units, outputs);
157
+ if (!unit) return 0;
158
+ return aiJobStepRunMinimumMs(aiSiteUnitJob(forUnits(job, plan), unit, aiSiteBuiltRefs(units, outputs)));
159
+ };
160
+ }
161
+ /**
162
+ * Why a job of `kind` cannot build the creations its plan names, as the door
163
+ * that confirms the plan answers; `null` when it can, or when there is no plan
164
+ * yet. A creation the kind does not build, one this deployment has no step
165
+ * for, and one the workspace may not make on this site now are each refused
166
+ * before a credit is spent, the way a page job's door refuses them.
167
+ */ export async function aiJobPlanCreationsRefusal(kind, context, hostId, deps = {}) {
168
+ var _ref, _deps_runnerFor, _deps_readCapabilities, _AI_JOB_CREATE_NOUNS_kind;
169
+ var _AI_BUILD_PLAN_CREATION_NOUNS_kind;
170
+ const plan = context.plan;
171
+ const creates = AI_JOB_CREATE_KINDS[kind];
172
+ if (!plan || !creates) return null;
173
+ // A plan creating only the job's own record is held by the draft allowance, as it always was.
174
+ const own = plan.create.findIndex((entry)=>entry.kind === kind);
175
+ if (!plan.create.some((_, index)=>index !== own)) return null;
176
+ const noun = (_ref = (_AI_BUILD_PLAN_CREATION_NOUNS_kind = AI_BUILD_PLAN_CREATION_NOUNS[kind]) == null ? void 0 : _AI_BUILD_PLAN_CREATION_NOUNS_kind.noun) != null ? _ref : kind;
177
+ const runnerFor = (_deps_runnerFor = deps.runnerFor) != null ? _deps_runnerFor : aiJobStepRunnerFor;
178
+ if (aiJobCreationUnits(kind, plan).some((unit)=>!runnerFor(unit.jobKind))) {
179
+ return {
180
+ status: 400,
181
+ error: AI_JOB_CREATION_UNAVAILABLE_COPY
182
+ };
183
+ }
184
+ const readCapabilities = (_deps_readCapabilities = deps.readCapabilities) != null ? _deps_readCapabilities : readAiPlanCapabilities;
185
+ const capabilities = aiPlanCapabilitiesForJob(await readCapabilities(context.firestore, {
186
+ hostId,
187
+ org: context.org
188
+ }), {
189
+ noun: (_AI_JOB_CREATE_NOUNS_kind = AI_JOB_CREATE_NOUNS[kind]) != null ? _AI_JOB_CREATE_NOUNS_kind : `a ${kind} job`,
190
+ creates
191
+ });
192
+ const refusal = aiPageCreationRefusal(aiPlanUncreatable(plan, capabilities), noun);
193
+ return refusal ? {
194
+ status: 403,
195
+ error: refusal
196
+ } : null;
197
+ }
198
+
199
+ //# sourceMappingURL=ai-job-plan-creations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-plan-creations.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { AI_BUILD_PLAN_CREATION_NOUNS, type AiBuildPlanCreateKind } from '../model/ai-build-plan'\nimport { AI_JOB_CREATE_KINDS, AI_JOB_CREATE_NOUNS, aiJobOtherCreations } from '../model/ai-job-creations'\nimport type { AiJob, AiJobKind, AiJobPlan } from '../model/ai-jobs.types'\nimport { aiPageCreationRefusal } from '../model/ai-page-job'\nimport { aiPlanCapabilitiesForJob, aiPlanUncreatable } from '../model/ai-plan-capabilities'\nimport type { AiStepKind } from '../providers/catalog'\nimport { aiModelForStep } from '../providers/routing'\nimport type { AiJobAdmissionContext, AiJobAdmissionRefusal } from './ai-job-admission'\nimport { readAiPlanCapabilities } from './ai-job-drafts'\nimport { aiConfirmedPlan, aiUnspentOutcome } from './ai-job-generation'\nimport {\n aiCreationUnit,\n aiRunJobUnit,\n aiSiteBuiltRefs,\n aiSitePendingUnits,\n aiSiteResolvedRef,\n aiSiteUnitJob,\n type AiSiteUnit,\n} from './ai-job-site-step'\nimport type { AiJobStepRunner } from './ai-job-text-step'\nimport { aiJobStepRunMinimumMs, aiJobStepRunnerFor } from './ai-jobs'\n\n/**\n * A template, layout or component job builds the rest of its plan before its\n * own record (AGL-3143 §15), through the machinery a page job builds its\n * creations with (AGL-3031): one creation a pass, handed to the step that\n * builds that kind under a job derived from this one, where the job stands\n * read from its outputs. Once every creation is built, the kind's own step\n * runs on the plan with each `new:<name>` resolved to the record that was\n * built and listed among what the plan reuses — so the template's reuse check\n * holds it to placing the component its plan created, exactly as it holds it\n * to one the site always had.\n *\n * What each kind builds is `AI_JOB_CREATE_KINDS`; a form or an email design\n * places nothing another job builds, so its plan is only held to that list.\n */\n\n/** A creation the plan names that this deployment has no step to build. */\nexport const AI_JOB_CREATION_UNAVAILABLE_COPY =\n 'This needs something that cannot be built here yet. Describe it again.'\n\n/** A creation that finished without reporting what it built; the job's own record cannot place it. */\nexport const AI_JOB_CREATION_EMPTY_COPY = 'Part of this could not be built. Describe it again.'\n\n/** The order a page job builds creations in: a form before the component that may place it. */\nconst UNIT_ORDER: readonly AiBuildPlanCreateKind[] = ['form', 'component']\n\n/**\n * The units the creations a job of `kind` builds before its own record are\n * built as, in build order, each addressed by its place in the plan so a unit\n * run again finds its own draft.\n */\nexport function aiJobCreationUnits(kind: AiJobKind, plan: Pick<AiJobPlan, 'create'>): AiSiteUnit[] {\n const others = aiJobOtherCreations(kind, plan)\n return UNIT_ORDER.flatMap((order) =>\n others.flatMap(({ creation, index }) => {\n if (creation.kind !== order) return []\n const unit = aiCreationUnit(creation, `c${index}`)\n return unit ? [unit] : []\n }),\n )\n}\n\n/**\n * The job a unit is derived from. A unit places none of the components the\n * plan reuses: those are the job's own record's to place, and a card told to\n * place them would be refused for leaving them out.\n */\nfunction forUnits(job: AiJob, plan: AiJobPlan): AiJob {\n return { ...job, plan: { ...plan, reuse: plan.reuse.filter((entry) => entry.kind !== 'component') } }\n}\n\n/**\n * The job the kind's own step builds its record under once every creation is\n * built: the plan's own creation alone, from a copy resolved to what was\n * built, and every built record reused, with the reason the plan gave for it.\n */\nexport function aiJobWithBuiltCreations(job: AiJob, kind: AiJobKind, units: readonly AiSiteUnit[]): AiJob {\n const plan = job.plan as AiJobPlan\n const built = aiSiteBuiltRefs(units, job.outputs ?? [])\n const labels = { ...(plan.labels ?? {}) }\n const reuse = [...plan.reuse]\n for (const unit of units) {\n const record = unit.creation ? built.get(unit.creation.name.toLowerCase()) : undefined\n if (!record || !unit.creation) continue\n labels[record.id] = record.label\n reuse.push({ kind: record.kind, id: record.id, purpose: `made for this plan: ${unit.creation.why}` })\n }\n const own = plan.create.find((entry) => entry.kind === kind)\n return {\n ...job,\n plan: {\n ...plan,\n labels,\n reuse,\n create: own ? [{ ...own, duplicateOf: aiSiteResolvedRef(own.duplicateOf, built) }] : [],\n },\n }\n}\n\nexport interface AiJobPlanCreationsDeps {\n /** The runners a plan's creations are built by; the registry's otherwise. */\n runnerFor?: typeof aiJobStepRunnerFor\n}\n\n/**\n * The kind's own runner, building the plan's other creations first: one a\n * pass while any is unbuilt, then the kind's own record on the plan they\n * resolve. A plan with nothing else to build runs the kind's own step as it\n * always ran.\n */\nexport function aiBuildingPlanCreations(\n kind: AiJobKind,\n step: AiStepKind,\n runner: AiJobStepRunner,\n deps: AiJobPlanCreationsDeps = {},\n): AiJobStepRunner {\n const runnerFor = deps.runnerFor ?? aiJobStepRunnerFor\n return async (context) => {\n const { job } = context\n const plan = aiConfirmedPlan(job)\n const units = plan ? aiJobCreationUnits(kind, plan) : []\n if (!plan || !units.length) return runner(context)\n const [unit] = aiSitePendingUnits(units, job.outputs ?? [])\n if (!unit) return runner({ ...context, job: aiJobWithBuiltCreations(job, kind, units) })\n const unitRunner = runnerFor(unit.jobKind)\n if (!unitRunner) {\n const model = context.modelFor?.(step) ?? aiModelForStep(step)\n return { ...aiUnspentOutcome(model), failure: AI_JOB_CREATION_UNAVAILABLE_COPY }\n }\n const pass = await aiRunJobUnit(\n { ...context, job: forUnits(job, plan) },\n { unit, units, runner: unitRunner, emptyCopy: AI_JOB_CREATION_EMPTY_COPY },\n )\n // A built creation is followed by the next, or by the job's own record.\n return pass.built ? { ...pass.outcome, continue: true } : pass.outcome\n }\n}\n\n/**\n * The least time this job's next pass needs (AGL-3035): a pass that builds a\n * creation needs what the step that builds it registers for the job derived\n * for it; the kind's own pass needs what the kind registered.\n */\nexport function aiPlanCreationsRunMinimumMs(kind: AiJobKind): (job: AiJob) => number {\n return (job) => {\n const plan = aiConfirmedPlan(job)\n if (!plan) return 0\n const units = aiJobCreationUnits(kind, plan)\n const outputs = job.outputs ?? []\n const [unit] = aiSitePendingUnits(units, outputs)\n if (!unit) return 0\n return aiJobStepRunMinimumMs(aiSiteUnitJob(forUnits(job, plan), unit, aiSiteBuiltRefs(units, outputs)))\n }\n}\n\n/**\n * Why a job of `kind` cannot build the creations its plan names, as the door\n * that confirms the plan answers; `null` when it can, or when there is no plan\n * yet. A creation the kind does not build, one this deployment has no step\n * for, and one the workspace may not make on this site now are each refused\n * before a credit is spent, the way a page job's door refuses them.\n */\nexport async function aiJobPlanCreationsRefusal(\n kind: AiJobKind,\n context: Pick<AiJobAdmissionContext, 'firestore' | 'org' | 'plan'>,\n hostId: string,\n deps: AiJobPlanCreationsDeps & { readCapabilities?: typeof readAiPlanCapabilities } = {},\n): Promise<AiJobAdmissionRefusal | null> {\n const plan = context.plan\n const creates = AI_JOB_CREATE_KINDS[kind]\n if (!plan || !creates) return null\n // A plan creating only the job's own record is held by the draft allowance, as it always was.\n const own = plan.create.findIndex((entry) => entry.kind === kind)\n if (!plan.create.some((_, index) => index !== own)) return null\n const noun = AI_BUILD_PLAN_CREATION_NOUNS[kind as AiBuildPlanCreateKind]?.noun ?? kind\n const runnerFor = deps.runnerFor ?? aiJobStepRunnerFor\n if (aiJobCreationUnits(kind, plan).some((unit) => !runnerFor(unit.jobKind))) {\n return { status: 400, error: AI_JOB_CREATION_UNAVAILABLE_COPY }\n }\n const readCapabilities = deps.readCapabilities ?? readAiPlanCapabilities\n const capabilities = aiPlanCapabilitiesForJob(\n await readCapabilities(context.firestore, {\n hostId,\n org: context.org as Partial<AglynOrgBilling> | null,\n }),\n { noun: AI_JOB_CREATE_NOUNS[kind] ?? `a ${kind} job`, creates },\n )\n const refusal = aiPageCreationRefusal(aiPlanUncreatable(plan, capabilities), noun)\n return refusal ? { status: 403, error: refusal } : null\n}\n"],"names":["AI_BUILD_PLAN_CREATION_NOUNS","AI_JOB_CREATE_KINDS","AI_JOB_CREATE_NOUNS","aiJobOtherCreations","aiPageCreationRefusal","aiPlanCapabilitiesForJob","aiPlanUncreatable","aiModelForStep","readAiPlanCapabilities","aiConfirmedPlan","aiUnspentOutcome","aiCreationUnit","aiRunJobUnit","aiSiteBuiltRefs","aiSitePendingUnits","aiSiteResolvedRef","aiSiteUnitJob","aiJobStepRunMinimumMs","aiJobStepRunnerFor","AI_JOB_CREATION_UNAVAILABLE_COPY","AI_JOB_CREATION_EMPTY_COPY","UNIT_ORDER","aiJobCreationUnits","kind","plan","others","flatMap","order","creation","index","unit","forUnits","job","reuse","filter","entry","aiJobWithBuiltCreations","units","built","outputs","labels","record","get","name","toLowerCase","undefined","id","label","push","purpose","why","own","create","find","duplicateOf","aiBuildingPlanCreations","step","runner","deps","runnerFor","context","length","unitRunner","jobKind","model","modelFor","failure","pass","emptyCopy","outcome","continue","aiPlanCreationsRunMinimumMs","aiJobPlanCreationsRefusal","hostId","creates","findIndex","some","_","noun","status","error","readCapabilities","capabilities","firestore","org","refusal"],"mappings":"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"}
@@ -97,6 +97,11 @@ export declare function aiJobPlanPrompt(job: Pick<AiJob, 'kind' | 'brief' | 'inp
97
97
  * of the Free page's 300-credit wall, taking the room it keeps for a re-asked
98
98
  * section from 45 credits to 44 and breaking `ai-job-free-page.spec.ts`. A
99
99
  * page job must not pay for a sentence about templates.
100
+ *
101
+ * The list is the tokens a reader sees, never the whole catalog (AGL-3143
102
+ * §16). Offered a slug and the publish timestamp, a live plan promised them,
103
+ * and its build kept the promise the only way a slug can be kept: it printed
104
+ * "entry slug: {{entry.slug}}" on every article.
100
105
  */
101
106
  export declare function aiPlanTemplateTokenLines(job: Pick<AiJob, 'kind' | 'inputs'>): string[];
102
107
  /** A kind that builds only some plans: the creations it makes, and the shapes it refuses. */
@@ -107,8 +112,9 @@ export interface AiJobPlanScope extends AiPlanJobScope {
107
112
  /**
108
113
  * The kinds whose confirm door builds only some plans (AGL-3030). A page job
109
114
  * builds one screen and a scaffold four to eight; each builds only the
110
- * creations its list names. Every other planned kind builds its own one
111
- * output, and its plan is held to the workspace alone.
115
+ * creations its list names. A template, layout, component, form or email job
116
+ * builds its own record and what that record places (AGL-3143 §15,
117
+ * `AI_JOB_CREATE_KINDS`), and holds no plan to a shape.
112
118
  */
113
119
  export declare const AI_JOB_PLAN_SCOPES: Readonly<Partial<Record<AiJobKind, AiJobPlanScope>>>;
114
120
  /**
@@ -16,7 +16,8 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
16
16
  * limitations under the License.
17
17
  */ import { createHash } from "node:crypto";
18
18
  import { AI_BUILD_PLAN_TOOL, aiBuildPlanToolFor, isAiPlanNewRef } from "../model/ai-build-plan.js";
19
- import { AI_TEMPLATE_SUBJECT_DEFINITIONS } from "../model/ai-template-subjects.js";
19
+ import { AI_TEMPLATE_SUBJECT_DEFINITIONS, aiTemplateShownTokens } from "../model/ai-template-subjects.js";
20
+ import { AI_JOB_CREATE_KINDS, AI_JOB_CREATE_NOUNS } from "../model/ai-job-creations.js";
20
21
  import { AI_PAGE_CREATE_KINDS, aiPagePlanShapeRefusal } from "../model/ai-page-job.js";
21
22
  import { aiPlanCapabilitiesForJob, aiPlanCapabilityLines } from "../model/ai-plan-capabilities.js";
22
23
  import { AI_SITE_CREATE_KINDS, aiSitePlanShapeRefusal } from "../model/ai-site-job.js";
@@ -121,6 +122,11 @@ import { AI_JOBS_COLLECTION, registerAiJobPlanStep } from "./ai-jobs.js";
121
122
  * of the Free page's 300-credit wall, taking the room it keeps for a re-asked
122
123
  * section from 45 credits to 44 and breaking `ai-job-free-page.spec.ts`. A
123
124
  * page job must not pay for a sentence about templates.
125
+ *
126
+ * The list is the tokens a reader sees, never the whole catalog (AGL-3143
127
+ * §16). Offered a slug and the publish timestamp, a live plan promised them,
128
+ * and its build kept the promise the only way a slug can be kept: it printed
129
+ * "entry slug: {{entry.slug}}" on every article.
124
130
  */ export function aiPlanTemplateTokenLines(job) {
125
131
  var _job_inputs;
126
132
  if (job.kind !== 'template') return [];
@@ -128,15 +134,16 @@ import { AI_JOBS_COLLECTION, registerAiJobPlanStep } from "./ai-jobs.js";
128
134
  const definition = typeof subject === 'string' ? AI_TEMPLATE_SUBJECT_DEFINITIONS[subject] : undefined;
129
135
  if (!definition) return [];
130
136
  return [
131
- `The template's fields are the binding tokens its page shows, each one of: ${definition.tokens.map((entry)=>entry.token).join(', ')}. Promise only what the page fills.`
137
+ `The template's fields are the binding tokens its page shows a reader, each one of: ${aiTemplateShownTokens(definition).join(', ')}. A link or picture token is shown by the link or picture holding it. Promise only what the page fills.`
132
138
  ];
133
139
  }
134
140
  /**
135
141
  * The kinds whose confirm door builds only some plans (AGL-3030). A page job
136
142
  * builds one screen and a scaffold four to eight; each builds only the
137
- * creations its list names. Every other planned kind builds its own one
138
- * output, and its plan is held to the workspace alone.
139
- */ export const AI_JOB_PLAN_SCOPES = {
143
+ * creations its list names. A template, layout, component, form or email job
144
+ * builds its own record and what that record places (AGL-3143 §15,
145
+ * `AI_JOB_CREATE_KINDS`), and holds no plan to a shape.
146
+ */ export const AI_JOB_PLAN_SCOPES = _extends({
140
147
  page: {
141
148
  noun: 'a page job',
142
149
  creates: AI_PAGE_CREATE_KINDS,
@@ -147,7 +154,14 @@ import { AI_JOBS_COLLECTION, registerAiJobPlanStep } from "./ai-jobs.js";
147
154
  creates: AI_SITE_CREATE_KINDS,
148
155
  shapeRefusal: aiSitePlanShapeRefusal
149
156
  }
150
- };
157
+ }, Object.fromEntries(Object.entries(AI_JOB_CREATE_KINDS).map(([kind, creates])=>[
158
+ kind,
159
+ {
160
+ noun: AI_JOB_CREATE_NOUNS[kind],
161
+ creates,
162
+ shapeRefusal: ()=>null
163
+ }
164
+ ])));
151
165
  /**
152
166
  * What the plan step holds beyond the doctrine's plan rules, as the
153
167
  * violations the plan's one re-ask names: the players the plan lists against