@elytracms/studio 0.0.92 → 0.0.94

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 (599) hide show
  1. package/convex/analytics-cost.test.ts +817 -0
  2. package/convex/asset-alt-migration.test.ts +127 -0
  3. package/convex/asset-replace.test.ts +168 -0
  4. package/convex/asset-revalidate.test.ts +6 -6
  5. package/convex/assets.ts +41 -5
  6. package/convex/auth.ts +5 -2
  7. package/convex/authority.ts +35 -11
  8. package/convex/cms.ts +25 -11
  9. package/convex/contract-parity.test.ts +25 -3
  10. package/convex/delivery.ts +12 -9
  11. package/convex/document-revalidate.test.ts +10 -12
  12. package/convex/guard.ts +9 -7
  13. package/convex/http-routes.test.ts +18 -8
  14. package/convex/http.ts +23 -17
  15. package/convex/ingest.test.ts +326 -0
  16. package/convex/ingest.ts +281 -0
  17. package/convex/invites.ts +13 -5
  18. package/convex/members.ts +10 -4
  19. package/convex/orphan-hydration.test.ts +116 -0
  20. package/convex/rail-write-parity.test.ts +11 -8
  21. package/convex/record-functions.ts +325 -0
  22. package/convex/records.test.ts +156 -0
  23. package/convex/redirect-migration.test.ts +205 -0
  24. package/convex/redirect-revalidate.test.ts +5 -1
  25. package/convex/redirects.ts +120 -79
  26. package/convex/role-policy.test.ts +315 -0
  27. package/convex/roles.ts +83 -0
  28. package/convex/schema.ts +36 -0
  29. package/convex/triggers.ts +21 -1
  30. package/dist/addon.d.ts +71 -0
  31. package/dist/addon.js +67 -0
  32. package/dist/addon.js.map +1 -0
  33. package/dist/components/account/account-settings.js +2 -2
  34. package/dist/components/account/account-settings.js.map +1 -1
  35. package/dist/components/account/credential-panel.js +2 -2
  36. package/dist/components/account/credential-panel.js.map +1 -1
  37. package/dist/components/account/self-account-schema.d.ts +6 -15
  38. package/dist/components/account/self-account-schema.js +52 -34
  39. package/dist/components/account/self-account-schema.js.map +1 -1
  40. package/dist/components/account/self-account-view-source.d.ts +1 -1
  41. package/dist/components/account/self-account-view-source.js +6 -4
  42. package/dist/components/account/self-account-view-source.js.map +1 -1
  43. package/dist/components/ai/ai-chat-panel-lazy.js +1 -1
  44. package/dist/components/ai/ai-chat-panel-lazy.js.map +1 -1
  45. package/dist/components/ai/ai-chat-panel.js +6 -6
  46. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  47. package/dist/components/ai/ai-cms-panel.js +2 -2
  48. package/dist/components/ai/ai-cms-panel.js.map +1 -1
  49. package/dist/components/ai/ai-panel-toggle.js +4 -4
  50. package/dist/components/ai/ai-panel-toggle.js.map +1 -1
  51. package/dist/components/ai/chat-ui.js +2 -2
  52. package/dist/components/ai/chat-ui.js.map +1 -1
  53. package/dist/components/ai/proposal-card.d.ts +2 -2
  54. package/dist/components/ai/proposal-card.js +4 -3
  55. package/dist/components/ai/proposal-card.js.map +1 -1
  56. package/dist/components/ai/proposal-issues.js +2 -2
  57. package/dist/components/ai/proposal-issues.js.map +1 -1
  58. package/dist/components/ai/translate-document-action.js +2 -2
  59. package/dist/components/ai/translate-document-action.js.map +1 -1
  60. package/dist/components/ai/translate-document-dialog.js +10 -8
  61. package/dist/components/ai/translate-document-dialog.js.map +1 -1
  62. package/dist/components/ai/translate-skipped.d.ts +19 -0
  63. package/dist/components/ai/translate-skipped.js +46 -0
  64. package/dist/components/ai/translate-skipped.js.map +1 -0
  65. package/dist/components/app-views/default-modules.d.ts +50 -2
  66. package/dist/components/app-views/default-modules.js +65 -20
  67. package/dist/components/app-views/default-modules.js.map +1 -1
  68. package/dist/components/app-views/module.d.ts +53 -3
  69. package/dist/components/app-views/module.js +14 -7
  70. package/dist/components/app-views/module.js.map +1 -1
  71. package/dist/components/app-views/sidebar.js +1 -1
  72. package/dist/components/app-views/sidebar.js.map +1 -1
  73. package/dist/components/app-views/studio-config.d.ts +11 -2
  74. package/dist/components/app-views/studio-config.js.map +1 -1
  75. package/dist/components/assets/asset-detail.js +20 -10
  76. package/dist/components/assets/asset-detail.js.map +1 -1
  77. package/dist/components/assets/asset-grid.js +2 -2
  78. package/dist/components/assets/asset-grid.js.map +1 -1
  79. package/dist/components/assets/asset-metadata.d.ts +15 -4
  80. package/dist/components/assets/asset-metadata.js +15 -2
  81. package/dist/components/assets/asset-metadata.js.map +1 -1
  82. package/dist/components/assets/asset-search.js +4 -1
  83. package/dist/components/assets/asset-search.js.map +1 -1
  84. package/dist/components/assets/asset-selection.d.ts +0 -8
  85. package/dist/components/assets/asset-selection.js +4 -23
  86. package/dist/components/assets/asset-selection.js.map +1 -1
  87. package/dist/components/assets/asset-thumb.js +6 -5
  88. package/dist/components/assets/asset-thumb.js.map +1 -1
  89. package/dist/components/assets/assets-collection-view-source.d.ts +5 -5
  90. package/dist/components/assets/assets-collection-view-source.js +9 -5
  91. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  92. package/dist/components/assets/assets-module.js +1 -1
  93. package/dist/components/assets/assets-module.js.map +1 -1
  94. package/dist/components/assets/assets-store.d.ts +16 -2
  95. package/dist/components/assets/assets-store.js +28 -6
  96. package/dist/components/assets/assets-store.js.map +1 -1
  97. package/dist/components/assets/assets-view-preference.js +1 -1
  98. package/dist/components/assets/assets-view-preference.js.map +1 -1
  99. package/dist/components/assets/replace-file.d.ts +22 -0
  100. package/dist/components/assets/replace-file.js +59 -0
  101. package/dist/components/assets/replace-file.js.map +1 -0
  102. package/dist/components/auth-context.d.ts +1 -1
  103. package/dist/components/auth-context.js +6 -6
  104. package/dist/components/auth-context.js.map +1 -1
  105. package/dist/components/builder/blocks-model.d.ts +15 -27
  106. package/dist/components/builder/blocks-model.js +38 -44
  107. package/dist/components/builder/blocks-model.js.map +1 -1
  108. package/dist/components/builder/blocks-panel.d.ts +8 -4
  109. package/dist/components/builder/blocks-panel.js +10 -9
  110. package/dist/components/builder/blocks-panel.js.map +1 -1
  111. package/dist/components/builder/builder-store.d.ts +104 -15
  112. package/dist/components/builder/builder-store.js +157 -32
  113. package/dist/components/builder/builder-store.js.map +1 -1
  114. package/dist/components/builder/canvas-body.d.ts +30 -0
  115. package/dist/components/builder/canvas-body.js +38 -0
  116. package/dist/components/builder/canvas-body.js.map +1 -0
  117. package/dist/components/builder/canvas-breakpoints.js +1 -1
  118. package/dist/components/builder/canvas-breakpoints.js.map +1 -1
  119. package/dist/components/builder/canvas-chrome-css.js +7 -0
  120. package/dist/components/builder/canvas-chrome-css.js.map +1 -1
  121. package/dist/components/builder/canvas-dnd.d.ts +9 -9
  122. package/dist/components/builder/canvas-dnd.js +6 -6
  123. package/dist/components/builder/canvas-dnd.js.map +1 -1
  124. package/dist/components/builder/canvas-inline-editing.d.ts +1 -1
  125. package/dist/components/builder/canvas-inline-editing.js.map +1 -1
  126. package/dist/components/builder/canvas-overlay-geometry.d.ts +1 -1
  127. package/dist/components/builder/canvas-overlay-geometry.js.map +1 -1
  128. package/dist/components/builder/canvas-render-context.d.ts +28 -20
  129. package/dist/components/builder/canvas-render-context.js +46 -47
  130. package/dist/components/builder/canvas-render-context.js.map +1 -1
  131. package/dist/components/builder/canvas-runs.d.ts +45 -0
  132. package/dist/components/builder/canvas-runs.js +51 -0
  133. package/dist/components/builder/canvas-runs.js.map +1 -0
  134. package/dist/components/builder/canvas-theme-notice.js +2 -2
  135. package/dist/components/builder/canvas-theme-notice.js.map +1 -1
  136. package/dist/components/builder/canvas.js +35 -8
  137. package/dist/components/builder/canvas.js.map +1 -1
  138. package/dist/components/builder/clipboard-actions.d.ts +16 -1
  139. package/dist/components/builder/clipboard-actions.js +48 -11
  140. package/dist/components/builder/clipboard-actions.js.map +1 -1
  141. package/dist/components/builder/composition-field-editor-lazy.js +1 -1
  142. package/dist/components/builder/composition-field-editor-lazy.js.map +1 -1
  143. package/dist/components/builder/composition-field-editor.d.ts +4 -16
  144. package/dist/components/builder/composition-field-editor.js +32 -43
  145. package/dist/components/builder/composition-field-editor.js.map +1 -1
  146. package/dist/components/builder/delete-key.d.ts +5 -0
  147. package/dist/components/builder/delete-key.js +4 -0
  148. package/dist/components/builder/delete-key.js.map +1 -1
  149. package/dist/components/builder/drag-session.d.ts +1 -1
  150. package/dist/components/builder/drag-session.js.map +1 -1
  151. package/dist/components/builder/fixture-thumbnail.js +1 -1
  152. package/dist/components/builder/fixture-thumbnail.js.map +1 -1
  153. package/dist/components/builder/insert-context.d.ts +18 -26
  154. package/dist/components/builder/insert-context.js +47 -94
  155. package/dist/components/builder/insert-context.js.map +1 -1
  156. package/dist/components/builder/layout-inspector.js +1 -1
  157. package/dist/components/builder/layout-inspector.js.map +1 -1
  158. package/dist/components/builder/node-title.js +1 -1
  159. package/dist/components/builder/node-title.js.map +1 -1
  160. package/dist/components/builder/page-tree.d.ts +4 -4
  161. package/dist/components/builder/page-tree.js +8 -8
  162. package/dist/components/builder/page-tree.js.map +1 -1
  163. package/dist/components/builder/placement-refusal.d.ts +18 -0
  164. package/dist/components/builder/placement-refusal.js +46 -0
  165. package/dist/components/builder/placement-refusal.js.map +1 -0
  166. package/dist/components/builder/property-inspector.js +8 -4
  167. package/dist/components/builder/property-inspector.js.map +1 -1
  168. package/dist/components/builder/reuse-context.js +68 -30
  169. package/dist/components/builder/reuse-context.js.map +1 -1
  170. package/dist/components/builder/reuse.d.ts +79 -1
  171. package/dist/components/builder/reuse.js +70 -3
  172. package/dist/components/builder/reuse.js.map +1 -1
  173. package/dist/components/builder/rich-text-toolbar.js +1 -1
  174. package/dist/components/builder/rich-text-toolbar.js.map +1 -1
  175. package/dist/components/builder/run-inspector.d.ts +11 -0
  176. package/dist/components/builder/run-inspector.js +33 -0
  177. package/dist/components/builder/run-inspector.js.map +1 -0
  178. package/dist/components/builder/selection-overlay.js +3 -3
  179. package/dist/components/builder/selection-overlay.js.map +1 -1
  180. package/dist/components/builder/spacing-handles.d.ts +2 -2
  181. package/dist/components/builder/spacing-handles.js +3 -3
  182. package/dist/components/builder/spacing-handles.js.map +1 -1
  183. package/dist/components/cms/asset-field.d.ts +38 -1
  184. package/dist/components/cms/asset-field.js +184 -60
  185. package/dist/components/cms/asset-field.js.map +1 -1
  186. package/dist/components/cms/asset-picker-core.d.ts +16 -0
  187. package/dist/components/cms/asset-picker-core.js +20 -0
  188. package/dist/components/cms/asset-picker-core.js.map +1 -1
  189. package/dist/components/cms/asset-picker.js +14 -20
  190. package/dist/components/cms/asset-picker.js.map +1 -1
  191. package/dist/components/cms/bulk-actions.d.ts +4 -20
  192. package/dist/components/cms/bulk-actions.js +5 -26
  193. package/dist/components/cms/bulk-actions.js.map +1 -1
  194. package/dist/components/cms/bulk-bar.d.ts +2 -2
  195. package/dist/components/cms/bulk-bar.js +2 -2
  196. package/dist/components/cms/bulk-bar.js.map +1 -1
  197. package/dist/components/cms/cell-edit.d.ts +1 -1
  198. package/dist/components/cms/cell-edit.js +1 -1
  199. package/dist/components/cms/cell-edit.js.map +1 -1
  200. package/dist/components/cms/cell.d.ts +1 -1
  201. package/dist/components/cms/cell.js +3 -3
  202. package/dist/components/cms/cell.js.map +1 -1
  203. package/dist/components/cms/cms-index.js +2 -2
  204. package/dist/components/cms/cms-index.js.map +1 -1
  205. package/dist/components/cms/cms-labels.js +2 -2
  206. package/dist/components/cms/cms-labels.js.map +1 -1
  207. package/dist/components/cms/cms-store.d.ts +7 -6
  208. package/dist/components/cms/cms-store.js +23 -43
  209. package/dist/components/cms/cms-store.js.map +1 -1
  210. package/dist/components/cms/collection-table.js +7 -7
  211. package/dist/components/cms/collection-table.js.map +1 -1
  212. package/dist/components/cms/detail-editor.d.ts +5 -2
  213. package/dist/components/cms/detail-editor.js +20 -15
  214. package/dist/components/cms/detail-editor.js.map +1 -1
  215. package/dist/components/cms/details-pane.js +1 -1
  216. package/dist/components/cms/details-pane.js.map +1 -1
  217. package/dist/components/cms/doc-state.js +1 -1
  218. package/dist/components/cms/doc-state.js.map +1 -1
  219. package/dist/components/cms/document-diff-view.d.ts +3 -1
  220. package/dist/components/cms/document-diff-view.js +17 -12
  221. package/dist/components/cms/document-diff-view.js.map +1 -1
  222. package/dist/components/cms/document-editor-context.js +1 -1
  223. package/dist/components/cms/document-editor-context.js.map +1 -1
  224. package/dist/components/cms/document-staging.js +1 -1
  225. package/dist/components/cms/document-staging.js.map +1 -1
  226. package/dist/components/cms/document-url-path.js +2 -2
  227. package/dist/components/cms/document-url-path.js.map +1 -1
  228. package/dist/components/cms/duplicate-document.d.ts +1 -1
  229. package/dist/components/cms/duplicate-document.js.map +1 -1
  230. package/dist/components/cms/editable-cell.js +3 -3
  231. package/dist/components/cms/editable-cell.js.map +1 -1
  232. package/dist/components/cms/field-anchors.d.ts +7 -4
  233. package/dist/components/cms/field-anchors.js +9 -7
  234. package/dist/components/cms/field-anchors.js.map +1 -1
  235. package/dist/components/cms/field-collaboration.d.ts +1 -1
  236. package/dist/components/cms/field-collaboration.js +1 -1
  237. package/dist/components/cms/field-collaboration.js.map +1 -1
  238. package/dist/components/cms/field-connection.d.ts +1 -1
  239. package/dist/components/cms/field-connection.js +2 -2
  240. package/dist/components/cms/field-connection.js.map +1 -1
  241. package/dist/components/cms/field-control-lib.d.ts +15 -1
  242. package/dist/components/cms/field-control-lib.js +23 -4
  243. package/dist/components/cms/field-control-lib.js.map +1 -1
  244. package/dist/components/cms/field-control.js +4 -4
  245. package/dist/components/cms/field-control.js.map +1 -1
  246. package/dist/components/cms/field-renderer.d.ts +1 -1
  247. package/dist/components/cms/field-renderer.js +2 -2
  248. package/dist/components/cms/field-renderer.js.map +1 -1
  249. package/dist/components/cms/filter-builder.d.ts +1 -1
  250. package/dist/components/cms/filter-builder.js +3 -3
  251. package/dist/components/cms/filter-builder.js.map +1 -1
  252. package/dist/components/cms/gate-validation.d.ts +21 -30
  253. package/dist/components/cms/gate-validation.js +11 -32
  254. package/dist/components/cms/gate-validation.js.map +1 -1
  255. package/dist/components/cms/import-export.js +2 -2
  256. package/dist/components/cms/import-export.js.map +1 -1
  257. package/dist/components/cms/issue-copy.js +1 -1
  258. package/dist/components/cms/issue-copy.js.map +1 -1
  259. package/dist/components/cms/issues-pane.js +3 -3
  260. package/dist/components/cms/issues-pane.js.map +1 -1
  261. package/dist/components/cms/object-array-field.js +6 -6
  262. package/dist/components/cms/object-array-field.js.map +1 -1
  263. package/dist/components/cms/prop-fields.d.ts +7 -2
  264. package/dist/components/cms/prop-fields.js +14 -7
  265. package/dist/components/cms/prop-fields.js.map +1 -1
  266. package/dist/components/cms/reference-confirm.d.ts +1 -1
  267. package/dist/components/cms/reference-confirm.js +2 -2
  268. package/dist/components/cms/reference-confirm.js.map +1 -1
  269. package/dist/components/cms/relation-picker.js +2 -2
  270. package/dist/components/cms/relation-picker.js.map +1 -1
  271. package/dist/components/cms/rich-text/block-style.js +1 -1
  272. package/dist/components/cms/rich-text/block-style.js.map +1 -1
  273. package/dist/components/cms/rich-text/component-embed.js +4 -4
  274. package/dist/components/cms/rich-text/component-embed.js.map +1 -1
  275. package/dist/components/cms/rich-text/editor.js +3 -3
  276. package/dist/components/cms/rich-text/editor.js.map +1 -1
  277. package/dist/components/cms/rich-text/embed-picker.d.ts +1 -1
  278. package/dist/components/cms/rich-text/embed-picker.js +3 -3
  279. package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
  280. package/dist/components/cms/rich-text/embed-props.js +3 -3
  281. package/dist/components/cms/rich-text/embed-props.js.map +1 -1
  282. package/dist/components/cms/rich-text/extensions.js +1 -1
  283. package/dist/components/cms/rich-text/extensions.js.map +1 -1
  284. package/dist/components/cms/rich-text/font-size-control.d.ts +1 -1
  285. package/dist/components/cms/rich-text/font-size-control.js +2 -2
  286. package/dist/components/cms/rich-text/font-size-control.js.map +1 -1
  287. package/dist/components/cms/rich-text/insert.d.ts +20 -9
  288. package/dist/components/cms/rich-text/insert.js +25 -17
  289. package/dist/components/cms/rich-text/insert.js.map +1 -1
  290. package/dist/components/cms/rich-text/lazy.js +1 -1
  291. package/dist/components/cms/rich-text/lazy.js.map +1 -1
  292. package/dist/components/cms/rich-text/link-control.js +2 -2
  293. package/dist/components/cms/rich-text/link-control.js.map +1 -1
  294. package/dist/components/cms/rich-text/slash-command.js +3 -3
  295. package/dist/components/cms/rich-text/slash-command.js.map +1 -1
  296. package/dist/components/cms/rich-text/toolbar.js +1 -1
  297. package/dist/components/cms/rich-text/toolbar.js.map +1 -1
  298. package/dist/components/cms/rich-text/unknown-node.js +2 -2
  299. package/dist/components/cms/rich-text/unknown-node.js.map +1 -1
  300. package/dist/components/cms/rich-text-preview.js +2 -2
  301. package/dist/components/cms/rich-text-preview.js.map +1 -1
  302. package/dist/components/cms/row-actions.d.ts +1 -1
  303. package/dist/components/cms/row-actions.js.map +1 -1
  304. package/dist/components/cms/scalar-value-editor.d.ts +1 -1
  305. package/dist/components/cms/scalar-value-editor.js +2 -2
  306. package/dist/components/cms/scalar-value-editor.js.map +1 -1
  307. package/dist/components/cms/use-discard-controller.d.ts +1 -1
  308. package/dist/components/cms/use-discard-controller.js +1 -1
  309. package/dist/components/cms/use-discard-controller.js.map +1 -1
  310. package/dist/components/cms/use-document-draft.d.ts +7 -1
  311. package/dist/components/cms/use-document-draft.js +16 -2
  312. package/dist/components/cms/use-document-draft.js.map +1 -1
  313. package/dist/components/cms/use-document-versions.js +1 -1
  314. package/dist/components/cms/use-document-versions.js.map +1 -1
  315. package/dist/components/cms/use-field-focus.d.ts +1 -1
  316. package/dist/components/cms/use-field-focus.js +2 -2
  317. package/dist/components/cms/use-field-focus.js.map +1 -1
  318. package/dist/components/cms/use-live-url.d.ts +1 -1
  319. package/dist/components/cms/use-live-url.js +3 -3
  320. package/dist/components/cms/use-live-url.js.map +1 -1
  321. package/dist/components/cms/use-publish-controller.d.ts +2 -3
  322. package/dist/components/cms/use-publish-controller.js +8 -20
  323. package/dist/components/cms/use-publish-controller.js.map +1 -1
  324. package/dist/components/cms/use-published-perspective.d.ts +1 -1
  325. package/dist/components/cms/use-published-perspective.js.map +1 -1
  326. package/dist/components/cms/use-restore-version.d.ts +1 -1
  327. package/dist/components/cms/use-restore-version.js +1 -1
  328. package/dist/components/cms/use-restore-version.js.map +1 -1
  329. package/dist/components/cms/version-compare-lazy.js +1 -1
  330. package/dist/components/cms/version-compare-lazy.js.map +1 -1
  331. package/dist/components/cms/version-compare.js +1 -1
  332. package/dist/components/cms/version-compare.js.map +1 -1
  333. package/dist/components/cms/versions-pane.js +11 -4
  334. package/dist/components/cms/versions-pane.js.map +1 -1
  335. package/dist/components/cms/view-bar.js +3 -3
  336. package/dist/components/cms/view-bar.js.map +1 -1
  337. package/dist/components/collection-view/cms-collection-view-source.d.ts +1 -1
  338. package/dist/components/collection-view/cms-collection-view-source.js +5 -3
  339. package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
  340. package/dist/components/collection-view/collection-view-context.d.ts +1 -1
  341. package/dist/components/collection-view/collection-view-context.js +1 -1
  342. package/dist/components/collection-view/collection-view-context.js.map +1 -1
  343. package/dist/components/collection-view/collection-view-surface.d.ts +13 -0
  344. package/dist/components/collection-view/collection-view-surface.js +18 -0
  345. package/dist/components/collection-view/collection-view-surface.js.map +1 -0
  346. package/dist/components/components/component-detail.js +10 -10
  347. package/dist/components/components/component-detail.js.map +1 -1
  348. package/dist/components/components/component-preview.d.ts +2 -2
  349. package/dist/components/components/component-preview.js +5 -5
  350. package/dist/components/components/component-preview.js.map +1 -1
  351. package/dist/components/components/component-status.d.ts +1 -1
  352. package/dist/components/components/component-status.js +1 -1
  353. package/dist/components/components/component-status.js.map +1 -1
  354. package/dist/components/components/components-index.js +2 -2
  355. package/dist/components/components/components-index.js.map +1 -1
  356. package/dist/components/components/field-inspector.js +1 -1
  357. package/dist/components/components/field-inspector.js.map +1 -1
  358. package/dist/components/components/registry-browser.js +3 -3
  359. package/dist/components/components/registry-browser.js.map +1 -1
  360. package/dist/components/components/taxonomy.d.ts +1 -1
  361. package/dist/components/components/taxonomy.js.map +1 -1
  362. package/dist/components/components/transfer-dialog.js +4 -3
  363. package/dist/components/components/transfer-dialog.js.map +1 -1
  364. package/dist/components/components/transfer.d.ts +10 -10
  365. package/dist/components/components/transfer.js +13 -12
  366. package/dist/components/components/transfer.js.map +1 -1
  367. package/dist/components/current-user-menu.js +1 -1
  368. package/dist/components/current-user-menu.js.map +1 -1
  369. package/dist/components/delivery-health-banner.js +2 -2
  370. package/dist/components/delivery-health-banner.js.map +1 -1
  371. package/dist/components/deployment-setup-gate.js +3 -3
  372. package/dist/components/deployment-setup-gate.js.map +1 -1
  373. package/dist/components/dev-login-badge.js +2 -2
  374. package/dist/components/dev-login-badge.js.map +1 -1
  375. package/dist/components/guards.js +1 -1
  376. package/dist/components/guards.js.map +1 -1
  377. package/dist/components/inspector/document-panes.d.ts +54 -0
  378. package/dist/components/inspector/document-panes.js +41 -0
  379. package/dist/components/inspector/document-panes.js.map +1 -0
  380. package/dist/components/inspector/inspector-sidebar.js +1 -1
  381. package/dist/components/inspector/inspector-sidebar.js.map +1 -1
  382. package/dist/components/nav-issues-registry.d.ts +53 -0
  383. package/dist/components/nav-issues-registry.js +69 -0
  384. package/dist/components/nav-issues-registry.js.map +1 -0
  385. package/dist/components/nav-issues.js +4 -4
  386. package/dist/components/nav-issues.js.map +1 -1
  387. package/dist/components/operation-error.d.ts +1 -1
  388. package/dist/components/operation-error.js +4 -4
  389. package/dist/components/operation-error.js.map +1 -1
  390. package/dist/components/playground-badge.js +2 -2
  391. package/dist/components/playground-badge.js.map +1 -1
  392. package/dist/components/project-list.js +3 -3
  393. package/dist/components/project-list.js.map +1 -1
  394. package/dist/components/project-session.d.ts +2 -2
  395. package/dist/components/project-session.js +5 -5
  396. package/dist/components/project-session.js.map +1 -1
  397. package/dist/components/project-switcher.js +2 -2
  398. package/dist/components/project-switcher.js.map +1 -1
  399. package/dist/components/rail-store-status.js +1 -1
  400. package/dist/components/rail-store-status.js.map +1 -1
  401. package/dist/components/rail-view-source.d.ts +6 -5
  402. package/dist/components/rail-view-source.js +4 -3
  403. package/dist/components/rail-view-source.js.map +1 -1
  404. package/dist/components/records/record-lens.d.ts +51 -0
  405. package/dist/components/records/record-lens.js +156 -0
  406. package/dist/components/records/record-lens.js.map +1 -0
  407. package/dist/components/records/record-rail.d.ts +86 -0
  408. package/dist/components/records/record-rail.js +241 -0
  409. package/dist/components/records/record-rail.js.map +1 -0
  410. package/dist/components/redirects/redirects-schema.js +1 -1
  411. package/dist/components/redirects/redirects-schema.js.map +1 -1
  412. package/dist/components/references/references-panel.js +2 -2
  413. package/dist/components/references/references-panel.js.map +1 -1
  414. package/dist/components/section-nav.js +27 -11
  415. package/dist/components/section-nav.js.map +1 -1
  416. package/dist/components/section-state.js +2 -2
  417. package/dist/components/section-state.js.map +1 -1
  418. package/dist/components/section-surface.js +2 -2
  419. package/dist/components/section-surface.js.map +1 -1
  420. package/dist/components/session-expired-overlay.js +3 -3
  421. package/dist/components/session-expired-overlay.js.map +1 -1
  422. package/dist/components/settings-form.js +1 -1
  423. package/dist/components/settings-form.js.map +1 -1
  424. package/dist/components/sign-in-needed-badge.js +1 -1
  425. package/dist/components/sign-in-needed-badge.js.map +1 -1
  426. package/dist/components/sign-in-view.d.ts +2 -2
  427. package/dist/components/sign-in-view.js +5 -5
  428. package/dist/components/sign-in-view.js.map +1 -1
  429. package/dist/components/studio-context.js +3 -3
  430. package/dist/components/studio-context.js.map +1 -1
  431. package/dist/components/studio-project-screen.js +30 -3
  432. package/dist/components/studio-project-screen.js.map +1 -1
  433. package/dist/components/studio-shell.js +6 -5
  434. package/dist/components/studio-shell.js.map +1 -1
  435. package/dist/components/theme-context.d.ts +1 -1
  436. package/dist/components/theme-context.js +2 -2
  437. package/dist/components/theme-context.js.map +1 -1
  438. package/dist/components/use-hydration-phase.d.ts +1 -1
  439. package/dist/components/use-hydration-phase.js +1 -1
  440. package/dist/components/use-hydration-phase.js.map +1 -1
  441. package/dist/components/users/invite-user-bar.d.ts +27 -0
  442. package/dist/components/users/invite-user-bar.js +25 -9
  443. package/dist/components/users/invite-user-bar.js.map +1 -1
  444. package/dist/components/users/role-policy-drift-issues.d.ts +38 -0
  445. package/dist/components/users/role-policy-drift-issues.js +71 -0
  446. package/dist/components/users/role-policy-drift-issues.js.map +1 -0
  447. package/dist/components/users/users-collection-view-source.d.ts +1 -1
  448. package/dist/components/users/users-collection-view-source.js +4 -2
  449. package/dist/components/users/users-collection-view-source.js.map +1 -1
  450. package/dist/components/users/users-module.js +1 -1
  451. package/dist/components/users/users-module.js.map +1 -1
  452. package/dist/components/users/users-schema.d.ts +10 -1
  453. package/dist/components/users/users-schema.js +45 -25
  454. package/dist/components/users/users-schema.js.map +1 -1
  455. package/dist/components/users/users-store.js +3 -3
  456. package/dist/components/users/users-store.js.map +1 -1
  457. package/dist/components/workspace-projects.d.ts +1 -1
  458. package/dist/components/workspace-projects.js +1 -1
  459. package/dist/components/workspace-projects.js.map +1 -1
  460. package/dist/lib/ai/apply-content.js +56 -116
  461. package/dist/lib/ai/apply-content.js.map +1 -1
  462. package/dist/lib/ai/chat-context.d.ts +8 -0
  463. package/dist/lib/ai/chat-context.js +37 -6
  464. package/dist/lib/ai/chat-context.js.map +1 -1
  465. package/dist/lib/ai/chat-state.d.ts +6 -0
  466. package/dist/lib/ai/chat-state.js.map +1 -1
  467. package/dist/lib/ai/constraints.d.ts +20 -5
  468. package/dist/lib/ai/constraints.js +125 -33
  469. package/dist/lib/ai/constraints.js.map +1 -1
  470. package/dist/lib/ai/fake-provider.d.ts +16 -3
  471. package/dist/lib/ai/fake-provider.js +22 -6
  472. package/dist/lib/ai/fake-provider.js.map +1 -1
  473. package/dist/lib/ai/index.d.ts +3 -1
  474. package/dist/lib/ai/index.js +3 -1
  475. package/dist/lib/ai/index.js.map +1 -1
  476. package/dist/lib/ai/leash.d.ts +34 -0
  477. package/dist/lib/ai/leash.js +28 -0
  478. package/dist/lib/ai/leash.js.map +1 -0
  479. package/dist/lib/ai/node-handler.d.ts +1 -1
  480. package/dist/lib/ai/node-handler.js.map +1 -1
  481. package/dist/lib/ai/rich-text-input.d.ts +8 -8
  482. package/dist/lib/ai/rich-text-input.js +2 -1
  483. package/dist/lib/ai/rich-text-input.js.map +1 -1
  484. package/dist/lib/ai/rich-text-translate.d.ts +27 -4
  485. package/dist/lib/ai/rich-text-translate.js +28 -10
  486. package/dist/lib/ai/rich-text-translate.js.map +1 -1
  487. package/dist/lib/ai/shape.d.ts +13 -13
  488. package/dist/lib/ai/shape.js +69 -93
  489. package/dist/lib/ai/shape.js.map +1 -1
  490. package/dist/lib/ai/tools.d.ts +36 -41
  491. package/dist/lib/ai/tools.js +83 -87
  492. package/dist/lib/ai/tools.js.map +1 -1
  493. package/dist/lib/ai/translate-document.d.ts +32 -4
  494. package/dist/lib/ai/translate-document.js +145 -47
  495. package/dist/lib/ai/translate-document.js.map +1 -1
  496. package/dist/lib/ai/types.d.ts +34 -23
  497. package/dist/lib/ai/types.js.map +1 -1
  498. package/dist/lib/ai/update-fields.d.ts +15 -12
  499. package/dist/lib/ai/update-fields.js +112 -188
  500. package/dist/lib/ai/update-fields.js.map +1 -1
  501. package/dist/lib/ai/writer-form.d.ts +116 -0
  502. package/dist/lib/ai/writer-form.js +429 -0
  503. package/dist/lib/ai/writer-form.js.map +1 -0
  504. package/dist/lib/assets/upload.d.ts +31 -2
  505. package/dist/lib/assets/upload.js +61 -6
  506. package/dist/lib/assets/upload.js.map +1 -1
  507. package/dist/lib/clipboard/os-clipboard.d.ts +10 -2
  508. package/dist/lib/clipboard/os-clipboard.js +8 -4
  509. package/dist/lib/clipboard/os-clipboard.js.map +1 -1
  510. package/dist/lib/clipboard/paste-issues.js +1 -1
  511. package/dist/lib/clipboard/paste-issues.js.map +1 -1
  512. package/dist/lib/clipboard/paste-target.d.ts +18 -9
  513. package/dist/lib/clipboard/paste-target.js +10 -9
  514. package/dist/lib/clipboard/paste-target.js.map +1 -1
  515. package/dist/lib/cms/field-patch.d.ts +0 -10
  516. package/dist/lib/cms/field-patch.js +4 -30
  517. package/dist/lib/cms/field-patch.js.map +1 -1
  518. package/dist/lib/cms/published-documents.js +1 -1
  519. package/dist/lib/cms/published-documents.js.map +1 -1
  520. package/dist/lib/cms-views/view-model.d.ts +9 -9
  521. package/dist/lib/cms-views/view-model.js +2 -2
  522. package/dist/lib/cms-views/view-model.js.map +1 -1
  523. package/dist/lib/collection-view/collection-view-source.d.ts +14 -5
  524. package/dist/lib/collection-view/collection-view-source.js.map +1 -1
  525. package/dist/lib/data/collection-content.js +3 -2
  526. package/dist/lib/data/collection-content.js.map +1 -1
  527. package/dist/lib/data/content-session-refresh.d.ts +1 -1
  528. package/dist/lib/data/content-session-refresh.js.map +1 -1
  529. package/dist/lib/data/playground-unload-guard.d.ts +1 -1
  530. package/dist/lib/data/playground-unload-guard.js +1 -1
  531. package/dist/lib/data/playground-unload-guard.js.map +1 -1
  532. package/dist/lib/dnd/drop-rules.d.ts +25 -31
  533. package/dist/lib/dnd/drop-rules.js +53 -59
  534. package/dist/lib/dnd/drop-rules.js.map +1 -1
  535. package/dist/lib/engine-version.d.ts +1 -1
  536. package/dist/lib/engine-version.js +1 -1
  537. package/dist/lib/engine-version.js.map +1 -1
  538. package/dist/lib/i18n/i18n.js +1 -1
  539. package/dist/lib/i18n/i18n.js.map +1 -1
  540. package/dist/lib/i18n/messages.de.js +44 -3
  541. package/dist/lib/i18n/messages.de.js.map +1 -1
  542. package/dist/lib/i18n/messages.en.d.ts +38 -2
  543. package/dist/lib/i18n/messages.en.js +51 -3
  544. package/dist/lib/i18n/messages.en.js.map +1 -1
  545. package/dist/lib/operations/project-runtime-config.d.ts +34 -6
  546. package/dist/lib/operations/project-runtime-config.js +50 -10
  547. package/dist/lib/operations/project-runtime-config.js.map +1 -1
  548. package/dist/lib/operations/publish-with-revision.d.ts +33 -1
  549. package/dist/lib/operations/publish-with-revision.js +32 -0
  550. package/dist/lib/operations/publish-with-revision.js.map +1 -1
  551. package/dist/lib/operations/runtime-validator.d.ts +19 -68
  552. package/dist/lib/operations/runtime-validator.js +12 -138
  553. package/dist/lib/operations/runtime-validator.js.map +1 -1
  554. package/dist/lib/operations/studio-operations.d.ts +17 -2
  555. package/dist/lib/operations/studio-operations.js +18 -11
  556. package/dist/lib/operations/studio-operations.js.map +1 -1
  557. package/dist/lib/persistence/convex-adapter.d.ts +16 -3
  558. package/dist/lib/persistence/convex-adapter.js +83 -47
  559. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  560. package/dist/lib/persistence/index.js +14 -1
  561. package/dist/lib/persistence/index.js.map +1 -1
  562. package/dist/lib/persona/labels.js +4 -2
  563. package/dist/lib/persona/labels.js.map +1 -1
  564. package/dist/lib/query/operations-query.d.ts +1 -1
  565. package/dist/lib/query/operations-query.js +1 -1
  566. package/dist/lib/query/operations-query.js.map +1 -1
  567. package/dist/lib/references/reference-model.js +1 -1
  568. package/dist/lib/references/reference-model.js.map +1 -1
  569. package/dist/lib/references/references-store.js +1 -1
  570. package/dist/lib/references/references-store.js.map +1 -1
  571. package/dist/lib/workspace/index.d.ts +14 -11
  572. package/dist/lib/workspace/index.js +20 -24
  573. package/dist/lib/workspace/index.js.map +1 -1
  574. package/dist/mount.d.ts +28 -0
  575. package/dist/mount.js +27 -0
  576. package/dist/mount.js.map +1 -0
  577. package/dist/router/root.js +10 -10
  578. package/dist/router/root.js.map +1 -1
  579. package/dist/ui/badge.d.ts +1 -1
  580. package/dist/ui/button.d.ts +2 -2
  581. package/package.json +15 -11
  582. package/dist/components/cms/cms-composition-validators.d.ts +0 -16
  583. package/dist/components/cms/cms-composition-validators.js +0 -133
  584. package/dist/components/cms/cms-composition-validators.js.map +0 -1
  585. package/dist/components/cms/publish-clean.d.ts +0 -28
  586. package/dist/components/cms/publish-clean.js +0 -38
  587. package/dist/components/cms/publish-clean.js.map +0 -1
  588. package/dist/components/redirects/redirects-collection-view-source.d.ts +0 -53
  589. package/dist/components/redirects/redirects-collection-view-source.js +0 -146
  590. package/dist/components/redirects/redirects-collection-view-source.js.map +0 -1
  591. package/dist/components/redirects/redirects-module.d.ts +0 -21
  592. package/dist/components/redirects/redirects-module.js +0 -40
  593. package/dist/components/redirects/redirects-module.js.map +0 -1
  594. package/dist/components/redirects/redirects-store.d.ts +0 -68
  595. package/dist/components/redirects/redirects-store.js +0 -132
  596. package/dist/components/redirects/redirects-store.js.map +0 -1
  597. package/dist/lib/assets/asset-placements.d.ts +0 -28
  598. package/dist/lib/assets/asset-placements.js +0 -83
  599. package/dist/lib/assets/asset-placements.js.map +0 -1
@@ -1,8 +1,7 @@
1
1
  import { activeDeploymentKey, selectPersistenceAdapter } from '../persistence/index.js';
2
- import { resolveProjectCollections } from '../workspace/index.js';
3
2
  import { attachSessionExpiryReporting } from '../session-expiry.js';
4
3
  import { projectManifestLookup } from './config-manifest-lookup.js';
5
- import { configProjectFor, projectLocaleConfig } from './project-runtime-config.js';
4
+ import { configProjectFor, projectCollections, projectComponentRegistry, projectLocaleConfig, projectRolePolicyFor, } from './project-runtime-config.js';
6
5
  import { createRuntimeDocumentValidator } from './runtime-validator.js';
7
6
  import { attachReferenceIndex, createChangesets, createOperations, createRoleAuthorization, summarizeInput, toOperationError, } from './operations-package.js';
8
7
  /**
@@ -15,31 +14,38 @@ function noopReferenceIndexAttachment() {
15
14
  }
16
15
  /** Build a studio operations bundle over an adapter (exported for tests). */
17
16
  export function createStudioOperations(adapter, options = {}) {
17
+ // EC-605: the role hook is built WITHOUT a fixed policy — it grants by the policy the
18
+ // runtime resolves for each operation's project (`rolePolicy` below), the same one the
19
+ // operation's roles were validated against: one injection point, not two copies.
18
20
  const authorized = options.actor
19
21
  ? { actor: options.actor, authorize: createRoleAuthorization(adapter.members) }
20
22
  : {};
21
23
  // EC-583: the collections come from the config (never the store), per project id;
22
24
  // EC-595 A1: so does the manifest lookup. Both ride the changesets runtime too.
23
- const collections = options.collections ?? ((projectId) => resolveProjectCollections(projectId));
25
+ // EC-585: every per-project fact below is read from ONE source — the config project
26
+ // with the operation's slug (`configProjectFor`): collections, manifests, locales.
27
+ const collections = options.collections ?? projectCollections;
28
+ const locales = options.locales ?? projectLocaleConfig;
29
+ const rolePolicy = options.rolePolicy ?? projectRolePolicyFor;
24
30
  const idFactory = options.idFactory ? { idFactory: options.idFactory } : {};
25
31
  const manifestLookup = options.manifestLookup ?? projectManifestLookup((projectId) => configProjectFor(projectId)?.components);
26
- // EC-595 A2 (F23): the publish gate's validator is bound to THIS bundle — the
27
- // operation's project config + oracles prefetched through the adapter — never to
28
- // whichever CMS session happens to be mounted (the retired `lib/cms/document-gate.ts`
29
- // stack, where the innermost provider won). The option bag is the session's own
30
- // (`gateValidateOptions`, EC-497), loaded lazily so the registry behind the
31
- // composition leashes stays out of this bootstrap's static graph.
32
+ // EC-595 A2 (F23) → EC-582: the publish gate's validator is bound to THIS bundle —
33
+ // core's canonical gate (`@elytracms/core/validation`) over the OPERATION's project:
34
+ // its collections, locales and component registry (never the active project's) +
35
+ // oracles prefetched through the adapter — never whichever CMS session is mounted.
32
36
  const validateDocument = options.validateDocument ??
33
37
  createRuntimeDocumentValidator({
34
38
  collections,
35
- localeConfig: projectLocaleConfig,
36
- loadBag: () => import('../../components/cms/gate-validation.js').then((module) => module.gateValidateOptions),
39
+ localeConfig: locales,
40
+ registry: projectComponentRegistry,
37
41
  });
38
42
  const operations = createOperations({
39
43
  adapter,
40
44
  collections,
41
45
  validateDocument,
42
46
  manifestLookup,
47
+ locales,
48
+ rolePolicy,
43
49
  ...idFactory,
44
50
  ...authorized,
45
51
  });
@@ -60,6 +66,7 @@ export function createStudioOperations(adapter, options = {}) {
60
66
  // runtime validator, prefetching through the step's own (scratch or real) adapter.
61
67
  validateDocument,
62
68
  manifestLookup,
69
+ locales,
63
70
  ...idFactory,
64
71
  ...authorized,
65
72
  });
@@ -1 +1 @@
1
- {"version":3,"file":"studio-operations.js","sourceRoot":"","sources":["../../../src/lib/operations/studio-operations.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n DocumentListener,\n IdFactory,\n PersistenceAdapter,\n PresenceInput,\n} from '@elytracms/core/persistence'\nimport type { DocumentRef } from '@elytracms/core/cms-core'\nimport { activeDeploymentKey, selectPersistenceAdapter } from '../persistence'\nimport { resolveProjectCollections } from '../workspace'\nimport { attachSessionExpiryReporting } from '../session-expiry'\nimport { projectManifestLookup } from './config-manifest-lookup'\nimport { configProjectFor, projectLocaleConfig } from './project-runtime-config'\nimport { createRuntimeDocumentValidator } from './runtime-validator'\nimport {\n attachReferenceIndex,\n createChangesets,\n createOperations,\n createRoleAuthorization,\n summarizeInput,\n toOperationError,\n} from './operations-package'\nimport type {\n ChangesetOperations,\n CollectionsResolver,\n DocumentValidator,\n ManifestLookupResolver,\n OperationsClient,\n Principal,\n ReferenceIndexAttachment,\n} from './operations-package'\n\n/**\n * The studio's single operations bootstrap (EC-141, vision AD-7).\n *\n * One `OperationsClient` + one `ChangesetOperations` instance, built over the\n * selected persistence adapter, shared by every store and surface in the app.\n * The UI never talks to a repository for writes — every mutation is a typed\n * operation command (validate → authorize → mutate → event), so the studio\n * dogfoods the same seam plugins, AI changesets, and the CLI use.\n *\n * The EC-155 reference index is attached to the shared event bus here, so any\n * document/graph/publishing command issued anywhere in the studio keeps the\n * index maintained.\n */\nexport interface StudioOperations {\n readonly operations: OperationsClient\n readonly changesets: ChangesetOperations\n /**\n * The raw adapter, exposed for *transport and read paths only*: blob byte\n * upload/download (`adapter.blobs`) and existing read stores. Studio code\n * must not call repository write methods on it — writes go through\n * `operations`.\n */\n readonly adapter: PersistenceAdapter\n /** EC-155 maintained-on-write index attachment (flushable in tests). */\n readonly references: ReferenceIndexAttachment\n /**\n * EC-595 binding point 7 — subscribe to ONE document (the narrow EC-309 slice): the\n * listener gets the current row + the presence on it, then one snapshot per change\n * (Convex: the reactive client's server push; playground: the in-memory store's own\n * writes). An adapter capability exposed through the in-process bundle — the UI never\n * holds a reactive Convex client. A subscription failure is ALSO emitted as a failed\n * `documents.subscribe` event on the operations bus, so an expired session reaches\n * EC-403's one session-expiry chokepoint like any failed operation (never a silently\n * stopped stream). Returns the unsubscribe function.\n */\n subscribeDocument(projectId: string, ref: DocumentRef, listener: DocumentListener): () => void\n /** EC-595 — this session's field-presence heartbeat on a document (thin presence, Stage A). */\n reportPresence(projectId: string, ref: DocumentRef, presence: PresenceInput): Promise<void>\n /** EC-595 — remove this session's presence from a document. */\n clearPresence(projectId: string, ref: DocumentRef, sessionId: string): Promise<void>\n}\n\nexport interface CreateStudioOperationsOptions {\n /**\n * The signed-in principal (EC-150). When present, the EC-150 role hook\n * (`createRoleAuthorization` over `adapter.members`) becomes the\n * authorization chokepoint for every command and query — viewers get\n * structured denials on mutations, editors on administration. Without an\n * actor the client runs pass-through (seeds, fixtures, tests that exercise\n * domain logic rather than permissions); the route guard keeps\n * unauthenticated users away from every operation surface.\n */\n actor?: Principal\n /**\n * EC-583 / EC-559 — the collection definitions on the operations context, resolved for\n * each operation's OWN project id. Defaults to the workspace config's project with that\n * slug (`resolveProjectCollections` — the resolved `elytra.config.ts`, functions\n * included), read on every call: the bundle is cached per deployment KEY (EC-179) and\n * one deployment may serve several config projects, so capturing one project's\n * collections at build time would canonicalise another project's documents against\n * them (operator correction to phase 1). The store holds no schema.\n */\n collections?: CollectionsResolver\n /** EC-578 — the document-id factory (`documents.create`); UUIDs by default. */\n idFactory?: IdFactory\n /**\n * EC-595 A1 (critic correction 4) → A2 — the manifest half of the write canonicaliser\n * on the operations context, resolved for each operation's OWN project. Defaults to a\n * lookup over that config project's component entries (`projectManifestLookup`), so\n * every patch / replace canonicalises a composition's props with its project's\n * manifests — never the active project's (A1 found-but-left #1).\n */\n manifestLookup?: ManifestLookupResolver\n /**\n * EC-595 A2 (F23) — the publish gate's validator, bound to THIS bundle. Defaults to\n * the runtime validator (`createRuntimeDocumentValidator`: the operation's project's\n * config + oracles prefetched through the adapter). Tests that pin persistence\n * routing rather than gate strength inject their own here — at construction, never\n * through a mount.\n */\n validateDocument?: DocumentValidator\n}\n\n/**\n * A no-op {@link ReferenceIndexAttachment} for self-maintaining adapters (EC-277):\n * nothing is subscribed, so `detach` and `flush` are trivially satisfied (the index\n * is kept current in the adapter's write path, not by a queued subscriber).\n */\nfunction noopReferenceIndexAttachment(): ReferenceIndexAttachment {\n return { detach: () => {}, flush: async () => {} }\n}\n\n/** Build a studio operations bundle over an adapter (exported for tests). */\nexport function createStudioOperations(\n adapter: PersistenceAdapter,\n options: CreateStudioOperationsOptions = {},\n): StudioOperations {\n const authorized = options.actor\n ? { actor: options.actor, authorize: createRoleAuthorization(adapter.members) }\n : {}\n // EC-583: the collections come from the config (never the store), per project id;\n // EC-595 A1: so does the manifest lookup. Both ride the changesets runtime too.\n const collections: CollectionsResolver =\n options.collections ?? ((projectId) => resolveProjectCollections(projectId))\n const idFactory = options.idFactory ? { idFactory: options.idFactory } : {}\n const manifestLookup =\n options.manifestLookup ?? projectManifestLookup((projectId) => configProjectFor(projectId)?.components)\n // EC-595 A2 (F23): the publish gate's validator is bound to THIS bundle — the\n // operation's project config + oracles prefetched through the adapter — never to\n // whichever CMS session happens to be mounted (the retired `lib/cms/document-gate.ts`\n // stack, where the innermost provider won). The option bag is the session's own\n // (`gateValidateOptions`, EC-497), loaded lazily so the registry behind the\n // composition leashes stays out of this bootstrap's static graph.\n const validateDocument =\n options.validateDocument ??\n createRuntimeDocumentValidator({\n collections,\n localeConfig: projectLocaleConfig,\n loadBag: () =>\n import('../../components/cms/gate-validation').then((module) => module.gateValidateOptions),\n })\n const operations = createOperations({\n adapter,\n collections,\n validateDocument,\n manifestLookup,\n ...idFactory,\n ...authorized,\n })\n // EC-277: when the adapter maintains the index in its own write path (AD-6 \"at the\n // source\" — the Convex provider does this server-side, so a raw `convex run` is\n // caught too), the client-side subscriber would only double the work. Attach it\n // ONLY for providers that don't self-maintain. Where it does run, extraction is a\n // schema-free, manifest-free walk over self-describing content, so it needs no\n // registry (compositions self-typed their relation/asset props on write).\n const references = adapter.maintainsReferenceIndex\n ? noopReferenceIndexAttachment()\n : attachReferenceIndex(operations.events, adapter)\n const changesets = createChangesets({\n adapter,\n collections,\n events: operations.events,\n // EC-497 / EC-595 A2: a changeset-driven publish is gated identically — the same\n // runtime validator, prefetching through the step's own (scratch or real) adapter.\n validateDocument,\n manifestLookup,\n ...idFactory,\n ...authorized,\n })\n // EC-579: no revalidation emitter attaches here. Host revalidation is a property\n // of the Convex write chokepoint (`convex/triggers.ts`), the one emitter — it\n // catches every writer, keeps the signing secret server-side, and the playground's\n // in-browser adapter is read by no host, so there is nothing to emit there.\n // EC-403: session-expiry detection rides the SAME lifecycle bus — every failed\n // command/query/changeset operation is checked against the session-expired\n // predicate here, so expiry raises the ONE app-level blocking re-auth state no\n // matter which surface issued the operation (per-surface banners can't cover\n // surfaces that render no banner).\n attachSessionExpiryReporting(operations.events)\n\n /** Report a capability failure on the bus (EC-403 sees an expiry like any failed operation). */\n const reportFailure = (operation: string, input: unknown, error: unknown) => {\n operations.events.emit({\n operation,\n domain: 'documents',\n kind: 'query',\n status: 'failed',\n inputSummary: summarizeInput(input),\n error: toOperationError(error),\n })\n }\n return {\n operations,\n changesets,\n adapter,\n references,\n subscribeDocument: (projectId, ref, listener) =>\n adapter.cms.subscribeDocument(projectId, ref, {\n onSnapshot: (snapshot) => listener.onSnapshot(snapshot),\n onError: (error) => {\n reportFailure('documents.subscribe', { projectId, ref }, error)\n listener.onError?.(error)\n },\n }),\n reportPresence: async (projectId, ref, presence) => {\n try {\n await adapter.cms.reportPresence(projectId, ref, presence)\n } catch (error) {\n reportFailure('documents.presence', { projectId, ref }, error)\n throw error\n }\n },\n clearPresence: (projectId, ref, sessionId) => adapter.cms.clearPresence(projectId, ref, sessionId),\n }\n}\n\n// One adapter + one operations bundle per backend (EC-179): keyed by the active\n// deployment so switching to a project on another Convex deployment selects (or\n// lazily builds) that backend's client, rather than reusing the first one.\nconst adaptersByKey = new Map<string, Promise<PersistenceAdapter>>()\nconst instancesByKey = new Map<string, Promise<StudioOperations>>()\nlet currentActor: Principal | null = null\n\n/**\n * The active backend key, tolerant of an unconfigured env: the real app never\n * reaches `getStudioOperations` while misconfigured (the `DeploymentSetupGate` renders a\n * boot error first), but tests inject a bundle directly via the seam below — so\n * a missing backend resolves to one stable key rather than throwing.\n */\nfunction deploymentKey(): string {\n try {\n return activeDeploymentKey()\n } catch {\n return '__unconfigured__'\n }\n}\n\nfunction getAdapter(key: string): Promise<PersistenceAdapter> {\n let adapter = adaptersByKey.get(key)\n if (!adapter) {\n adapter = selectPersistenceAdapter()\n adaptersByKey.set(key, adapter)\n }\n return adapter\n}\n\n/**\n * The shared studio operations client for the active backend (lazily\n * bootstrapped, cached per deployment). In-memory mode this owns the one seeded\n * adapter the whole session mutates; against Convex it owns one client per\n * deployment — the active project (EC-179) selects which.\n */\nexport function getStudioOperations(): Promise<StudioOperations> {\n const key = deploymentKey()\n let instance = instancesByKey.get(key)\n if (!instance) {\n const actor = currentActor\n instance = getAdapter(key).then((adapter) =>\n createStudioOperations(adapter, actor ? { actor } : {}),\n )\n instancesByKey.set(key, instance)\n }\n return instance\n}\n\n/**\n * Bind (or clear) the session principal (EC-150). Called by the auth context\n * on sign-in/sign-out. Rebuilds the operations clients over the *same* adapters\n * — the persisted state (and, in-memory, the whole session's edits) survives;\n * only the authorization identity changes. All cached backends are rebuilt so a\n * later switch picks up the new actor too.\n */\nexport function setStudioSession(actor: Principal | null): void {\n if (currentActor?.id === actor?.id) return // same user (or both null) — keep the clients\n currentActor = actor\n instancesByKey.clear()\n}\n\n/**\n * EC-595 A2 — the signed-in principal the operations clients act as (`null` signed out):\n * what this session's presence heartbeat names (\"X is in this field\") and how a conflict\n * tells \"you, in another tab\" from a colleague. Read-only; set by {@link setStudioSession}.\n */\nexport function getStudioActor(): Principal | null {\n return currentActor\n}\n\n/** Test seam: replace (or clear) the singleton for the active backend. */\nexport function setStudioOperationsForTests(next: StudioOperations | null): void {\n if (next) {\n instancesByKey.set(deploymentKey(), Promise.resolve(next))\n return\n }\n instancesByKey.clear()\n adaptersByKey.clear()\n currentActor = null\n}\n"]}
1
+ {"version":3,"file":"studio-operations.js","sourceRoot":"","sources":["../../../src/lib/operations/studio-operations.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n DocumentListener,\n IdFactory,\n PersistenceAdapter,\n PresenceInput,\n} from '@elytracms/core/persistence'\nimport type { DocumentRef } from '@elytracms/core/cms-core'\nimport { activeDeploymentKey, selectPersistenceAdapter } from '../persistence'\nimport { attachSessionExpiryReporting } from '../session-expiry'\nimport { projectManifestLookup } from './config-manifest-lookup'\nimport {\n configProjectFor,\n projectCollections,\n projectComponentRegistry,\n projectLocaleConfig,\n projectRolePolicyFor,\n} from './project-runtime-config'\nimport { createRuntimeDocumentValidator } from './runtime-validator'\nimport {\n attachReferenceIndex,\n createChangesets,\n createOperations,\n createRoleAuthorization,\n summarizeInput,\n toOperationError,\n} from './operations-package'\nimport type {\n ChangesetOperations,\n CollectionsResolver,\n DocumentValidator,\n LocalesResolver,\n ManifestLookupResolver,\n OperationsClient,\n Principal,\n ReferenceIndexAttachment,\n RolePolicyResolver,\n} from './operations-package'\n\n/**\n * The studio's single operations bootstrap (EC-141, vision AD-7).\n *\n * One `OperationsClient` + one `ChangesetOperations` instance, built over the\n * selected persistence adapter, shared by every store and surface in the app.\n * The UI never talks to a repository for writes — every mutation is a typed\n * operation command (validate → authorize → mutate → event), so the studio\n * dogfoods the same seam plugins, AI changesets, and the CLI use.\n *\n * The EC-155 reference index is attached to the shared event bus here, so any\n * document/graph/publishing command issued anywhere in the studio keeps the\n * index maintained.\n */\nexport interface StudioOperations {\n readonly operations: OperationsClient\n readonly changesets: ChangesetOperations\n /**\n * The raw adapter, exposed for *transport and read paths only*: blob byte\n * upload/download (`adapter.blobs`) and existing read stores. Studio code\n * must not call repository write methods on it — writes go through\n * `operations`.\n */\n readonly adapter: PersistenceAdapter\n /** EC-155 maintained-on-write index attachment (flushable in tests). */\n readonly references: ReferenceIndexAttachment\n /**\n * EC-595 binding point 7 — subscribe to ONE document (the narrow EC-309 slice): the\n * listener gets the current row + the presence on it, then one snapshot per change\n * (Convex: the reactive client's server push; playground: the in-memory store's own\n * writes). An adapter capability exposed through the in-process bundle — the UI never\n * holds a reactive Convex client. A subscription failure is ALSO emitted as a failed\n * `documents.subscribe` event on the operations bus, so an expired session reaches\n * EC-403's one session-expiry chokepoint like any failed operation (never a silently\n * stopped stream). Returns the unsubscribe function.\n */\n subscribeDocument(projectId: string, ref: DocumentRef, listener: DocumentListener): () => void\n /** EC-595 — this session's field-presence heartbeat on a document (thin presence, Stage A). */\n reportPresence(projectId: string, ref: DocumentRef, presence: PresenceInput): Promise<void>\n /** EC-595 — remove this session's presence from a document. */\n clearPresence(projectId: string, ref: DocumentRef, sessionId: string): Promise<void>\n}\n\nexport interface CreateStudioOperationsOptions {\n /**\n * The signed-in principal (EC-150). When present, the EC-150 role hook\n * (`createRoleAuthorization` over `adapter.members`) becomes the\n * authorization chokepoint for every command and query — viewers get\n * structured denials on mutations, editors on administration. Without an\n * actor the client runs pass-through (seeds, fixtures, tests that exercise\n * domain logic rather than permissions); the route guard keeps\n * unauthenticated users away from every operation surface.\n */\n actor?: Principal\n /**\n * EC-583 / EC-559 — the collection definitions on the operations context, resolved for\n * each operation's OWN project id. Defaults to the workspace config's project with that\n * slug (`projectCollections` — the resolved `elytra.config.ts`, functions\n * included), read on every call: the bundle is cached per deployment KEY (EC-179) and\n * one deployment may serve several config projects, so capturing one project's\n * collections at build time would canonicalise another project's documents against\n * them (operator correction to phase 1). The store holds no schema.\n */\n collections?: CollectionsResolver\n /** EC-578 — the document-id factory (`documents.create`); UUIDs by default. */\n idFactory?: IdFactory\n /**\n * EC-595 A1 (critic correction 4) → A2 — the manifest half of the write canonicaliser\n * on the operations context, resolved for each operation's OWN project. Defaults to a\n * lookup over that config project's component entries (`projectManifestLookup`), so\n * every patch / replace canonicalises a composition's props with its project's\n * manifests — never the active project's (A1 found-but-left #1).\n */\n manifestLookup?: ManifestLookupResolver\n /**\n * EC-595 A2 (F23) — the publish gate's validator, bound to THIS bundle. Defaults to\n * the runtime validator (`createRuntimeDocumentValidator`: the operation's project's\n * config + oracles prefetched through the adapter). Tests that pin persistence\n * routing rather than gate strength inject their own here — at construction, never\n * through a mount.\n */\n validateDocument?: DocumentValidator\n /**\n * EC-585 — the locale config on the operations context, per project: the view\n * `documents.publish` runs its hidden-clear in. Defaults to that config project's\n * `locales` (`projectLocaleConfig`) — the same project, read the same way, as the\n * collections, the manifests and the gate's validator.\n */\n locales?: LocalesResolver\n /**\n * EC-605 — the role policy, per project: what a role an operation grants is validated\n * against and what the role hook (`createRoleAuthorization`, which reads it from the\n * runtime) grants by. Defaults to that config project's `roles` declaration\n * (`projectRolePolicyFor`; the default policy when it declares none) — read the same\n * way as the collections and locales.\n */\n rolePolicy?: RolePolicyResolver\n}\n\n/**\n * A no-op {@link ReferenceIndexAttachment} for self-maintaining adapters (EC-277):\n * nothing is subscribed, so `detach` and `flush` are trivially satisfied (the index\n * is kept current in the adapter's write path, not by a queued subscriber).\n */\nfunction noopReferenceIndexAttachment(): ReferenceIndexAttachment {\n return { detach: () => {}, flush: async () => {} }\n}\n\n/** Build a studio operations bundle over an adapter (exported for tests). */\nexport function createStudioOperations(\n adapter: PersistenceAdapter,\n options: CreateStudioOperationsOptions = {},\n): StudioOperations {\n // EC-605: the role hook is built WITHOUT a fixed policy — it grants by the policy the\n // runtime resolves for each operation's project (`rolePolicy` below), the same one the\n // operation's roles were validated against: one injection point, not two copies.\n const authorized = options.actor\n ? { actor: options.actor, authorize: createRoleAuthorization(adapter.members) }\n : {}\n // EC-583: the collections come from the config (never the store), per project id;\n // EC-595 A1: so does the manifest lookup. Both ride the changesets runtime too.\n // EC-585: every per-project fact below is read from ONE source — the config project\n // with the operation's slug (`configProjectFor`): collections, manifests, locales.\n const collections: CollectionsResolver = options.collections ?? projectCollections\n const locales: LocalesResolver = options.locales ?? projectLocaleConfig\n const rolePolicy: RolePolicyResolver = options.rolePolicy ?? projectRolePolicyFor\n const idFactory = options.idFactory ? { idFactory: options.idFactory } : {}\n const manifestLookup =\n options.manifestLookup ?? projectManifestLookup((projectId) => configProjectFor(projectId)?.components)\n // EC-595 A2 (F23) → EC-582: the publish gate's validator is bound to THIS bundle —\n // core's canonical gate (`@elytracms/core/validation`) over the OPERATION's project:\n // its collections, locales and component registry (never the active project's) +\n // oracles prefetched through the adapter — never whichever CMS session is mounted.\n const validateDocument =\n options.validateDocument ??\n createRuntimeDocumentValidator({\n collections,\n localeConfig: locales,\n registry: projectComponentRegistry,\n })\n const operations = createOperations({\n adapter,\n collections,\n validateDocument,\n manifestLookup,\n locales,\n rolePolicy,\n ...idFactory,\n ...authorized,\n })\n // EC-277: when the adapter maintains the index in its own write path (AD-6 \"at the\n // source\" — the Convex provider does this server-side, so a raw `convex run` is\n // caught too), the client-side subscriber would only double the work. Attach it\n // ONLY for providers that don't self-maintain. Where it does run, extraction is a\n // schema-free, manifest-free walk over self-describing content, so it needs no\n // registry (compositions self-typed their relation/asset props on write).\n const references = adapter.maintainsReferenceIndex\n ? noopReferenceIndexAttachment()\n : attachReferenceIndex(operations.events, adapter)\n const changesets = createChangesets({\n adapter,\n collections,\n events: operations.events,\n // EC-497 / EC-595 A2: a changeset-driven publish is gated identically — the same\n // runtime validator, prefetching through the step's own (scratch or real) adapter.\n validateDocument,\n manifestLookup,\n locales,\n ...idFactory,\n ...authorized,\n })\n // EC-579: no revalidation emitter attaches here. Host revalidation is a property\n // of the Convex write chokepoint (`convex/triggers.ts`), the one emitter — it\n // catches every writer, keeps the signing secret server-side, and the playground's\n // in-browser adapter is read by no host, so there is nothing to emit there.\n // EC-403: session-expiry detection rides the SAME lifecycle bus — every failed\n // command/query/changeset operation is checked against the session-expired\n // predicate here, so expiry raises the ONE app-level blocking re-auth state no\n // matter which surface issued the operation (per-surface banners can't cover\n // surfaces that render no banner).\n attachSessionExpiryReporting(operations.events)\n\n /** Report a capability failure on the bus (EC-403 sees an expiry like any failed operation). */\n const reportFailure = (operation: string, input: unknown, error: unknown) => {\n operations.events.emit({\n operation,\n domain: 'documents',\n kind: 'query',\n status: 'failed',\n inputSummary: summarizeInput(input),\n error: toOperationError(error),\n })\n }\n return {\n operations,\n changesets,\n adapter,\n references,\n subscribeDocument: (projectId, ref, listener) =>\n adapter.cms.subscribeDocument(projectId, ref, {\n onSnapshot: (snapshot) => listener.onSnapshot(snapshot),\n onError: (error) => {\n reportFailure('documents.subscribe', { projectId, ref }, error)\n listener.onError?.(error)\n },\n }),\n reportPresence: async (projectId, ref, presence) => {\n try {\n await adapter.cms.reportPresence(projectId, ref, presence)\n } catch (error) {\n reportFailure('documents.presence', { projectId, ref }, error)\n throw error\n }\n },\n clearPresence: (projectId, ref, sessionId) => adapter.cms.clearPresence(projectId, ref, sessionId),\n }\n}\n\n// One adapter + one operations bundle per backend (EC-179): keyed by the active\n// deployment so switching to a project on another Convex deployment selects (or\n// lazily builds) that backend's client, rather than reusing the first one.\nconst adaptersByKey = new Map<string, Promise<PersistenceAdapter>>()\nconst instancesByKey = new Map<string, Promise<StudioOperations>>()\nlet currentActor: Principal | null = null\n\n/**\n * The active backend key, tolerant of an unconfigured env: the real app never\n * reaches `getStudioOperations` while misconfigured (the `DeploymentSetupGate` renders a\n * boot error first), but tests inject a bundle directly via the seam below — so\n * a missing backend resolves to one stable key rather than throwing.\n */\nfunction deploymentKey(): string {\n try {\n return activeDeploymentKey()\n } catch {\n return '__unconfigured__'\n }\n}\n\nfunction getAdapter(key: string): Promise<PersistenceAdapter> {\n let adapter = adaptersByKey.get(key)\n if (!adapter) {\n adapter = selectPersistenceAdapter()\n adaptersByKey.set(key, adapter)\n }\n return adapter\n}\n\n/**\n * The shared studio operations client for the active backend (lazily\n * bootstrapped, cached per deployment). In-memory mode this owns the one seeded\n * adapter the whole session mutates; against Convex it owns one client per\n * deployment — the active project (EC-179) selects which.\n */\nexport function getStudioOperations(): Promise<StudioOperations> {\n const key = deploymentKey()\n let instance = instancesByKey.get(key)\n if (!instance) {\n const actor = currentActor\n instance = getAdapter(key).then((adapter) =>\n createStudioOperations(adapter, actor ? { actor } : {}),\n )\n instancesByKey.set(key, instance)\n }\n return instance\n}\n\n/**\n * Bind (or clear) the session principal (EC-150). Called by the auth context\n * on sign-in/sign-out. Rebuilds the operations clients over the *same* adapters\n * — the persisted state (and, in-memory, the whole session's edits) survives;\n * only the authorization identity changes. All cached backends are rebuilt so a\n * later switch picks up the new actor too.\n */\nexport function setStudioSession(actor: Principal | null): void {\n if (currentActor?.id === actor?.id) return // same user (or both null) — keep the clients\n currentActor = actor\n instancesByKey.clear()\n}\n\n/**\n * EC-595 A2 — the signed-in principal the operations clients act as (`null` signed out):\n * what this session's presence heartbeat names (\"X is in this field\") and how a conflict\n * tells \"you, in another tab\" from a colleague. Read-only; set by {@link setStudioSession}.\n */\nexport function getStudioActor(): Principal | null {\n return currentActor\n}\n\n/** Test seam: replace (or clear) the singleton for the active backend. */\nexport function setStudioOperationsForTests(next: StudioOperations | null): void {\n if (next) {\n instancesByKey.set(deploymentKey(), Promise.resolve(next))\n return\n }\n instancesByKey.clear()\n adaptersByKey.clear()\n currentActor = null\n}\n"]}
@@ -1,6 +1,16 @@
1
1
  import type { ConvexHttpClient } from 'convex/browser';
2
- import type { AssetRepository, BackendStatus, BackendValidationInput, BackendValidationResult, BlobStorageAdapter, CmsRepository, DeliveryRepository, MembersRepository, PersistenceAdapter, ReferenceIndexRepository, RedirectRepository, UserRepository, InviteRepository } from '@elytracms/core/persistence';
2
+ import type { RecordStore, AssetRepository, BackendStatus, BackendValidationInput, BackendValidationResult, BlobStorageAdapter, CmsRepository, DeliveryRepository, MembersRepository, PersistenceAdapter, ProjectRole, ReferenceIndexRepository, UserRepository, InviteRepository } from '@elytracms/core/persistence';
3
3
  import type { ReactiveDocumentClient } from './convex-client.js';
4
+ /**
5
+ * The single `members.role` column a deployment stores for the 240c-ready `roles[]`
6
+ * (the 240a→240c bridge). EC-605: exactly ONE role, passed through as given — the
7
+ * operations layer validated it against the project's policy, and `members:set`
8
+ * refuses one the DEPLOYED policy does not declare, visibly. This used to filter
9
+ * against the default policy and floor an unknown or empty set to `viewer` — a silent
10
+ * rewrite that turned a project's own role into a viewer. Several roles cannot be
11
+ * collapsed here without a policy, so they are refused rather than guessed at.
12
+ */
13
+ export declare function singleStoredRole(roles: readonly string[]): ProjectRole;
4
14
  /**
5
15
  * Convex-backed {@link PersistenceAdapter} (brief §10, EC-039). Every repository
6
16
  * is implemented against the Convex functions in `apps/builder/convex/*`, mapping
@@ -17,8 +27,11 @@ export declare class ConvexPersistenceAdapter implements PersistenceAdapter {
17
27
  readonly invites: InviteRepository;
18
28
  /** Studio users rail — role + custom fields joined to auth identity (EC-240). */
19
29
  readonly users: UserRepository;
20
- /** Editorial redirects rail — data-backed 301/302 forwards (EC-303). */
21
- readonly redirects: RedirectRepository;
30
+ /**
31
+ * EC-616 — the generic record store: one repository per declared kind, each over its
32
+ * kind's Convex module (cached per kind object, so a rail and its foot share one).
33
+ */
34
+ readonly records: RecordStore;
22
35
  /** Convex file storage behind the EC-133 blob boundary (EC-151). */
23
36
  readonly blobs: BlobStorageAdapter;
24
37
  /** Delivery-pipeline (instant-publish) health, read-only (EC-268/EC-329). */
@@ -1,6 +1,6 @@
1
1
  import { ConvexError } from 'convex/values';
2
2
  import { DRAFT_VARIANT, documentKey, documentSchema } from '@elytracms/core/cms-core';
3
- import { PRESENCE_TTL_MS, RecordNotFoundError, PersistenceValidationError, presenceInputSchema, assetCreateDefaults, assetRecordFromRow, compareReferenceEntries, highestRole, isKnownRole, persistenceIssueSchema, presentWriteKeys, redirectEntryFromRow, validateForPersistence, } from '@elytracms/core/persistence';
3
+ import { PRESENCE_TTL_MS, RecordNotFoundError, PersistenceValidationError, presenceInputSchema, assetCreateDefaults, assetRecordFromRow, compareReferenceEntries, persistenceIssueSchema, presentWriteKeys, recordEntryFromWire, validateForPersistence, versionNotPinnableError, } from '@elytracms/core/persistence';
4
4
  import { normalizeInviteEmail } from '@elytracms/core/persistence';
5
5
  import { createUuidIdFactory } from '@elytracms/core/determinism';
6
6
  import { SUBSCRIPTION_UNAUTHENTICATED_MESSAGE, queryRef, mutationRef } from './convex-client.js';
@@ -52,6 +52,7 @@ function versionRecordFromDoc(doc, projectId) {
52
52
  createdAt: doc.createdAt,
53
53
  ...(doc.label ? { label: doc.label } : {}),
54
54
  ...(doc.origin ? { origin: doc.origin } : {}),
55
+ ...(doc.kind ? { kind: doc.kind } : {}),
55
56
  };
56
57
  }
57
58
  /**
@@ -312,6 +313,10 @@ class ConvexCmsRepository {
312
313
  if (result.status === 'missing-version') {
313
314
  throw new RecordNotFoundError('cms-document-version', `${projectId}/${documentKey(ref)}#${version}`);
314
315
  }
316
+ // EC-585: the version was recorded by a snapshot, never by a publish — the same
317
+ // refusal the in-memory store raises.
318
+ if (result.status === 'not-pinnable')
319
+ throw versionNotPinnableError(`${projectId}/${documentKey(ref)}`, version);
315
320
  return docRecordFromDoc(result.row, projectId);
316
321
  }
317
322
  async unpublishDocument(projectId, ref) {
@@ -589,15 +594,26 @@ function userRecordFromDoc(doc, projectId) {
589
594
  };
590
595
  }
591
596
  /**
592
- * Collapse the 240c-ready `roles[]` to the single `members.role` column by
593
- * applying the strongest role (the 240a→240c enforcement bridge). Unknown
594
- * strings are ignored; an empty/unknown set floors to `viewer`.
597
+ * The single `members.role` column a deployment stores for the 240c-ready `roles[]`
598
+ * (the 240a→240c bridge). EC-605: exactly ONE role, passed through as given — the
599
+ * operations layer validated it against the project's policy, and `members:set`
600
+ * refuses one the DEPLOYED policy does not declare, visibly. This used to filter
601
+ * against the default policy and floor an unknown or empty set to `viewer` — a silent
602
+ * rewrite that turned a project's own role into a viewer. Several roles cannot be
603
+ * collapsed here without a policy, so they are refused rather than guessed at.
595
604
  */
596
- function strongestRole(roles) {
597
- // Validate against the active role policy (EC-240c) rather than a hardcoded
598
- // enum, then take the canonical rank max; floor an empty/unknown set to `viewer`.
599
- const valid = roles.filter((r) => isKnownRole(r));
600
- return highestRole(valid) ?? 'viewer';
605
+ export function singleStoredRole(roles) {
606
+ if (roles.length !== 1) {
607
+ throw new PersistenceValidationError('A deployment stores exactly one role per member.', [
608
+ {
609
+ code: 'invalid-role-count',
610
+ message: `Expected exactly one role, got ${roles.length} (${roles.join(', ') || 'none'}).`,
611
+ path: ['roles'],
612
+ domain: 'users',
613
+ },
614
+ ]);
615
+ }
616
+ return roles[0];
601
617
  }
602
618
  /**
603
619
  * Convex-backed {@link UserRepository} (EC-240, 240a). Reads are the admin-gated
@@ -638,7 +654,7 @@ class ConvexUserRepository {
638
654
  const now = this.clock();
639
655
  await this.client.mutation(mutationRef('members:set'), {
640
656
  userId: input.id,
641
- role: strongestRole(input.roles),
657
+ role: singleStoredRole(input.roles),
642
658
  ...(input.meta !== undefined ? { meta: input.meta } : {}),
643
659
  createdAt: now,
644
660
  updatedAt: now,
@@ -717,56 +733,65 @@ class ConvexInviteRepository {
717
733
  }
718
734
  }
719
735
  /**
720
- * Convex-backed {@link RedirectRepository} (EC-303) — editorial redirects as data
721
- * (not config). Content-gated CRUD over the `redirects` table; the studio's
722
- * editorial surface + the delivery snapshot both read through here. A save sends
723
- * the write as written (EC-577): the create defaults live in the record schema and
724
- * apply inside the handler's transaction, only when the save creates the redirect.
736
+ * Convex-backed {@link RecordRepository} (EC-616) — one record kind over its own Convex
737
+ * module (`<kind.module>:save|get|list|remove`, built by `defineRecordFunctions(kind)`),
738
+ * stored in the one `records` table. The write is sent as written (EC-577): the handler
739
+ * resolves it against the stored record in its transaction with the rule the in-memory
740
+ * store uses. Reads come back in the ONE order (`orderRecords`, applied server-side), and
741
+ * a refused query or write arrives as the same `PersistenceValidationError` the in-memory
742
+ * store throws.
725
743
  */
726
- class ConvexRedirectRepository {
744
+ class ConvexRecordRepository {
727
745
  client;
746
+ kind;
728
747
  clock;
729
- constructor(client, clock = realClock) {
748
+ constructor(client, kind, clock = realClock) {
730
749
  this.client = client;
750
+ this.kind = kind;
731
751
  this.clock = clock;
732
752
  }
733
- async saveRedirect(projectId, input) {
734
- const now = this.clock();
735
- const { id: givenId, ...write } = input;
736
- const id = givenId ?? newId('redirect');
737
- const doc = (await this.client
738
- .mutation(mutationRef('redirects:save'), {
739
- redirectId: id,
740
- ...presentWriteKeys(write),
741
- createdAt: now,
742
- updatedAt: now,
743
- })
753
+ ref(fn) {
754
+ return `${this.kind.module}:${fn}`;
755
+ }
756
+ async list(projectId, query = {}) {
757
+ const rows = (await this.client
758
+ .query(queryRef(this.ref('list')), presentWriteKeys({ ...query }))
744
759
  .catch((error) => {
745
760
  throw asRailWriteError(error);
746
761
  }));
747
- return redirectEntryFromRow(doc, projectId);
762
+ return rows.map((row) => recordEntryFromWire(this.kind, row, projectId));
748
763
  }
749
- async getRedirect(projectId, id) {
750
- const found = await this.findRedirect(projectId, id);
764
+ async get(projectId, id) {
765
+ const found = await this.find(projectId, id);
751
766
  if (!found)
752
- throw new RecordNotFoundError('redirect', `${projectId}/${id}`);
767
+ throw new RecordNotFoundError(this.kind.kind, `${projectId}/${id}`);
753
768
  return found;
754
769
  }
755
- async findRedirect(projectId, id) {
756
- const doc = (await this.client.query(queryRef('redirects:get'), {
757
- redirectId: id,
770
+ async find(projectId, id) {
771
+ const row = (await this.client.query(queryRef(this.ref('get')), {
772
+ [this.kind.idKey]: id,
758
773
  }));
759
- return doc ? redirectEntryFromRow(doc, projectId) : undefined;
774
+ return row ? recordEntryFromWire(this.kind, row, projectId) : undefined;
760
775
  }
761
- async listRedirects(projectId) {
762
- const docs = (await this.client.query(queryRef('redirects:list'), {}));
763
- return docs
764
- .map((doc) => redirectEntryFromRow(doc, projectId))
765
- .sort((a, b) => (a.from < b.from ? -1 : a.from > b.from ? 1 : 0));
776
+ async save(projectId, input) {
777
+ const now = this.clock();
778
+ const { id: givenId, ...write } = input;
779
+ const id = givenId ?? newId(this.kind.idPrefix);
780
+ const row = (await this.client
781
+ .mutation(mutationRef(this.ref('save')), {
782
+ [this.kind.idKey]: id,
783
+ ...presentWriteKeys(write),
784
+ createdAt: now,
785
+ updatedAt: now,
786
+ })
787
+ .catch((error) => {
788
+ throw asRailWriteError(error);
789
+ }));
790
+ return recordEntryFromWire(this.kind, row, projectId);
766
791
  }
767
- async removeRedirect(projectId, id) {
768
- await this.getRedirect(projectId, id); // not-found parity with the in-memory rail
769
- await this.client.mutation(mutationRef('redirects:remove'), { redirectId: id });
792
+ async remove(projectId, id) {
793
+ await this.get(projectId, id); // not-found parity with the in-memory store
794
+ await this.client.mutation(mutationRef(this.ref('remove')), { [this.kind.idKey]: id });
770
795
  }
771
796
  }
772
797
  /**
@@ -802,8 +827,11 @@ export class ConvexPersistenceAdapter {
802
827
  invites;
803
828
  /** Studio users rail — role + custom fields joined to auth identity (EC-240). */
804
829
  users;
805
- /** Editorial redirects rail — data-backed 301/302 forwards (EC-303). */
806
- redirects;
830
+ /**
831
+ * EC-616 — the generic record store: one repository per declared kind, each over its
832
+ * kind's Convex module (cached per kind object, so a rail and its foot share one).
833
+ */
834
+ records;
807
835
  /** Convex file storage behind the EC-133 blob boundary (EC-151). */
808
836
  blobs;
809
837
  /** Delivery-pipeline (instant-publish) health, read-only (EC-268/EC-329). */
@@ -837,7 +865,15 @@ export class ConvexPersistenceAdapter {
837
865
  this.members = new ConvexMembersRepository(client, projectId);
838
866
  this.users = new ConvexUserRepository(client);
839
867
  this.invites = new ConvexInviteRepository(client);
840
- this.redirects = new ConvexRedirectRepository(client);
868
+ const recordRepositories = new Map();
869
+ this.records = (kind) => {
870
+ let repository = recordRepositories.get(kind);
871
+ if (!repository) {
872
+ repository = new ConvexRecordRepository(client, kind);
873
+ recordRepositories.set(kind, repository);
874
+ }
875
+ return repository;
876
+ };
841
877
  this.blobs = new ConvexBlobStorage(client);
842
878
  this.delivery = new ConvexDeliveryRepository(client);
843
879
  }