@elytracms/studio 0.0.100 → 0.0.101

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 (124) hide show
  1. package/convex/analytics-cost.test.ts +1 -1
  2. package/convex/redirect-migration.test.ts +1 -1
  3. package/convex/redirects.ts +2 -2
  4. package/convex/shared-capture.convex.test.ts +165 -0
  5. package/dist/components/ai/ai-chat-panel-lazy.js +7 -2
  6. package/dist/components/ai/ai-chat-panel-lazy.js.map +1 -1
  7. package/dist/components/ai/proposal-card.js +2 -1
  8. package/dist/components/ai/proposal-card.js.map +1 -1
  9. package/dist/components/ai/proposal-unfilled.d.ts +14 -0
  10. package/dist/components/ai/proposal-unfilled.js +33 -0
  11. package/dist/components/ai/proposal-unfilled.js.map +1 -0
  12. package/dist/components/ai/translate-document-action.js +8 -6
  13. package/dist/components/ai/translate-document-action.js.map +1 -1
  14. package/dist/components/app-views/studio-config.d.ts +12 -12
  15. package/dist/components/app-views/studio-config.js.map +1 -1
  16. package/dist/components/builder/builder-store.d.ts +37 -1
  17. package/dist/components/builder/builder-store.js +21 -6
  18. package/dist/components/builder/builder-store.js.map +1 -1
  19. package/dist/components/builder/canvas-chrome-css.d.ts +1 -1
  20. package/dist/components/builder/canvas-chrome-css.js +1 -1
  21. package/dist/components/builder/canvas-chrome-css.js.map +1 -1
  22. package/dist/components/builder/canvas-chrome.d.ts +1 -1
  23. package/dist/components/builder/canvas-chrome.js +1 -1
  24. package/dist/components/builder/canvas-chrome.js.map +1 -1
  25. package/dist/components/builder/canvas-rebase.d.ts +4 -4
  26. package/dist/components/builder/canvas-rebase.js +3 -2
  27. package/dist/components/builder/canvas-rebase.js.map +1 -1
  28. package/dist/components/builder/canvas-render-context.d.ts +1 -1
  29. package/dist/components/builder/canvas-render-context.js +1 -1
  30. package/dist/components/builder/canvas-render-context.js.map +1 -1
  31. package/dist/components/builder/composition-field-editor-lazy.js +2 -1
  32. package/dist/components/builder/composition-field-editor-lazy.js.map +1 -1
  33. package/dist/components/builder/reuse-context.d.ts +8 -2
  34. package/dist/components/builder/reuse-context.js +62 -7
  35. package/dist/components/builder/reuse-context.js.map +1 -1
  36. package/dist/components/builder/reuse.d.ts +106 -70
  37. package/dist/components/builder/reuse.js +125 -108
  38. package/dist/components/builder/reuse.js.map +1 -1
  39. package/dist/components/cms/cms-store.d.ts +9 -1
  40. package/dist/components/cms/cms-store.js +1 -0
  41. package/dist/components/cms/cms-store.js.map +1 -1
  42. package/dist/components/cms/detail-editor.js +13 -13
  43. package/dist/components/cms/detail-editor.js.map +1 -1
  44. package/dist/components/cms/prop-fields.js +1 -1
  45. package/dist/components/cms/prop-fields.js.map +1 -1
  46. package/dist/components/cms/rich-text/lazy.js +2 -1
  47. package/dist/components/cms/rich-text/lazy.js.map +1 -1
  48. package/dist/components/cms/rich-text/toolbar-state.d.ts +1 -1
  49. package/dist/components/cms/rich-text/toolbar-state.js +1 -1
  50. package/dist/components/cms/rich-text/toolbar-state.js.map +1 -1
  51. package/dist/components/cms/use-field-focus.js +3 -3
  52. package/dist/components/cms/use-field-focus.js.map +1 -1
  53. package/dist/components/cms/version-compare-lazy.js +2 -1
  54. package/dist/components/cms/version-compare-lazy.js.map +1 -1
  55. package/dist/components/components/components-index.js +1 -1
  56. package/dist/components/components/components-index.js.map +1 -1
  57. package/dist/components/deployment-setup-gate.d.ts +1 -1
  58. package/dist/components/deployment-setup-gate.js +1 -1
  59. package/dist/components/deployment-setup-gate.js.map +1 -1
  60. package/dist/components/load-failure.d.ts +62 -0
  61. package/dist/components/load-failure.js +110 -0
  62. package/dist/components/load-failure.js.map +1 -0
  63. package/dist/components/notifications/shell-findings.js +3 -3
  64. package/dist/components/notifications/shell-findings.js.map +1 -1
  65. package/dist/customization.d.ts +3 -3
  66. package/dist/customization.js.map +1 -1
  67. package/dist/lib/ai/apply-content.js +99 -28
  68. package/dist/lib/ai/apply-content.js.map +1 -1
  69. package/dist/lib/ai/chat-context.d.ts +73 -7
  70. package/dist/lib/ai/chat-context.js +133 -14
  71. package/dist/lib/ai/chat-context.js.map +1 -1
  72. package/dist/lib/ai/chat-state.d.ts +7 -1
  73. package/dist/lib/ai/chat-state.js.map +1 -1
  74. package/dist/lib/ai/constraints.d.ts +5 -0
  75. package/dist/lib/ai/constraints.js +21 -6
  76. package/dist/lib/ai/constraints.js.map +1 -1
  77. package/dist/lib/ai/fake-provider.d.ts +76 -26
  78. package/dist/lib/ai/fake-provider.js +210 -66
  79. package/dist/lib/ai/fake-provider.js.map +1 -1
  80. package/dist/lib/ai/index.d.ts +1 -0
  81. package/dist/lib/ai/index.js +1 -0
  82. package/dist/lib/ai/index.js.map +1 -1
  83. package/dist/lib/ai/keyless-assistant.d.ts +37 -0
  84. package/dist/lib/ai/keyless-assistant.js +193 -0
  85. package/dist/lib/ai/keyless-assistant.js.map +1 -0
  86. package/dist/lib/ai/shape.d.ts +25 -2
  87. package/dist/lib/ai/shape.js +58 -7
  88. package/dist/lib/ai/shape.js.map +1 -1
  89. package/dist/lib/ai/tools.d.ts +11 -1
  90. package/dist/lib/ai/tools.js +51 -6
  91. package/dist/lib/ai/tools.js.map +1 -1
  92. package/dist/lib/ai/translate-document.js +6 -5
  93. package/dist/lib/ai/translate-document.js.map +1 -1
  94. package/dist/lib/ai/types.d.ts +22 -7
  95. package/dist/lib/ai/types.js.map +1 -1
  96. package/dist/lib/ai/update-fields.js +9 -6
  97. package/dist/lib/ai/update-fields.js.map +1 -1
  98. package/dist/lib/ai/writer-form.d.ts +11 -3
  99. package/dist/lib/ai/writer-form.js +105 -18
  100. package/dist/lib/ai/writer-form.js.map +1 -1
  101. package/dist/lib/auth-errors.d.ts +1 -1
  102. package/dist/lib/auth-errors.js +1 -1
  103. package/dist/lib/auth-errors.js.map +1 -1
  104. package/dist/lib/engine-version.d.ts +1 -1
  105. package/dist/lib/engine-version.js +1 -1
  106. package/dist/lib/engine-version.js.map +1 -1
  107. package/dist/lib/i18n/messages.de.d.ts +1 -1
  108. package/dist/lib/i18n/messages.de.js +23 -7
  109. package/dist/lib/i18n/messages.de.js.map +1 -1
  110. package/dist/lib/i18n/messages.en.d.ts +18 -3
  111. package/dist/lib/i18n/messages.en.js +29 -9
  112. package/dist/lib/i18n/messages.en.js.map +1 -1
  113. package/dist/lib/operations/cms-persistence.d.ts +18 -0
  114. package/dist/lib/operations/cms-persistence.js +47 -0
  115. package/dist/lib/operations/cms-persistence.js.map +1 -1
  116. package/dist/lib/workspace/index.d.ts +3 -3
  117. package/dist/lib/workspace/index.js +3 -3
  118. package/dist/lib/workspace/index.js.map +1 -1
  119. package/dist/router/navigate-once.d.ts +1 -1
  120. package/dist/router/navigate-once.js.map +1 -1
  121. package/dist/router/root.d.ts +3 -2
  122. package/dist/router/root.js +25 -4
  123. package/dist/router/root.js.map +1 -1
  124. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"chat-state.js","sourceRoot":"","sources":["../../../src/lib/ai/chat-state.ts"],"names":[],"mappings":"AAmFA,MAAM,CAAC,MAAM,sBAAsB,GAAoB;IACrD,SAAS,EAAE,EAAE;IACb,KAAK,EAAE,IAAI;CACZ,CAAA;AAWD,SAAS,UAAU,CACjB,KAAsB,EACtB,UAAkB,EAClB,MAA8B;IAE9B,MAAM,QAAQ,GAAG,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;IAC5C,mEAAmE;IACnE,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAA;IAC5D,OAAO;QACL,GAAG,KAAK;QACR,SAAS,EAAE,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,GAAG,QAAQ,EAAE,MAAM,EAAE,EAAE;KACzE,CAAA;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,KAAsB,EACtB,MAAwB;IAExB,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,SAAS;YACZ,OAAO;gBACL,GAAG,KAAK;gBACR,SAAS,EAAE;oBACT,GAAG,KAAK,CAAC,SAAS;oBAClB,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE;iBACtF;aACF,CAAA;QACH,KAAK,OAAO;YACV,OAAO,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,CAAA;QACxD,KAAK,QAAQ;YACX,OAAO,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,UAAU,CAAC,CAAA;QACzD,KAAK,MAAM;YACT,OAAO,KAAK,CAAC,KAAK,KAAK,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,CAAA;QACrF,KAAK,aAAa;YAChB,OAAO,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;QACjE,KAAK,OAAO;YACV,OAAO,sBAAsB,CAAA;QAC/B;YACE,OAAO,KAAK,CAAA;IAChB,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAmB,EACnB,MAA0E;IAE1E,OAAO;QACL,MAAM,EAAE;YACN,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,EAAE,EAAE,KAAK,CAAC,EAAE;YACZ,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE;YAC5B,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9D;QACD,KAAK,EAAE;YACL,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,EAAE,EAAE,KAAK,CAAC,EAAE;YACZ,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC3D;KACF,CAAA;AACH,CAAC","sourcesContent":["/**\n * The chat panel's proposal-review state (EC-199, reshaped by EC-334).\n *\n * `useChat` (the AI SDK) owns the conversation: messages, streaming text, tool\n * parts, busy-status. This reducer keeps ONLY what the SDK does not own:\n *\n * - the proposal review map — the FULL proposals the terminal tools\n * (`createDocument` / `translateDocument`) produce, keyed by `toolCallId`\n * (the model receives only a compact summary as the tool output; the editor\n * reviews the real patch here), each `pending` → `applied` | `rejected`;\n * - the last blocking error (route/provider/step-cap failure), surfaced as a\n * visible note and cleared on the next submit.\n *\n * EC-380 (`clear`): a new conversation resets this state entirely — pending\n * proposals are discarded with REJECT semantics (they were never applied and\n * nothing half-applied survives; an applied draft already lives in the CMS and\n * is untouched). The panel pairs the dispatch with a fresh `useChat` id.\n *\n * House testing pattern (AGENTS.md): the panel's behaviour lives in this\n * deterministic reducer, tested directly; the React component is a thin shell.\n */\nimport type { CmsDocument, CmsValidationIssue, DocumentPatch, FieldValues } from '@elytracms/core/cms-core'\nimport type { AiProvenance, ContentPatch } from './types'\nimport type { TranslateSkip } from './translate-document'\n\n/**\n * A proposal's kind: fresh authoring, a fill-empty translation of an existing\n * doc, or an EC-378 field update of an existing doc (old → new diff).\n */\nexport type ProposalOrigin = 'create' | 'translate' | 'update'\n\n/**\n * The full proposal payload a terminal tool hands to the review UI (via\n * `onProposal` on the executor — see `tools.ts`). The model never sees this;\n * it gets a compact summary as the tool output.\n */\nexport interface ChatProposal {\n origin: ProposalOrigin\n collection: string\n documentId: string\n patch: ContentPatch\n provenance: AiProvenance\n /** Generation attempts made (1 = happy path, >1 = repaired). */\n attempts: number\n /** Non-error issues on the accepted content (warnings). */\n issues: CmsValidationIssue[]\n /** For a translation/update: the existing document values the diff renders \"before\". */\n before?: { values?: FieldValues; localized?: Record<string, FieldValues> }\n /**\n * EC-595 A2 — for an update or a translation: the positions it changes, as patches\n * (a field's bucket, a block prop by node id). Applying the proposal persists exactly\n * these through `documents.patch` — never the merged whole document, which would\n * revert any change made since the proposal was built. Absent for a create.\n */\n patches?: readonly DocumentPatch[]\n /**\n * EC-647 — for an update or a translation: the revision of the read the proposal was\n * built from. Applying it writes {@link patches} AT this revision, so a position changed\n * since (by anyone else) is refused on that field, never overwritten. Absent → the apply\n * names no revision and is refused.\n */\n revision?: number\n /**\n * EC-593 (ratified 4e) — for a translation: every text position it could NOT\n * translate, shown on the card the editor reviews (never a silent skip).\n */\n skipped?: readonly TranslateSkip[]\n}\n\n/** A proposal under review: {@link ChatProposal} + its review status. */\nexport interface ProposalReview extends ChatProposal {\n /** The tool call that produced it (`toolCallId`) — the review key. */\n id: string\n status: 'pending' | 'applied' | 'rejected'\n}\n\nexport interface ChatReviewState {\n /** Proposals keyed by `toolCallId`. */\n proposals: Record<string, ProposalReview>\n /** Last blocking error (route/provider/step-cap), surfaced then cleared on submit. */\n error: string | null\n}\n\nexport const initialChatReviewState: ChatReviewState = {\n proposals: {},\n error: null,\n}\n\nexport type ChatReviewAction =\n | { type: 'propose'; toolCallId: string; proposal: ChatProposal }\n | { type: 'apply'; toolCallId: string }\n | { type: 'reject'; toolCallId: string }\n | { type: 'fail'; message: string }\n | { type: 'clear-error' }\n /** EC-380: new conversation — full reset; pending proposals discarded (reject semantics). */\n | { type: 'clear' }\n\nfunction transition(\n state: ChatReviewState,\n toolCallId: string,\n status: 'applied' | 'rejected',\n): ChatReviewState {\n const existing = state.proposals[toolCallId]\n // Only a PENDING proposal transitions — apply/reject are one-shot.\n if (!existing || existing.status !== 'pending') return state\n return {\n ...state,\n proposals: { ...state.proposals, [toolCallId]: { ...existing, status } },\n }\n}\n\nexport function chatReviewReducer(\n state: ChatReviewState,\n action: ChatReviewAction,\n): ChatReviewState {\n switch (action.type) {\n case 'propose':\n return {\n ...state,\n proposals: {\n ...state.proposals,\n [action.toolCallId]: { ...action.proposal, id: action.toolCallId, status: 'pending' },\n },\n }\n case 'apply':\n return transition(state, action.toolCallId, 'applied')\n case 'reject':\n return transition(state, action.toolCallId, 'rejected')\n case 'fail':\n return state.error === action.message ? state : { ...state, error: action.message }\n case 'clear-error':\n return state.error === null ? state : { ...state, error: null }\n case 'clear':\n return initialChatReviewState\n default:\n return state\n }\n}\n\n/**\n * Turn a proposal patch into the before/after document pair the EC-317 diff\n * engine (`diffDocuments`) compares. For a fresh document `before` is empty\n * (the whole proposal reads as \"added\"); for a translation pass the existing\n * document's values. Pure — the reviewable diff is a straight\n * `diffDocuments(collection, before, after)`.\n */\nexport function proposalDocuments(\n patch: ContentPatch,\n before?: { values?: FieldValues; localized?: Record<string, FieldValues> },\n): { before: CmsDocument; after: CmsDocument } {\n return {\n before: {\n collection: patch.collection,\n id: patch.id,\n values: before?.values ?? {},\n ...(before?.localized ? { localized: before.localized } : {}),\n },\n after: {\n collection: patch.collection,\n id: patch.id,\n values: patch.values,\n ...(patch.localized ? { localized: patch.localized } : {}),\n },\n }\n}\n"]}
1
+ {"version":3,"file":"chat-state.js","sourceRoot":"","sources":["../../../src/lib/ai/chat-state.ts"],"names":[],"mappings":"AAyFA,MAAM,CAAC,MAAM,sBAAsB,GAAoB;IACrD,SAAS,EAAE,EAAE;IACb,KAAK,EAAE,IAAI;CACZ,CAAA;AAWD,SAAS,UAAU,CACjB,KAAsB,EACtB,UAAkB,EAClB,MAA8B;IAE9B,MAAM,QAAQ,GAAG,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;IAC5C,mEAAmE;IACnE,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAA;IAC5D,OAAO;QACL,GAAG,KAAK;QACR,SAAS,EAAE,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,GAAG,QAAQ,EAAE,MAAM,EAAE,EAAE;KACzE,CAAA;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,KAAsB,EACtB,MAAwB;IAExB,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,SAAS;YACZ,OAAO;gBACL,GAAG,KAAK;gBACR,SAAS,EAAE;oBACT,GAAG,KAAK,CAAC,SAAS;oBAClB,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE;iBACtF;aACF,CAAA;QACH,KAAK,OAAO;YACV,OAAO,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,CAAA;QACxD,KAAK,QAAQ;YACX,OAAO,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,UAAU,CAAC,CAAA;QACzD,KAAK,MAAM;YACT,OAAO,KAAK,CAAC,KAAK,KAAK,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,CAAA;QACrF,KAAK,aAAa;YAChB,OAAO,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;QACjE,KAAK,OAAO;YACV,OAAO,sBAAsB,CAAA;QAC/B;YACE,OAAO,KAAK,CAAA;IAChB,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAmB,EACnB,MAA0E;IAE1E,OAAO;QACL,MAAM,EAAE;YACN,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,EAAE,EAAE,KAAK,CAAC,EAAE;YACZ,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE;YAC5B,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9D;QACD,KAAK,EAAE;YACL,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,EAAE,EAAE,KAAK,CAAC,EAAE;YACZ,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC3D;KACF,CAAA;AACH,CAAC","sourcesContent":["/**\n * The chat panel's proposal-review state (EC-199, reshaped by EC-334).\n *\n * `useChat` (the AI SDK) owns the conversation: messages, streaming text, tool\n * parts, busy-status. This reducer keeps ONLY what the SDK does not own:\n *\n * - the proposal review map — the FULL proposals the terminal tools\n * (`createDocument` / `translateDocument`) produce, keyed by `toolCallId`\n * (the model receives only a compact summary as the tool output; the editor\n * reviews the real patch here), each `pending` → `applied` | `rejected`;\n * - the last blocking error (route/provider/step-cap failure), surfaced as a\n * visible note and cleared on the next submit.\n *\n * EC-380 (`clear`): a new conversation resets this state entirely — pending\n * proposals are discarded with REJECT semantics (they were never applied and\n * nothing half-applied survives; an applied draft already lives in the CMS and\n * is untouched). The panel pairs the dispatch with a fresh `useChat` id.\n *\n * House testing pattern (AGENTS.md): the panel's behaviour lives in this\n * deterministic reducer, tested directly; the React component is a thin shell.\n */\nimport type { CmsDocument, CmsValidationIssue, DocumentPatch, FieldValues } from '@elytracms/core/cms-core'\nimport type { AiProvenance, ContentPatch, UnfilledPosition } from './types'\nimport type { TranslateSkip } from './translate-document'\n\n/**\n * A proposal's kind: fresh authoring, a fill-empty translation of an existing\n * doc, or an EC-378 field update of an existing doc (old → new diff).\n */\nexport type ProposalOrigin = 'create' | 'translate' | 'update'\n\n/**\n * The full proposal payload a terminal tool hands to the review UI (via\n * `onProposal` on the executor — see `tools.ts`). The model never sees this;\n * it gets a compact summary as the tool output.\n */\nexport interface ChatProposal {\n origin: ProposalOrigin\n collection: string\n documentId: string\n patch: ContentPatch\n provenance: AiProvenance\n /** Generation attempts made (1 = happy path, >1 = repaired). */\n attempts: number\n /** Non-error issues on the accepted content (warnings). */\n issues: CmsValidationIssue[]\n /** For a translation/update: the existing document values the diff renders \"before\". */\n before?: { values?: FieldValues; localized?: Record<string, FieldValues> }\n /**\n * EC-595 A2 — for an update or a translation: the positions it changes, as patches\n * (a field's bucket, a block prop by node id). Applying the proposal persists exactly\n * these through `documents.patch` — never the merged whole document, which would\n * revert any change made since the proposal was built. Absent for a create.\n */\n patches?: readonly DocumentPatch[]\n /**\n * EC-647 — for an update or a translation: the revision of the read the proposal was\n * built from. Applying it writes {@link patches} AT this revision, so a position changed\n * since (by anyone else) is refused on that field, never overwritten. Absent → the apply\n * names no revision and is refused.\n */\n revision?: number\n /**\n * EC-593 (ratified 4e) — for a translation: every text position it could NOT\n * translate, shown on the card the editor reviews (never a silent skip).\n */\n skipped?: readonly TranslateSkip[]\n /**\n * EC-715 — for a create: the writable positions the draft left empty and the ids the leash\n * dropped, listed on the card (an image the library had nothing for is SAID, never just\n * missing).\n */\n unfilled?: readonly UnfilledPosition[]\n}\n\n/** A proposal under review: {@link ChatProposal} + its review status. */\nexport interface ProposalReview extends ChatProposal {\n /** The tool call that produced it (`toolCallId`) — the review key. */\n id: string\n status: 'pending' | 'applied' | 'rejected'\n}\n\nexport interface ChatReviewState {\n /** Proposals keyed by `toolCallId`. */\n proposals: Record<string, ProposalReview>\n /** Last blocking error (route/provider/step-cap), surfaced then cleared on submit. */\n error: string | null\n}\n\nexport const initialChatReviewState: ChatReviewState = {\n proposals: {},\n error: null,\n}\n\nexport type ChatReviewAction =\n | { type: 'propose'; toolCallId: string; proposal: ChatProposal }\n | { type: 'apply'; toolCallId: string }\n | { type: 'reject'; toolCallId: string }\n | { type: 'fail'; message: string }\n | { type: 'clear-error' }\n /** EC-380: new conversation — full reset; pending proposals discarded (reject semantics). */\n | { type: 'clear' }\n\nfunction transition(\n state: ChatReviewState,\n toolCallId: string,\n status: 'applied' | 'rejected',\n): ChatReviewState {\n const existing = state.proposals[toolCallId]\n // Only a PENDING proposal transitions — apply/reject are one-shot.\n if (!existing || existing.status !== 'pending') return state\n return {\n ...state,\n proposals: { ...state.proposals, [toolCallId]: { ...existing, status } },\n }\n}\n\nexport function chatReviewReducer(\n state: ChatReviewState,\n action: ChatReviewAction,\n): ChatReviewState {\n switch (action.type) {\n case 'propose':\n return {\n ...state,\n proposals: {\n ...state.proposals,\n [action.toolCallId]: { ...action.proposal, id: action.toolCallId, status: 'pending' },\n },\n }\n case 'apply':\n return transition(state, action.toolCallId, 'applied')\n case 'reject':\n return transition(state, action.toolCallId, 'rejected')\n case 'fail':\n return state.error === action.message ? state : { ...state, error: action.message }\n case 'clear-error':\n return state.error === null ? state : { ...state, error: null }\n case 'clear':\n return initialChatReviewState\n default:\n return state\n }\n}\n\n/**\n * Turn a proposal patch into the before/after document pair the EC-317 diff\n * engine (`diffDocuments`) compares. For a fresh document `before` is empty\n * (the whole proposal reads as \"added\"); for a translation pass the existing\n * document's values. Pure — the reviewable diff is a straight\n * `diffDocuments(collection, before, after)`.\n */\nexport function proposalDocuments(\n patch: ContentPatch,\n before?: { values?: FieldValues; localized?: Record<string, FieldValues> },\n): { before: CmsDocument; after: CmsDocument } {\n return {\n before: {\n collection: patch.collection,\n id: patch.id,\n values: before?.values ?? {},\n ...(before?.localized ? { localized: before.localized } : {}),\n },\n after: {\n collection: patch.collection,\n id: patch.id,\n values: patch.values,\n ...(patch.localized ? { localized: patch.localized } : {}),\n },\n }\n}\n"]}
@@ -68,6 +68,11 @@ export declare function richTextWriterSchema(field: FieldDef): JsonSchema;
68
68
  * searched for that position (a relation's target, an asset's `accept`), and a position
69
69
  * with nothing searched is omitted (empty beats wrong). Without one (the catalog prose)
70
70
  * the plain writer forms are described.
71
+ *
72
+ * A writer-locked SUB-field (EC-716: `ai.exclude` or `form.readOnly`, core's
73
+ * `isWriterLocked`) has no writer form at any depth: it is omitted from its object or
74
+ * placement, so a writer cannot name it. The field's OWN lock is its caller's to apply
75
+ * (the create shape omits the field; the catalog marks it read-only).
71
76
  */
72
77
  export declare function writerFieldSchema(field: FieldDef, leash?: WriterLeash): JsonSchema | undefined;
73
78
  /**
@@ -24,7 +24,7 @@
24
24
  * right place to say what the dropped refinement wants — the note nudges toward
25
25
  * writing one.)
26
26
  */
27
- import { BOUND_UNIT_NOUN, fieldJsonSchema, fieldTypeOf } from '@elytracms/core/cms-core';
27
+ import { BOUND_UNIT_NOUN, fieldJsonSchema, fieldTypeOf, isWriterLocked, writerLockOf } from '@elytracms/core/cms-core';
28
28
  import { MARKDOWN_PROSE_DESCRIPTION } from '@elytracms/core/rich-text';
29
29
  /** The note appended when a field's constraint projection is knowingly partial. */
30
30
  export const INCOMPLETE_CONSTRAINTS_NOTE = 'further validation rules apply that are not expressible here — keep the value conventional and plausible';
@@ -92,13 +92,22 @@ function subFieldsOf(field) {
92
92
  * - a plain-text bound ({@link plainTextBoundPhrase}, EC-581);
93
93
  * - a sub-field's `required` (RULES F3d — the shape's JSON `required` carried it, the
94
94
  * prose never did); the field's own is said by {@link describeFieldConstraints};
95
- * - a sub-field's rules the projection cannot state ({@link hasUnstatedRules}).
95
+ * - a sub-field's rules the projection cannot state ({@link hasUnstatedRules});
96
+ * - a READ-ONLY sub-field (EC-716): `locked: read-only` — the writer form omits it, the
97
+ * prose still says it is there. An `ai.exclude` sub-field is not mentioned at all.
96
98
  */
97
99
  function declarationClauses(field, prefix = '') {
98
100
  const own = plainTextBoundPhrase(field);
99
101
  const out = own !== undefined ? [`${prefix}${own}`] : [];
100
102
  for (const sub of subFieldsOf(field)) {
103
+ const lock = writerLockOf(sub);
104
+ if (lock === 'excluded')
105
+ continue;
101
106
  const subPrefix = `${prefix}${sub.name}: `;
107
+ if (lock === 'read-only') {
108
+ out.push(`${subPrefix}read-only`);
109
+ continue;
110
+ }
102
111
  if (sub.validation?.required === true)
103
112
  out.push(`${subPrefix}required`);
104
113
  out.push(...declarationClauses(sub, subPrefix));
@@ -152,6 +161,11 @@ export function richTextWriterSchema(field) {
152
161
  * searched for that position (a relation's target, an asset's `accept`), and a position
153
162
  * with nothing searched is omitted (empty beats wrong). Without one (the catalog prose)
154
163
  * the plain writer forms are described.
164
+ *
165
+ * A writer-locked SUB-field (EC-716: `ai.exclude` or `form.readOnly`, core's
166
+ * `isWriterLocked`) has no writer form at any depth: it is omitted from its object or
167
+ * placement, so a writer cannot name it. The field's OWN lock is its caller's to apply
168
+ * (the create shape omits the field; the catalog marks it read-only).
155
169
  */
156
170
  export function writerFieldSchema(field, leash) {
157
171
  return writerForm(field, fieldJsonSchema(field), leash);
@@ -184,7 +198,8 @@ function writerForm(field, base, leash) {
184
198
  if (candidates !== undefined && candidates.length === 0)
185
199
  return undefined;
186
200
  const idSchema = (item) => candidates !== undefined ? candidateEnum(candidates, true) : bareAssetId(item);
187
- return perItem(field.multiple, base, (item) => subfields.length === 0
201
+ // EC-716: with every placement sub-field locked, the writer hands in the bare id.
202
+ return perItem(field.multiple, base, (item) => subfields.every((sub) => isWriterLocked(sub))
188
203
  ? idSchema(item)
189
204
  : writerObject(subfields, { ...item, properties: { ...propertiesOf(item), id: idSchema(item) } }, leash));
190
205
  }
@@ -230,8 +245,8 @@ function bareReference(envelope) {
230
245
  }
231
246
  /**
232
247
  * An object-shaped node with each declared subfield in its writer form, closed. A
233
- * sub-field with no writer form here (nothing searched for its id) is omitted, and
234
- * leaves the `required` list with it.
248
+ * sub-field with no writer form here — a writer-locked one (EC-716), or nothing searched
249
+ * for its id — is omitted, and leaves the `required` list with it.
235
250
  */
236
251
  function writerObject(fields, node, leash) {
237
252
  const properties = { ...propertiesOf(node) };
@@ -240,7 +255,7 @@ function writerObject(fields, node, leash) {
240
255
  const subBase = properties[sub.name];
241
256
  if (subBase === undefined)
242
257
  continue;
243
- const written = writerForm(sub, subBase, leash);
258
+ const written = isWriterLocked(sub) ? undefined : writerForm(sub, subBase, leash);
244
259
  if (written !== undefined)
245
260
  properties[sub.name] = written;
246
261
  else {
@@ -1 +1 @@
1
- {"version":3,"file":"constraints.js","sourceRoot":"","sources":["../../../src/lib/ai/constraints.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAExF,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAA;AAGtE,mFAAmF;AACnF,MAAM,CAAC,MAAM,2BAA2B,GACtC,0GAA0G,CAAA;AAE5G;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAC3B,qNAAqN,CAAA;AAEvN;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,KAAe;IACvC,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAA;IACnC,IAAI,CAAC,UAAU;QAAE,OAAO,KAAK,CAAA;IAC7B,OAAO,UAAU,CAAC,cAAc,KAAK,KAAK,IAAI,CAAC,UAAU,CAAC,QAAQ,KAAK,SAAS,IAAI,UAAU,CAAC,cAAc,KAAK,SAAS,CAAC,CAAA;AAC9H,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAA;AAE5C,SAAS,YAAY,CAAC,KAAc;IAClC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAC7E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAe;IAClD,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,YAAY;QAAE,OAAO,SAAS,CAAA;IACvE,MAAM,GAAG,GAAG,OAAO,KAAK,CAAC,UAAU,EAAE,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAA;IACxF,MAAM,GAAG,GAAG,OAAO,KAAK,CAAC,UAAU,EAAE,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAA;IACxF,MAAM,IAAI,GAAG,eAAe,CAAC,YAAY,CAAC,CAAA;IAC1C,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,GAAG,GAAG,IAAI,GAAG,IAAI,IAAI,EAAE,CAAA;IAC1E,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,YAAY,GAAG,IAAI,IAAI,EAAE,CAAA;IACvD,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,WAAW,GAAG,IAAI,IAAI,EAAE,CAAA;IACtD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,4FAA4F;AAC5F,SAAS,WAAW,CAAC,KAAe;IAClC,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AACpG,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,kBAAkB,CAAC,KAAe,EAAE,MAAM,GAAG,EAAE;IACtD,MAAM,GAAG,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAA;IACvC,MAAM,GAAG,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IACxD,KAAK,MAAM,GAAG,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;QACrC,MAAM,SAAS,GAAG,GAAG,MAAM,GAAG,GAAG,CAAC,IAAI,IAAI,CAAA;QAC1C,IAAI,GAAG,CAAC,UAAU,EAAE,QAAQ,KAAK,IAAI;YAAE,GAAG,CAAC,IAAI,CAAC,GAAG,SAAS,UAAU,CAAC,CAAA;QACvE,GAAG,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAA;QAC/C,IAAI,gBAAgB,CAAC,GAAG,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,GAAG,SAAS,GAAG,2BAA2B,EAAE,CAAC,CAAA;IACnF,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAe;IAClD,MAAM,MAAM,GAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;IAC7C,MAAM,KAAK,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAA;IACzC,MAAM,CAAC,WAAW,GAAG,yBAAyB,0BAA0B,qCAAqC,KAAK,CAAC,CAAC,CAAC,YAAY,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;IAChJ,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAe,EAAE,KAAmB;IACpE,OAAO,UAAU,CAAC,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAA;AACzD,CAAC;AAED,qFAAqF;AACrF,SAAS,aAAa,CAAC,UAA2D,EAAE,KAAc;IAChG,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACzF,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjC,WAAW,EAAE,KAAK;YAChB,CAAC,CAAC,gDAAgD,KAAK,6BAA6B;YACpF,CAAC,CAAC,kBAAkB,KAAK,EAAE;KAC9B,CAAA;AACH,CAAC;AAED,6FAA6F;AAC7F,SAAS,UAAU,CAAC,KAAe,EAAE,IAAgB,EAAE,KAAmB;IACxE,QAAQ,WAAW,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;QAClC,KAAK,UAAU;YACb,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAA;QACpC,KAAK,aAAa;YAChB,OAAO,SAAS,CAAA;QAClB,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO;gBAAE,OAAO,IAAI,CAAA;YACvC,4EAA4E;YAC5E,8DAA8D;YAC9D,MAAM,SAAS,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;YAChG,MAAM,UAAU,GAAG,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,CAAA;YAChD,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,SAAS,CAAA;YACzE,MAAM,QAAQ,GAAG,CAAC,IAAgB,EAAc,EAAE,CAChD,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;YAChF,OAAO,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAC5C,SAAS,CAAC,MAAM,KAAK,CAAC;gBACpB,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAChB,CAAC,CAAC,YAAY,CAAC,SAAS,EAAE,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,CAC3G,CAAA;QACH,CAAC;QACD,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;gBAAE,OAAO,IAAI,CAAA;YAC1C,IAAI,CAAC,KAAK;gBAAE,OAAO,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,aAAa,CAAC,CAAA;YAC/D,MAAM,UAAU,GAAG,KAAK,CAAC,kBAAkB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;YACzD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,SAAS,CAAA;YAC7C,OAAO,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,CAAA;QAC9E,CAAC;QACD,KAAK,QAAQ;YACX,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;gBAC5B,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;gBAClF,CAAC,CAAC,IAAI,CAAA;QACV,KAAK,OAAO;YACV,OAAO,IAAI,CAAA;IACf,CAAC;AACH,CAAC;AAED,0FAA0F;AAC1F,SAAS,OAAO,CAAC,QAAiB,EAAE,IAAgB,EAAE,GAAqC;IACzF,IAAI,CAAC,QAAQ;QAAE,OAAO,GAAG,CAAC,IAAI,CAAC,CAAA;IAC/B,OAAO,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;AAC9E,CAAC;AAED,SAAS,YAAY,CAAC,IAAgB;IACpC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAA;IAClC,OAAO,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAE,UAAyC,CAAC,CAAC,CAAC,EAAE,CAAA;AACnF,CAAC;AAED,wEAAwE;AACxE,SAAS,WAAW,CAAC,QAAoB;IACvC,OAAO,YAAY,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;AACxD,CAAC;AAED,+EAA+E;AAC/E,SAAS,aAAa,CAAC,QAAoB;IACzC,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,UAAU,EAAE,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAA;IACtE,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC/C,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,MAAM,CAAC;QACnD,CAAC,CAAC,EAAE,CAAA;IACN,OAAO,EAAE,GAAG,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAAA;AAC3E,CAAC;AAED;;;;GAIG;AACH,SAAS,YAAY,CAAC,MAA2B,EAAE,IAAgB,EAAE,KAAmB;IACtF,MAAM,UAAU,GAA+B,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,EAAE,CAAA;IACxE,IAAI,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,QAAqB,CAAC,CAAC,CAAC,SAAS,CAAA;IACrF,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACpC,IAAI,OAAO,KAAK,SAAS;YAAE,SAAQ;QACnC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;QAC/C,IAAI,OAAO,KAAK,SAAS;YAAE,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,OAAO,CAAA;aACpD,CAAC;YACJ,OAAO,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;YAC3B,QAAQ,GAAG,QAAQ,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,CAAC,CAAA;QAC1D,CAAC;IACH,CAAC;IACD,OAAO,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAAA;AAChG,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAkB;IACtD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,MAAM,CAAA;IAC9E,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,CAAA;IAC7C,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa,CAAC,MAAkB,EAAE,SAA2C;IACpF,IAAI,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,CAAA;IAC3B,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAA;IACvB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzE,MAAM,IAAI,GAAG,aAAa,CAAC,KAAmB,EAAE,SAAS,CAAC,CAAA;QAC1D,IAAI,IAAI,KAAK,KAAK;YAAE,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IACnD,CAAC;IACD,MAAM,UAAU,GAAG,GAAG,CAAC,UAAU,CAAA;IACjC,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACxF,IAAI,OAAO,GAAG,KAAK,CAAA;QACnB,MAAM,SAAS,GAA+B,EAAE,CAAA;QAChD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAqC,CAAC,EAAE,CAAC;YACjF,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzE,MAAM,IAAI,GAAG,aAAa,CAAC,KAAmB,EAAE,SAAS,CAAC,CAAA;gBAC1D,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,CAAA;gBACrB,IAAI,IAAI,KAAK,KAAK;oBAAE,OAAO,GAAG,IAAI,CAAA;YACpC,CAAC;iBAAM,CAAC;gBACN,SAAS,CAAC,GAAG,CAAC,GAAG,KAAmB,CAAA;YACtC,CAAC;QACH,CAAC;QACD,IAAI,OAAO;YAAE,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,CAAA;IACtD,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,0BAA0B,CAAC,MAAkB;IAC3D,OAAO,aAAa,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAA;AACrD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAkB;IACpD,OAAO,aAAa,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAA;AACrD,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,yBAAyB,CAAC,MAAkB;IACnD,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,MAAM,GAAG,GAAG,CAAC,KAAc,EAAsB,EAAE,CACjD,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;IAC/C,MAAM,IAAI,GAAG,CAAC,KAAc,EAAsB,EAAE,CAClD,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;IAE/C,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzD,qEAAqE;QACrE,GAAG,CAAC,IAAI,CAAC,UAAU,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IACvF,CAAC;IAED,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IACvC,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IACvC,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QACvD,GAAG,CAAC,IAAI,CAAC,GAAG,SAAS,IAAI,SAAS,aAAa,CAAC,CAAA;IAClD,CAAC;SAAM,IAAI,SAAS,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,CAAC,YAAY,SAAS,aAAa,CAAC,CAAA;SAC3E,IAAI,SAAS,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,CAAC,WAAW,SAAS,aAAa,CAAC,CAAA;IAE7E,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACnC,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACnC,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,CAAC,WAAW,OAAO,QAAQ,OAAO,EAAE,CAAC,CAAA;SAC5F,IAAI,OAAO,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,CAAC,YAAY,OAAO,EAAE,CAAC,CAAA;SAC1D,IAAI,OAAO,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,CAAC,WAAW,OAAO,EAAE,CAAC,CAAA;IAE9D,2EAA2E;IAC3E,MAAM,gBAAgB,GAAG,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAA;IACrD,MAAM,gBAAgB,GAAG,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAA;IACrD,IAAI,gBAAgB,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,CAAC,aAAa,gBAAgB,EAAE,CAAC,CAAA;IAC7E,IAAI,gBAAgB,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,CAAC,aAAa,gBAAgB,EAAE,CAAC,CAAA;IAC7E,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;IACzC,IAAI,UAAU,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,CAAC,iBAAiB,UAAU,EAAE,CAAC,CAAA;IAErE,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IACrC,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IACrC,IAAI,QAAQ,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,CAAC,YAAY,QAAQ,QAAQ,CAAC,CAAA;IAClE,IAAI,QAAQ,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,CAAC,WAAW,QAAQ,QAAQ,CAAC,CAAA;IACjE,IAAI,MAAM,CAAC,WAAW,KAAK,IAAI;QAAE,GAAG,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;IAE/D,gFAAgF;IAChF,sEAAsE;IACtE,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAA;IACvC,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAA;IACrC,IAAI,IAAI,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAA;IAC5D,IAAI,EAAE,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAA;IAEtD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IAClC,iFAAiF;IACjF,+EAA+E;IAC/E,IAAI,MAAM,KAAK,WAAW;QAAE,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;SAC9C,IAAI,MAAM,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,CAAC,WAAW,MAAM,EAAE,CAAC,CAAA;IAC5D,wEAAwE;IACxE,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACpC,IAAI,OAAO,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,CAAC,aAAa,OAAO,GAAG,CAAC,CAAA;IAEpF,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAS,kBAAkB,CAAC,MAAkB;IAC5C,MAAM,GAAG,GAAG,yBAAyB,CAAC,MAAM,CAAC,CAAA;IAC7C,IAAI,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,SAAS,GAAG,kBAAkB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAClD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,cAAc,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC1E,CAAC;IACD,IAAI,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;QACpC,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5D,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC;gBAAE,SAAQ;YAChC,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAA;YACxC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACtE,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAe;IACtD,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,IAAI,KAAK,CAAC,UAAU,EAAE,QAAQ,KAAK,IAAI;QAAE,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAC7D,gFAAgF;IAChF,iEAAiE;IACjE,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAA;IACvC,IAAI,MAAM,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;;QAC/C,GAAG,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAA;IAC5C,6EAA6E;IAC7E,mFAAmF;IACnF,iFAAiF;IACjF,GAAG,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAA;IACtC,IAAI,KAAK,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI;QAAE,GAAG,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAA;IAC/E,+EAA+E;IAC/E,kFAAkF;IAClF,oFAAoF;IACpF,oEAAoE;IACpE,IAAI,gBAAgB,CAAC,KAAK,CAAC;QAAE,GAAG,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAA;IAClE,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,kBAAkB,CAAC,KAAe;IAChD,MAAM,KAAK,GAAG,wBAAwB,CAAC,KAAK,CAAC,CAAA;IAC7C,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;AACxD,CAAC","sourcesContent":["/**\n * Model-facing constraint rendering (EC-473, tuned by EC-505).\n *\n * The engine has ONE constraint projection: `fieldJsonSchema` merges a field's\n * declarative rules (`validation.min`/`max`/`pattern`) with the fragment derived\n * from a schema-form `validation.validate` (EC-522: nested `items`/`properties`\n * bounds included, fitted to the field's type at config resolve). This module turns\n * that projection into what the MODEL should see — which is not the same artifact a\n * JSON-Schema consumer wants:\n *\n * - **a regex is a poor instruction for a model.** `z.string().email()` emits a\n * ~200-character pattern and `z.iso.date()` a leap-year monster (measured,\n * EC-474 §3); `format` + bounds says the same thing in four tokens. So on the AI\n * path a `pattern` is dropped wherever a `format` is present. The JSON Schema\n * OUTPUT artifact keeps it — different consumer, different needs.\n * - **a date is one instant string** (EC-505 → EC-586). The projection used to say\n * `type: ['string','number']` (an epoch was stored as-is) and this module narrowed\n * it for the model; since EC-586 every write canonicalises a date to ONE ISO-8601\n * UTC instant, so the projection itself says `type: 'string'` and there is nothing\n * left to narrow.\n * - **incompleteness must be stated.** When `validation.schemaComplete` is `false`\n * the projection is a partial view of the rule, and the model is told so rather\n * than being handed a half-truth as the whole rule. (An `ai.hint` is exactly the\n * right place to say what the dropped refinement wants — the note nudges toward\n * writing one.)\n */\nimport { BOUND_UNIT_NOUN, fieldJsonSchema, fieldTypeOf } from '@elytracms/core/cms-core'\nimport type { FieldDef, JsonSchema } from '@elytracms/core/cms-core'\nimport { MARKDOWN_PROSE_DESCRIPTION } from '@elytracms/core/rich-text'\nimport type { AssetCandidate, RelationCandidate, WriterLeash } from './types'\n\n/** The note appended when a field's constraint projection is knowingly partial. */\nexport const INCOMPLETE_CONSTRAINTS_NOTE =\n 'further validation rules apply that are not expressible here — keep the value conventional and plausible'\n\n/**\n * What the catalog says about a composition (`blocks`) — its writer form is NONE\n * (`FIELD_TYPES.blocks.writer === 'composition'`, ratified 4c, EC-593): the assistant\n * changes component props, never which components there are. Said in the prose because\n * the omission must be VISIBLE, not a field the model simply never hears of.\n */\nexport const COMPOSITION_NOTE =\n 'page-builder composition: the assistant never adds, moves or removes its components (that is done on the canvas) — change one component prop with blockId + prop (getDocument lists the components and their props)'\n\n/**\n * Whether a field's validation has rules its projection cannot state (RULES D13): a\n * schema whose projection is knowingly partial (`schemaComplete: false`), or a FUNCTION\n * `validate` — normalisation stamps `schemaComplete` on a schema-form validator only, so\n * a declared `validate` without it is a function no consumer can read.\n */\nfunction hasUnstatedRules(field: FieldDef): boolean {\n const validation = field.validation\n if (!validation) return false\n return validation.schemaComplete === false || (validation.validate !== undefined && validation.schemaComplete === undefined)\n}\n\n/**\n * The prose form of `format: 'date-time'` (EC-505). The JSON-Schema keyword rides\n * on the target shape for the provider's validator; the CATALOG has no JSON Schema\n * at all — it is prose only, and the chat update path authors date values through\n * it. So the format is said in words too, or the only channel that path has says\n * nothing about the one format the engine accepts.\n *\n * Kept to three words deliberately: measured at 6 tokens, against 20 for the same\n * note with a worked example — and it rides every date field of every turn.\n */\nexport const ISO_DATE_NOTE = 'ISO 8601 date'\n\nfunction isSchemaNode(value: unknown): value is JsonSchema {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * The phrase for a field's declared `min`/`max` in the UNIT its type declares\n * (`FIELD_TYPES[type].bounds`, EC-581) — or `undefined` when the field declares none\n * or the unit has a JSON-Schema keyword the projection already carries. Today that is\n * rich text: its bounds count PLAIN-TEXT characters of the stored value (ratified\n * 2a(iii)), which no keyword on the markdown string the model writes can express.\n */\nexport function plainTextBoundPhrase(field: FieldDef): string | undefined {\n if (fieldTypeOf(field).bounds(field) !== 'plain-text') return undefined\n const min = typeof field.validation?.min === 'number' ? field.validation.min : undefined\n const max = typeof field.validation?.max === 'number' ? field.validation.max : undefined\n const noun = BOUND_UNIT_NOUN['plain-text']\n if (min !== undefined && max !== undefined) return `${min}–${max} ${noun}`\n if (min !== undefined) return `at least ${min} ${noun}`\n if (max !== undefined) return `at most ${max} ${noun}`\n return undefined\n}\n\n/** The declared sub-fields of an object or an asset placement (`[]` for any other type). */\nfunction subFieldsOf(field: FieldDef): readonly FieldDef[] {\n return field.type === 'object' ? field.fields : field.type === 'asset' ? (field.fields ?? []) : []\n}\n\n/**\n * The clauses only the DECLARATION can say, for a field and every sub-field below it,\n * name-prefixed (`note: at most 400 plain-text characters`, `label: required`):\n * - a plain-text bound ({@link plainTextBoundPhrase}, EC-581);\n * - a sub-field's `required` (RULES F3d — the shape's JSON `required` carried it, the\n * prose never did); the field's own is said by {@link describeFieldConstraints};\n * - a sub-field's rules the projection cannot state ({@link hasUnstatedRules}).\n */\nfunction declarationClauses(field: FieldDef, prefix = ''): string[] {\n const own = plainTextBoundPhrase(field)\n const out = own !== undefined ? [`${prefix}${own}`] : []\n for (const sub of subFieldsOf(field)) {\n const subPrefix = `${prefix}${sub.name}: `\n if (sub.validation?.required === true) out.push(`${subPrefix}required`)\n out.push(...declarationClauses(sub, subPrefix))\n if (hasUnstatedRules(sub)) out.push(`${subPrefix}${INCOMPLETE_CONSTRAINTS_NOTE}`)\n }\n return out\n}\n\n/**\n * A rich-text field's WRITER form (EC-379, D2 of EC-556): a MARKDOWN string. The\n * description carries the allowed subset (from the engine converter — one source\n * of truth); the engine converts the markdown into the v2 storage value and\n * rejects out-of-subset constructs with explicit issues.\n *\n * EC-563/EC-581: the declared bounds are said in their unit — plain-text characters,\n * markup not counted — in the description, read off the per-FieldType definition.\n * They used to ride as `minLength`/`maxLength` on the markdown string: a different\n * unit (markdown characters) than anything enforced, and a provider validating the\n * structured output would have enforced the wrong one.\n */\nexport function richTextWriterSchema(field: FieldDef): JsonSchema {\n const schema: JsonSchema = { type: 'string' }\n const bound = plainTextBoundPhrase(field)\n schema.description = `Rich text, written as ${MARKDOWN_PROSE_DESCRIPTION} Write genuine, publishable prose.${bound ? ` Length: ${bound}.` : ''}`\n return schema\n}\n\n/**\n * The WRITER-facing form of one field, at EVERY depth (EC-556): what a model should\n * hand in. Derived from the validator-facing projection (`fieldJsonSchema` — the\n * value at rest, every merged constraint included) by one rule: **the writer omits\n * exactly what the write-time lens (`selfTypeFieldValue`) restores.**\n *\n * - asset, no per-use sub-fields → the bare id (the projection's `id` property):\n * the lens wraps a bare id string into `{ type: 'asset', id }`;\n * - asset WITH sub-fields → the envelope itself, `type` included: the lens wraps a\n * string, never an object missing its `type`, and the sub-fields need a place;\n * - relation → `{ collection, id }` (the envelope minus `type`): the lens rebuilds\n * a bare reference into its envelope;\n * - rich text → a markdown string (`richTextWriterSchema`) — the engine converts it\n * wherever it sits (`writer-form.ts`, EC-593);\n * - an `object` → its own projection with every subfield in ITS writer form;\n * - a composition (`blocks`) → NONE (`undefined`): its structure is the canvas's\n * (ratified 4c, EC-593) — a target shape omits it and says so.\n *\n * Writer objects are closed (`additionalProperties: false`): a writer inventing keys\n * is a fault, where a stored envelope carrying extra keys is drift the lens\n * tolerates (EC-556 §B) — which is why the projection itself stays open.\n *\n * With a `leash` (the create target shape, EC-381 → EC-593) every relation / asset id\n * — at EVERY depth, not only a top-level field — is an ENUM over what the model\n * searched for that position (a relation's target, an asset's `accept`), and a position\n * with nothing searched is omitted (empty beats wrong). Without one (the catalog prose)\n * the plain writer forms are described.\n */\nexport function writerFieldSchema(field: FieldDef, leash?: WriterLeash): JsonSchema | undefined {\n return writerForm(field, fieldJsonSchema(field), leash)\n}\n\n/** An id enum over searched candidates, labelled for the model to choose against. */\nfunction candidateEnum(candidates: readonly (RelationCandidate | AssetCandidate)[], asset: boolean): JsonSchema {\n const label = candidates.map((c) => (c.label ? `${c.id} (${c.label})` : c.id)).join(', ')\n return {\n type: 'string',\n enum: candidates.map((c) => c.id),\n description: asset\n ? `Choose an EXISTING library asset id, one of: ${label}. Leave empty if none fits.`\n : `Choose one of: ${label}`,\n }\n}\n\n/** EC-581: the writer form is DECLARED per type (`FIELD_TYPES[type].writer`); built here. */\nfunction writerForm(field: FieldDef, base: JsonSchema, leash?: WriterLeash): JsonSchema | undefined {\n switch (fieldTypeOf(field).writer) {\n case 'markdown':\n return richTextWriterSchema(field)\n case 'composition':\n return undefined\n case 'placement': {\n if (field.type !== 'asset') return base\n // `type`/`id` belong to the envelope; the projection never lets a sub-field\n // of that name shadow them, and neither does the writer form.\n const subfields = (field.fields ?? []).filter((sub) => sub.name !== 'type' && sub.name !== 'id')\n const candidates = leash?.assetCandidates(field)\n if (candidates !== undefined && candidates.length === 0) return undefined\n const idSchema = (item: JsonSchema): JsonSchema =>\n candidates !== undefined ? candidateEnum(candidates, true) : bareAssetId(item)\n return perItem(field.multiple, base, (item) =>\n subfields.length === 0\n ? idSchema(item)\n : writerObject(subfields, { ...item, properties: { ...propertiesOf(item), id: idSchema(item) } }, leash),\n )\n }\n case 'reference': {\n if (field.type !== 'relation') return base\n if (!leash) return perItem(field.multiple, base, bareReference)\n const candidates = leash.relationCandidates(field.target)\n if (candidates.length === 0) return undefined\n return perItem(field.multiple, base, () => candidateEnum(candidates, false))\n }\n case 'object':\n return field.type === 'object'\n ? perItem(field.multiple, base, (item) => writerObject(field.fields, item, leash))\n : base\n case 'value':\n return base\n }\n}\n\n/** Apply `one` to the item schema of a `multiple` field, or to the field's own schema. */\nfunction perItem(multiple: boolean, base: JsonSchema, one: (item: JsonSchema) => JsonSchema): JsonSchema {\n if (!multiple) return one(base)\n return isSchemaNode(base.items) ? { ...base, items: one(base.items) } : base\n}\n\nfunction propertiesOf(node: JsonSchema): Record<string, JsonSchema> {\n const properties = node.properties\n return isSchemaNode(properties) ? (properties as Record<string, JsonSchema>) : {}\n}\n\n/** The envelope's `id` property — the bare id string the lens wraps. */\nfunction bareAssetId(envelope: JsonSchema): JsonSchema {\n return propertiesOf(envelope).id ?? { type: 'string' }\n}\n\n/** The reference envelope minus the `type` discriminator the lens restores. */\nfunction bareReference(envelope: JsonSchema): JsonSchema {\n const { type: _discriminator, ...properties } = propertiesOf(envelope)\n const required = Array.isArray(envelope.required)\n ? envelope.required.filter((key) => key !== 'type')\n : []\n return { ...envelope, properties, required, additionalProperties: false }\n}\n\n/**\n * An object-shaped node with each declared subfield in its writer form, closed. A\n * sub-field with no writer form here (nothing searched for its id) is omitted, and\n * leaves the `required` list with it.\n */\nfunction writerObject(fields: readonly FieldDef[], node: JsonSchema, leash?: WriterLeash): JsonSchema {\n const properties: Record<string, JsonSchema> = { ...propertiesOf(node) }\n let required = Array.isArray(node.required) ? (node.required as string[]) : undefined\n for (const sub of fields) {\n const subBase = properties[sub.name]\n if (subBase === undefined) continue\n const written = writerForm(sub, subBase, leash)\n if (written !== undefined) properties[sub.name] = written\n else {\n delete properties[sub.name]\n required = required?.filter((name) => name !== sub.name)\n }\n }\n return { ...node, properties, ...(required ? { required } : {}), additionalProperties: false }\n}\n\n/**\n * Drop `pattern` wherever `format` is present (EC-473/EC-474 req 6). Returns the\n * same object when there is nothing to strip, so callers can rely on identity.\n */\nexport function stripRedundantPattern(schema: JsonSchema): JsonSchema {\n if (schema.format === undefined || schema.pattern === undefined) return schema\n const { pattern: _pattern, ...rest } = schema\n return rest\n}\n\n/**\n * Apply a node transform through a schema tree (`items` + `properties`), preserving\n * object identity wherever nothing changed — `buildTargetShape` relies on that to\n * avoid copying a property it did not touch.\n */\nfunction mapSchemaDeep(schema: JsonSchema, transform: (node: JsonSchema) => JsonSchema): JsonSchema {\n let out = transform(schema)\n const items = out.items\n if (typeof items === 'object' && items !== null && !Array.isArray(items)) {\n const next = mapSchemaDeep(items as JsonSchema, transform)\n if (next !== items) out = { ...out, items: next }\n }\n const properties = out.properties\n if (typeof properties === 'object' && properties !== null && !Array.isArray(properties)) {\n let changed = false\n const nextProps: Record<string, JsonSchema> = {}\n for (const [key, value] of Object.entries(properties as Record<string, unknown>)) {\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n const next = mapSchemaDeep(value as JsonSchema, transform)\n nextProps[key] = next\n if (next !== value) changed = true\n } else {\n nextProps[key] = value as JsonSchema\n }\n }\n if (changed) out = { ...out, properties: nextProps }\n }\n return out\n}\n\n/** Recursively strip redundant patterns through `items` / `properties`. */\nexport function stripRedundantPatternsDeep(schema: JsonSchema): JsonSchema {\n return mapSchemaDeep(schema, stripRedundantPattern)\n}\n\n/**\n * The whole model-facing rewrite of a projected schema (EC-505): redundant patterns\n * stripped everywhere in the tree — an `object` field's SUBfield gets the same\n * treatment as a top-level one. Identity-preserving. (The date-union narrowing that\n * also ran here is gone: a date is one instant string at rest since EC-586.)\n */\nexport function toModelFacingSchema(schema: JsonSchema): JsonSchema {\n return mapSchemaDeep(schema, stripRedundantPattern)\n}\n\n/**\n * Human-readable phrases for the constraint keywords of ONE schema node, in a stable\n * order — the form a model reads best. Node-level only: nested `items`/`properties`\n * are {@link describeSchemaDeep}'s business. Total over the projection's numeric,\n * string, array and option vocabulary. An `enum` IS phrased (RULES D14, EC-593): the\n * catalog is prose only, so a `select`'s options never reached the update path that\n * authors through it. A `const` is not (a relation's `collection` is the envelope's,\n * not a rule the writer chooses against).\n */\nfunction describeSchemaConstraints(schema: JsonSchema): string[] {\n const out: string[] = []\n const num = (value: unknown): number | undefined =>\n typeof value === 'number' ? value : undefined\n const text = (value: unknown): string | undefined =>\n typeof value === 'string' ? value : undefined\n\n if (Array.isArray(schema.enum) && schema.enum.length > 0) {\n // Quoted and `|`-separated: the clause itself is joined with commas.\n out.push(`one of ${schema.enum.map((option) => JSON.stringify(option)).join(' | ')}`)\n }\n\n const minLength = num(schema.minLength)\n const maxLength = num(schema.maxLength)\n if (minLength !== undefined && maxLength !== undefined) {\n out.push(`${minLength}–${maxLength} characters`)\n } else if (minLength !== undefined) out.push(`at least ${minLength} characters`)\n else if (maxLength !== undefined) out.push(`at most ${maxLength} characters`)\n\n const minimum = num(schema.minimum)\n const maximum = num(schema.maximum)\n if (minimum !== undefined && maximum !== undefined) out.push(`between ${minimum} and ${maximum}`)\n else if (minimum !== undefined) out.push(`at least ${minimum}`)\n else if (maximum !== undefined) out.push(`at most ${maximum}`)\n\n // EC-522: the projection carried these already; the prose never said them.\n const exclusiveMinimum = num(schema.exclusiveMinimum)\n const exclusiveMaximum = num(schema.exclusiveMaximum)\n if (exclusiveMinimum !== undefined) out.push(`more than ${exclusiveMinimum}`)\n if (exclusiveMaximum !== undefined) out.push(`less than ${exclusiveMaximum}`)\n const multipleOf = num(schema.multipleOf)\n if (multipleOf !== undefined) out.push(`a multiple of ${multipleOf}`)\n\n const minItems = num(schema.minItems)\n const maxItems = num(schema.maxItems)\n if (minItems !== undefined) out.push(`at least ${minItems} items`)\n if (maxItems !== undefined) out.push(`at most ${maxItems} items`)\n if (schema.uniqueItems === true) out.push('no duplicate items')\n\n // EC-473: date bounds finally reach a consumer. `formatMinimum`/`formatMaximum`\n // is the projection's machine form; this is the form a model acts on.\n const from = text(schema.formatMinimum)\n const to = text(schema.formatMaximum)\n if (from !== undefined) out.push(`not earlier than ${from}`)\n if (to !== undefined) out.push(`not later than ${to}`)\n\n const format = text(schema.format)\n // EC-505: `date-time` is said in words rather than skipped — the catalog carries\n // no JSON Schema, so this is the only place the update path learns the format.\n if (format === 'date-time') out.push(ISO_DATE_NOTE)\n else if (format !== undefined) out.push(`format: ${format}`)\n // Only a pattern that is NOT explained by a format is worth the tokens.\n const pattern = text(schema.pattern)\n if (pattern !== undefined && format === undefined) out.push(`matching /${pattern}/`)\n\n return out\n}\n\n/**\n * The phrases of one projected schema node AND everything below it (EC-522): a\n * `multiple` field's per-item bounds become an `each item: …` clause, an object's\n * subfield bounds a `<name>: …` clause, recursively. The projection is the ONLY\n * input — since EC-522 the merge carries nested `items`/`properties` constraints\n * itself, so this module never looks past `fieldJsonSchema` (EC-505's direct read\n * of the derived fragment's `items` is gone, and with it the second place that\n * understood fragment structure).\n *\n * Which side a schema addresses is still the executor's call, not this module's:\n * `validateDocument` hands the validator the field's WHOLE value — a `multiple`\n * field's array — so the per-item form is `z.array(z.string().min(3))`, and a\n * bare `z.string().min(3)` there is a config error at load (EC-522), never a\n * silently mis-applied bound.\n */\nfunction describeSchemaDeep(schema: JsonSchema): string[] {\n const out = describeSchemaConstraints(schema)\n if (isSchemaNode(schema.items)) {\n const itemParts = describeSchemaDeep(schema.items)\n if (itemParts.length > 0) out.push(`each item: ${itemParts.join(', ')}`)\n }\n if (isSchemaNode(schema.properties)) {\n for (const [name, sub] of Object.entries(schema.properties)) {\n if (!isSchemaNode(sub)) continue\n const subParts = describeSchemaDeep(sub)\n if (subParts.length > 0) out.push(`${name}: ${subParts.join(', ')}`)\n }\n }\n return out\n}\n\n/**\n * Human-readable constraint phrases for one field, in a stable order — the form a\n * model reads best (and the form the collections catalog carries, where there is no\n * JSON Schema to hand the model at all). Empty when the field constrains nothing.\n */\nexport function describeFieldConstraints(field: FieldDef): string[] {\n const out: string[] = []\n if (field.validation?.required === true) out.push('required')\n // EC-556: the prose tells a WRITER what to write, so it reads the writer form —\n // and a type with NO writer form says why (EC-593, ratified 4c).\n const writer = writerFieldSchema(field)\n if (writer === undefined) out.push(COMPOSITION_NOTE)\n else out.push(...describeSchemaDeep(writer))\n // EC-581 / EC-593: what only the declaration can say — a plain-text bound, a\n // sub-field's `required`, a sub-field's unstated rules — at every depth the writer\n // form reaches (`note: at most 400 plain-text characters …`, `label: required`).\n out.push(...declarationClauses(field))\n if (field.validation?.unique === true) out.push('unique across the collection')\n // The one thing the projection cannot say about itself: that it is partial (an\n // opaque vendor, a `.refine()`, — EC-522 — a rule at a position the field's shape\n // has no counterpart for, or — RULES D13 — a validate FUNCTION, which no projection\n // can read at all). Set at config resolve, read here, nowhere else.\n if (hasUnstatedRules(field)) out.push(INCOMPLETE_CONSTRAINTS_NOTE)\n return out\n}\n\n/** The constraint phrases as one clause (`min 2 characters; required`), or `undefined`. */\nexport function constraintSentence(field: FieldDef): string | undefined {\n const parts = describeFieldConstraints(field)\n return parts.length > 0 ? parts.join('; ') : undefined\n}\n"]}
1
+ {"version":3,"file":"constraints.js","sourceRoot":"","sources":["../../../src/lib/ai/constraints.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Model-facing constraint rendering (EC-473, tuned by EC-505).\n *\n * The engine has ONE constraint projection: `fieldJsonSchema` merges a field's\n * declarative rules (`validation.min`/`max`/`pattern`) with the fragment derived\n * from a schema-form `validation.validate` (EC-522: nested `items`/`properties`\n * bounds included, fitted to the field's type at config resolve). This module turns\n * that projection into what the MODEL should see — which is not the same artifact a\n * JSON-Schema consumer wants:\n *\n * - **a regex is a poor instruction for a model.** `z.string().email()` emits a\n * ~200-character pattern and `z.iso.date()` a leap-year monster (measured,\n * EC-474 §3); `format` + bounds says the same thing in four tokens. So on the AI\n * path a `pattern` is dropped wherever a `format` is present. The JSON Schema\n * OUTPUT artifact keeps it — different consumer, different needs.\n * - **a date is one instant string** (EC-505 → EC-586). The projection used to say\n * `type: ['string','number']` (an epoch was stored as-is) and this module narrowed\n * it for the model; since EC-586 every write canonicalises a date to ONE ISO-8601\n * UTC instant, so the projection itself says `type: 'string'` and there is nothing\n * left to narrow.\n * - **incompleteness must be stated.** When `validation.schemaComplete` is `false`\n * the projection is a partial view of the rule, and the model is told so rather\n * than being handed a half-truth as the whole rule. (An `ai.hint` is exactly the\n * right place to say what the dropped refinement wants — the note nudges toward\n * writing one.)\n */\nimport { BOUND_UNIT_NOUN, fieldJsonSchema, fieldTypeOf, isWriterLocked, writerLockOf } from '@elytracms/core/cms-core'\nimport type { FieldDef, JsonSchema } from '@elytracms/core/cms-core'\nimport { MARKDOWN_PROSE_DESCRIPTION } from '@elytracms/core/rich-text'\nimport type { AssetCandidate, RelationCandidate, WriterLeash } from './types'\n\n/** The note appended when a field's constraint projection is knowingly partial. */\nexport const INCOMPLETE_CONSTRAINTS_NOTE =\n 'further validation rules apply that are not expressible here — keep the value conventional and plausible'\n\n/**\n * What the catalog says about a composition (`blocks`) — its writer form is NONE\n * (`FIELD_TYPES.blocks.writer === 'composition'`, ratified 4c, EC-593): the assistant\n * changes component props, never which components there are. Said in the prose because\n * the omission must be VISIBLE, not a field the model simply never hears of.\n */\nexport const COMPOSITION_NOTE =\n 'page-builder composition: the assistant never adds, moves or removes its components (that is done on the canvas) — change one component prop with blockId + prop (getDocument lists the components and their props)'\n\n/**\n * Whether a field's validation has rules its projection cannot state (RULES D13): a\n * schema whose projection is knowingly partial (`schemaComplete: false`), or a FUNCTION\n * `validate` — normalisation stamps `schemaComplete` on a schema-form validator only, so\n * a declared `validate` without it is a function no consumer can read.\n */\nfunction hasUnstatedRules(field: FieldDef): boolean {\n const validation = field.validation\n if (!validation) return false\n return validation.schemaComplete === false || (validation.validate !== undefined && validation.schemaComplete === undefined)\n}\n\n/**\n * The prose form of `format: 'date-time'` (EC-505). The JSON-Schema keyword rides\n * on the target shape for the provider's validator; the CATALOG has no JSON Schema\n * at all — it is prose only, and the chat update path authors date values through\n * it. So the format is said in words too, or the only channel that path has says\n * nothing about the one format the engine accepts.\n *\n * Kept to three words deliberately: measured at 6 tokens, against 20 for the same\n * note with a worked example — and it rides every date field of every turn.\n */\nexport const ISO_DATE_NOTE = 'ISO 8601 date'\n\nfunction isSchemaNode(value: unknown): value is JsonSchema {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * The phrase for a field's declared `min`/`max` in the UNIT its type declares\n * (`FIELD_TYPES[type].bounds`, EC-581) — or `undefined` when the field declares none\n * or the unit has a JSON-Schema keyword the projection already carries. Today that is\n * rich text: its bounds count PLAIN-TEXT characters of the stored value (ratified\n * 2a(iii)), which no keyword on the markdown string the model writes can express.\n */\nexport function plainTextBoundPhrase(field: FieldDef): string | undefined {\n if (fieldTypeOf(field).bounds(field) !== 'plain-text') return undefined\n const min = typeof field.validation?.min === 'number' ? field.validation.min : undefined\n const max = typeof field.validation?.max === 'number' ? field.validation.max : undefined\n const noun = BOUND_UNIT_NOUN['plain-text']\n if (min !== undefined && max !== undefined) return `${min}–${max} ${noun}`\n if (min !== undefined) return `at least ${min} ${noun}`\n if (max !== undefined) return `at most ${max} ${noun}`\n return undefined\n}\n\n/** The declared sub-fields of an object or an asset placement (`[]` for any other type). */\nfunction subFieldsOf(field: FieldDef): readonly FieldDef[] {\n return field.type === 'object' ? field.fields : field.type === 'asset' ? (field.fields ?? []) : []\n}\n\n/**\n * The clauses only the DECLARATION can say, for a field and every sub-field below it,\n * name-prefixed (`note: at most 400 plain-text characters`, `label: required`):\n * - a plain-text bound ({@link plainTextBoundPhrase}, EC-581);\n * - a sub-field's `required` (RULES F3d — the shape's JSON `required` carried it, the\n * prose never did); the field's own is said by {@link describeFieldConstraints};\n * - a sub-field's rules the projection cannot state ({@link hasUnstatedRules});\n * - a READ-ONLY sub-field (EC-716): `locked: read-only` — the writer form omits it, the\n * prose still says it is there. An `ai.exclude` sub-field is not mentioned at all.\n */\nfunction declarationClauses(field: FieldDef, prefix = ''): string[] {\n const own = plainTextBoundPhrase(field)\n const out = own !== undefined ? [`${prefix}${own}`] : []\n for (const sub of subFieldsOf(field)) {\n const lock = writerLockOf(sub)\n if (lock === 'excluded') continue\n const subPrefix = `${prefix}${sub.name}: `\n if (lock === 'read-only') {\n out.push(`${subPrefix}read-only`)\n continue\n }\n if (sub.validation?.required === true) out.push(`${subPrefix}required`)\n out.push(...declarationClauses(sub, subPrefix))\n if (hasUnstatedRules(sub)) out.push(`${subPrefix}${INCOMPLETE_CONSTRAINTS_NOTE}`)\n }\n return out\n}\n\n/**\n * A rich-text field's WRITER form (EC-379, D2 of EC-556): a MARKDOWN string. The\n * description carries the allowed subset (from the engine converter — one source\n * of truth); the engine converts the markdown into the v2 storage value and\n * rejects out-of-subset constructs with explicit issues.\n *\n * EC-563/EC-581: the declared bounds are said in their unit — plain-text characters,\n * markup not counted — in the description, read off the per-FieldType definition.\n * They used to ride as `minLength`/`maxLength` on the markdown string: a different\n * unit (markdown characters) than anything enforced, and a provider validating the\n * structured output would have enforced the wrong one.\n */\nexport function richTextWriterSchema(field: FieldDef): JsonSchema {\n const schema: JsonSchema = { type: 'string' }\n const bound = plainTextBoundPhrase(field)\n schema.description = `Rich text, written as ${MARKDOWN_PROSE_DESCRIPTION} Write genuine, publishable prose.${bound ? ` Length: ${bound}.` : ''}`\n return schema\n}\n\n/**\n * The WRITER-facing form of one field, at EVERY depth (EC-556): what a model should\n * hand in. Derived from the validator-facing projection (`fieldJsonSchema` — the\n * value at rest, every merged constraint included) by one rule: **the writer omits\n * exactly what the write-time lens (`selfTypeFieldValue`) restores.**\n *\n * - asset, no per-use sub-fields → the bare id (the projection's `id` property):\n * the lens wraps a bare id string into `{ type: 'asset', id }`;\n * - asset WITH sub-fields → the envelope itself, `type` included: the lens wraps a\n * string, never an object missing its `type`, and the sub-fields need a place;\n * - relation → `{ collection, id }` (the envelope minus `type`): the lens rebuilds\n * a bare reference into its envelope;\n * - rich text → a markdown string (`richTextWriterSchema`) — the engine converts it\n * wherever it sits (`writer-form.ts`, EC-593);\n * - an `object` → its own projection with every subfield in ITS writer form;\n * - a composition (`blocks`) → NONE (`undefined`): its structure is the canvas's\n * (ratified 4c, EC-593) — a target shape omits it and says so.\n *\n * Writer objects are closed (`additionalProperties: false`): a writer inventing keys\n * is a fault, where a stored envelope carrying extra keys is drift the lens\n * tolerates (EC-556 §B) — which is why the projection itself stays open.\n *\n * With a `leash` (the create target shape, EC-381 → EC-593) every relation / asset id\n * — at EVERY depth, not only a top-level field — is an ENUM over what the model\n * searched for that position (a relation's target, an asset's `accept`), and a position\n * with nothing searched is omitted (empty beats wrong). Without one (the catalog prose)\n * the plain writer forms are described.\n *\n * A writer-locked SUB-field (EC-716: `ai.exclude` or `form.readOnly`, core's\n * `isWriterLocked`) has no writer form at any depth: it is omitted from its object or\n * placement, so a writer cannot name it. The field's OWN lock is its caller's to apply\n * (the create shape omits the field; the catalog marks it read-only).\n */\nexport function writerFieldSchema(field: FieldDef, leash?: WriterLeash): JsonSchema | undefined {\n return writerForm(field, fieldJsonSchema(field), leash)\n}\n\n/** An id enum over searched candidates, labelled for the model to choose against. */\nfunction candidateEnum(candidates: readonly (RelationCandidate | AssetCandidate)[], asset: boolean): JsonSchema {\n const label = candidates.map((c) => (c.label ? `${c.id} (${c.label})` : c.id)).join(', ')\n return {\n type: 'string',\n enum: candidates.map((c) => c.id),\n description: asset\n ? `Choose an EXISTING library asset id, one of: ${label}. Leave empty if none fits.`\n : `Choose one of: ${label}`,\n }\n}\n\n/** EC-581: the writer form is DECLARED per type (`FIELD_TYPES[type].writer`); built here. */\nfunction writerForm(field: FieldDef, base: JsonSchema, leash?: WriterLeash): JsonSchema | undefined {\n switch (fieldTypeOf(field).writer) {\n case 'markdown':\n return richTextWriterSchema(field)\n case 'composition':\n return undefined\n case 'placement': {\n if (field.type !== 'asset') return base\n // `type`/`id` belong to the envelope; the projection never lets a sub-field\n // of that name shadow them, and neither does the writer form.\n const subfields = (field.fields ?? []).filter((sub) => sub.name !== 'type' && sub.name !== 'id')\n const candidates = leash?.assetCandidates(field)\n if (candidates !== undefined && candidates.length === 0) return undefined\n const idSchema = (item: JsonSchema): JsonSchema =>\n candidates !== undefined ? candidateEnum(candidates, true) : bareAssetId(item)\n // EC-716: with every placement sub-field locked, the writer hands in the bare id.\n return perItem(field.multiple, base, (item) =>\n subfields.every((sub) => isWriterLocked(sub))\n ? idSchema(item)\n : writerObject(subfields, { ...item, properties: { ...propertiesOf(item), id: idSchema(item) } }, leash),\n )\n }\n case 'reference': {\n if (field.type !== 'relation') return base\n if (!leash) return perItem(field.multiple, base, bareReference)\n const candidates = leash.relationCandidates(field.target)\n if (candidates.length === 0) return undefined\n return perItem(field.multiple, base, () => candidateEnum(candidates, false))\n }\n case 'object':\n return field.type === 'object'\n ? perItem(field.multiple, base, (item) => writerObject(field.fields, item, leash))\n : base\n case 'value':\n return base\n }\n}\n\n/** Apply `one` to the item schema of a `multiple` field, or to the field's own schema. */\nfunction perItem(multiple: boolean, base: JsonSchema, one: (item: JsonSchema) => JsonSchema): JsonSchema {\n if (!multiple) return one(base)\n return isSchemaNode(base.items) ? { ...base, items: one(base.items) } : base\n}\n\nfunction propertiesOf(node: JsonSchema): Record<string, JsonSchema> {\n const properties = node.properties\n return isSchemaNode(properties) ? (properties as Record<string, JsonSchema>) : {}\n}\n\n/** The envelope's `id` property — the bare id string the lens wraps. */\nfunction bareAssetId(envelope: JsonSchema): JsonSchema {\n return propertiesOf(envelope).id ?? { type: 'string' }\n}\n\n/** The reference envelope minus the `type` discriminator the lens restores. */\nfunction bareReference(envelope: JsonSchema): JsonSchema {\n const { type: _discriminator, ...properties } = propertiesOf(envelope)\n const required = Array.isArray(envelope.required)\n ? envelope.required.filter((key) => key !== 'type')\n : []\n return { ...envelope, properties, required, additionalProperties: false }\n}\n\n/**\n * An object-shaped node with each declared subfield in its writer form, closed. A\n * sub-field with no writer form here — a writer-locked one (EC-716), or nothing searched\n * for its id — is omitted, and leaves the `required` list with it.\n */\nfunction writerObject(fields: readonly FieldDef[], node: JsonSchema, leash?: WriterLeash): JsonSchema {\n const properties: Record<string, JsonSchema> = { ...propertiesOf(node) }\n let required = Array.isArray(node.required) ? (node.required as string[]) : undefined\n for (const sub of fields) {\n const subBase = properties[sub.name]\n if (subBase === undefined) continue\n const written = isWriterLocked(sub) ? undefined : writerForm(sub, subBase, leash)\n if (written !== undefined) properties[sub.name] = written\n else {\n delete properties[sub.name]\n required = required?.filter((name) => name !== sub.name)\n }\n }\n return { ...node, properties, ...(required ? { required } : {}), additionalProperties: false }\n}\n\n/**\n * Drop `pattern` wherever `format` is present (EC-473/EC-474 req 6). Returns the\n * same object when there is nothing to strip, so callers can rely on identity.\n */\nexport function stripRedundantPattern(schema: JsonSchema): JsonSchema {\n if (schema.format === undefined || schema.pattern === undefined) return schema\n const { pattern: _pattern, ...rest } = schema\n return rest\n}\n\n/**\n * Apply a node transform through a schema tree (`items` + `properties`), preserving\n * object identity wherever nothing changed — `buildTargetShape` relies on that to\n * avoid copying a property it did not touch.\n */\nfunction mapSchemaDeep(schema: JsonSchema, transform: (node: JsonSchema) => JsonSchema): JsonSchema {\n let out = transform(schema)\n const items = out.items\n if (typeof items === 'object' && items !== null && !Array.isArray(items)) {\n const next = mapSchemaDeep(items as JsonSchema, transform)\n if (next !== items) out = { ...out, items: next }\n }\n const properties = out.properties\n if (typeof properties === 'object' && properties !== null && !Array.isArray(properties)) {\n let changed = false\n const nextProps: Record<string, JsonSchema> = {}\n for (const [key, value] of Object.entries(properties as Record<string, unknown>)) {\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n const next = mapSchemaDeep(value as JsonSchema, transform)\n nextProps[key] = next\n if (next !== value) changed = true\n } else {\n nextProps[key] = value as JsonSchema\n }\n }\n if (changed) out = { ...out, properties: nextProps }\n }\n return out\n}\n\n/** Recursively strip redundant patterns through `items` / `properties`. */\nexport function stripRedundantPatternsDeep(schema: JsonSchema): JsonSchema {\n return mapSchemaDeep(schema, stripRedundantPattern)\n}\n\n/**\n * The whole model-facing rewrite of a projected schema (EC-505): redundant patterns\n * stripped everywhere in the tree — an `object` field's SUBfield gets the same\n * treatment as a top-level one. Identity-preserving. (The date-union narrowing that\n * also ran here is gone: a date is one instant string at rest since EC-586.)\n */\nexport function toModelFacingSchema(schema: JsonSchema): JsonSchema {\n return mapSchemaDeep(schema, stripRedundantPattern)\n}\n\n/**\n * Human-readable phrases for the constraint keywords of ONE schema node, in a stable\n * order — the form a model reads best. Node-level only: nested `items`/`properties`\n * are {@link describeSchemaDeep}'s business. Total over the projection's numeric,\n * string, array and option vocabulary. An `enum` IS phrased (RULES D14, EC-593): the\n * catalog is prose only, so a `select`'s options never reached the update path that\n * authors through it. A `const` is not (a relation's `collection` is the envelope's,\n * not a rule the writer chooses against).\n */\nfunction describeSchemaConstraints(schema: JsonSchema): string[] {\n const out: string[] = []\n const num = (value: unknown): number | undefined =>\n typeof value === 'number' ? value : undefined\n const text = (value: unknown): string | undefined =>\n typeof value === 'string' ? value : undefined\n\n if (Array.isArray(schema.enum) && schema.enum.length > 0) {\n // Quoted and `|`-separated: the clause itself is joined with commas.\n out.push(`one of ${schema.enum.map((option) => JSON.stringify(option)).join(' | ')}`)\n }\n\n const minLength = num(schema.minLength)\n const maxLength = num(schema.maxLength)\n if (minLength !== undefined && maxLength !== undefined) {\n out.push(`${minLength}–${maxLength} characters`)\n } else if (minLength !== undefined) out.push(`at least ${minLength} characters`)\n else if (maxLength !== undefined) out.push(`at most ${maxLength} characters`)\n\n const minimum = num(schema.minimum)\n const maximum = num(schema.maximum)\n if (minimum !== undefined && maximum !== undefined) out.push(`between ${minimum} and ${maximum}`)\n else if (minimum !== undefined) out.push(`at least ${minimum}`)\n else if (maximum !== undefined) out.push(`at most ${maximum}`)\n\n // EC-522: the projection carried these already; the prose never said them.\n const exclusiveMinimum = num(schema.exclusiveMinimum)\n const exclusiveMaximum = num(schema.exclusiveMaximum)\n if (exclusiveMinimum !== undefined) out.push(`more than ${exclusiveMinimum}`)\n if (exclusiveMaximum !== undefined) out.push(`less than ${exclusiveMaximum}`)\n const multipleOf = num(schema.multipleOf)\n if (multipleOf !== undefined) out.push(`a multiple of ${multipleOf}`)\n\n const minItems = num(schema.minItems)\n const maxItems = num(schema.maxItems)\n if (minItems !== undefined) out.push(`at least ${minItems} items`)\n if (maxItems !== undefined) out.push(`at most ${maxItems} items`)\n if (schema.uniqueItems === true) out.push('no duplicate items')\n\n // EC-473: date bounds finally reach a consumer. `formatMinimum`/`formatMaximum`\n // is the projection's machine form; this is the form a model acts on.\n const from = text(schema.formatMinimum)\n const to = text(schema.formatMaximum)\n if (from !== undefined) out.push(`not earlier than ${from}`)\n if (to !== undefined) out.push(`not later than ${to}`)\n\n const format = text(schema.format)\n // EC-505: `date-time` is said in words rather than skipped — the catalog carries\n // no JSON Schema, so this is the only place the update path learns the format.\n if (format === 'date-time') out.push(ISO_DATE_NOTE)\n else if (format !== undefined) out.push(`format: ${format}`)\n // Only a pattern that is NOT explained by a format is worth the tokens.\n const pattern = text(schema.pattern)\n if (pattern !== undefined && format === undefined) out.push(`matching /${pattern}/`)\n\n return out\n}\n\n/**\n * The phrases of one projected schema node AND everything below it (EC-522): a\n * `multiple` field's per-item bounds become an `each item: …` clause, an object's\n * subfield bounds a `<name>: …` clause, recursively. The projection is the ONLY\n * input — since EC-522 the merge carries nested `items`/`properties` constraints\n * itself, so this module never looks past `fieldJsonSchema` (EC-505's direct read\n * of the derived fragment's `items` is gone, and with it the second place that\n * understood fragment structure).\n *\n * Which side a schema addresses is still the executor's call, not this module's:\n * `validateDocument` hands the validator the field's WHOLE value — a `multiple`\n * field's array — so the per-item form is `z.array(z.string().min(3))`, and a\n * bare `z.string().min(3)` there is a config error at load (EC-522), never a\n * silently mis-applied bound.\n */\nfunction describeSchemaDeep(schema: JsonSchema): string[] {\n const out = describeSchemaConstraints(schema)\n if (isSchemaNode(schema.items)) {\n const itemParts = describeSchemaDeep(schema.items)\n if (itemParts.length > 0) out.push(`each item: ${itemParts.join(', ')}`)\n }\n if (isSchemaNode(schema.properties)) {\n for (const [name, sub] of Object.entries(schema.properties)) {\n if (!isSchemaNode(sub)) continue\n const subParts = describeSchemaDeep(sub)\n if (subParts.length > 0) out.push(`${name}: ${subParts.join(', ')}`)\n }\n }\n return out\n}\n\n/**\n * Human-readable constraint phrases for one field, in a stable order — the form a\n * model reads best (and the form the collections catalog carries, where there is no\n * JSON Schema to hand the model at all). Empty when the field constrains nothing.\n */\nexport function describeFieldConstraints(field: FieldDef): string[] {\n const out: string[] = []\n if (field.validation?.required === true) out.push('required')\n // EC-556: the prose tells a WRITER what to write, so it reads the writer form —\n // and a type with NO writer form says why (EC-593, ratified 4c).\n const writer = writerFieldSchema(field)\n if (writer === undefined) out.push(COMPOSITION_NOTE)\n else out.push(...describeSchemaDeep(writer))\n // EC-581 / EC-593: what only the declaration can say — a plain-text bound, a\n // sub-field's `required`, a sub-field's unstated rules — at every depth the writer\n // form reaches (`note: at most 400 plain-text characters …`, `label: required`).\n out.push(...declarationClauses(field))\n if (field.validation?.unique === true) out.push('unique across the collection')\n // The one thing the projection cannot say about itself: that it is partial (an\n // opaque vendor, a `.refine()`, — EC-522 — a rule at a position the field's shape\n // has no counterpart for, or — RULES D13 — a validate FUNCTION, which no projection\n // can read at all). Set at config resolve, read here, nowhere else.\n if (hasUnstatedRules(field)) out.push(INCOMPLETE_CONSTRAINTS_NOTE)\n return out\n}\n\n/** The constraint phrases as one clause (`min 2 characters; required`), or `undefined`. */\nexport function constraintSentence(field: FieldDef): string | undefined {\n const parts = describeFieldConstraints(field)\n return parts.length > 0 ? parts.join('; ') : undefined\n}\n"]}
@@ -18,6 +18,7 @@
18
18
  * tool result emits a tool call; a prompt carrying one streams a text answer.
19
19
  */
20
20
  import type { LanguageModel } from 'ai';
21
+ import type { CatalogCollection } from './chat-context.js';
21
22
  import type { AiProvider, AiProviderCapabilities, GenerateStructuredRequest } from './types.js';
22
23
  /** The SDK's v4 language-model spec, extracted structurally from the type-only union. */
23
24
  export type FakeLanguageModelV4 = Extract<LanguageModel, {
@@ -66,20 +67,30 @@ export declare function createFakeChatModel(options?: FakeChatModelOptions): Fak
66
67
  export interface FakeCreateFlowChatModelOptions {
67
68
  /** Model id reported to the SDK (default `fake-create-flow-model`). */
68
69
  modelId?: string;
69
- /** The collection the first stage searches (a relation target; default `author`). */
70
+ /**
71
+ * @deprecated EC-715 — never read. The keyless assistant reads the project's catalog from
72
+ * the system prompt and searches every REQUIRED relation target of the collection it
73
+ * creates in. Kept so a project scaffolded before the change still type-checks; delete it.
74
+ */
70
75
  searchCollection?: string;
71
76
  /**
72
- * EC-381: when set, the create flow inserts a `listAssets` stage after the
73
- * relation search and before `createDocument`, so a collection with an image
74
- * field gets it filled from a REAL library asset (the enum leash). Optional
75
- * search text passed to `listAssets` (omit to list all).
77
+ * EC-381: whether the create flow inserts a `listAssets` stage before `createDocument`,
78
+ * so an image field is filled from a REAL library asset (the enum leash). Default
79
+ * (EC-715): when the collection it creates in has an asset field.
76
80
  */
77
81
  searchAssets?: boolean;
78
82
  /** Optional query passed to the scripted `listAssets` stage (EC-381). */
79
83
  assetQuery?: string;
80
- /** The collection the terminal stage creates in (default `post`). */
84
+ /**
85
+ * @deprecated EC-715 — never read. The collection is chosen from the catalog: one the
86
+ * user's text names, else the open document's, else the first creatable one. Kept so a
87
+ * project scaffolded before the change still type-checks; delete it.
88
+ */
81
89
  createCollection?: string;
82
- /** The final answer streamed word-by-word after the proposal lands. */
90
+ /**
91
+ * The answer streamed after a proposal LANDED (default: what was drafted, then
92
+ * {@link FAKE_CREATE_FLOW_ANSWER}). A refused create always answers with the refusal.
93
+ */
83
94
  answerText?: string;
84
95
  /**
85
96
  * EC-378: the field the scripted UPDATE flow changes (default `title`). A user
@@ -89,13 +100,18 @@ export interface FakeCreateFlowChatModelOptions {
89
100
  updateField?: string;
90
101
  /** Routes a user turn to the update flow (default {@link FAKE_UPDATE_PATTERN}). */
91
102
  updatePattern?: RegExp;
92
- /** The final answer of the scripted update flow. */
103
+ /**
104
+ * The answer streamed after an update proposal LANDED (default: what changed, then
105
+ * {@link FAKE_UPDATE_FLOW_ANSWER}). A refused update always answers with the refusal.
106
+ */
93
107
  updateAnswerText?: string;
94
108
  }
95
- /** Default final answer of the scripted create flow. */
96
- export declare const FAKE_CREATE_FLOW_ANSWER = "Done \u2014 review the proposal below and apply it as a draft.";
97
- /** Default final answer of the scripted update flow (EC-378). */
98
- export declare const FAKE_UPDATE_FLOW_ANSWER = "Done \u2014 review the proposed change below and apply it as a draft.";
109
+ /** The closing sentence of a create that landed (EC-715: said only when one did). */
110
+ export declare const FAKE_CREATE_FLOW_ANSWER = "Review the proposal below and apply it as a draft.";
111
+ /** The closing sentence of an update that landed (EC-378; EC-715: said only when one did). */
112
+ export declare const FAKE_UPDATE_FLOW_ANSWER = "Review the proposed change below and apply it as a draft.";
113
+ /** What the keyless assistant says when the catalog offers nothing it may create in (EC-715). */
114
+ export declare const FAKE_NOTHING_CREATABLE_ANSWER = "This project has no collection I can create a document in \u2014 singletons and the media library take no new documents.";
99
115
  /** Default edit-request detector of the scripted assistant (EC-378). */
100
116
  export declare const FAKE_UPDATE_PATTERN: RegExp;
101
117
  /**
@@ -123,37 +139,71 @@ export declare function fakeUpdateTargetFrom(userText: string, fallback: string)
123
139
  blockId?: string;
124
140
  prop?: string;
125
141
  };
142
+ /** One tool call or tool result of the CURRENT user turn. */
143
+ export interface TurnToolPart {
144
+ toolCallId: string;
145
+ toolName: string;
146
+ /** A call's parsed input. */
147
+ input?: unknown;
148
+ /** A result's output (`{ type: 'json' | 'text' | 'error-text' | 'error-json', value }`). */
149
+ output?: {
150
+ type?: unknown;
151
+ value?: unknown;
152
+ };
153
+ }
154
+ /**
155
+ * The collection a keyless create targets (EC-715), read off the catalog the system prompt
156
+ * carries — never a hardcoded name: one the user's text NAMES (id or label; the earliest
157
+ * mention wins), else the collection of the document open in the editor, else the first
158
+ * creatable one. A singleton or the media library is never a create target.
159
+ */
160
+ export declare function chooseCreateCollection(catalog: readonly CatalogCollection[], userText: string, openCollection: string | undefined): CatalogCollection | undefined;
161
+ /**
162
+ * The create flow's answer, DERIVED from the `createDocument` result (EC-715): a refused
163
+ * create states the refusal, quoting the error; a proposal says what was drafted and which
164
+ * positions it left empty (`unfilled`). It used to say "Done" whatever the result said.
165
+ */
166
+ export declare function fakeCreateAnswer(result: TurnToolPart, catalog: readonly CatalogCollection[], collection: unknown, successText?: string): string;
167
+ /**
168
+ * The update flow's answer, DERIVED from the tool results (EC-715): a refused update states
169
+ * the refusal, quoting the error; a proposal names what changed.
170
+ */
171
+ export declare function fakeUpdateAnswer(result: TurnToolPart, successText?: string): string;
126
172
  /**
127
173
  * A scripted, STATELESS fake chat model that drives the FULL create flow — and,
128
174
  * for a user turn matching the update pattern, the EC-378 UPDATE flow — over the
129
175
  * native loop (EC-334): keyless playground / CI exercise the whole client-tool
130
176
  * round-trip deterministically. Stage is read from the CURRENT user turn's tool
131
- * results (prompt suffix after the last user message), so every follow-up turn
132
- * starts a fresh flow — multi-turn works without model state.
177
+ * calls and results (prompt suffix after the last user message), so every follow-up
178
+ * turn starts a fresh flow — multi-turn works without model state.
133
179
  *
134
- * Create flow (default):
135
- * 1. no tool result yet → `searchDocuments({ collection: searchCollection })`
136
- * 2. search result, no create → `createDocument({ collection, instruction: <last user text> })`
137
- * 3. create result present → the answer text, word by word.
180
+ * EC-715: it reads THE PROJECT, never a hardcoded collection — the catalog and the open
181
+ * document arrive in the system prompt (the route has no data access; that prose is the only
182
+ * channel), read back by `parseCollectionCatalog` / `parseCurrentDocumentSection`. Every
183
+ * answer is derived from the tool results (a refusal is said, never "Done"), and every tool
184
+ * call id carries the user-turn number, so a second proposal never replaces the first card.
138
185
  *
139
- * With `searchAssets` (EC-381), a `listAssets` stage is inserted between 1 and 2:
140
- * 1. no tool result yet → `searchDocuments({ collection: searchCollection })`
141
- * 2. search done, no listAssets → `listAssets({ query?: assetQuery })`
142
- * 3. listAssets done, no create → `createDocument({ collection, instruction })`
143
- * 4. create result present → the answer text, word by word.
186
+ * Create flow:
187
+ * 1. the target: {@link chooseCreateCollection} (none creatable → it says so);
188
+ * 2. each REQUIRED relation target not yet searched this turn → `searchDocuments`;
189
+ * 3. the target has an asset field (or `searchAssets`) and the library was not listed
190
+ * this turn → `listAssets({ query?: assetQuery })`;
191
+ * 4. → `createDocument({ collection, instruction: <last user text> })`;
192
+ * 5. a `createDocument` result → {@link fakeCreateAnswer}.
144
193
  *
145
194
  * Update flow (user text matches `updatePattern`, e.g. "Change the title to: X"):
146
195
  * 1. no tool result yet → `getDocument({})` — target omitted on purpose:
147
196
  * the executor resolves the document OPEN in the
148
197
  * editor (the EC-378 current-document seam)
149
- * 2. document read, no update → `updateFields({ changes: [{
198
+ * 2. document read → `updateFields({ changes: [{
150
199
  * ...fakeUpdateTargetFrom(<text>, updateField),
151
200
  * value: fakeUpdateValueFrom(<last user text>) }] })`
152
201
  * — "Set the body to: ## Neu" drives a markdown
153
202
  * rich-text update deterministically (EC-379);
154
203
  * "Set the body component <id> <prop> to: X" one
155
- * component prop at any depth (EC-593)
156
- * 3. update result present → the update answer, word by word.
204
+ * component prop at any depth (EC-593); a refused read
205
+ * answers with the refusal instead
206
+ * 3. update result present → {@link fakeUpdateAnswer}.
157
207
  */
158
208
  export declare function createFakeCreateFlowChatModel(options?: FakeCreateFlowChatModelOptions): FakeChatModel;
159
209
  export interface FakeProviderOptions {