@aglyn/plugins-ai 1.0.0-beta.227 → 1.0.0-beta.228

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 (92) hide show
  1. package/README.md +1 -1
  2. package/package.json +17 -17
  3. package/src/lib/components/ai-brief-dialog.component.js +1 -1
  4. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  5. package/src/lib/components/ai-describe-automation.component.js +7 -2
  6. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  7. package/src/lib/components/ai-describe-button.component.js +6 -1
  8. package/src/lib/components/ai-describe-button.component.js.map +1 -1
  9. package/src/lib/components/ai-describe-page.component.d.ts +1 -1
  10. package/src/lib/components/ai-describe-page.component.js +1 -1
  11. package/src/lib/components/ai-describe-page.component.js.map +1 -1
  12. package/src/lib/components/ai-job-events.d.ts +7 -1
  13. package/src/lib/components/ai-job-events.js +4 -2
  14. package/src/lib/components/ai-job-events.js.map +1 -1
  15. package/src/lib/components/ai-job-site.d.ts +22 -0
  16. package/src/lib/components/ai-job-site.js +73 -0
  17. package/src/lib/components/ai-job-site.js.map +1 -0
  18. package/src/lib/components/ai-jobs-list-page.component.d.ts +23 -0
  19. package/src/lib/components/ai-jobs-list-page.component.js +303 -0
  20. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -0
  21. package/src/lib/components/ai-jobs-page.component.d.ts +7 -0
  22. package/src/lib/components/ai-jobs-page.component.js +30 -0
  23. package/src/lib/components/ai-jobs-page.component.js.map +1 -0
  24. package/src/lib/components/ai-page-brief-dialog.component.d.ts +1 -1
  25. package/src/lib/components/ai-page-brief-dialog.component.js.map +1 -1
  26. package/src/lib/components/ai-site-build-page.component.d.ts +30 -2
  27. package/src/lib/components/ai-site-build-page.component.js +359 -241
  28. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  29. package/src/lib/components/ai-site-start-card.component.d.ts +8 -0
  30. package/src/lib/components/ai-site-start-card.component.js +20 -11
  31. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  32. package/src/lib/components/assist-panel.component.js +9 -4
  33. package/src/lib/components/assist-panel.component.js.map +1 -1
  34. package/src/lib/jobs/ai-job-draft-ids.d.ts +6 -0
  35. package/src/lib/jobs/ai-job-draft-ids.js +9 -0
  36. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  37. package/src/lib/jobs/ai-job-drafts.d.ts +7 -0
  38. package/src/lib/jobs/ai-job-drafts.js +16 -7
  39. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  40. package/src/lib/jobs/ai-job-layout-step.js +2 -1
  41. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  42. package/src/lib/jobs/ai-job-page-sections.d.ts +6 -5
  43. package/src/lib/jobs/ai-job-page-sections.js +3 -3
  44. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  45. package/src/lib/jobs/ai-job-page-step.d.ts +19 -1
  46. package/src/lib/jobs/ai-job-page-step.js +50 -8
  47. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  48. package/src/lib/jobs/ai-job-plan-step.js +7 -2
  49. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  50. package/src/lib/jobs/ai-job-site-step.d.ts +21 -0
  51. package/src/lib/jobs/ai-job-site-step.js +96 -17
  52. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  53. package/src/lib/jobs/ai-job-text-step.d.ts +7 -1
  54. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  55. package/src/lib/jobs/ai-jobs.d.ts +9 -1
  56. package/src/lib/jobs/ai-jobs.js +9 -2
  57. package/src/lib/jobs/ai-jobs.js.map +1 -1
  58. package/src/lib/jobs/ai-site-publish.d.ts +98 -0
  59. package/src/lib/jobs/ai-site-publish.js +372 -0
  60. package/src/lib/jobs/ai-site-publish.js.map +1 -0
  61. package/src/lib/jobs/fixtures/ai-page-briefs.d.ts +7 -6
  62. package/src/lib/jobs/fixtures/ai-page-briefs.js +23 -56
  63. package/src/lib/jobs/fixtures/ai-page-briefs.js.map +1 -1
  64. package/src/lib/model/ai-job-notice.d.ts +3 -1
  65. package/src/lib/model/ai-job-notice.js +18 -1
  66. package/src/lib/model/ai-job-notice.js.map +1 -1
  67. package/src/lib/model/ai-jobs.types.d.ts +27 -0
  68. package/src/lib/model/ai-jobs.types.js.map +1 -1
  69. package/src/lib/model/ai-plan-capabilities.d.ts +25 -10
  70. package/src/lib/model/ai-plan-capabilities.js +15 -2
  71. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  72. package/src/lib/model/ai-site-build-progress.d.ts +17 -1
  73. package/src/lib/model/ai-site-build-progress.js +64 -4
  74. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  75. package/src/lib/model/ai-site-start-seo.d.ts +19 -0
  76. package/src/lib/model/ai-site-start-seo.js +91 -3
  77. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  78. package/src/lib/plugin.js +18 -10
  79. package/src/lib/plugin.js.map +1 -1
  80. package/src/lib/runtime/ai-doctrine-validators.d.ts +16 -12
  81. package/src/lib/runtime/ai-doctrine-validators.js +19 -36
  82. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  83. package/src/lib/runtime/ai-doctrine.js +1 -1
  84. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  85. package/src/lib/runtime/ai-eval-live.d.ts +1 -1
  86. package/src/lib/runtime/ai-eval-live.js +1 -1
  87. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  88. package/src/lib/server/ai-jobs-live-sites.d.ts +26 -0
  89. package/src/lib/server/ai-jobs-live-sites.js +58 -0
  90. package/src/lib/server/ai-jobs-live-sites.js.map +1 -0
  91. package/src/lib/server/ai-jobs-route.js +22 -3
  92. package/src/lib/server/ai-jobs-route.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-drafts.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 { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { uniqueDuplicateName } from '@aglyn/aglyn/app-utils/duplicate-resource'\nimport {\n FORM_COMPONENT_ID,\n newFormListFields,\n normalizeFormSlug,\n type FormFieldDecl,\n type FormRouting,\n} from '@aglyn/aglyn/app-utils/forms'\nimport { artifactCreateListKeys } from '@aglyn/aglyn/app-utils/artifact-list-keys'\nimport {\n checkDatasetQuota,\n checkEntitlement,\n checkQuota,\n resolveEffectivePlan,\n resolveOrgEntitlements,\n} from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport {\n billableScreenIds,\n normalizeScreenSlug,\n reservedScreenRouteSegment,\n SCREEN_ROOT_PATH,\n} from '@aglyn/aglyn/app-utils/screen-route'\nimport { decodeStoredNodes, encodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport type {\n AglynOrgBilling,\n OrgFeatureFlags,\n} from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport type { ReusableComponentProp } from '@aglyn/aglyn/foundation/definitions/platform.types'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport {\n aiUnrestrictedPlanCapabilities,\n type AiPlanCapabilities,\n type AiPlanCreation,\n} from '../model/ai-plan-capabilities'\nimport type { AiJobAdmissionRefusal } from './ai-job-admission'\n\n/**\n * Where a generation job's layout, page template, form, reusable component\n * or page lands (AGL-2909, AGL-2913, AGL-2908, AGL-2907): a new draft, made\n * as the console's host resources route makes one.\n *\n * ── The document the create route makes ─────────────────────────────────\n *\n * `AI_DRAFT_FIELDS` is that route's allow-list for each kind, and a spec\n * reads the route's source to hold the two together. Nothing outside the\n * list is written except the route's own stamps: `createdAt`, `updatedAt`,\n * `createdBy`, the list keys every artifact create carries\n * (`artifactCreateListKeys`: the name keys, a component's or template's stored\n * kind, a template's library row), and a template's `source`, which is\n * `authored` — a template a member's job generated is theirs, never a starter\n * or a listing. A layout's or a screen's first version carries the keys the\n * versions route seeds one with. A form is the document the Forms page's\n * Create sends: its design canvas-shaped under `rootId` with the form node\n * bound to the form's id and captioned with its name, a slug read from that\n * name, and no version — the form's page mints the first one when someone\n * opens it. Where Create sends an empty `fields`, a generated form sends the\n * declaration read off its own design, so the two agree from the start. A\n * component carries its tree and the props it declares on its own document,\n * as Use template writes one, and gets its first version where every\n * component does: when a member opens it.\n *\n * ── Counted like a create ────────────────────────────────────────────────\n *\n * The band is met INSIDE the transaction that writes (AGL-2231), with the\n * route's arithmetic: every layout document counts against\n * `sharedLayoutsPerHost`; a template counts against `templatesPerHost` when it\n * records a source other than a platform starter — the route's `!=` query,\n * which never matches a document with no `source.type` at all; a screen counts\n * against `screensPerHost` by `billableScreenIds`, over the screens and the\n * host's routing map, which counts an unrouted screen too (AGL-1445); and a\n * form needs the plan to include the route's `entitlement` first, refused in\n * the route's own words, after which every form document counts against\n * `formsPerHost`. A reusable component counts against no allowance at all:\n * the route admits one on a plan with the `reusableComponents` entitlement\n * and refuses it otherwise, and so does the writer.\n *\n * ── Applied to nothing ───────────────────────────────────────────────────\n *\n * A layout renders only around a screen or layout that names it, or as the\n * built-in page layout the host document names; a template is inert until a\n * member uses it; a form renders only where a page places it; a component\n * renders only where an instance places it, and a new one has no instance;\n * and a screen serves nothing until the host's routing map names it, which is\n * what publishing writes (`publishScreenRoute`). So the writer touches the new\n * documents and nothing else — no host document, no routing map, no\n * collection, store setting, other layout or component — and never a\n * `publishedAt` or a publish schedule. What the job hands back is a link to\n * the draft, never a binding.\n *\n * ── One draft per recorded id ────────────────────────────────────────────\n *\n * The caller names the document id: the console resource id the job recorded\n * for the draft before its first write (`ai-job-draft-ids.ts`). A step run\n * again after its draft was written — a process cut off before the machine\n * recorded the step — reads the same id, finds that draft and reports it,\n * rather than writing a second.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport type AiDraftKind = 'layout' | 'template' | 'form' | 'component' | 'screen'\n\n/** The draft kinds that keep their tree in versions, as the besigner opens them. */\nexport type AiVersionedDraftKind = Extract<AiDraftKind, 'layout' | 'screen'>\n\n/** The console host resources route's allow-list for each kind. */\nexport const AI_DRAFT_FIELDS: Readonly<Record<AiDraftKind, readonly string[]>> = {\n layout: ['displayName', 'description', 'versionId'],\n template: [\n 'kind',\n 'displayName',\n 'description',\n 'placeholders',\n 'nodes',\n 'rootId',\n 'props',\n 'slug',\n 'seo',\n // A component template's own kind (AGL-3287); a job never sends one.\n 'componentKind',\n ],\n form: [\n 'displayName',\n 'slug',\n 'fields',\n 'consentFieldName',\n 'routing',\n 'legacyMatch',\n 'rootId',\n 'nodes',\n ],\n // `kind` marks a reusable email block (AGL-3287); a job makes page\n // components and never sends one.\n component: ['displayName', 'description', 'rootId', 'nodes', 'props', 'kind'],\n // `kind` is on the route's list for the email composer; a page has none, and\n // the writer never sends one.\n screen: ['displayName', 'description', 'slug', 'seo', 'kind', 'versionId'],\n}\n\n/** The keys a layout's first version is seeded with, all on the versions route's list. */\nexport const AI_DRAFT_VERSION_FIELDS: readonly string[] = ['layoutId', 'hostId', 'displayName', 'nodes']\n\n/**\n * The keys a screen's first version is seeded with, all on the versions\n * route's list: the screen it belongs to, the site, the label, the tree, and\n * the layout it renders inside when the plan names one the site has.\n */\nexport const AI_DRAFT_SCREEN_VERSION_FIELDS: readonly string[] = [\n 'screenId',\n 'hostId',\n 'displayName',\n 'nodes',\n 'layoutId',\n]\n\n/** The label on a generated draft's first version, as on one Use template makes. */\nexport const AI_DRAFT_VERSION_NAME = 'Initial version'\n\n/** The route's refusal when the site's plan does not include the kind at all. */\nexport const AI_DRAFT_ENTITLEMENT_REFUSAL = 'This feature is not included in your plan — see Billing'\n\nexport interface AiDraftBand {\n collection: 'layouts' | 'templates' | 'forms' | 'components' | 'screens'\n /** The allowance the route counts the kind against, where it counts one. */\n quotaKey?:\n | 'sharedLayoutsPerHost'\n | 'templatesPerHost'\n | 'formsPerHost'\n | 'screensPerHost'\n /** The feature the plan must include before any document of the kind counts. */\n entitlement?: keyof OrgFeatureFlags\n /** The route's plural label, in the refusal it gives. */\n label: string\n}\n\n/** Each kind's collection, counter, feature and label, as the create route declares them. */\nexport const AI_DRAFT_BANDS: Readonly<Record<AiDraftKind, AiDraftBand>> = {\n layout: { collection: 'layouts', quotaKey: 'sharedLayoutsPerHost', label: 'shared layouts' },\n template: { collection: 'templates', quotaKey: 'templatesPerHost', label: 'templates' },\n form: {\n collection: 'forms',\n quotaKey: 'formsPerHost',\n entitlement: 'reusableComponents',\n label: 'forms',\n },\n component: {\n collection: 'components',\n entitlement: 'reusableComponents',\n label: 'reusable components',\n },\n screen: { collection: 'screens', quotaKey: 'screensPerHost', label: 'pages' },\n}\n\ninterface SiblingRow {\n id: string\n name: string\n slug: string\n kind: unknown\n sourceType: unknown\n deletedAt: unknown\n deleted: boolean\n}\n\n/** A host's routing map: screen id → the path it is published at. */\ntype RoutingMap = Record<string, unknown> | null | undefined\n\nfunction draftCollection(firestore: Firestore, hostId: string, kind: AiDraftKind) {\n return firestore.collection('hosts').doc(hostId).collection(AI_DRAFT_BANDS[kind].collection)\n}\n\n/** The fields the band, the name, the address and a deletion are read from. */\nfunction siblingsOf(collection: FirebaseFirestore.CollectionReference) {\n return collection.select('displayName', 'slug', 'kind', 'source.type', 'deletedAt')\n}\n\nfunction siblingRows(snapshot: FirebaseFirestore.QuerySnapshot): SiblingRow[] {\n return snapshot.docs.map((doc) => ({\n id: doc.id,\n name: String(doc.get('displayName') ?? ''),\n slug: String(doc.get('slug') ?? ''),\n kind: doc.get('kind'),\n sourceType: doc.get('source.type'),\n deletedAt: doc.get('deletedAt'),\n deleted: doc.get('deletedAt') != null,\n }))\n}\n\nfunction subdomainOf(host: FirebaseFirestore.DocumentSnapshot): string | null {\n const subdomain = host.get('subdomain')\n return typeof subdomain === 'string' && subdomain ? subdomain : null\n}\n\n/**\n * The route's refusal when the site's plan does not include the kind, or\n * holds no more of it; `null` while it does. The feature is asked first, as\n * the route asks it before it counts.\n */\nexport function aiDraftBandRefusal(\n kind: AiDraftKind,\n rows: ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'>>,\n org: Partial<AglynOrgBilling> | null,\n routingMap?: RoutingMap,\n): string | null {\n const band = AI_DRAFT_BANDS[kind]\n if (band.entitlement && !checkEntitlement(org, band.entitlement)) {\n return AI_DRAFT_ENTITLEMENT_REFUSAL\n }\n if (!band.quotaKey) return null\n const used =\n kind === 'template'\n ? rows.filter((row) => row.sourceType !== undefined && row.sourceType !== 'starter').length\n : kind === 'screen'\n ? billableScreenIds(\n rows.map((row) => ({ id: row.id, kind: row.kind, deletedAt: row.deletedAt })),\n (routingMap ?? undefined) as never,\n ).size\n : rows.length\n const quota = checkQuota(org, band.quotaKey, used)\n return quota.allowed\n ? null\n : `Your plan includes ${quota.limit} ${band.label} — upgrade in Billing for more`\n}\n\n/** The draft kinds a plan's creations land as, whose band a plan is told about (AGL-3030). */\nconst AI_PLAN_DRAFT_KINDS = ['component', 'form', 'layout', 'template'] as const\n\ntype AiPlanDraftKind = (typeof AI_PLAN_DRAFT_KINDS)[number]\n\n/** Every draft kind's plural label made singular, for a band of one. */\nfunction bandLabel(label: string, count: number): string {\n return count === 1 ? label.replace(/s$/, '') : label\n}\n\n/**\n * Whether the site may take `used + 1` of a kind, in the create route's\n * arithmetic: the feature first, then the allowance. `null` for a kind whose\n * allowance is unlimited or counts nothing, which needs no rows read.\n */\nfunction planCreation(\n kind: AiPlanDraftKind,\n org: Partial<AglynOrgBilling> | null,\n rows: ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'>> | null,\n): AiPlanCreation {\n const band = AI_DRAFT_BANDS[kind]\n if (band.entitlement && !checkEntitlement(org, band.entitlement)) {\n return {\n allowed: false,\n left: 0,\n reason: `this workspace's plan does not include ${kind === 'form' ? 'saved forms' : band.label}`,\n }\n }\n const limit = band.quotaKey ? resolveOrgEntitlements(org)[band.quotaKey] : Number.POSITIVE_INFINITY\n if (!rows || !Number.isFinite(limit)) return { allowed: true, left: null, reason: null }\n // The route's own arithmetic decides; the count beside it is what is left.\n const refusal = aiDraftBandRefusal(kind, rows, org)\n const used =\n kind === 'template'\n ? rows.filter((row) => row.sourceType !== undefined && row.sourceType !== 'starter').length\n : rows.length\n return refusal\n ? {\n allowed: false,\n left: 0,\n reason: `this site already holds the ${limit} ${bandLabel(band.label, limit)} its plan includes`,\n }\n : { allowed: true, left: Math.max(0, limit - used), reason: null }\n}\n\n/** Whether a kind's rows are counted at all on this plan: included, and under a finite allowance. */\nfunction countsRows(kind: AiPlanDraftKind, org: Partial<AglynOrgBilling> | null): boolean {\n const band = AI_DRAFT_BANDS[kind]\n if (!band.quotaKey) return false\n if (band.entitlement && !checkEntitlement(org, band.entitlement)) return false\n return Number.isFinite(resolveOrgEntitlements(org)[band.quotaKey])\n}\n\n/**\n * What a plan may create on this site (AGL-3030), from the workspace's plan\n * and the rows each counted kind already holds — pure, so the specs and the\n * eval harness hold the arithmetic without a Firestore. A kind whose rows\n * are not given is counted as having room, which is what an unlimited\n * allowance means.\n *\n * A theme change is a proposal a member applies, never a draft, so it counts\n * against nothing. A dataset is org data that no job writes; a plan may name\n * one only where the workspace's plan includes datasets at all. An email\n * design is written by the email plugin, whose own gate decides.\n *\n * A workspace on the Free plan spends the Free taste, a wall of monthly\n * credits, so its plan is held to the sections that wall pays for (AGL-3070).\n * The effective plan decides, as it decides the taste's own allowance: a\n * workspace whose subscription died is Free again.\n */\nexport function aiPlanCapabilitiesFrom(\n org: Partial<AglynOrgBilling> | null,\n rows: Partial<Record<AiPlanDraftKind, ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'>>>> = {},\n): AiPlanCapabilities {\n const unrestricted = aiUnrestrictedPlanCapabilities()\n const datasets = checkDatasetQuota(org, 0).limit > 0\n return {\n reusableComponents: checkEntitlement(org, 'reusableComponents'),\n create: {\n ...unrestricted.create,\n component: planCreation('component', org, rows.component ?? null),\n form: planCreation('form', org, rows.form ?? null),\n layout: planCreation('layout', org, rows.layout ?? null),\n template: planCreation('template', org, rows.template ?? null),\n dataset: datasets\n ? unrestricted.create.dataset\n : { allowed: false, left: 0, reason: \"this workspace's plan does not include datasets\" },\n },\n ...(resolveEffectivePlan(org) === 'free' ? { freeTaste: true } : {}),\n }\n}\n\n/**\n * What a plan may create on this site, read: the rows of each kind whose\n * allowance the workspace's plan counts, as the draft writer reads them, and\n * nothing for a kind the plan leaves out or leaves unlimited.\n */\nexport async function readAiPlanCapabilities(\n firestore: Firestore,\n input: { hostId: string; org: Partial<AglynOrgBilling> | null },\n): Promise<AiPlanCapabilities> {\n const counted = AI_PLAN_DRAFT_KINDS.filter((kind) => countsRows(kind, input.org))\n const snapshots = await Promise.all(\n counted.map((kind) => siblingsOf(draftCollection(firestore, input.hostId, kind)).get()),\n )\n return aiPlanCapabilitiesFrom(\n input.org,\n Object.fromEntries(counted.map((kind, index) => [kind, siblingRows(snapshots[index])])),\n )\n}\n\n/**\n * Whether the site can take one more draft of the kind: a read, for a door\n * that asks before it spends. The write asks again inside its transaction.\n */\nexport async function aiDraftAllowanceRefusal(\n firestore: Firestore,\n input: { kind: AiDraftKind; hostId: string; org: Partial<AglynOrgBilling> | null },\n): Promise<string | null> {\n const [snapshot, host] = await Promise.all([\n siblingsOf(draftCollection(firestore, input.hostId, input.kind)).get(),\n input.kind === 'screen'\n ? firestore.collection('hosts').doc(input.hostId).get()\n : Promise.resolve(null),\n ])\n return aiDraftBandRefusal(\n input.kind,\n siblingRows(snapshot),\n input.org,\n host?.get('screens') as RoutingMap,\n )\n}\n\n/**\n * A draft screen's one-segment address: the one asked for, else one from its\n * name, never a reserved segment, and never an address a live screen keeps\n * or the routing map already serves — so the member's publish at it later\n * takes nothing from a live page. The root is asked for only while no screen\n * holds it.\n *\n * Except the placeholder home page a site is created with (AGL-3408), which\n * holds `/` only until a real home page is published there: the guided start\n * is offered to exactly the sites that still have it, and the home page it\n * drafts has to be able to ask for the root. Publishing it replaces the\n * placeholder in the same write.\n */\nexport function aiDraftScreenSlug(\n requested: string | null | undefined,\n name: string,\n rows: ReadonlyArray<Pick<SiblingRow, 'slug' | 'deleted'> & { id?: string }>,\n routingMap: RoutingMap,\n defaultHomeScreenId?: string | null,\n): string {\n const placeholder = (id: string | undefined, path: unknown) =>\n Boolean(defaultHomeScreenId) && id === defaultHomeScreenId && path === SCREEN_ROOT_PATH\n const taken = new Set<string>([\n ...rows\n .filter((row) => !row.deleted && row.slug && !placeholder(row.id, row.slug))\n .map((row) => row.slug),\n ...Object.entries(routingMap ?? {})\n .filter(([id, path]) => typeof path === 'string' && !placeholder(id, path))\n .map(([, path]) => path as string),\n ])\n const usable = (value: string | undefined) =>\n value && !reservedScreenRouteSegment(value) && !(value === SCREEN_ROOT_PATH && taken.has(value))\n ? value\n : undefined\n const base = usable(normalizeScreenSlug(requested ?? undefined)) ?? usable(normalizeScreenSlug(name)) ?? 'page'\n if (base === SCREEN_ROOT_PATH) return base\n let slug = base\n let attempt = 2\n while (taken.has(slug)) slug = `${base}-${attempt++}`\n return slug\n}\n\nexport interface AiDraftRecord {\n id: string\n /** A layout's or a screen's first version; `null` for every other kind, which the writer gives none. */\n versionId: string | null\n name: string\n hostSubdomain: string | null\n /** A screen's search listing as the draft holds it now; absent on other kinds. */\n seo?: { title?: string; description?: string } | null\n /** Whether a member deleted the draft since it was written. */\n deleted?: boolean\n}\n\nfunction recordOf(\n id: string,\n draft: FirebaseFirestore.DocumentSnapshot,\n host: FirebaseFirestore.DocumentSnapshot,\n): AiDraftRecord {\n const versionId = draft.get('versionId')\n const seo = draft.get('seo')\n return {\n id,\n versionId: typeof versionId === 'string' && versionId ? versionId : null,\n name: String(draft.get('displayName') ?? ''),\n hostSubdomain: subdomainOf(host),\n ...(seo && typeof seo === 'object' ? { seo: seo as AiDraftRecord['seo'] } : {}),\n ...(draft.get('deletedAt') != null ? { deleted: true } : {}),\n }\n}\n\n/** The draft a job already wrote under `id`, when a run of its step got that far. */\nexport async function readAiDraft(\n firestore: Firestore,\n input: { kind: AiDraftKind; hostId: string; id: string },\n): Promise<AiDraftRecord | null> {\n const draft = await draftCollection(firestore, input.hostId, input.kind).doc(input.id).get()\n if (!draft.exists) return null\n return recordOf(input.id, draft, await firestore.collection('hosts').doc(input.hostId).get())\n}\n\n/**\n * The tree a template draft holds now, decoded; `null` when there is none.\n *\n * A template is not a versioned draft — its nodes live on the document\n * itself, which is why `readAiDraftNodes` refuses its kind.\n */\nexport async function readAiTemplateDraftNodes(\n firestore: Firestore,\n input: { hostId: string; id: string },\n): Promise<NodesMap | null> {\n const draft = await draftCollection(firestore, input.hostId, 'template').doc(input.id).get()\n if (!draft.exists) return null\n return decodeStoredNodes<NodesMap>(draft.get('nodes')) ?? null\n}\n\n/**\n * The property names a component draft declares now; `null` when there is no\n * such draft. Read from the draft document rather than its version, because a\n * component's props live beside its tree and a copy carries its source's.\n */\nexport async function readAiDraftComponentProps(\n firestore: Firestore,\n input: { hostId: string; id: string },\n): Promise<string[] | null> {\n const draft = await draftCollection(firestore, input.hostId, 'component').doc(input.id).get()\n if (!draft.exists) return null\n const props = draft.get('props')\n if (!Array.isArray(props)) return []\n return props\n .map((prop) => (prop && typeof prop === 'object' ? (prop as Record<string, unknown>)['name'] : null))\n .filter((name): name is string => typeof name === 'string' && name.length > 0)\n}\n\n/** The tree a versioned draft's first version holds now, decoded; `null` when there is none. */\nexport async function readAiDraftNodes(\n firestore: Firestore,\n input: { kind: AiVersionedDraftKind; hostId: string; id: string },\n): Promise<{ versionId: string; nodes: NodesMap } | null> {\n const draftRef = draftCollection(firestore, input.hostId, input.kind).doc(input.id)\n const draft = await draftRef.get()\n const versionId = draft.exists ? draft.get('versionId') : null\n if (typeof versionId !== 'string' || !versionId) return null\n const version = await draftRef.collection('versions').doc(versionId).get()\n const nodes = version.exists ? decodeStoredNodes<NodesMap>(version.get('nodes')) : null\n return nodes ? { versionId, nodes } : null\n}\n\nexport interface AiDraftInput {\n kind: AiDraftKind\n hostId: string\n /** The draft's document id: the id the job recorded for this draft. */\n id: string\n /** Whose work the draft is: the job's creator, whose brief it built. */\n uid: string\n org: Partial<AglynOrgBilling> | null\n /** The name asked for; a live sibling already called that gets a number. */\n name: string\n nodes: NodesMap\n /** A page template's suggested address, or a screen's one-segment address. */\n slug?: string | null\n /** A form's declaration and the ids its design hangs from; required for a form. */\n form?: AiFormDraftDeclaration\n /** A component's root, which the node map holds; required for a component. */\n rootId?: string\n /** The properties a component declares, which its tree binds. */\n props?: readonly ReusableComponentProp[]\n /** A screen's layout, for its first version to render inside; a layout the site has. */\n layoutId?: string | null\n now: Date\n}\n\n/** What a form draft declares beside its design, read off that design. */\nexport interface AiFormDraftDeclaration {\n /** The canvas root `nodes` is stored under, as the Forms page's Create stores it. */\n rootId: string\n /** The form node, which carries the form's id and is captioned with the draft's name. */\n formNodeId: string\n fields: FormFieldDecl[]\n consentFieldName: string | null\n routing: FormRouting | null\n}\n\nexport type AiDraftWrite =\n | (AiDraftRecord & { ok: true; replayed: boolean })\n | { ok: false; status: 403 | 404; error: string }\n\n/** Keep only the keys the create route admits for the kind. */\nfunction allowListed(kind: AiDraftKind, data: Record<string, unknown>): Record<string, unknown> {\n const allowed = new Set(AI_DRAFT_FIELDS[kind])\n return Object.fromEntries(\n Object.entries(data).filter(([key, value]) => allowed.has(key) && value !== undefined),\n )\n}\n\n/** The node map with its form node captioned with `name`, as Create captions a new form. */\nfunction withFormCaption(nodes: NodesMap, formNodeId: string, name: string): NodesMap {\n const node = nodes[formNodeId]\n if (!node || node.componentId !== FORM_COMPONENT_ID) return nodes\n return { ...nodes, [formNodeId]: { ...node, props: { ...(node.props ?? {}), formName: name } } }\n}\n\n/**\n * Write one draft: the band met, the name made unique among the live\n * siblings, the node map stored as msgpack, all in one transaction. A\n * refusal is returned, never thrown; a Firestore failure propagates, so the\n * machine's retry applies.\n */\nexport async function writeAiDraft(firestore: Firestore, input: AiDraftInput): Promise<AiDraftWrite> {\n const packed = encodeStoredNodes(input.nodes)\n if (!packed) throw new Error('an AI draft needs a node map')\n if (input.kind === 'form' && !input.form) throw new Error('an AI form draft needs its declaration')\n if (input.kind === 'component' && !(input.rootId && input.nodes[input.rootId])) {\n throw new Error('a component draft needs a root its node map holds')\n }\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const collection = draftCollection(firestore, input.hostId, input.kind)\n const draftRef = collection.doc(input.id)\n return firestore.runTransaction(async (tx): Promise<AiDraftWrite> => {\n // Every read before any write, which Firestore requires.\n const [host, existing, siblings] = await Promise.all([\n tx.get(hostRef),\n tx.get(draftRef),\n tx.get(siblingsOf(collection)),\n ])\n if (!host.exists) return { ok: false, status: 404, error: 'Unknown site' }\n if (existing.exists) {\n return { ok: true, replayed: true, ...recordOf(input.id, existing, host) }\n }\n const rows = siblingRows(siblings)\n // Read, never written: the routing map decides which screens count.\n const routingMap = host.get('screens') as RoutingMap\n const refusal = aiDraftBandRefusal(input.kind, rows, input.org, routingMap)\n if (refusal) return { ok: false, status: 403, error: refusal }\n const name = uniqueDuplicateName(\n input.name,\n rows.filter((row) => !row.deleted).map((row) => row.name),\n )\n const stamps = { createdAt: input.now, updatedAt: input.now, createdBy: input.uid }\n const nodes = Buffer.from(packed)\n let versionId: string | null = null\n if (input.kind === 'layout') {\n versionId = createResourceUid()\n tx.create(draftRef, {\n ...allowListed('layout', { displayName: name, versionId }),\n ...artifactCreateListKeys('layouts', { displayName: name }),\n ...stamps,\n })\n tx.create(draftRef.collection('versions').doc(versionId), {\n layoutId: input.id,\n hostId: input.hostId,\n displayName: AI_DRAFT_VERSION_NAME,\n nodes,\n ...stamps,\n })\n } else if (input.kind === 'screen') {\n versionId = createResourceUid()\n const slug = aiDraftScreenSlug(\n input.slug,\n name,\n rows,\n routingMap,\n host.get('defaultHomeScreenId'),\n )\n tx.create(draftRef, {\n ...allowListed('screen', { displayName: name, slug, versionId }),\n ...artifactCreateListKeys('screens', { displayName: name }),\n ...stamps,\n })\n tx.create(draftRef.collection('versions').doc(versionId), {\n screenId: input.id,\n hostId: input.hostId,\n displayName: AI_DRAFT_VERSION_NAME,\n nodes,\n ...(input.layoutId ? { layoutId: input.layoutId } : {}),\n ...stamps,\n })\n } else if (input.kind === 'template') {\n tx.create(draftRef, {\n ...allowListed('template', {\n kind: 'page',\n displayName: name,\n nodes,\n slug: input.slug || undefined,\n }),\n source: { type: 'authored' },\n ...artifactCreateListKeys('templates', {\n kind: 'page',\n displayName: name,\n source: { type: 'authored' },\n }),\n ...stamps,\n })\n } else if (input.kind === 'component') {\n tx.create(draftRef, {\n ...allowListed('component', {\n displayName: name,\n rootId: input.rootId,\n nodes,\n props: input.props?.length ? [...input.props] : undefined,\n }),\n ...artifactCreateListKeys('components', { displayName: name }),\n ...stamps,\n })\n } else {\n const form = input.form as AiFormDraftDeclaration\n const captioned = encodeStoredNodes(withFormCaption(input.nodes, form.formNodeId, name)) ?? packed\n const slug = normalizeFormSlug(name) || input.id\n tx.create(draftRef, {\n ...allowListed('form', {\n displayName: name,\n slug,\n fields: form.fields,\n consentFieldName: form.consentFieldName || undefined,\n routing: form.routing ?? undefined,\n rootId: form.rootId,\n nodes: Buffer.from(captioned),\n }),\n // What the Forms list queries (AGL-3330), as a console create writes it.\n ...newFormListFields({ id: input.id, displayName: name, slug, routing: form.routing }),\n ...stamps,\n })\n }\n return {\n ok: true,\n replayed: false,\n id: input.id,\n versionId,\n name,\n hostSubdomain: subdomainOf(host),\n }\n })\n}\n\nexport type AiDraftNodesUpdate =\n | { ok: true; changed: boolean }\n | { ok: false; status: 404; error: string }\n\n/**\n * Change the tree of a versioned draft the job wrote, in one transaction:\n * read the first version as it is stored NOW — a member may have opened and\n * saved it — hand it to `update`, and store what comes back with a fresh\n * `updatedAt`, which is what the besigner's save guard compares. `update`\n * answering `null` changes nothing: a pass run again after its write.\n */\nexport async function updateAiDraftNodes(\n firestore: Firestore,\n input: {\n kind: AiVersionedDraftKind\n hostId: string\n id: string\n now: Date\n update: (nodes: NodesMap) => NodesMap | null\n },\n): Promise<AiDraftNodesUpdate> {\n const draftRef = draftCollection(firestore, input.hostId, input.kind).doc(input.id)\n return firestore.runTransaction(async (tx): Promise<AiDraftNodesUpdate> => {\n const draft = await tx.get(draftRef)\n const versionId = draft.exists && draft.get('deletedAt') == null ? draft.get('versionId') : null\n if (typeof versionId !== 'string' || !versionId) {\n return { ok: false, status: 404, error: 'The draft is no longer on the site' }\n }\n const versionRef = draftRef.collection('versions').doc(versionId)\n const version = await tx.get(versionRef)\n const nodes = version.exists ? decodeStoredNodes<NodesMap>(version.get('nodes')) : null\n if (!nodes) return { ok: false, status: 404, error: 'The draft is no longer on the site' }\n const next = input.update(nodes)\n if (!next) return { ok: true, changed: false }\n const packed = encodeStoredNodes(next)\n if (!packed) throw new Error('an AI draft needs a node map')\n tx.update(versionRef, { nodes: Buffer.from(packed), updatedAt: input.now })\n return { ok: true, changed: true }\n })\n}\n\n/**\n * Put a search title and description on a draft screen the job wrote, each\n * only where the draft has none: a value a member typed since is theirs.\n */\nexport async function writeAiDraftScreenSeo(\n firestore: Firestore,\n input: {\n hostId: string\n id: string\n seo: { title?: string | null; description?: string | null }\n now: Date\n },\n): Promise<void> {\n const ref = draftCollection(firestore, input.hostId, 'screen').doc(input.id)\n await firestore.runTransaction(async (tx) => {\n const screen = await tx.get(ref)\n if (!screen.exists || screen.get('deletedAt') != null) return\n const patch: Record<string, unknown> = {}\n for (const key of ['title', 'description'] as const) {\n const value = String(input.seo[key] ?? '').trim()\n if (value && !String(screen.get(`seo.${key}`) ?? '').trim()) patch[`seo.${key}`] = value\n }\n if (Object.keys(patch).length) tx.update(ref, { ...patch, updatedAt: input.now })\n })\n}\n\n/** The site's subdomain, which a console link names the site by. */\nexport async function aiSiteSubdomain(firestore: Firestore, hostId: string): Promise<string | null> {\n return subdomainOf(await firestore.collection('hosts').doc(hostId).get())\n}\n\n/**\n * What a door says before a job that writes a draft of this kind exists: a\n * site is named, and it is the job's own org's; whatever the kind checks of\n * its own; then the allowance. `null` admits the job.\n */\nexport async function aiDraftAdmissionRefusal(\n firestore: Firestore,\n input: {\n orgId: string\n hostId: string | null\n kind: AiDraftKind\n /** What the refusal calls the job's output; the kind when absent. */\n noun?: string\n org: object | null\n /** The kind's own check, asked once the site is known to be the org's. */\n ownCheck?: (hostId: string) => Promise<AiJobAdmissionRefusal | null>\n },\n): Promise<AiJobAdmissionRefusal | null> {\n if (!input.hostId) {\n return {\n status: 400,\n error: `Open the site the ${input.noun ?? input.kind} is for before starting the job`,\n }\n }\n const owner = await resolveOrgIdForHost(input.hostId)\n if (!owner || owner !== input.orgId) return { status: 404, error: 'Unknown site' }\n const own = await input.ownCheck?.(input.hostId)\n if (own) return own\n const refusal = await aiDraftAllowanceRefusal(firestore, {\n kind: input.kind,\n hostId: input.hostId,\n org: input.org as Partial<AglynOrgBilling> | null,\n })\n return refusal ? { status: 403, error: refusal } : null\n}\n"],"names":["createResourceUid","uniqueDuplicateName","FORM_COMPONENT_ID","newFormListFields","normalizeFormSlug","artifactCreateListKeys","checkDatasetQuota","checkEntitlement","checkQuota","resolveEffectivePlan","resolveOrgEntitlements","billableScreenIds","normalizeScreenSlug","reservedScreenRouteSegment","SCREEN_ROOT_PATH","decodeStoredNodes","encodeStoredNodes","resolveOrgIdForHost","aiUnrestrictedPlanCapabilities","AI_DRAFT_FIELDS","layout","template","form","component","screen","AI_DRAFT_VERSION_FIELDS","AI_DRAFT_SCREEN_VERSION_FIELDS","AI_DRAFT_VERSION_NAME","AI_DRAFT_ENTITLEMENT_REFUSAL","AI_DRAFT_BANDS","collection","quotaKey","label","entitlement","draftCollection","firestore","hostId","kind","doc","siblingsOf","select","siblingRows","snapshot","docs","map","id","name","String","get","slug","sourceType","deletedAt","deleted","subdomainOf","host","subdomain","aiDraftBandRefusal","rows","org","routingMap","band","used","filter","row","undefined","length","size","quota","allowed","limit","AI_PLAN_DRAFT_KINDS","bandLabel","count","replace","planCreation","left","reason","Number","POSITIVE_INFINITY","isFinite","refusal","Math","max","countsRows","aiPlanCapabilitiesFrom","unrestricted","datasets","reusableComponents","create","dataset","freeTaste","readAiPlanCapabilities","input","counted","snapshots","Promise","all","Object","fromEntries","index","aiDraftAllowanceRefusal","resolve","aiDraftScreenSlug","requested","defaultHomeScreenId","usable","placeholder","path","Boolean","taken","Set","entries","value","has","base","attempt","recordOf","draft","versionId","seo","hostSubdomain","readAiDraft","exists","readAiTemplateDraftNodes","readAiDraftComponentProps","props","Array","isArray","prop","readAiDraftNodes","draftRef","version","nodes","allowListed","data","key","withFormCaption","formNodeId","node","componentId","formName","writeAiDraft","packed","Error","rootId","hostRef","runTransaction","tx","existing","siblings","ok","status","error","replayed","stamps","createdAt","now","updatedAt","createdBy","uid","Buffer","from","displayName","layoutId","screenId","source","type","captioned","fields","consentFieldName","routing","updateAiDraftNodes","versionRef","next","update","changed","writeAiDraftScreenSeo","ref","patch","trim","keys","aiSiteSubdomain","aiDraftAdmissionRefusal","noun","owner","orgId","own","ownCheck"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-drafts.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 { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { uniqueDuplicateName } from '@aglyn/aglyn/app-utils/duplicate-resource'\nimport {\n FORM_COMPONENT_ID,\n newFormListFields,\n normalizeFormSlug,\n type FormFieldDecl,\n type FormRouting,\n} from '@aglyn/aglyn/app-utils/forms'\nimport { artifactCreateListKeys } from '@aglyn/aglyn/app-utils/artifact-list-keys'\nimport {\n checkDatasetQuota,\n checkEntitlement,\n checkQuota,\n resolveEffectivePlan,\n resolveOrgEntitlements,\n} from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport {\n billableScreenIds,\n normalizeScreenSlug,\n reservedScreenRouteSegment,\n SCREEN_ROOT_PATH,\n} from '@aglyn/aglyn/app-utils/screen-route'\nimport { decodeStoredNodes, encodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport type {\n AglynOrgBilling,\n OrgFeatureFlags,\n} from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport type { ReusableComponentProp } from '@aglyn/aglyn/foundation/definitions/platform.types'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport {\n aiUnrestrictedPlanCapabilities,\n type AiPlanCapabilities,\n type AiPlanCreation,\n} from '../model/ai-plan-capabilities'\nimport type { AiJobAdmissionRefusal } from './ai-job-admission'\n\n/**\n * Where a generation job's layout, page template, form, reusable component\n * or page lands (AGL-2909, AGL-2913, AGL-2908, AGL-2907): a new draft, made\n * as the console's host resources route makes one.\n *\n * ── The document the create route makes ─────────────────────────────────\n *\n * `AI_DRAFT_FIELDS` is that route's allow-list for each kind, and a spec\n * reads the route's source to hold the two together. Nothing outside the\n * list is written except the route's own stamps: `createdAt`, `updatedAt`,\n * `createdBy`, the list keys every artifact create carries\n * (`artifactCreateListKeys`: the name keys, a component's or template's stored\n * kind, a template's library row), and a template's `source`, which is\n * `authored` — a template a member's job generated is theirs, never a starter\n * or a listing. A layout's or a screen's first version carries the keys the\n * versions route seeds one with. A form is the document the Forms page's\n * Create sends: its design canvas-shaped under `rootId` with the form node\n * bound to the form's id and captioned with its name, a slug read from that\n * name, and no version — the form's page mints the first one when someone\n * opens it. Where Create sends an empty `fields`, a generated form sends the\n * declaration read off its own design, so the two agree from the start. A\n * component carries its tree and the props it declares on its own document,\n * as Use template writes one, and gets its first version where every\n * component does: when a member opens it.\n *\n * ── Counted like a create ────────────────────────────────────────────────\n *\n * The band is met INSIDE the transaction that writes (AGL-2231), with the\n * route's arithmetic: every layout document counts against\n * `sharedLayoutsPerHost`; a template counts against `templatesPerHost` when it\n * records a source other than a platform starter — the route's `!=` query,\n * which never matches a document with no `source.type` at all; a screen counts\n * against `screensPerHost` by `billableScreenIds`, over the screens and the\n * host's routing map, which counts an unrouted screen too (AGL-1445); and a\n * form needs the plan to include the route's `entitlement` first, refused in\n * the route's own words, after which every form document counts against\n * `formsPerHost`. A reusable component counts against no allowance at all:\n * the route admits one on a plan with the `reusableComponents` entitlement\n * and refuses it otherwise, and so does the writer.\n *\n * ── Applied to nothing ───────────────────────────────────────────────────\n *\n * A layout renders only around a screen or layout that names it, or as the\n * built-in page layout the host document names; a template is inert until a\n * member uses it; a form renders only where a page places it; a component\n * renders only where an instance places it, and a new one has no instance;\n * and a screen serves nothing until the host's routing map names it, which is\n * what publishing writes (`publishScreenRoute`). So the writer touches the new\n * documents and nothing else — no host document, no routing map, no\n * collection, store setting, other layout or component — and never a\n * `publishedAt` or a publish schedule. What the job hands back is a link to\n * the draft, never a binding.\n *\n * ── One draft per recorded id ────────────────────────────────────────────\n *\n * The caller names the document id: the console resource id the job recorded\n * for the draft before its first write (`ai-job-draft-ids.ts`). A step run\n * again after its draft was written — a process cut off before the machine\n * recorded the step — reads the same id, finds that draft and reports it,\n * rather than writing a second.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport type AiDraftKind = 'layout' | 'template' | 'form' | 'component' | 'screen'\n\n/** The draft kinds that keep their tree in versions, as the besigner opens them. */\nexport type AiVersionedDraftKind = Extract<AiDraftKind, 'layout' | 'screen'>\n\n/** The console host resources route's allow-list for each kind. */\nexport const AI_DRAFT_FIELDS: Readonly<Record<AiDraftKind, readonly string[]>> = {\n layout: ['displayName', 'description', 'versionId'],\n template: [\n 'kind',\n 'displayName',\n 'description',\n 'placeholders',\n 'nodes',\n 'rootId',\n 'props',\n 'slug',\n 'seo',\n // A component template's own kind (AGL-3287); a job never sends one.\n 'componentKind',\n ],\n form: [\n 'displayName',\n 'slug',\n 'fields',\n 'consentFieldName',\n 'routing',\n 'legacyMatch',\n 'rootId',\n 'nodes',\n ],\n // `kind` marks a reusable email block (AGL-3287); a job makes page\n // components and never sends one.\n component: ['displayName', 'description', 'rootId', 'nodes', 'props', 'kind'],\n // `kind` is on the route's list for the email composer; a page has none, and\n // the writer never sends one.\n screen: ['displayName', 'description', 'slug', 'seo', 'kind', 'versionId'],\n}\n\n/** The keys a layout's first version is seeded with, all on the versions route's list. */\nexport const AI_DRAFT_VERSION_FIELDS: readonly string[] = ['layoutId', 'hostId', 'displayName', 'nodes']\n\n/**\n * The keys a screen's first version is seeded with, all on the versions\n * route's list: the screen it belongs to, the site, the label, the tree, and\n * the layout it renders inside when the plan names one the site has.\n */\nexport const AI_DRAFT_SCREEN_VERSION_FIELDS: readonly string[] = [\n 'screenId',\n 'hostId',\n 'displayName',\n 'nodes',\n 'layoutId',\n]\n\n/** The label on a generated draft's first version, as on one Use template makes. */\nexport const AI_DRAFT_VERSION_NAME = 'Initial version'\n\n/** The route's refusal when the site's plan does not include the kind at all. */\nexport const AI_DRAFT_ENTITLEMENT_REFUSAL = 'This feature is not included in your plan — see Billing'\n\nexport interface AiDraftBand {\n collection: 'layouts' | 'templates' | 'forms' | 'components' | 'screens'\n /** The allowance the route counts the kind against, where it counts one. */\n quotaKey?:\n | 'sharedLayoutsPerHost'\n | 'templatesPerHost'\n | 'formsPerHost'\n | 'screensPerHost'\n /** The feature the plan must include before any document of the kind counts. */\n entitlement?: keyof OrgFeatureFlags\n /** The route's plural label, in the refusal it gives. */\n label: string\n}\n\n/** Each kind's collection, counter, feature and label, as the create route declares them. */\nexport const AI_DRAFT_BANDS: Readonly<Record<AiDraftKind, AiDraftBand>> = {\n layout: { collection: 'layouts', quotaKey: 'sharedLayoutsPerHost', label: 'shared layouts' },\n template: { collection: 'templates', quotaKey: 'templatesPerHost', label: 'templates' },\n // A saved form is counted against `formsPerHost` alone — Free 1 through\n // the ceiling — and is not behind `reusableComponents` (AGL-3597).\n form: { collection: 'forms', quotaKey: 'formsPerHost', label: 'forms' },\n component: {\n collection: 'components',\n entitlement: 'reusableComponents',\n label: 'reusable components',\n },\n screen: { collection: 'screens', quotaKey: 'screensPerHost', label: 'pages' },\n}\n\ninterface SiblingRow {\n id: string\n name: string\n slug: string\n kind: unknown\n sourceType: unknown\n deletedAt: unknown\n deleted: boolean\n}\n\n/** A host's routing map: screen id → the path it is published at. */\ntype RoutingMap = Record<string, unknown> | null | undefined\n\nfunction draftCollection(firestore: Firestore, hostId: string, kind: AiDraftKind) {\n return firestore.collection('hosts').doc(hostId).collection(AI_DRAFT_BANDS[kind].collection)\n}\n\n/** The fields the band, the name, the address and a deletion are read from. */\nfunction siblingsOf(collection: FirebaseFirestore.CollectionReference) {\n return collection.select('displayName', 'slug', 'kind', 'source.type', 'deletedAt')\n}\n\nfunction siblingRows(snapshot: FirebaseFirestore.QuerySnapshot): SiblingRow[] {\n return snapshot.docs.map((doc) => ({\n id: doc.id,\n name: String(doc.get('displayName') ?? ''),\n slug: String(doc.get('slug') ?? ''),\n kind: doc.get('kind'),\n sourceType: doc.get('source.type'),\n deletedAt: doc.get('deletedAt'),\n deleted: doc.get('deletedAt') != null,\n }))\n}\n\nfunction subdomainOf(host: FirebaseFirestore.DocumentSnapshot): string | null {\n const subdomain = host.get('subdomain')\n return typeof subdomain === 'string' && subdomain ? subdomain : null\n}\n\n/**\n * The route's refusal when the site's plan does not include the kind, or\n * holds no more of it; `null` while it does. The feature is asked first, as\n * the route asks it before it counts.\n */\nexport function aiDraftBandRefusal(\n kind: AiDraftKind,\n rows: ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'>>,\n org: Partial<AglynOrgBilling> | null,\n routingMap?: RoutingMap,\n): string | null {\n const band = AI_DRAFT_BANDS[kind]\n if (band.entitlement && !checkEntitlement(org, band.entitlement)) {\n return AI_DRAFT_ENTITLEMENT_REFUSAL\n }\n if (!band.quotaKey) return null\n const used =\n kind === 'template'\n ? rows.filter((row) => row.sourceType !== undefined && row.sourceType !== 'starter').length\n : kind === 'screen'\n ? billableScreenIds(\n rows.map((row) => ({ id: row.id, kind: row.kind, deletedAt: row.deletedAt })),\n (routingMap ?? undefined) as never,\n ).size\n : rows.length\n const quota = checkQuota(org, band.quotaKey, used)\n return quota.allowed\n ? null\n : `Your plan includes ${quota.limit} ${band.label} — upgrade in Billing for more`\n}\n\n/** The draft kinds a plan's creations land as, whose band a plan is told about (AGL-3030). */\nconst AI_PLAN_DRAFT_KINDS = ['component', 'form', 'layout', 'template'] as const\n\ntype AiPlanDraftKind = (typeof AI_PLAN_DRAFT_KINDS)[number]\n\n/** Every draft kind's plural label made singular, for a band of one. */\nfunction bandLabel(label: string, count: number): string {\n return count === 1 ? label.replace(/s$/, '') : label\n}\n\n/**\n * Whether the site may take `used + 1` of a kind, in the create route's\n * arithmetic: the feature first, then the allowance. `null` for a kind whose\n * allowance is unlimited or counts nothing, which needs no rows read.\n */\nfunction planCreation(\n kind: AiPlanDraftKind,\n org: Partial<AglynOrgBilling> | null,\n rows: ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'>> | null,\n): AiPlanCreation {\n const band = AI_DRAFT_BANDS[kind]\n if (band.entitlement && !checkEntitlement(org, band.entitlement)) {\n return {\n allowed: false,\n left: 0,\n reason: `this workspace's plan does not include ${kind === 'form' ? 'saved forms' : band.label}`,\n }\n }\n const limit = band.quotaKey ? resolveOrgEntitlements(org)[band.quotaKey] : Number.POSITIVE_INFINITY\n if (!rows || !Number.isFinite(limit)) return { allowed: true, left: null, reason: null }\n // The route's own arithmetic decides; the count beside it is what is left.\n const refusal = aiDraftBandRefusal(kind, rows, org)\n const used =\n kind === 'template'\n ? rows.filter((row) => row.sourceType !== undefined && row.sourceType !== 'starter').length\n : rows.length\n return refusal\n ? {\n allowed: false,\n left: 0,\n reason: `this site already holds the ${limit} ${bandLabel(band.label, limit)} its plan includes`,\n }\n : { allowed: true, left: Math.max(0, limit - used), reason: null }\n}\n\n/** Whether a kind's rows are counted at all on this plan: included, and under a finite allowance. */\nfunction countsRows(kind: AiPlanDraftKind, org: Partial<AglynOrgBilling> | null): boolean {\n const band = AI_DRAFT_BANDS[kind]\n if (!band.quotaKey) return false\n if (band.entitlement && !checkEntitlement(org, band.entitlement)) return false\n return Number.isFinite(resolveOrgEntitlements(org)[band.quotaKey])\n}\n\n/**\n * What a plan may create on this site (AGL-3030), from the workspace's plan\n * and the rows each counted kind already holds — pure, so the specs and the\n * eval harness hold the arithmetic without a Firestore. A kind whose rows\n * are not given is counted as having room, which is what an unlimited\n * allowance means.\n *\n * A theme change is a proposal a member applies, never a draft, so it counts\n * against nothing. A dataset is org data that no job writes; a plan may name\n * one only where the workspace's plan includes datasets at all. An email\n * design is written by the email plugin, whose own gate decides.\n *\n * A workspace on the Free plan spends the Free taste, a wall of monthly\n * credits, so its plan is held to the sections that wall pays for (AGL-3070).\n * The effective plan decides, as it decides the taste's own allowance: a\n * workspace whose subscription died is Free again.\n */\nexport function aiPlanCapabilitiesFrom(\n org: Partial<AglynOrgBilling> | null,\n rows: Partial<Record<AiPlanDraftKind, ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'>>>> = {},\n): AiPlanCapabilities {\n const unrestricted = aiUnrestrictedPlanCapabilities()\n const datasets = checkDatasetQuota(org, 0).limit > 0\n return {\n reusableComponents: checkEntitlement(org, 'reusableComponents'),\n create: {\n ...unrestricted.create,\n component: planCreation('component', org, rows.component ?? null),\n form: planCreation('form', org, rows.form ?? null),\n layout: planCreation('layout', org, rows.layout ?? null),\n template: planCreation('template', org, rows.template ?? null),\n dataset: datasets\n ? unrestricted.create.dataset\n : { allowed: false, left: 0, reason: \"this workspace's plan does not include datasets\" },\n },\n ...(resolveEffectivePlan(org) === 'free' ? { freeTaste: true } : {}),\n }\n}\n\n/**\n * What a plan may create on this site, read: the rows of each kind whose\n * allowance the workspace's plan counts, as the draft writer reads them, and\n * nothing for a kind the plan leaves out or leaves unlimited.\n */\nexport async function readAiPlanCapabilities(\n firestore: Firestore,\n input: { hostId: string; org: Partial<AglynOrgBilling> | null },\n): Promise<AiPlanCapabilities> {\n const counted = AI_PLAN_DRAFT_KINDS.filter((kind) => countsRows(kind, input.org))\n const snapshots = await Promise.all(\n counted.map((kind) => siblingsOf(draftCollection(firestore, input.hostId, kind)).get()),\n )\n return aiPlanCapabilitiesFrom(\n input.org,\n Object.fromEntries(counted.map((kind, index) => [kind, siblingRows(snapshots[index])])),\n )\n}\n\n/**\n * Whether the site can take one more draft of the kind: a read, for a door\n * that asks before it spends. The write asks again inside its transaction.\n */\nexport async function aiDraftAllowanceRefusal(\n firestore: Firestore,\n input: { kind: AiDraftKind; hostId: string; org: Partial<AglynOrgBilling> | null },\n): Promise<string | null> {\n const [snapshot, host] = await Promise.all([\n siblingsOf(draftCollection(firestore, input.hostId, input.kind)).get(),\n input.kind === 'screen'\n ? firestore.collection('hosts').doc(input.hostId).get()\n : Promise.resolve(null),\n ])\n return aiDraftBandRefusal(\n input.kind,\n siblingRows(snapshot),\n input.org,\n host?.get('screens') as RoutingMap,\n )\n}\n\n/**\n * A draft screen's one-segment address: the one asked for, else one from its\n * name, never a reserved segment, and never an address a live screen keeps\n * or the routing map already serves — so the member's publish at it later\n * takes nothing from a live page. The root is asked for only while no screen\n * holds it.\n *\n * Except the placeholder home page a site is created with (AGL-3408), which\n * holds `/` only until a real home page is published there: the guided start\n * is offered to exactly the sites that still have it, and the home page it\n * drafts has to be able to ask for the root. Publishing it replaces the\n * placeholder in the same write.\n */\nexport function aiDraftScreenSlug(\n requested: string | null | undefined,\n name: string,\n rows: ReadonlyArray<Pick<SiblingRow, 'slug' | 'deleted'> & { id?: string }>,\n routingMap: RoutingMap,\n defaultHomeScreenId?: string | null,\n): string {\n const placeholder = (id: string | undefined, path: unknown) =>\n Boolean(defaultHomeScreenId) && id === defaultHomeScreenId && path === SCREEN_ROOT_PATH\n const taken = new Set<string>([\n ...rows\n .filter((row) => !row.deleted && row.slug && !placeholder(row.id, row.slug))\n .map((row) => row.slug),\n ...Object.entries(routingMap ?? {})\n .filter(([id, path]) => typeof path === 'string' && !placeholder(id, path))\n .map(([, path]) => path as string),\n ])\n const usable = (value: string | undefined) =>\n value && !reservedScreenRouteSegment(value) && !(value === SCREEN_ROOT_PATH && taken.has(value))\n ? value\n : undefined\n const base = usable(normalizeScreenSlug(requested ?? undefined)) ?? usable(normalizeScreenSlug(name)) ?? 'page'\n if (base === SCREEN_ROOT_PATH) return base\n let slug = base\n let attempt = 2\n while (taken.has(slug)) slug = `${base}-${attempt++}`\n return slug\n}\n\nexport interface AiDraftRecord {\n id: string\n /** A layout's or a screen's first version; `null` for every other kind, which the writer gives none. */\n versionId: string | null\n name: string\n hostSubdomain: string | null\n /** A screen's search listing as the draft holds it now; absent on other kinds. */\n seo?: { title?: string; description?: string } | null\n /** Whether a member deleted the draft since it was written. */\n deleted?: boolean\n}\n\nfunction recordOf(\n id: string,\n draft: FirebaseFirestore.DocumentSnapshot,\n host: FirebaseFirestore.DocumentSnapshot,\n): AiDraftRecord {\n const versionId = draft.get('versionId')\n const seo = draft.get('seo')\n return {\n id,\n versionId: typeof versionId === 'string' && versionId ? versionId : null,\n name: String(draft.get('displayName') ?? ''),\n hostSubdomain: subdomainOf(host),\n ...(seo && typeof seo === 'object' ? { seo: seo as AiDraftRecord['seo'] } : {}),\n ...(draft.get('deletedAt') != null ? { deleted: true } : {}),\n }\n}\n\n/** The draft a job already wrote under `id`, when a run of its step got that far. */\nexport async function readAiDraft(\n firestore: Firestore,\n input: { kind: AiDraftKind; hostId: string; id: string },\n): Promise<AiDraftRecord | null> {\n const draft = await draftCollection(firestore, input.hostId, input.kind).doc(input.id).get()\n if (!draft.exists) return null\n return recordOf(input.id, draft, await firestore.collection('hosts').doc(input.hostId).get())\n}\n\n/**\n * The tree a template draft holds now, decoded; `null` when there is none.\n *\n * A template is not a versioned draft — its nodes live on the document\n * itself, which is why `readAiDraftNodes` refuses its kind.\n */\nexport async function readAiTemplateDraftNodes(\n firestore: Firestore,\n input: { hostId: string; id: string },\n): Promise<NodesMap | null> {\n const draft = await draftCollection(firestore, input.hostId, 'template').doc(input.id).get()\n if (!draft.exists) return null\n return decodeStoredNodes<NodesMap>(draft.get('nodes')) ?? null\n}\n\n/**\n * The property names a component draft declares now; `null` when there is no\n * such draft. Read from the draft document rather than its version, because a\n * component's props live beside its tree and a copy carries its source's.\n */\nexport async function readAiDraftComponentProps(\n firestore: Firestore,\n input: { hostId: string; id: string },\n): Promise<string[] | null> {\n const draft = await draftCollection(firestore, input.hostId, 'component').doc(input.id).get()\n if (!draft.exists) return null\n const props = draft.get('props')\n if (!Array.isArray(props)) return []\n return props\n .map((prop) => (prop && typeof prop === 'object' ? (prop as Record<string, unknown>)['name'] : null))\n .filter((name): name is string => typeof name === 'string' && name.length > 0)\n}\n\n/** The tree a versioned draft's first version holds now, decoded; `null` when there is none. */\nexport async function readAiDraftNodes(\n firestore: Firestore,\n input: { kind: AiVersionedDraftKind; hostId: string; id: string },\n): Promise<{ versionId: string; nodes: NodesMap } | null> {\n const draftRef = draftCollection(firestore, input.hostId, input.kind).doc(input.id)\n const draft = await draftRef.get()\n const versionId = draft.exists ? draft.get('versionId') : null\n if (typeof versionId !== 'string' || !versionId) return null\n const version = await draftRef.collection('versions').doc(versionId).get()\n const nodes = version.exists ? decodeStoredNodes<NodesMap>(version.get('nodes')) : null\n return nodes ? { versionId, nodes } : null\n}\n\nexport interface AiDraftInput {\n kind: AiDraftKind\n hostId: string\n /** The draft's document id: the id the job recorded for this draft. */\n id: string\n /** Whose work the draft is: the job's creator, whose brief it built. */\n uid: string\n org: Partial<AglynOrgBilling> | null\n /** The name asked for; a live sibling already called that gets a number. */\n name: string\n nodes: NodesMap\n /** A page template's suggested address, or a screen's one-segment address. */\n slug?: string | null\n /** A form's declaration and the ids its design hangs from; required for a form. */\n form?: AiFormDraftDeclaration\n /** A component's root, which the node map holds; required for a component. */\n rootId?: string\n /** The properties a component declares, which its tree binds. */\n props?: readonly ReusableComponentProp[]\n /** A screen's layout, for its first version to render inside; a layout the site has. */\n layoutId?: string | null\n /**\n * The job the member started that wrote this draft (AGL-3596), stamped as\n * `aiJobId` on a page's or a layout's document and first version, so staff\n * can tell a generated page from a copy someone made. A scaffold's unit\n * names the site job, not the unit's derived id.\n */\n aiJobId?: string | null\n now: Date\n}\n\n/** What a form draft declares beside its design, read off that design. */\nexport interface AiFormDraftDeclaration {\n /** The canvas root `nodes` is stored under, as the Forms page's Create stores it. */\n rootId: string\n /** The form node, which carries the form's id and is captioned with the draft's name. */\n formNodeId: string\n fields: FormFieldDecl[]\n consentFieldName: string | null\n routing: FormRouting | null\n}\n\nexport type AiDraftWrite =\n | (AiDraftRecord & { ok: true; replayed: boolean })\n | { ok: false; status: 403 | 404; error: string }\n\n/** Keep only the keys the create route admits for the kind. */\nfunction allowListed(kind: AiDraftKind, data: Record<string, unknown>): Record<string, unknown> {\n const allowed = new Set(AI_DRAFT_FIELDS[kind])\n return Object.fromEntries(\n Object.entries(data).filter(([key, value]) => allowed.has(key) && value !== undefined),\n )\n}\n\n/** The node map with its form node captioned with `name`, as Create captions a new form. */\nfunction withFormCaption(nodes: NodesMap, formNodeId: string, name: string): NodesMap {\n const node = nodes[formNodeId]\n if (!node || node.componentId !== FORM_COMPONENT_ID) return nodes\n return { ...nodes, [formNodeId]: { ...node, props: { ...(node.props ?? {}), formName: name } } }\n}\n\n/**\n * Write one draft: the band met, the name made unique among the live\n * siblings, the node map stored as msgpack, all in one transaction. A\n * refusal is returned, never thrown; a Firestore failure propagates, so the\n * machine's retry applies.\n */\nexport async function writeAiDraft(firestore: Firestore, input: AiDraftInput): Promise<AiDraftWrite> {\n const packed = encodeStoredNodes(input.nodes)\n if (!packed) throw new Error('an AI draft needs a node map')\n if (input.kind === 'form' && !input.form) throw new Error('an AI form draft needs its declaration')\n if (input.kind === 'component' && !(input.rootId && input.nodes[input.rootId])) {\n throw new Error('a component draft needs a root its node map holds')\n }\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const collection = draftCollection(firestore, input.hostId, input.kind)\n const draftRef = collection.doc(input.id)\n return firestore.runTransaction(async (tx): Promise<AiDraftWrite> => {\n // Every read before any write, which Firestore requires.\n const [host, existing, siblings] = await Promise.all([\n tx.get(hostRef),\n tx.get(draftRef),\n tx.get(siblingsOf(collection)),\n ])\n if (!host.exists) return { ok: false, status: 404, error: 'Unknown site' }\n if (existing.exists) {\n return { ok: true, replayed: true, ...recordOf(input.id, existing, host) }\n }\n const rows = siblingRows(siblings)\n // Read, never written: the routing map decides which screens count.\n const routingMap = host.get('screens') as RoutingMap\n const refusal = aiDraftBandRefusal(input.kind, rows, input.org, routingMap)\n if (refusal) return { ok: false, status: 403, error: refusal }\n const defaultHomeScreenId = host.get('defaultHomeScreenId') as string | null | undefined\n // A screen draft that takes the root the placeholder home page holds\n // replaces that page when it is published (AGL-3408), so it is named as\n // the page it replaces, \"Home\", rather than as a copy of it (AGL-3596).\n const slug =\n input.kind === 'screen'\n ? aiDraftScreenSlug(input.slug, input.name, rows, routingMap, defaultHomeScreenId)\n : null\n const replacesPlaceholder = (row: SiblingRow) =>\n slug === SCREEN_ROOT_PATH &&\n Boolean(defaultHomeScreenId) &&\n row.id === defaultHomeScreenId &&\n (routingMap ?? {})[row.id] === SCREEN_ROOT_PATH\n const name = uniqueDuplicateName(\n input.name,\n rows.filter((row) => !row.deleted && !replacesPlaceholder(row)).map((row) => row.name),\n )\n const stamps = { createdAt: input.now, updatedAt: input.now, createdBy: input.uid }\n const marker = input.aiJobId ? { aiJobId: input.aiJobId } : {}\n const nodes = Buffer.from(packed)\n let versionId: string | null = null\n if (input.kind === 'layout') {\n versionId = createResourceUid()\n tx.create(draftRef, {\n ...allowListed('layout', { displayName: name, versionId }),\n ...artifactCreateListKeys('layouts', { displayName: name }),\n ...stamps,\n ...marker,\n })\n tx.create(draftRef.collection('versions').doc(versionId), {\n layoutId: input.id,\n hostId: input.hostId,\n displayName: AI_DRAFT_VERSION_NAME,\n nodes,\n ...stamps,\n ...marker,\n })\n } else if (input.kind === 'screen') {\n versionId = createResourceUid()\n tx.create(draftRef, {\n ...allowListed('screen', { displayName: name, slug, versionId }),\n ...artifactCreateListKeys('screens', { displayName: name }),\n ...stamps,\n ...marker,\n })\n tx.create(draftRef.collection('versions').doc(versionId), {\n screenId: input.id,\n hostId: input.hostId,\n displayName: AI_DRAFT_VERSION_NAME,\n nodes,\n ...(input.layoutId ? { layoutId: input.layoutId } : {}),\n ...stamps,\n ...marker,\n })\n } else if (input.kind === 'template') {\n tx.create(draftRef, {\n ...allowListed('template', {\n kind: 'page',\n displayName: name,\n nodes,\n slug: input.slug || undefined,\n }),\n source: { type: 'authored' },\n ...artifactCreateListKeys('templates', {\n kind: 'page',\n displayName: name,\n source: { type: 'authored' },\n }),\n ...stamps,\n })\n } else if (input.kind === 'component') {\n tx.create(draftRef, {\n ...allowListed('component', {\n displayName: name,\n rootId: input.rootId,\n nodes,\n props: input.props?.length ? [...input.props] : undefined,\n }),\n ...artifactCreateListKeys('components', { displayName: name }),\n ...stamps,\n })\n } else {\n const form = input.form as AiFormDraftDeclaration\n const captioned = encodeStoredNodes(withFormCaption(input.nodes, form.formNodeId, name)) ?? packed\n const slug = normalizeFormSlug(name) || input.id\n tx.create(draftRef, {\n ...allowListed('form', {\n displayName: name,\n slug,\n fields: form.fields,\n consentFieldName: form.consentFieldName || undefined,\n routing: form.routing ?? undefined,\n rootId: form.rootId,\n nodes: Buffer.from(captioned),\n }),\n // What the Forms list queries (AGL-3330), as a console create writes it.\n ...newFormListFields({ id: input.id, displayName: name, slug, routing: form.routing }),\n ...stamps,\n })\n }\n return {\n ok: true,\n replayed: false,\n id: input.id,\n versionId,\n name,\n hostSubdomain: subdomainOf(host),\n }\n })\n}\n\nexport type AiDraftNodesUpdate =\n | { ok: true; changed: boolean }\n | { ok: false; status: 404; error: string }\n\n/**\n * Change the tree of a versioned draft the job wrote, in one transaction:\n * read the first version as it is stored NOW — a member may have opened and\n * saved it — hand it to `update`, and store what comes back with a fresh\n * `updatedAt`, which is what the besigner's save guard compares. `update`\n * answering `null` changes nothing: a pass run again after its write.\n */\nexport async function updateAiDraftNodes(\n firestore: Firestore,\n input: {\n kind: AiVersionedDraftKind\n hostId: string\n id: string\n now: Date\n update: (nodes: NodesMap) => NodesMap | null\n },\n): Promise<AiDraftNodesUpdate> {\n const draftRef = draftCollection(firestore, input.hostId, input.kind).doc(input.id)\n return firestore.runTransaction(async (tx): Promise<AiDraftNodesUpdate> => {\n const draft = await tx.get(draftRef)\n const versionId = draft.exists && draft.get('deletedAt') == null ? draft.get('versionId') : null\n if (typeof versionId !== 'string' || !versionId) {\n return { ok: false, status: 404, error: 'The draft is no longer on the site' }\n }\n const versionRef = draftRef.collection('versions').doc(versionId)\n const version = await tx.get(versionRef)\n const nodes = version.exists ? decodeStoredNodes<NodesMap>(version.get('nodes')) : null\n if (!nodes) return { ok: false, status: 404, error: 'The draft is no longer on the site' }\n const next = input.update(nodes)\n if (!next) return { ok: true, changed: false }\n const packed = encodeStoredNodes(next)\n if (!packed) throw new Error('an AI draft needs a node map')\n tx.update(versionRef, { nodes: Buffer.from(packed), updatedAt: input.now })\n return { ok: true, changed: true }\n })\n}\n\n/**\n * Put a search title and description on a draft screen the job wrote, each\n * only where the draft has none: a value a member typed since is theirs.\n */\nexport async function writeAiDraftScreenSeo(\n firestore: Firestore,\n input: {\n hostId: string\n id: string\n seo: { title?: string | null; description?: string | null }\n now: Date\n },\n): Promise<void> {\n const ref = draftCollection(firestore, input.hostId, 'screen').doc(input.id)\n await firestore.runTransaction(async (tx) => {\n const screen = await tx.get(ref)\n if (!screen.exists || screen.get('deletedAt') != null) return\n const patch: Record<string, unknown> = {}\n for (const key of ['title', 'description'] as const) {\n const value = String(input.seo[key] ?? '').trim()\n if (value && !String(screen.get(`seo.${key}`) ?? '').trim()) patch[`seo.${key}`] = value\n }\n if (Object.keys(patch).length) tx.update(ref, { ...patch, updatedAt: input.now })\n })\n}\n\n/** The site's subdomain, which a console link names the site by. */\nexport async function aiSiteSubdomain(firestore: Firestore, hostId: string): Promise<string | null> {\n return subdomainOf(await firestore.collection('hosts').doc(hostId).get())\n}\n\n/**\n * What a door says before a job that writes a draft of this kind exists: a\n * site is named, and it is the job's own org's; whatever the kind checks of\n * its own; then the allowance. `null` admits the job.\n */\nexport async function aiDraftAdmissionRefusal(\n firestore: Firestore,\n input: {\n orgId: string\n hostId: string | null\n kind: AiDraftKind\n /** What the refusal calls the job's output; the kind when absent. */\n noun?: string\n org: object | null\n /** The kind's own check, asked once the site is known to be the org's. */\n ownCheck?: (hostId: string) => Promise<AiJobAdmissionRefusal | null>\n },\n): Promise<AiJobAdmissionRefusal | null> {\n if (!input.hostId) {\n return {\n status: 400,\n error: `Open the site the ${input.noun ?? input.kind} is for before starting the job`,\n }\n }\n const owner = await resolveOrgIdForHost(input.hostId)\n if (!owner || owner !== input.orgId) return { status: 404, error: 'Unknown site' }\n const own = await input.ownCheck?.(input.hostId)\n if (own) return own\n const refusal = await aiDraftAllowanceRefusal(firestore, {\n kind: input.kind,\n hostId: input.hostId,\n org: input.org as Partial<AglynOrgBilling> | null,\n })\n return refusal ? { status: 403, error: refusal } : null\n}\n"],"names":["createResourceUid","uniqueDuplicateName","FORM_COMPONENT_ID","newFormListFields","normalizeFormSlug","artifactCreateListKeys","checkDatasetQuota","checkEntitlement","checkQuota","resolveEffectivePlan","resolveOrgEntitlements","billableScreenIds","normalizeScreenSlug","reservedScreenRouteSegment","SCREEN_ROOT_PATH","decodeStoredNodes","encodeStoredNodes","resolveOrgIdForHost","aiUnrestrictedPlanCapabilities","AI_DRAFT_FIELDS","layout","template","form","component","screen","AI_DRAFT_VERSION_FIELDS","AI_DRAFT_SCREEN_VERSION_FIELDS","AI_DRAFT_VERSION_NAME","AI_DRAFT_ENTITLEMENT_REFUSAL","AI_DRAFT_BANDS","collection","quotaKey","label","entitlement","draftCollection","firestore","hostId","kind","doc","siblingsOf","select","siblingRows","snapshot","docs","map","id","name","String","get","slug","sourceType","deletedAt","deleted","subdomainOf","host","subdomain","aiDraftBandRefusal","rows","org","routingMap","band","used","filter","row","undefined","length","size","quota","allowed","limit","AI_PLAN_DRAFT_KINDS","bandLabel","count","replace","planCreation","left","reason","Number","POSITIVE_INFINITY","isFinite","refusal","Math","max","countsRows","aiPlanCapabilitiesFrom","unrestricted","datasets","reusableComponents","create","dataset","freeTaste","readAiPlanCapabilities","input","counted","snapshots","Promise","all","Object","fromEntries","index","aiDraftAllowanceRefusal","resolve","aiDraftScreenSlug","requested","defaultHomeScreenId","usable","placeholder","path","Boolean","taken","Set","entries","value","has","base","attempt","recordOf","draft","versionId","seo","hostSubdomain","readAiDraft","exists","readAiTemplateDraftNodes","readAiDraftComponentProps","props","Array","isArray","prop","readAiDraftNodes","draftRef","version","nodes","allowListed","data","key","withFormCaption","formNodeId","node","componentId","formName","writeAiDraft","packed","Error","rootId","hostRef","runTransaction","tx","existing","siblings","ok","status","error","replayed","replacesPlaceholder","stamps","createdAt","now","updatedAt","createdBy","uid","marker","aiJobId","Buffer","from","displayName","layoutId","screenId","source","type","captioned","fields","consentFieldName","routing","updateAiDraftNodes","versionRef","next","update","changed","writeAiDraftScreenSeo","ref","patch","trim","keys","aiSiteSubdomain","aiDraftAdmissionRefusal","noun","owner","orgId","own","ownCheck"],"mappings":"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"}
@@ -23,7 +23,7 @@ import { walkTree } from "../runtime/ai-doctrine-validators.js";
23
23
  import { readSiteInventory } from "../runtime/site-inventory.js";
24
24
  import { registerAiJobAdmission } from "./ai-job-admission.js";
25
25
  import { aiBuildingPlanCreations, aiJobPlanCreationsRefusal, aiPlanCreationsRunMinimumMs } from "./ai-job-plan-creations.js";
26
- import { aiJobDraftId } from "./ai-job-draft-ids.js";
26
+ import { aiJobDraftId, aiOriginJobId } from "./ai-job-draft-ids.js";
27
27
  import { aiDraftAdmissionRefusal, aiDraftAllowanceRefusal, aiSiteSubdomain, readAiDraft, readAiDraftNodes, writeAiDraft } from "./ai-job-drafts.js";
28
28
  import { aiBracketedFactsNote, aiCopySourceLines, aiConfirmedPlan, aiDoctrineReview, aiGenerationSpent, aiJobBriefLine, aiLimitReview, aiModelNodeIds, aiPlacedComponentIds, aiPlanCreation, aiPlanReferenceLines, aiPlanReuseViolations, aiUnspentOutcome } from "./ai-job-generation.js";
29
29
  import { aiPlanRegionViolations, aiPlannedLayoutRegions } from "./ai-job-plan-conformance.js";
@@ -343,6 +343,7 @@ export function createAiJobLayoutStep(deps = {}) {
343
343
  org,
344
344
  name,
345
345
  nodes: result.value.nodes,
346
+ aiJobId: aiOriginJobId(job),
346
347
  now
347
348
  });
348
349
  if (draft.ok === false) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-step.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 { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { duplicateResource } from '@aglyn/tenant-data-admin/server/duplicate-resource'\nimport type { AiJob, AiJobOutput, AiJobPlan, AiJobReview } from '../model/ai-jobs.types'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n aiDoctrineTreeTool,\n runValidatedGeneration,\n type AiValidatedTree,\n} from '../runtime/ai-doctrine'\nimport {\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineTree,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport {\n aiBuildingPlanCreations,\n aiJobPlanCreationsRefusal,\n aiPlanCreationsRunMinimumMs,\n} from './ai-job-plan-creations'\nimport { aiJobDraftId } from './ai-job-draft-ids'\nimport {\n aiDraftAdmissionRefusal,\n aiDraftAllowanceRefusal,\n aiSiteSubdomain,\n readAiDraft,\n readAiDraftNodes,\n writeAiDraft,\n type AiDraftRecord,\n} from './ai-job-drafts'\nimport {\n aiBracketedFactsNote,\n aiCopySourceLines,\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiJobBriefLine,\n aiLimitReview,\n aiModelNodeIds,\n aiPlacedComponentIds,\n aiPlanCreation,\n aiPlanReferenceLines,\n aiPlanReuseViolations,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport { aiPlanRegionViolations, aiPlannedLayoutRegions } from './ai-job-plan-conformance'\nimport type { AiJobStepRunner } from './ai-job-text-step'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The layout step (AGL-2909): the generation step of a `layout` job, run once\n * a member confirmed the job's plan.\n *\n * It builds ONE shared layout — a header with the site's navigation, the\n * Layout Slot each page renders in, a footer — through\n * `runValidatedGeneration('layout', …)`, so every building rule holds on the\n * tree before it is kept, and adds three checks of its own through `extend`: a\n * component the confirmed plan reuses is placed (rule 7), every region the\n * plan gives the layout is built (AGL-3024), and a navigation or menu\n * component the site already has is placed rather than drawn again (rule 1).\n *\n * The layout lands as a new draft (`ai-job-drafts.ts`): no screen uses it and\n * no layout nests it, so nothing on the live site changes until a member\n * assigns it. A plan that starts from a copy of a layout the site has gets\n * that copy, through the platform's duplicate module, and nothing generated —\n * so the copy is read back and held to the plan's regions the same way\n * (`aiCopiedLayoutReview`), because a branch that generates nothing is the one\n * branch where a promise can go unmet with no model to answer for it. A\n * source that already lacks a region the plan gives the layout is not copied\n * at all: the layout is built from it (AGL-3143 §14).\n *\n * A generation step runs on the job beat, never at an inline door: it\n * registers the least time its lookup rounds, its answer and its re-ask need\n * (AGL-3035), runs without extended thinking — the plan was reasoned out and\n * confirmed before it — and asks for no more of a tighter ceiling than the\n * doctrine's default for a layout than fits that time on the model it runs.\n */\n\n/** The longest answer a layout may run to; the tree is held to the layout budget either way. */\nexport const AI_JOB_LAYOUT_MAX_TOKENS = AI_ROUTING_TABLE['job.layout'].maxTokens\n\n/**\n * The layout step's time (AGL-3035, AGL-3036): its two inventory-lookup\n * rounds, its answer and its re-ask at the routing table's ceiling on the tier\n * `job.layout` is served from, fitted to what a beat can give a step.\n */\nexport const AI_JOB_LAYOUT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.layout'],\n maxTokens: AI_JOB_LAYOUT_MAX_TOKENS,\n})\n\n/** The least time one layout step needs before it starts. */\nexport const AI_JOB_LAYOUT_STEP_MINIMUM_MS = AI_JOB_LAYOUT_STEP_BUDGET.minimumMs\n\n/** A layout's name when the plan names none. */\nexport const AI_JOB_LAYOUT_DEFAULT_NAME = 'Site layout'\n\n/** The layout step's own instructions, cached after the doctrine. */\nexport const AI_JOB_LAYOUT_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n 'You build one shared layout for a website: the frame every page of the site renders inside. Answer with submit_layout: the whole layout as one flat node map.',\n 'A layout is a header, one Layout Slot and a footer, top to bottom. The header is an App Bar (muiAppBar) holding a Toolbar Content (muiToolbar) with the site’s name as text and its navigation. The footer is a Section (section) whose element is footer.',\n 'Navigation links the site’s own pages by id: a Page Link (muiScreenLink) whose screenId is a page id from the site inventory, in the order the brief and the confirmed plan give, seven at most in the header. Never invent a page id, and never link a page by its address.',\n 'When the site inventory lists a component whose name says it is navigation, a menu or a mega menu, place it in the header as an instance instead of building the navigation again. Place every component the confirmed plan reuses.',\n 'The Layout Slot (layoutSlot) is where each page renders and is the page’s main landmark: leave its component unset or main, and put nothing inside it. A layout carries no h1, because every page brings its own.',\n ].join('\\n'),\n },\n]\n\n/**\n * The layout job as the generation's user turn: the name, the brief, the\n * confirmed plan, and the layout a short copy would have started from\n * (`aiCopySourceLines`, AGL-3143 §14).\n */\nexport function aiJobLayoutPrompt(\n job: Pick<AiJob, 'brief'>,\n plan: AiJobPlan | null,\n name: string,\n source: readonly string[] = [],\n): string {\n return [`Layout name: ${name}`, aiJobBriefLine(job), ...aiPlanReferenceLines(plan), ...source].join('\\n')\n}\n\n/** A component name that says it is the site's navigation; a footer's links are not. */\nconst NAVIGATION_NAME = /\\b(?:nav|navigation|navbar|menu|mega ?menu)\\b/i\nconst FOOTER_NAME = /\\bfooter\\b/i\n\n/** A node that links somewhere: a Screen Link, or a Button pointed at a screen or an address. */\nfunction isLink(node: AiDoctrineNode): boolean {\n if (node.componentId === 'muiScreenLink') return true\n return (\n node.componentId === 'muiButton' &&\n (typeof node.props?.['screenId'] === 'string' || typeof node.props?.['href'] === 'string')\n )\n}\n\n/**\n * The layout door's checks on a tree the doctrine admitted: every component\n * the confirmed plan reuses is placed, every region the confirmed plan gives\n * the layout is built (AGL-3024), and navigation the site already keeps as a\n * component is placed rather than drawn again in the header.\n */\nexport function aiLayoutReuseCheck(\n inventory: AiSiteInventory | null,\n plan: AiJobPlan | null,\n): (tree: AiValidatedTree) => AiDoctrineViolation[] {\n const components = inventory?.components ?? []\n const navigation = components.filter(\n (component) => NAVIGATION_NAME.test(component.name) && !FOOTER_NAME.test(component.name),\n )\n return (tree) => {\n const placed = aiPlacedComponentIds(tree)\n const violations: AiDoctrineViolation[] = [\n ...aiPlanReuseViolations(inventory, plan, placed, 'layout'),\n ...aiPlanRegionViolations(plan, {\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n }),\n ]\n if (navigation.length && !navigation.some((component) => placed.has(component.id))) {\n const visits = walkTree({\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n })\n const slot = visits.findIndex(({ node }) => node.componentId === 'layoutSlot')\n const header = slot === -1 ? visits : visits.slice(0, slot)\n const links = header.filter(({ node }) => isLink(node)).map(({ id }) => id)\n if (links.length >= 2) {\n violations.push({\n rule: 1,\n code: 'navigation-rebuilt',\n message: `The site already has the \"${navigation[0].name}\" component. Place it in the header instead of building the navigation again.`,\n nodeIds: aiModelNodeIds(links, tree.sourceIds),\n })\n }\n }\n return violations\n }\n}\n\n/**\n * The regions the confirmed plan gives the layout, against the layout a COPY\n * actually produced (AGL-3024); `null` when the copy keeps the plan, or when\n * the plan promised no region to keep.\n *\n * The duplicate branch generates nothing, so it is the one place a plan's\n * promise can go unmet without a model ever answering for it. A copy that\n * cannot be read — deleted between the write and this read, or stored under a\n * root this check does not know — settles nothing, and a promise this step\n * cannot settle is reviewed by a person rather than reported as kept.\n */\nexport async function aiCopiedLayoutReview(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; id: string; name: string; plan: AiJobPlan | null },\n): Promise<AiJobReview | null> {\n const planned = aiPlannedLayoutRegions(input.plan)\n if (!planned.regions.length && !planned.unreadable.length) return null\n const stored = await readAiDraftNodes(firestore, {\n kind: 'layout',\n hostId: input.hostId,\n id: input.id,\n })\n const nodes = (stored?.nodes ?? {}) as unknown as Record<string, AiDoctrineNode>\n if (!nodes[CANVAS_ROOT_ELEMENT_ID]) {\n return {\n reason: 'doctrine',\n message: `\"${input.name}\" was copied from the layout the plan names, and this job could not read the copy back to check it has the regions the plan gives it. Open the layout and check it before you put a page in it.`,\n findings: [],\n }\n }\n const violations = aiPlanRegionViolations(input.plan, { rootId: CANVAS_ROOT_ELEMENT_ID, nodes })\n if (!violations.length) return null\n return aiDoctrineReview({\n message: `\"${input.name}\" is a copy of the layout the plan names, and a copy is all it is: ${violations[0].message}`,\n violations,\n })\n}\n\n/**\n * A layout job is admitted for a site of the job's own org with a shared\n * layout to spare, and confirmed only for a plan whose other creations it\n * builds (AGL-3143 §15).\n */\nexport const aiLayoutJobAdmission: AiJobAdmission = (context) =>\n aiDraftAdmissionRefusal(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n kind: 'layout',\n org: context.org,\n ownCheck: (hostId) => aiJobPlanCreationsRefusal('layout', context, hostId),\n })\n\nexport interface AiJobLayoutStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The platform's duplicate module, for a plan that starts from a copy. */\n duplicate?: typeof duplicateResource\n}\n\nexport function createAiJobLayoutStep(deps: AiJobLayoutStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const duplicate = deps.duplicate ?? duplicateResource\n return async ({ job, now, signal, firestore, modelFor }) => {\n const hostId = job.hostId\n if (!hostId) throw new Error('a layout job names no site, and its admission refuses one')\n const output = (draft: AiDraftRecord, load?: AiLoadEstimate | null, note?: string | null): AiJobOutput => ({\n resource: 'layout',\n id: draft.id,\n versionId: draft.versionId,\n hostId,\n hostSubdomain: draft.hostSubdomain,\n label: draft.name,\n ...(load ? { load } : {}),\n ...(note ? { note } : {}),\n })\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.layout') ?? aiModelForStep('job.layout')\n\n // A run cut off after its draft was written reports that draft, found by the id the job recorded.\n const draftId = aiJobDraftId(job, 'layout')\n const written = await readAiDraft(firestore, { kind: 'layout', hostId, id: draftId })\n if (written) return aiUnspentOutcome(model, { outputs: [output(written)] })\n\n const [inventory, orgSnapshot] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n ])\n const org = (orgSnapshot.data() ?? null) as Partial<AglynOrgBilling> | null\n const plan = aiConfirmedPlan(job)\n const creation = aiPlanCreation(plan, 'layout')\n const name = creation?.name || AI_JOB_LAYOUT_DEFAULT_NAME\n\n // The plan starts from a copy of a layout the site has (rule 15): the copy\n // is the draft, and nothing is generated. A source gone since the plan\n // was made is built from the brief instead. A source that cannot keep the\n // plan's regions is built FROM instead (AGL-3143 §14), as the page step\n // builds from a short source: copying it could only end in the review\n // below, and Try again would copy the same thing. Only a source read and\n // found short diverts; one this step cannot read still copies, and the\n // review after the copy answers for it.\n const source = creation?.duplicateOf\n ? inventory.layouts.find((layout) => layout.id === creation.duplicateOf)\n : undefined\n const sourceReview = source\n ? await aiCopiedLayoutReview(firestore, { hostId, id: source.id, plan, name: source.name })\n : null\n const sourceShort = Boolean(sourceReview?.findings.length)\n const sourceTree =\n source && sourceShort ? await readAiDraftNodes(firestore, { kind: 'layout', hostId, id: source.id }) : null\n const sourceLines =\n source && sourceShort ? aiCopySourceLines('layout', source.name, sourceTree?.nodes ?? null) : []\n if (creation?.duplicateOf && source && !sourceShort) {\n const copy = await duplicate('layout', {\n orgId: job.orgId,\n hostId,\n sourceId: creation.duplicateOf,\n name,\n uid: job.createdBy,\n org: org as Record<string, unknown> | null,\n // The same claim the page step makes (AGL-3024): a copy core mints is\n // written under core's own id, never `draftId`, so a step that runs\n // again — a pass resumed, a run cut off after the copy — would mint a\n // second. The job's recorded id makes core replay the one it made.\n attemptKey: draftId,\n })\n if (copy.ok) {\n // A copy is not a build (AGL-3024). This branch generates nothing, so\n // whatever the plan promised BEYOND the source is not in the draft\n // unless the source already had it: a plan reading \"from a copy of the\n // base layout, adding a sidebar\" gets the base layout, the sidebar\n // nowhere in it, and until now reported Done. The copy's own regions\n // answer the plan's, and a copy that cannot be read answers nothing —\n // which is a stop for a person, never a Done.\n const review = await aiCopiedLayoutReview(firestore, { hostId, id: copy.id, plan, name })\n if (review) return aiUnspentOutcome(model, { review })\n const hostSubdomain = await aiSiteSubdomain(firestore, hostId)\n return aiUnspentOutcome(model, {\n outputs: [output({ id: copy.id, versionId: copy.versionId, name: copy.name, hostSubdomain })],\n })\n }\n if (copy.ok === false && copy.status === 403) {\n return aiUnspentOutcome(model, { review: aiLimitReview(copy.error) })\n }\n }\n\n const allowance = await aiDraftAllowanceRefusal(firestore, { kind: 'layout', hostId, org })\n if (allowance) return aiUnspentOutcome(model, { review: aiLimitReview(allowance) })\n\n const result = await runValidatedGeneration('layout', {\n step: 'job.layout',\n model,\n instructions: AI_JOB_LAYOUT_INSTRUCTIONS,\n inventory,\n messages: [{ role: 'user', content: aiJobLayoutPrompt(job, plan, name, sourceLines) }],\n tool: aiDoctrineTreeTool('layout'),\n maxTokens: AI_JOB_LAYOUT_STEP_BUDGET.maxTokens(model),\n ...(AI_ROUTING_TABLE['job.layout'].thinking ? { thinking: AI_ROUTING_TABLE['job.layout'].thinking } : {}),\n ...(AI_ROUTING_TABLE['job.layout'].effort ? { effort: AI_ROUTING_TABLE['job.layout'].effort } : {}),\n extend: aiLayoutReuseCheck(inventory, plan),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n\n const draft = await writeAiDraft(firestore, {\n kind: 'layout',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: result.value.nodes,\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its layout was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n // The facts the brief did not give, which the member fills before publishing (AGL-3056).\n const note = aiBracketedFactsNote({\n tree: { rootId: result.value.rootId, nodes: result.value.nodes as unknown as AiDoctrineTree['nodes'] },\n inventory,\n })\n return { ...spent, outputs: [output(draft, result.value.load, note)] }\n }\n}\n\n/** The layout step, building the components and forms its plan creates before the layout (AGL-3143 §15). */\nexport const runAiJobLayoutStep = aiBuildingPlanCreations('layout', 'job.layout', createAiJobLayoutStep())\n\n/** Registers the layout step and the check a layout job passes before it is created or resumed. */\nexport function registerAiLayoutJob(): void {\n registerAiJobStep('layout', runAiJobLayoutStep, {\n minimumMs: AI_JOB_LAYOUT_STEP_MINIMUM_MS,\n minimumMsFor: aiPlanCreationsRunMinimumMs('layout'),\n })\n registerAiJobAdmission('layout', aiLayoutJobAdmission)\n}\n"],"names":["CANVAS_ROOT_ELEMENT_ID","duplicateResource","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDoctrineTreeTool","runValidatedGeneration","walkTree","readSiteInventory","registerAiJobAdmission","aiBuildingPlanCreations","aiJobPlanCreationsRefusal","aiPlanCreationsRunMinimumMs","aiJobDraftId","aiDraftAdmissionRefusal","aiDraftAllowanceRefusal","aiSiteSubdomain","readAiDraft","readAiDraftNodes","writeAiDraft","aiBracketedFactsNote","aiCopySourceLines","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiJobBriefLine","aiLimitReview","aiModelNodeIds","aiPlacedComponentIds","aiPlanCreation","aiPlanReferenceLines","aiPlanReuseViolations","aiUnspentOutcome","aiPlanRegionViolations","aiPlannedLayoutRegions","aiJobStepBudget","registerAiJobStep","AI_JOB_LAYOUT_MAX_TOKENS","maxTokens","AI_JOB_LAYOUT_STEP_BUDGET","tier","AI_JOB_LAYOUT_STEP_MINIMUM_MS","minimumMs","AI_JOB_LAYOUT_DEFAULT_NAME","AI_JOB_LAYOUT_INSTRUCTIONS","text","join","aiJobLayoutPrompt","job","plan","name","source","NAVIGATION_NAME","FOOTER_NAME","isLink","node","componentId","props","aiLayoutReuseCheck","inventory","components","navigation","filter","component","test","tree","placed","violations","rootId","nodes","length","some","has","id","visits","slot","findIndex","header","slice","links","map","push","rule","code","message","nodeIds","sourceIds","aiCopiedLayoutReview","firestore","input","planned","regions","unreadable","stored","kind","hostId","reason","findings","aiLayoutJobAdmission","context","orgId","org","ownCheck","createAiJobLayoutStep","deps","readInventory","duplicate","now","signal","modelFor","orgSnapshot","Error","output","draft","load","note","resource","versionId","hostSubdomain","label","model","draftId","written","outputs","Promise","all","collection","doc","get","data","creation","duplicateOf","layouts","find","layout","undefined","sourceReview","sourceShort","Boolean","sourceTree","sourceLines","copy","sourceId","uid","createdBy","attemptKey","ok","review","status","error","allowance","result","step","instructions","messages","role","content","tool","thinking","effort","extend","spent","refused","value","runAiJobLayoutStep","registerAiLayoutJob","minimumMsFor"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-step.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 { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { duplicateResource } from '@aglyn/tenant-data-admin/server/duplicate-resource'\nimport type { AiJob, AiJobOutput, AiJobPlan, AiJobReview } from '../model/ai-jobs.types'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n aiDoctrineTreeTool,\n runValidatedGeneration,\n type AiValidatedTree,\n} from '../runtime/ai-doctrine'\nimport {\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineTree,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport {\n aiBuildingPlanCreations,\n aiJobPlanCreationsRefusal,\n aiPlanCreationsRunMinimumMs,\n} from './ai-job-plan-creations'\nimport { aiJobDraftId, aiOriginJobId } from './ai-job-draft-ids'\nimport {\n aiDraftAdmissionRefusal,\n aiDraftAllowanceRefusal,\n aiSiteSubdomain,\n readAiDraft,\n readAiDraftNodes,\n writeAiDraft,\n type AiDraftRecord,\n} from './ai-job-drafts'\nimport {\n aiBracketedFactsNote,\n aiCopySourceLines,\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiJobBriefLine,\n aiLimitReview,\n aiModelNodeIds,\n aiPlacedComponentIds,\n aiPlanCreation,\n aiPlanReferenceLines,\n aiPlanReuseViolations,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport { aiPlanRegionViolations, aiPlannedLayoutRegions } from './ai-job-plan-conformance'\nimport type { AiJobStepRunner } from './ai-job-text-step'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The layout step (AGL-2909): the generation step of a `layout` job, run once\n * a member confirmed the job's plan.\n *\n * It builds ONE shared layout — a header with the site's navigation, the\n * Layout Slot each page renders in, a footer — through\n * `runValidatedGeneration('layout', …)`, so every building rule holds on the\n * tree before it is kept, and adds three checks of its own through `extend`: a\n * component the confirmed plan reuses is placed (rule 7), every region the\n * plan gives the layout is built (AGL-3024), and a navigation or menu\n * component the site already has is placed rather than drawn again (rule 1).\n *\n * The layout lands as a new draft (`ai-job-drafts.ts`): no screen uses it and\n * no layout nests it, so nothing on the live site changes until a member\n * assigns it. A plan that starts from a copy of a layout the site has gets\n * that copy, through the platform's duplicate module, and nothing generated —\n * so the copy is read back and held to the plan's regions the same way\n * (`aiCopiedLayoutReview`), because a branch that generates nothing is the one\n * branch where a promise can go unmet with no model to answer for it. A\n * source that already lacks a region the plan gives the layout is not copied\n * at all: the layout is built from it (AGL-3143 §14).\n *\n * A generation step runs on the job beat, never at an inline door: it\n * registers the least time its lookup rounds, its answer and its re-ask need\n * (AGL-3035), runs without extended thinking — the plan was reasoned out and\n * confirmed before it — and asks for no more of a tighter ceiling than the\n * doctrine's default for a layout than fits that time on the model it runs.\n */\n\n/** The longest answer a layout may run to; the tree is held to the layout budget either way. */\nexport const AI_JOB_LAYOUT_MAX_TOKENS = AI_ROUTING_TABLE['job.layout'].maxTokens\n\n/**\n * The layout step's time (AGL-3035, AGL-3036): its two inventory-lookup\n * rounds, its answer and its re-ask at the routing table's ceiling on the tier\n * `job.layout` is served from, fitted to what a beat can give a step.\n */\nexport const AI_JOB_LAYOUT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.layout'],\n maxTokens: AI_JOB_LAYOUT_MAX_TOKENS,\n})\n\n/** The least time one layout step needs before it starts. */\nexport const AI_JOB_LAYOUT_STEP_MINIMUM_MS = AI_JOB_LAYOUT_STEP_BUDGET.minimumMs\n\n/** A layout's name when the plan names none. */\nexport const AI_JOB_LAYOUT_DEFAULT_NAME = 'Site layout'\n\n/** The layout step's own instructions, cached after the doctrine. */\nexport const AI_JOB_LAYOUT_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n 'You build one shared layout for a website: the frame every page of the site renders inside. Answer with submit_layout: the whole layout as one flat node map.',\n 'A layout is a header, one Layout Slot and a footer, top to bottom. The header is an App Bar (muiAppBar) holding a Toolbar Content (muiToolbar) with the site’s name as text and its navigation. The footer is a Section (section) whose element is footer.',\n 'Navigation links the site’s own pages by id: a Page Link (muiScreenLink) whose screenId is a page id from the site inventory, in the order the brief and the confirmed plan give, seven at most in the header. Never invent a page id, and never link a page by its address.',\n 'When the site inventory lists a component whose name says it is navigation, a menu or a mega menu, place it in the header as an instance instead of building the navigation again. Place every component the confirmed plan reuses.',\n 'The Layout Slot (layoutSlot) is where each page renders and is the page’s main landmark: leave its component unset or main, and put nothing inside it. A layout carries no h1, because every page brings its own.',\n ].join('\\n'),\n },\n]\n\n/**\n * The layout job as the generation's user turn: the name, the brief, the\n * confirmed plan, and the layout a short copy would have started from\n * (`aiCopySourceLines`, AGL-3143 §14).\n */\nexport function aiJobLayoutPrompt(\n job: Pick<AiJob, 'brief'>,\n plan: AiJobPlan | null,\n name: string,\n source: readonly string[] = [],\n): string {\n return [`Layout name: ${name}`, aiJobBriefLine(job), ...aiPlanReferenceLines(plan), ...source].join('\\n')\n}\n\n/** A component name that says it is the site's navigation; a footer's links are not. */\nconst NAVIGATION_NAME = /\\b(?:nav|navigation|navbar|menu|mega ?menu)\\b/i\nconst FOOTER_NAME = /\\bfooter\\b/i\n\n/** A node that links somewhere: a Screen Link, or a Button pointed at a screen or an address. */\nfunction isLink(node: AiDoctrineNode): boolean {\n if (node.componentId === 'muiScreenLink') return true\n return (\n node.componentId === 'muiButton' &&\n (typeof node.props?.['screenId'] === 'string' || typeof node.props?.['href'] === 'string')\n )\n}\n\n/**\n * The layout door's checks on a tree the doctrine admitted: every component\n * the confirmed plan reuses is placed, every region the confirmed plan gives\n * the layout is built (AGL-3024), and navigation the site already keeps as a\n * component is placed rather than drawn again in the header.\n */\nexport function aiLayoutReuseCheck(\n inventory: AiSiteInventory | null,\n plan: AiJobPlan | null,\n): (tree: AiValidatedTree) => AiDoctrineViolation[] {\n const components = inventory?.components ?? []\n const navigation = components.filter(\n (component) => NAVIGATION_NAME.test(component.name) && !FOOTER_NAME.test(component.name),\n )\n return (tree) => {\n const placed = aiPlacedComponentIds(tree)\n const violations: AiDoctrineViolation[] = [\n ...aiPlanReuseViolations(inventory, plan, placed, 'layout'),\n ...aiPlanRegionViolations(plan, {\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n }),\n ]\n if (navigation.length && !navigation.some((component) => placed.has(component.id))) {\n const visits = walkTree({\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n })\n const slot = visits.findIndex(({ node }) => node.componentId === 'layoutSlot')\n const header = slot === -1 ? visits : visits.slice(0, slot)\n const links = header.filter(({ node }) => isLink(node)).map(({ id }) => id)\n if (links.length >= 2) {\n violations.push({\n rule: 1,\n code: 'navigation-rebuilt',\n message: `The site already has the \"${navigation[0].name}\" component. Place it in the header instead of building the navigation again.`,\n nodeIds: aiModelNodeIds(links, tree.sourceIds),\n })\n }\n }\n return violations\n }\n}\n\n/**\n * The regions the confirmed plan gives the layout, against the layout a COPY\n * actually produced (AGL-3024); `null` when the copy keeps the plan, or when\n * the plan promised no region to keep.\n *\n * The duplicate branch generates nothing, so it is the one place a plan's\n * promise can go unmet without a model ever answering for it. A copy that\n * cannot be read — deleted between the write and this read, or stored under a\n * root this check does not know — settles nothing, and a promise this step\n * cannot settle is reviewed by a person rather than reported as kept.\n */\nexport async function aiCopiedLayoutReview(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; id: string; name: string; plan: AiJobPlan | null },\n): Promise<AiJobReview | null> {\n const planned = aiPlannedLayoutRegions(input.plan)\n if (!planned.regions.length && !planned.unreadable.length) return null\n const stored = await readAiDraftNodes(firestore, {\n kind: 'layout',\n hostId: input.hostId,\n id: input.id,\n })\n const nodes = (stored?.nodes ?? {}) as unknown as Record<string, AiDoctrineNode>\n if (!nodes[CANVAS_ROOT_ELEMENT_ID]) {\n return {\n reason: 'doctrine',\n message: `\"${input.name}\" was copied from the layout the plan names, and this job could not read the copy back to check it has the regions the plan gives it. Open the layout and check it before you put a page in it.`,\n findings: [],\n }\n }\n const violations = aiPlanRegionViolations(input.plan, { rootId: CANVAS_ROOT_ELEMENT_ID, nodes })\n if (!violations.length) return null\n return aiDoctrineReview({\n message: `\"${input.name}\" is a copy of the layout the plan names, and a copy is all it is: ${violations[0].message}`,\n violations,\n })\n}\n\n/**\n * A layout job is admitted for a site of the job's own org with a shared\n * layout to spare, and confirmed only for a plan whose other creations it\n * builds (AGL-3143 §15).\n */\nexport const aiLayoutJobAdmission: AiJobAdmission = (context) =>\n aiDraftAdmissionRefusal(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n kind: 'layout',\n org: context.org,\n ownCheck: (hostId) => aiJobPlanCreationsRefusal('layout', context, hostId),\n })\n\nexport interface AiJobLayoutStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The platform's duplicate module, for a plan that starts from a copy. */\n duplicate?: typeof duplicateResource\n}\n\nexport function createAiJobLayoutStep(deps: AiJobLayoutStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const duplicate = deps.duplicate ?? duplicateResource\n return async ({ job, now, signal, firestore, modelFor }) => {\n const hostId = job.hostId\n if (!hostId) throw new Error('a layout job names no site, and its admission refuses one')\n const output = (draft: AiDraftRecord, load?: AiLoadEstimate | null, note?: string | null): AiJobOutput => ({\n resource: 'layout',\n id: draft.id,\n versionId: draft.versionId,\n hostId,\n hostSubdomain: draft.hostSubdomain,\n label: draft.name,\n ...(load ? { load } : {}),\n ...(note ? { note } : {}),\n })\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.layout') ?? aiModelForStep('job.layout')\n\n // A run cut off after its draft was written reports that draft, found by the id the job recorded.\n const draftId = aiJobDraftId(job, 'layout')\n const written = await readAiDraft(firestore, { kind: 'layout', hostId, id: draftId })\n if (written) return aiUnspentOutcome(model, { outputs: [output(written)] })\n\n const [inventory, orgSnapshot] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n ])\n const org = (orgSnapshot.data() ?? null) as Partial<AglynOrgBilling> | null\n const plan = aiConfirmedPlan(job)\n const creation = aiPlanCreation(plan, 'layout')\n const name = creation?.name || AI_JOB_LAYOUT_DEFAULT_NAME\n\n // The plan starts from a copy of a layout the site has (rule 15): the copy\n // is the draft, and nothing is generated. A source gone since the plan\n // was made is built from the brief instead. A source that cannot keep the\n // plan's regions is built FROM instead (AGL-3143 §14), as the page step\n // builds from a short source: copying it could only end in the review\n // below, and Try again would copy the same thing. Only a source read and\n // found short diverts; one this step cannot read still copies, and the\n // review after the copy answers for it.\n const source = creation?.duplicateOf\n ? inventory.layouts.find((layout) => layout.id === creation.duplicateOf)\n : undefined\n const sourceReview = source\n ? await aiCopiedLayoutReview(firestore, { hostId, id: source.id, plan, name: source.name })\n : null\n const sourceShort = Boolean(sourceReview?.findings.length)\n const sourceTree =\n source && sourceShort ? await readAiDraftNodes(firestore, { kind: 'layout', hostId, id: source.id }) : null\n const sourceLines =\n source && sourceShort ? aiCopySourceLines('layout', source.name, sourceTree?.nodes ?? null) : []\n if (creation?.duplicateOf && source && !sourceShort) {\n const copy = await duplicate('layout', {\n orgId: job.orgId,\n hostId,\n sourceId: creation.duplicateOf,\n name,\n uid: job.createdBy,\n org: org as Record<string, unknown> | null,\n // The same claim the page step makes (AGL-3024): a copy core mints is\n // written under core's own id, never `draftId`, so a step that runs\n // again — a pass resumed, a run cut off after the copy — would mint a\n // second. The job's recorded id makes core replay the one it made.\n attemptKey: draftId,\n })\n if (copy.ok) {\n // A copy is not a build (AGL-3024). This branch generates nothing, so\n // whatever the plan promised BEYOND the source is not in the draft\n // unless the source already had it: a plan reading \"from a copy of the\n // base layout, adding a sidebar\" gets the base layout, the sidebar\n // nowhere in it, and until now reported Done. The copy's own regions\n // answer the plan's, and a copy that cannot be read answers nothing —\n // which is a stop for a person, never a Done.\n const review = await aiCopiedLayoutReview(firestore, { hostId, id: copy.id, plan, name })\n if (review) return aiUnspentOutcome(model, { review })\n const hostSubdomain = await aiSiteSubdomain(firestore, hostId)\n return aiUnspentOutcome(model, {\n outputs: [output({ id: copy.id, versionId: copy.versionId, name: copy.name, hostSubdomain })],\n })\n }\n if (copy.ok === false && copy.status === 403) {\n return aiUnspentOutcome(model, { review: aiLimitReview(copy.error) })\n }\n }\n\n const allowance = await aiDraftAllowanceRefusal(firestore, { kind: 'layout', hostId, org })\n if (allowance) return aiUnspentOutcome(model, { review: aiLimitReview(allowance) })\n\n const result = await runValidatedGeneration('layout', {\n step: 'job.layout',\n model,\n instructions: AI_JOB_LAYOUT_INSTRUCTIONS,\n inventory,\n messages: [{ role: 'user', content: aiJobLayoutPrompt(job, plan, name, sourceLines) }],\n tool: aiDoctrineTreeTool('layout'),\n maxTokens: AI_JOB_LAYOUT_STEP_BUDGET.maxTokens(model),\n ...(AI_ROUTING_TABLE['job.layout'].thinking ? { thinking: AI_ROUTING_TABLE['job.layout'].thinking } : {}),\n ...(AI_ROUTING_TABLE['job.layout'].effort ? { effort: AI_ROUTING_TABLE['job.layout'].effort } : {}),\n extend: aiLayoutReuseCheck(inventory, plan),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n\n const draft = await writeAiDraft(firestore, {\n kind: 'layout',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: result.value.nodes,\n aiJobId: aiOriginJobId(job),\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its layout was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n // The facts the brief did not give, which the member fills before publishing (AGL-3056).\n const note = aiBracketedFactsNote({\n tree: { rootId: result.value.rootId, nodes: result.value.nodes as unknown as AiDoctrineTree['nodes'] },\n inventory,\n })\n return { ...spent, outputs: [output(draft, result.value.load, note)] }\n }\n}\n\n/** The layout step, building the components and forms its plan creates before the layout (AGL-3143 §15). */\nexport const runAiJobLayoutStep = aiBuildingPlanCreations('layout', 'job.layout', createAiJobLayoutStep())\n\n/** Registers the layout step and the check a layout job passes before it is created or resumed. */\nexport function registerAiLayoutJob(): void {\n registerAiJobStep('layout', runAiJobLayoutStep, {\n minimumMs: AI_JOB_LAYOUT_STEP_MINIMUM_MS,\n minimumMsFor: aiPlanCreationsRunMinimumMs('layout'),\n })\n registerAiJobAdmission('layout', aiLayoutJobAdmission)\n}\n"],"names":["CANVAS_ROOT_ELEMENT_ID","duplicateResource","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDoctrineTreeTool","runValidatedGeneration","walkTree","readSiteInventory","registerAiJobAdmission","aiBuildingPlanCreations","aiJobPlanCreationsRefusal","aiPlanCreationsRunMinimumMs","aiJobDraftId","aiOriginJobId","aiDraftAdmissionRefusal","aiDraftAllowanceRefusal","aiSiteSubdomain","readAiDraft","readAiDraftNodes","writeAiDraft","aiBracketedFactsNote","aiCopySourceLines","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiJobBriefLine","aiLimitReview","aiModelNodeIds","aiPlacedComponentIds","aiPlanCreation","aiPlanReferenceLines","aiPlanReuseViolations","aiUnspentOutcome","aiPlanRegionViolations","aiPlannedLayoutRegions","aiJobStepBudget","registerAiJobStep","AI_JOB_LAYOUT_MAX_TOKENS","maxTokens","AI_JOB_LAYOUT_STEP_BUDGET","tier","AI_JOB_LAYOUT_STEP_MINIMUM_MS","minimumMs","AI_JOB_LAYOUT_DEFAULT_NAME","AI_JOB_LAYOUT_INSTRUCTIONS","text","join","aiJobLayoutPrompt","job","plan","name","source","NAVIGATION_NAME","FOOTER_NAME","isLink","node","componentId","props","aiLayoutReuseCheck","inventory","components","navigation","filter","component","test","tree","placed","violations","rootId","nodes","length","some","has","id","visits","slot","findIndex","header","slice","links","map","push","rule","code","message","nodeIds","sourceIds","aiCopiedLayoutReview","firestore","input","planned","regions","unreadable","stored","kind","hostId","reason","findings","aiLayoutJobAdmission","context","orgId","org","ownCheck","createAiJobLayoutStep","deps","readInventory","duplicate","now","signal","modelFor","orgSnapshot","Error","output","draft","load","note","resource","versionId","hostSubdomain","label","model","draftId","written","outputs","Promise","all","collection","doc","get","data","creation","duplicateOf","layouts","find","layout","undefined","sourceReview","sourceShort","Boolean","sourceTree","sourceLines","copy","sourceId","uid","createdBy","attemptKey","ok","review","status","error","allowance","result","step","instructions","messages","role","content","tool","thinking","effort","extend","spent","refused","value","aiJobId","runAiJobLayoutStep","registerAiLayoutJob","minimumMsFor"],"mappings":"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"}
@@ -96,15 +96,16 @@ export interface AiPageSectionPromptInput {
96
96
  /** The most elements the section may carry at this pass's answer ceiling. */
97
97
  maxElements: number;
98
98
  /**
99
- * Whether the workspace keeps reusable components and saved forms
100
- * (AGL-3030); `false` asks for the section built inline. Absent is `true`.
99
+ * Whether the workspace keeps reusable components (AGL-3030); `false`
100
+ * asks for a repeated item drawn in the section. Absent is `true`. A form
101
+ * is never drawn inline, whatever this says (AGL-3596).
101
102
  */
102
103
  reusableComponents?: boolean;
103
104
  /** The dataset this page is the record template of; absent for a page that is one page. */
104
105
  record?: AiPageRecordTemplate | null;
105
106
  }
106
- /** What a section request says where the workspace keeps no reusable components or saved forms. */
107
- export declare const AI_PAGE_SECTION_INLINE_LINE = "This site keeps no saved forms or reusable components: write a repeated item once, and draw a form as a Form holding its Form Fields.";
107
+ /** What a section request says where the workspace keeps no reusable components. */
108
+ export declare const AI_PAGE_SECTION_INLINE_LINE = "This site keeps no reusable components: write a repeated item once.";
108
109
  /**
109
110
  * How a repeated item is written once (AGL-3053): the item's subtree once,
110
111
  * with placeholders where its copies differ and their values listed, which
@@ -195,7 +196,7 @@ export declare function aiEmptyPage(): NodesMap;
195
196
  export declare function aiPageWithSection(page: NodesMap, section: AiPageSection, sectionIds: readonly string[]): NodesMap;
196
197
  /**
197
198
  * What a tree generated against this inventory may reference, the brand it is
198
- * held to, where the workspace keeps no reusable components or saved forms
199
+ * held to, where the workspace keeps no reusable components
199
200
  * (AGL-3030) that the page is built inline, the sections its plan names,
200
201
  * which a link may take a visitor to (AGL-3097), and the players its plan
201
202
  * lists for this page, the only ones it may embed (AGL-3433).
@@ -84,7 +84,7 @@ import { aiPlanItemCountViolations } from "./ai-job-plan-conformance.js";
84
84
  'You build a web page one section at a time, in the order a confirmed plan gives. Each answer is ONE section: call submit_section with a flat node map whose root is the document wrapper (componentId "div") holding exactly one Section (section).',
85
85
  'The first section is the top of the page and holds the page’s one h1, its title. Every later section opens with an h2, and headings below it step down one level at a time.',
86
86
  'Place what the section’s plan line names. A component the site has is placed as an instance, never drawn again: componentId "reusableInstance", props {"refId": "<component id>", "propValues": {…}} filling the props the inventory lists. A form the site has is a Form (form) whose formId is that form’s id. Another page of the site is linked by its page id, with a Page Link (muiScreenLink) or a Button (muiButton) whose screenId is that id.',
87
- 'Where the request says the site keeps no saved forms or reusable components, draw them in the section instead: a repeated item is written once, and a form is a Form (form) with no formId, a formName saying what it collects and a submitLabel, holding one Form Field (formField) for each answer with its fieldName, label and fieldType.',
87
+ 'Where the request says the site keeps no reusable components, draw a repeated item in the section instead, written once. A form is only ever a saved one placed by its formId: where the plan line names no form, the section draws no form and no form fields.',
88
88
  'Lay the section out with a Container, a Stack or a Grid ("container": true, "spacing": 3) of Grid items sized like "xs:12 md:4", and size nothing with a fixed width. Colors come from the theme’s palette tokens such as primary.main, spacing from the spacing scale, and type from the text variants.',
89
89
  'The page renders inside the site’s layout, so a section is never a header, navigation or footer.',
90
90
  'A picture is an Image (image) with alt text that says what it should show and no src, for the site owner to fill from the media library.',
@@ -149,7 +149,7 @@ import { aiPlanItemCountViolations } from "./ai-job-plan-conformance.js";
149
149
  */ export function aiPageRecordNote(template) {
150
150
  return `This page is drafted as the record template of "${template.name}". To serve a page per record at /${template.base}/…, open Page Properties → Record pages and save its dataset, address and fields.`;
151
151
  }
152
- /** What a section request says where the workspace keeps no reusable components or saved forms. */ export const AI_PAGE_SECTION_INLINE_LINE = 'This site keeps no saved forms or reusable components: write a repeated item once, and draw a form as a Form holding its Form Fields.';
152
+ /** What a section request says where the workspace keeps no reusable components. */ export const AI_PAGE_SECTION_INLINE_LINE = 'This site keeps no reusable components: write a repeated item once.';
153
153
  /** How a repeated item is written once, in the words a request and a re-ask share. */ const AI_PAGE_SECTION_REPEAT_SHAPE = `put {{1}}, {{2}}… where its copies differ, and give its outermost node "${AI_REPEAT_KEY}", one list of values a copy, in that order, as [["Title 1", "Text 1"], ["Title 2", "Text 2"]]`;
154
154
  /**
155
155
  * How a repeated item is written once (AGL-3053): the item's subtree once,
@@ -307,7 +307,7 @@ function isRecord(value) {
307
307
  }
308
308
  /**
309
309
  * What a tree generated against this inventory may reference, the brand it is
310
- * held to, where the workspace keeps no reusable components or saved forms
310
+ * held to, where the workspace keeps no reusable components
311
311
  * (AGL-3030) that the page is built inline, the sections its plan names,
312
312
  * which a link may take a visitor to (AGL-3097), and the players its plan
313
313
  * lists for this page, the only ones it may embed (AGL-3433).