@kelpie/server 0.10.0 → 0.12.0

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 (485) hide show
  1. package/dist/app.d.ts.map +1 -1
  2. package/dist/app.js +2 -0
  3. package/dist/app.js.map +1 -1
  4. package/dist/index.d.ts +3 -3
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/lib/actor.d.ts +3 -1
  9. package/dist/lib/actor.d.ts.map +1 -1
  10. package/dist/lib/actor.js.map +1 -1
  11. package/dist/lib/apiKeyScopes.d.ts +8 -0
  12. package/dist/lib/apiKeyScopes.d.ts.map +1 -0
  13. package/dist/lib/apiKeyScopes.js +166 -0
  14. package/dist/lib/apiKeyScopes.js.map +1 -0
  15. package/dist/lib/errors.d.ts +18 -0
  16. package/dist/lib/errors.d.ts.map +1 -1
  17. package/dist/lib/errors.js +17 -0
  18. package/dist/lib/errors.js.map +1 -1
  19. package/dist/modules/activities/repository.d.ts +2 -0
  20. package/dist/modules/activities/repository.d.ts.map +1 -1
  21. package/dist/modules/activities/repository.js +9 -0
  22. package/dist/modules/activities/repository.js.map +1 -1
  23. package/dist/modules/agent-tasks/repository.d.ts +2 -0
  24. package/dist/modules/agent-tasks/repository.d.ts.map +1 -1
  25. package/dist/modules/agent-tasks/repository.js +9 -0
  26. package/dist/modules/agent-tasks/repository.js.map +1 -1
  27. package/dist/modules/api-keys/routes.d.ts.map +1 -1
  28. package/dist/modules/api-keys/routes.js +4 -1
  29. package/dist/modules/api-keys/routes.js.map +1 -1
  30. package/dist/modules/api-keys/schema.d.ts +43 -0
  31. package/dist/modules/api-keys/schema.d.ts.map +1 -1
  32. package/dist/modules/api-keys/schema.js +2 -0
  33. package/dist/modules/api-keys/schema.js.map +1 -1
  34. package/dist/modules/api-keys/scopeMiddleware.d.ts +8 -0
  35. package/dist/modules/api-keys/scopeMiddleware.d.ts.map +1 -0
  36. package/dist/modules/api-keys/scopeMiddleware.js +21 -0
  37. package/dist/modules/api-keys/scopeMiddleware.js.map +1 -0
  38. package/dist/modules/api-keys/service.d.ts +3 -1
  39. package/dist/modules/api-keys/service.d.ts.map +1 -1
  40. package/dist/modules/api-keys/service.js +26 -1
  41. package/dist/modules/api-keys/service.js.map +1 -1
  42. package/dist/modules/auth/credentials.d.ts.map +1 -1
  43. package/dist/modules/auth/credentials.js +2 -0
  44. package/dist/modules/auth/credentials.js.map +1 -1
  45. package/dist/modules/auth/index.d.ts.map +1 -1
  46. package/dist/modules/auth/index.js +21 -4
  47. package/dist/modules/auth/index.js.map +1 -1
  48. package/dist/modules/auth/repository.d.ts +2 -1
  49. package/dist/modules/auth/repository.d.ts.map +1 -1
  50. package/dist/modules/auth/repository.js +1 -0
  51. package/dist/modules/auth/repository.js.map +1 -1
  52. package/dist/modules/auth/routes.d.ts.map +1 -1
  53. package/dist/modules/auth/routes.js +2 -6
  54. package/dist/modules/auth/routes.js.map +1 -1
  55. package/dist/modules/auth/schema.d.ts +18 -1
  56. package/dist/modules/auth/schema.d.ts.map +1 -1
  57. package/dist/modules/auth/schema.js +12 -1
  58. package/dist/modules/auth/schema.js.map +1 -1
  59. package/dist/modules/auth/service.d.ts +18 -0
  60. package/dist/modules/auth/service.d.ts.map +1 -1
  61. package/dist/modules/auth/service.js +95 -6
  62. package/dist/modules/auth/service.js.map +1 -1
  63. package/dist/modules/auth/session.d.ts +12 -0
  64. package/dist/modules/auth/session.d.ts.map +1 -1
  65. package/dist/modules/auth/session.js +12 -0
  66. package/dist/modules/auth/session.js.map +1 -1
  67. package/dist/modules/consent-purposes/index.d.ts +1 -1
  68. package/dist/modules/consent-purposes/index.js +1 -1
  69. package/dist/modules/consent-purposes/schema.d.ts +3 -3
  70. package/dist/modules/consent-purposes/schema.js +3 -3
  71. package/dist/modules/consent-purposes/service.d.ts +1 -1
  72. package/dist/modules/consent-purposes/service.d.ts.map +1 -1
  73. package/dist/modules/consent-purposes/service.js +7 -16
  74. package/dist/modules/consent-purposes/service.js.map +1 -1
  75. package/dist/modules/consent-purposes/starters.d.ts +1 -1
  76. package/dist/modules/consent-purposes/tools.js +1 -1
  77. package/dist/modules/consent-purposes/tools.js.map +1 -1
  78. package/dist/modules/conversions/clearPointers.d.ts +10 -0
  79. package/dist/modules/conversions/clearPointers.d.ts.map +1 -0
  80. package/dist/modules/conversions/clearPointers.js +40 -0
  81. package/dist/modules/conversions/clearPointers.js.map +1 -0
  82. package/dist/modules/conversions/factory.d.ts +5 -0
  83. package/dist/modules/conversions/factory.d.ts.map +1 -0
  84. package/dist/modules/conversions/factory.js +18 -0
  85. package/dist/modules/conversions/factory.js.map +1 -0
  86. package/dist/modules/conversions/fieldMap.d.ts +55 -0
  87. package/dist/modules/conversions/fieldMap.d.ts.map +1 -0
  88. package/dist/modules/conversions/fieldMap.js +398 -0
  89. package/dist/modules/conversions/fieldMap.js.map +1 -0
  90. package/dist/modules/conversions/index.d.ts +7 -0
  91. package/dist/modules/conversions/index.d.ts.map +1 -0
  92. package/dist/modules/conversions/index.js +6 -0
  93. package/dist/modules/conversions/index.js.map +1 -0
  94. package/dist/modules/conversions/mcp.d.ts +7 -0
  95. package/dist/modules/conversions/mcp.d.ts.map +1 -0
  96. package/dist/modules/conversions/mcp.js +47 -0
  97. package/dist/modules/conversions/mcp.js.map +1 -0
  98. package/dist/modules/conversions/repoint.d.ts +19 -0
  99. package/dist/modules/conversions/repoint.d.ts.map +1 -0
  100. package/dist/modules/conversions/repoint.js +51 -0
  101. package/dist/modules/conversions/repoint.js.map +1 -0
  102. package/dist/modules/conversions/routes.d.ts +26 -0
  103. package/dist/modules/conversions/routes.d.ts.map +1 -0
  104. package/dist/modules/conversions/routes.js +23 -0
  105. package/dist/modules/conversions/routes.js.map +1 -0
  106. package/dist/modules/conversions/service.d.ts +28 -0
  107. package/dist/modules/conversions/service.d.ts.map +1 -0
  108. package/dist/modules/conversions/service.js +170 -0
  109. package/dist/modules/conversions/service.js.map +1 -0
  110. package/dist/modules/conversions/wire.d.ts +4 -0
  111. package/dist/modules/conversions/wire.d.ts.map +1 -0
  112. package/dist/modules/conversions/wire.js +28 -0
  113. package/dist/modules/conversions/wire.js.map +1 -0
  114. package/dist/modules/deals/events.d.ts +9 -0
  115. package/dist/modules/deals/events.d.ts.map +1 -1
  116. package/dist/modules/deals/events.js +4 -0
  117. package/dist/modules/deals/events.js.map +1 -1
  118. package/dist/modules/deals/index.d.ts.map +1 -1
  119. package/dist/modules/deals/index.js +4 -2
  120. package/dist/modules/deals/index.js.map +1 -1
  121. package/dist/modules/deals/routes.d.ts +2 -0
  122. package/dist/modules/deals/routes.d.ts.map +1 -1
  123. package/dist/modules/deals/routes.js +8 -1
  124. package/dist/modules/deals/routes.js.map +1 -1
  125. package/dist/modules/deals/schema.d.ts +34 -0
  126. package/dist/modules/deals/schema.d.ts.map +1 -1
  127. package/dist/modules/deals/schema.js +7 -2
  128. package/dist/modules/deals/schema.js.map +1 -1
  129. package/dist/modules/deals/service.d.ts.map +1 -1
  130. package/dist/modules/deals/service.js +2 -0
  131. package/dist/modules/deals/service.js.map +1 -1
  132. package/dist/modules/deals/tools.d.ts +2 -1
  133. package/dist/modules/deals/tools.d.ts.map +1 -1
  134. package/dist/modules/deals/tools.js +3 -1
  135. package/dist/modules/deals/tools.js.map +1 -1
  136. package/dist/modules/decisions/repository.d.ts +2 -1
  137. package/dist/modules/decisions/repository.d.ts.map +1 -1
  138. package/dist/modules/decisions/repository.js +9 -1
  139. package/dist/modules/decisions/repository.js.map +1 -1
  140. package/dist/modules/enquiries/events.d.ts +6 -2
  141. package/dist/modules/enquiries/events.d.ts.map +1 -1
  142. package/dist/modules/enquiries/events.js +5 -1
  143. package/dist/modules/enquiries/events.js.map +1 -1
  144. package/dist/modules/enquiries/index.d.ts.map +1 -1
  145. package/dist/modules/enquiries/index.js +4 -2
  146. package/dist/modules/enquiries/index.js.map +1 -1
  147. package/dist/modules/enquiries/routes.d.ts +2 -0
  148. package/dist/modules/enquiries/routes.d.ts.map +1 -1
  149. package/dist/modules/enquiries/routes.js +9 -9
  150. package/dist/modules/enquiries/routes.js.map +1 -1
  151. package/dist/modules/enquiries/schema.d.ts +36 -1
  152. package/dist/modules/enquiries/schema.d.ts.map +1 -1
  153. package/dist/modules/enquiries/schema.js +9 -3
  154. package/dist/modules/enquiries/schema.js.map +1 -1
  155. package/dist/modules/enquiries/service.d.ts +0 -8
  156. package/dist/modules/enquiries/service.d.ts.map +1 -1
  157. package/dist/modules/enquiries/service.js +3 -95
  158. package/dist/modules/enquiries/service.js.map +1 -1
  159. package/dist/modules/enquiries/tools.d.ts +2 -1
  160. package/dist/modules/enquiries/tools.d.ts.map +1 -1
  161. package/dist/modules/enquiries/tools.js +4 -16
  162. package/dist/modules/enquiries/tools.js.map +1 -1
  163. package/dist/modules/forms/applyMappedFields.d.ts +20 -0
  164. package/dist/modules/forms/applyMappedFields.d.ts.map +1 -0
  165. package/dist/modules/forms/applyMappedFields.js +169 -0
  166. package/dist/modules/forms/applyMappedFields.js.map +1 -0
  167. package/dist/modules/forms/applySubmissionFields.d.ts +18 -0
  168. package/dist/modules/forms/applySubmissionFields.d.ts.map +1 -0
  169. package/dist/modules/forms/applySubmissionFields.js +230 -0
  170. package/dist/modules/forms/applySubmissionFields.js.map +1 -0
  171. package/dist/modules/forms/fields.d.ts +7 -2
  172. package/dist/modules/forms/fields.d.ts.map +1 -1
  173. package/dist/modules/forms/fields.js +17 -6
  174. package/dist/modules/forms/fields.js.map +1 -1
  175. package/dist/modules/forms/mapping.d.ts +8 -2
  176. package/dist/modules/forms/mapping.d.ts.map +1 -1
  177. package/dist/modules/forms/mapping.js +11 -0
  178. package/dist/modules/forms/mapping.js.map +1 -1
  179. package/dist/modules/forms/repository.d.ts +0 -5
  180. package/dist/modules/forms/repository.d.ts.map +1 -1
  181. package/dist/modules/forms/repository.js +0 -2
  182. package/dist/modules/forms/repository.js.map +1 -1
  183. package/dist/modules/forms/routes.d.ts +2 -30
  184. package/dist/modules/forms/routes.d.ts.map +1 -1
  185. package/dist/modules/forms/routes.js +2 -2
  186. package/dist/modules/forms/routes.js.map +1 -1
  187. package/dist/modules/forms/schema.d.ts.map +1 -1
  188. package/dist/modules/forms/schema.js +1 -2
  189. package/dist/modules/forms/schema.js.map +1 -1
  190. package/dist/modules/forms/service.d.ts.map +1 -1
  191. package/dist/modules/forms/service.js +18 -3
  192. package/dist/modules/forms/service.js.map +1 -1
  193. package/dist/modules/forms/starters.d.ts +31 -0
  194. package/dist/modules/forms/starters.d.ts.map +1 -0
  195. package/dist/modules/forms/starters.js +94 -0
  196. package/dist/modules/forms/starters.js.map +1 -0
  197. package/dist/modules/forms/submission.d.ts +1 -0
  198. package/dist/modules/forms/submission.d.ts.map +1 -1
  199. package/dist/modules/forms/submission.js +58 -37
  200. package/dist/modules/forms/submission.js.map +1 -1
  201. package/dist/modules/import-export/customFieldCsv.d.ts +9 -0
  202. package/dist/modules/import-export/customFieldCsv.d.ts.map +1 -0
  203. package/dist/modules/import-export/customFieldCsv.js +39 -0
  204. package/dist/modules/import-export/customFieldCsv.js.map +1 -0
  205. package/dist/modules/import-export/customFieldImport.d.ts +19 -0
  206. package/dist/modules/import-export/customFieldImport.d.ts.map +1 -0
  207. package/dist/modules/import-export/customFieldImport.js +76 -0
  208. package/dist/modules/import-export/customFieldImport.js.map +1 -0
  209. package/dist/modules/import-export/drafts.d.ts +57 -3
  210. package/dist/modules/import-export/drafts.d.ts.map +1 -1
  211. package/dist/modules/import-export/drafts.js +65 -3
  212. package/dist/modules/import-export/drafts.js.map +1 -1
  213. package/dist/modules/import-export/exportRows.d.ts +15 -13
  214. package/dist/modules/import-export/exportRows.d.ts.map +1 -1
  215. package/dist/modules/import-export/exportRows.js +99 -26
  216. package/dist/modules/import-export/exportRows.js.map +1 -1
  217. package/dist/modules/import-export/plan.d.ts +42 -11
  218. package/dist/modules/import-export/plan.d.ts.map +1 -1
  219. package/dist/modules/import-export/plan.js +198 -56
  220. package/dist/modules/import-export/plan.js.map +1 -1
  221. package/dist/modules/import-export/repository.d.ts +58 -2
  222. package/dist/modules/import-export/repository.d.ts.map +1 -1
  223. package/dist/modules/import-export/repository.js +201 -12
  224. package/dist/modules/import-export/repository.js.map +1 -1
  225. package/dist/modules/import-export/routes.d.ts.map +1 -1
  226. package/dist/modules/import-export/routes.js +5 -5
  227. package/dist/modules/import-export/routes.js.map +1 -1
  228. package/dist/modules/import-export/service.d.ts +8 -3
  229. package/dist/modules/import-export/service.d.ts.map +1 -1
  230. package/dist/modules/import-export/service.js +133 -40
  231. package/dist/modules/import-export/service.js.map +1 -1
  232. package/dist/modules/import-export/streams.d.ts +2 -2
  233. package/dist/modules/import-export/streams.d.ts.map +1 -1
  234. package/dist/modules/import-export/streams.js +50 -9
  235. package/dist/modules/import-export/streams.js.map +1 -1
  236. package/dist/modules/import-export/tools.d.ts.map +1 -1
  237. package/dist/modules/import-export/tools.js +13 -7
  238. package/dist/modules/import-export/tools.js.map +1 -1
  239. package/dist/modules/import-export/validation.d.ts +5 -1
  240. package/dist/modules/import-export/validation.d.ts.map +1 -1
  241. package/dist/modules/import-export/validation.js +89 -2
  242. package/dist/modules/import-export/validation.js.map +1 -1
  243. package/dist/modules/import-export/writes.d.ts +2 -1
  244. package/dist/modules/import-export/writes.d.ts.map +1 -1
  245. package/dist/modules/import-export/writes.js +358 -25
  246. package/dist/modules/import-export/writes.js.map +1 -1
  247. package/dist/modules/lists/repository.d.ts +0 -1
  248. package/dist/modules/lists/repository.d.ts.map +1 -1
  249. package/dist/modules/lists/repository.js +0 -1
  250. package/dist/modules/lists/repository.js.map +1 -1
  251. package/dist/modules/lists/routes.d.ts +0 -2
  252. package/dist/modules/lists/routes.d.ts.map +1 -1
  253. package/dist/modules/lists/routes.js +0 -8
  254. package/dist/modules/lists/routes.js.map +1 -1
  255. package/dist/modules/lists/schema.d.ts +0 -17
  256. package/dist/modules/lists/schema.d.ts.map +1 -1
  257. package/dist/modules/lists/schema.js +0 -10
  258. package/dist/modules/lists/schema.js.map +1 -1
  259. package/dist/modules/lists/service.d.ts +2 -14
  260. package/dist/modules/lists/service.d.ts.map +1 -1
  261. package/dist/modules/lists/service.js +7 -53
  262. package/dist/modules/lists/service.js.map +1 -1
  263. package/dist/modules/lists/starters.d.ts +12 -0
  264. package/dist/modules/lists/starters.d.ts.map +1 -0
  265. package/dist/modules/lists/starters.js +4 -0
  266. package/dist/modules/lists/starters.js.map +1 -0
  267. package/dist/modules/mcp/protocol.d.ts.map +1 -1
  268. package/dist/modules/mcp/protocol.js +13 -0
  269. package/dist/modules/mcp/protocol.js.map +1 -1
  270. package/dist/modules/notes/repository.d.ts +2 -0
  271. package/dist/modules/notes/repository.d.ts.map +1 -1
  272. package/dist/modules/notes/repository.js +9 -0
  273. package/dist/modules/notes/repository.js.map +1 -1
  274. package/dist/modules/opportunities/events.d.ts +9 -0
  275. package/dist/modules/opportunities/events.d.ts.map +1 -1
  276. package/dist/modules/opportunities/events.js +4 -0
  277. package/dist/modules/opportunities/events.js.map +1 -1
  278. package/dist/modules/opportunities/index.d.ts.map +1 -1
  279. package/dist/modules/opportunities/index.js +4 -2
  280. package/dist/modules/opportunities/index.js.map +1 -1
  281. package/dist/modules/opportunities/routes.d.ts +2 -0
  282. package/dist/modules/opportunities/routes.d.ts.map +1 -1
  283. package/dist/modules/opportunities/routes.js +8 -1
  284. package/dist/modules/opportunities/routes.js.map +1 -1
  285. package/dist/modules/opportunities/schema.d.ts +34 -0
  286. package/dist/modules/opportunities/schema.d.ts.map +1 -1
  287. package/dist/modules/opportunities/schema.js +7 -2
  288. package/dist/modules/opportunities/schema.js.map +1 -1
  289. package/dist/modules/opportunities/service.d.ts.map +1 -1
  290. package/dist/modules/opportunities/service.js +2 -0
  291. package/dist/modules/opportunities/service.js.map +1 -1
  292. package/dist/modules/opportunities/tools.d.ts +2 -1
  293. package/dist/modules/opportunities/tools.d.ts.map +1 -1
  294. package/dist/modules/opportunities/tools.js +3 -1
  295. package/dist/modules/opportunities/tools.js.map +1 -1
  296. package/dist/modules/partnerships/events.d.ts +9 -0
  297. package/dist/modules/partnerships/events.d.ts.map +1 -1
  298. package/dist/modules/partnerships/events.js +4 -0
  299. package/dist/modules/partnerships/events.js.map +1 -1
  300. package/dist/modules/partnerships/index.d.ts.map +1 -1
  301. package/dist/modules/partnerships/index.js +4 -2
  302. package/dist/modules/partnerships/index.js.map +1 -1
  303. package/dist/modules/partnerships/routes.d.ts +2 -0
  304. package/dist/modules/partnerships/routes.d.ts.map +1 -1
  305. package/dist/modules/partnerships/routes.js +8 -1
  306. package/dist/modules/partnerships/routes.js.map +1 -1
  307. package/dist/modules/partnerships/schema.d.ts +34 -0
  308. package/dist/modules/partnerships/schema.d.ts.map +1 -1
  309. package/dist/modules/partnerships/schema.js +7 -2
  310. package/dist/modules/partnerships/schema.js.map +1 -1
  311. package/dist/modules/partnerships/service.d.ts.map +1 -1
  312. package/dist/modules/partnerships/service.js +2 -0
  313. package/dist/modules/partnerships/service.js.map +1 -1
  314. package/dist/modules/partnerships/tools.d.ts +2 -1
  315. package/dist/modules/partnerships/tools.d.ts.map +1 -1
  316. package/dist/modules/partnerships/tools.js +3 -1
  317. package/dist/modules/partnerships/tools.js.map +1 -1
  318. package/dist/modules/people/personConsentWrites.d.ts +1 -1
  319. package/dist/modules/people/schema.js +1 -1
  320. package/dist/modules/people/schema.js.map +1 -1
  321. package/dist/modules/plans/repository.d.ts +2 -0
  322. package/dist/modules/plans/repository.d.ts.map +1 -1
  323. package/dist/modules/plans/repository.js +9 -0
  324. package/dist/modules/plans/repository.js.map +1 -1
  325. package/dist/modules/raises/events.d.ts +9 -0
  326. package/dist/modules/raises/events.d.ts.map +1 -1
  327. package/dist/modules/raises/events.js +4 -0
  328. package/dist/modules/raises/events.js.map +1 -1
  329. package/dist/modules/raises/index.d.ts.map +1 -1
  330. package/dist/modules/raises/index.js +4 -2
  331. package/dist/modules/raises/index.js.map +1 -1
  332. package/dist/modules/raises/routes.d.ts +2 -0
  333. package/dist/modules/raises/routes.d.ts.map +1 -1
  334. package/dist/modules/raises/routes.js +8 -1
  335. package/dist/modules/raises/routes.js.map +1 -1
  336. package/dist/modules/raises/schema.d.ts +34 -0
  337. package/dist/modules/raises/schema.d.ts.map +1 -1
  338. package/dist/modules/raises/schema.js +7 -2
  339. package/dist/modules/raises/schema.js.map +1 -1
  340. package/dist/modules/raises/service.d.ts.map +1 -1
  341. package/dist/modules/raises/service.js +2 -0
  342. package/dist/modules/raises/service.js.map +1 -1
  343. package/dist/modules/raises/tools.d.ts +2 -1
  344. package/dist/modules/raises/tools.d.ts.map +1 -1
  345. package/dist/modules/raises/tools.js +3 -1
  346. package/dist/modules/raises/tools.js.map +1 -1
  347. package/dist/modules/workspace/routes.d.ts +0 -2
  348. package/dist/modules/workspace/routes.d.ts.map +1 -1
  349. package/dist/modules/workspace/routes.js +0 -6
  350. package/dist/modules/workspace/routes.js.map +1 -1
  351. package/dist/modules/workspace/schema.d.ts +0 -34
  352. package/dist/modules/workspace/schema.d.ts.map +1 -1
  353. package/dist/modules/workspace/schema.js +0 -2
  354. package/dist/modules/workspace/schema.js.map +1 -1
  355. package/dist/modules/workspace/seedStarterForms.d.ts +17 -0
  356. package/dist/modules/workspace/seedStarterForms.d.ts.map +1 -0
  357. package/dist/modules/workspace/seedStarterForms.js +71 -0
  358. package/dist/modules/workspace/seedStarterForms.js.map +1 -0
  359. package/dist/modules/workspace/service.d.ts +0 -4
  360. package/dist/modules/workspace/service.d.ts.map +1 -1
  361. package/dist/modules/workspace/service.js +20 -10
  362. package/dist/modules/workspace/service.js.map +1 -1
  363. package/dist/modules/workspace/tools.js +1 -1
  364. package/dist/modules/workspace/tools.js.map +1 -1
  365. package/dist/runtime/module.d.ts +63 -1
  366. package/dist/runtime/module.d.ts.map +1 -1
  367. package/dist/runtime/registry.d.ts.map +1 -1
  368. package/dist/runtime/registry.js +40 -2
  369. package/dist/runtime/registry.js.map +1 -1
  370. package/dist/testing/fixtures.d.ts.map +1 -1
  371. package/dist/testing/fixtures.js +1 -0
  372. package/dist/testing/fixtures.js.map +1 -1
  373. package/migrations/0037_drop_lists_consent_purpose_id.sql +2 -0
  374. package/migrations/0038_smart_white_queen.sql +2 -0
  375. package/migrations/0039_form_map_to_open.sql +1 -0
  376. package/migrations/0040_pipeline_converted_to.sql +20 -0
  377. package/migrations/0041_api_key_scopes.sql +1 -0
  378. package/migrations/0042_drop_workspace_identity.sql +2 -0
  379. package/migrations/0043_external_sign_in.sql +2 -0
  380. package/migrations/meta/0038_snapshot.json +6554 -0
  381. package/migrations/meta/_journal.json +49 -0
  382. package/package.json +2 -2
  383. package/src/app.ts +3 -0
  384. package/src/index.ts +13 -1
  385. package/src/lib/actor.ts +3 -1
  386. package/src/lib/apiKeyScopes.ts +213 -0
  387. package/src/lib/errors.ts +30 -0
  388. package/src/modules/activities/repository.ts +24 -0
  389. package/src/modules/agent-tasks/repository.ts +24 -0
  390. package/src/modules/api-keys/routes.ts +5 -0
  391. package/src/modules/api-keys/schema.ts +2 -0
  392. package/src/modules/api-keys/scopeMiddleware.ts +30 -0
  393. package/src/modules/api-keys/service.ts +36 -2
  394. package/src/modules/auth/credentials.ts +4 -0
  395. package/src/modules/auth/index.ts +25 -4
  396. package/src/modules/auth/repository.ts +2 -1
  397. package/src/modules/auth/routes.ts +2 -8
  398. package/src/modules/auth/schema.ts +12 -1
  399. package/src/modules/auth/service.ts +143 -6
  400. package/src/modules/auth/session.ts +14 -0
  401. package/src/modules/consent-purposes/index.ts +1 -1
  402. package/src/modules/consent-purposes/schema.ts +3 -3
  403. package/src/modules/consent-purposes/service.ts +7 -16
  404. package/src/modules/consent-purposes/starters.ts +1 -1
  405. package/src/modules/consent-purposes/tools.ts +1 -1
  406. package/src/modules/conversions/clearPointers.ts +81 -0
  407. package/src/modules/conversions/factory.ts +20 -0
  408. package/src/modules/conversions/fieldMap.ts +548 -0
  409. package/src/modules/conversions/index.ts +6 -0
  410. package/src/modules/conversions/mcp.ts +71 -0
  411. package/src/modules/conversions/repoint.ts +146 -0
  412. package/src/modules/conversions/routes.ts +68 -0
  413. package/src/modules/conversions/service.ts +310 -0
  414. package/src/modules/conversions/wire.ts +42 -0
  415. package/src/modules/deals/events.ts +9 -0
  416. package/src/modules/deals/index.ts +4 -2
  417. package/src/modules/deals/routes.ts +11 -1
  418. package/src/modules/deals/schema.ts +10 -2
  419. package/src/modules/deals/service.ts +3 -0
  420. package/src/modules/deals/tools.ts +9 -1
  421. package/src/modules/decisions/repository.ts +23 -1
  422. package/src/modules/enquiries/events.ts +8 -2
  423. package/src/modules/enquiries/index.ts +4 -2
  424. package/src/modules/enquiries/routes.ts +11 -13
  425. package/src/modules/enquiries/schema.ts +12 -3
  426. package/src/modules/enquiries/service.ts +3 -150
  427. package/src/modules/enquiries/tools.ts +9 -18
  428. package/src/modules/forms/applyMappedFields.ts +248 -0
  429. package/src/modules/forms/applySubmissionFields.ts +435 -0
  430. package/src/modules/forms/fields.ts +31 -7
  431. package/src/modules/forms/mapping.ts +20 -3
  432. package/src/modules/forms/repository.ts +0 -7
  433. package/src/modules/forms/routes.ts +1 -2
  434. package/src/modules/forms/schema.ts +0 -2
  435. package/src/modules/forms/service.ts +31 -4
  436. package/src/modules/forms/starters.ts +129 -0
  437. package/src/modules/forms/submission.ts +135 -69
  438. package/src/modules/import-export/customFieldCsv.ts +55 -0
  439. package/src/modules/import-export/customFieldImport.ts +111 -0
  440. package/src/modules/import-export/drafts.ts +151 -3
  441. package/src/modules/import-export/exportRows.ts +222 -68
  442. package/src/modules/import-export/plan.ts +321 -67
  443. package/src/modules/import-export/repository.ts +356 -14
  444. package/src/modules/import-export/routes.ts +8 -8
  445. package/src/modules/import-export/service.ts +208 -45
  446. package/src/modules/import-export/streams.ts +95 -13
  447. package/src/modules/import-export/tools.ts +14 -6
  448. package/src/modules/import-export/validation.ts +128 -0
  449. package/src/modules/import-export/writes.ts +559 -27
  450. package/src/modules/lists/repository.ts +0 -2
  451. package/src/modules/lists/routes.ts +0 -8
  452. package/src/modules/lists/schema.ts +0 -10
  453. package/src/modules/lists/service.ts +9 -107
  454. package/src/modules/lists/starters.ts +16 -0
  455. package/src/modules/mcp/protocol.ts +18 -0
  456. package/src/modules/notes/repository.ts +24 -0
  457. package/src/modules/opportunities/events.ts +9 -0
  458. package/src/modules/opportunities/index.ts +4 -2
  459. package/src/modules/opportunities/routes.ts +11 -1
  460. package/src/modules/opportunities/schema.ts +10 -2
  461. package/src/modules/opportunities/service.ts +3 -0
  462. package/src/modules/opportunities/tools.ts +9 -1
  463. package/src/modules/partnerships/events.ts +9 -0
  464. package/src/modules/partnerships/index.ts +4 -2
  465. package/src/modules/partnerships/routes.ts +11 -1
  466. package/src/modules/partnerships/schema.ts +10 -2
  467. package/src/modules/partnerships/service.ts +3 -0
  468. package/src/modules/partnerships/tools.ts +9 -1
  469. package/src/modules/people/personConsentWrites.ts +1 -1
  470. package/src/modules/people/schema.ts +1 -1
  471. package/src/modules/plans/repository.ts +24 -0
  472. package/src/modules/raises/events.ts +9 -0
  473. package/src/modules/raises/index.ts +4 -2
  474. package/src/modules/raises/routes.ts +11 -1
  475. package/src/modules/raises/schema.ts +10 -2
  476. package/src/modules/raises/service.ts +3 -0
  477. package/src/modules/raises/tools.ts +9 -1
  478. package/src/modules/workspace/routes.ts +0 -6
  479. package/src/modules/workspace/schema.ts +0 -2
  480. package/src/modules/workspace/seedStarterForms.ts +98 -0
  481. package/src/modules/workspace/service.ts +23 -15
  482. package/src/modules/workspace/tools.ts +1 -1
  483. package/src/runtime/module.ts +70 -1
  484. package/src/runtime/registry.ts +54 -0
  485. package/src/testing/fixtures.ts +1 -0
@@ -260,6 +260,55 @@
260
260
  "when": 1788146891073,
261
261
  "tag": "0036_form_field_notice_type",
262
262
  "breakpoints": true
263
+ },
264
+ {
265
+ "idx": 37,
266
+ "version": "7",
267
+ "when": 1788150000000,
268
+ "tag": "0037_drop_lists_consent_purpose_id",
269
+ "breakpoints": true
270
+ },
271
+ {
272
+ "idx": 38,
273
+ "version": "7",
274
+ "when": 1788214656489,
275
+ "tag": "0038_smart_white_queen",
276
+ "breakpoints": true
277
+ },
278
+ {
279
+ "idx": 39,
280
+ "version": "7",
281
+ "when": 1788300000000,
282
+ "tag": "0039_form_map_to_open",
283
+ "breakpoints": true
284
+ },
285
+ {
286
+ "idx": 40,
287
+ "version": "7",
288
+ "when": 1788400000000,
289
+ "tag": "0040_pipeline_converted_to",
290
+ "breakpoints": true
291
+ },
292
+ {
293
+ "idx": 41,
294
+ "version": "7",
295
+ "when": 1788500000000,
296
+ "tag": "0041_api_key_scopes",
297
+ "breakpoints": true
298
+ },
299
+ {
300
+ "idx": 42,
301
+ "version": "7",
302
+ "when": 1788600000000,
303
+ "tag": "0042_drop_workspace_identity",
304
+ "breakpoints": true
305
+ },
306
+ {
307
+ "idx": 43,
308
+ "version": "7",
309
+ "when": 1788700000000,
310
+ "tag": "0043_external_sign_in",
311
+ "breakpoints": true
263
312
  }
264
313
  ]
265
314
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kelpie/server",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "The Kelpie service as a library: module runtime, core CRM modules, REST API, MCP surface, and the shared migration pipeline.",
5
5
  "keywords": [
6
6
  "kelpie",
@@ -52,7 +52,7 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@hono/node-server": "^2.0.12",
55
- "@kelpie/schemas": "^0.10.0",
55
+ "@kelpie/schemas": "^0.12.0",
56
56
  "@node-rs/argon2": "^2.0.2",
57
57
  "drizzle-orm": "^0.45.2",
58
58
  "emittery": "^2.0.0",
package/src/app.ts CHANGED
@@ -14,6 +14,7 @@ import { securityHeadersMiddleware } from './lib/securityHeaders.ts'
14
14
  import type { CredentialDependencies } from './modules/auth/credentials.ts'
15
15
  import { MCP_INSTRUCTIONS, MCP_ROUTE_PREFIX, MCP_SERVER_INFO } from './modules/mcp/index.ts'
16
16
  import { createMcpEndpoint } from './modules/mcp/router.ts'
17
+ import { createApiKeyScopeMiddleware } from './modules/api-keys/scopeMiddleware.ts'
17
18
  import {
18
19
  createAuthAndApiRateLimitMiddleware,
19
20
  createFormSubmitRateLimitMiddleware,
@@ -145,6 +146,8 @@ export function createApp(dependencies: AppDependencies): Hono<AppBindings> {
145
146
 
146
147
  app.use('/v1/*', workspaceAccessMiddleware)
147
148
 
149
+ app.use('/v1/*', createApiKeyScopeMiddleware(dependencies.credentials))
150
+
148
151
  // Every module's `POST` gets this the same way, decided once here rather than
149
152
  // per route (`api.md`). It skips `/v1/public/*` itself — a public request has
150
153
  // no `Actor` to scope a key to — so it is mounted ahead of the public CORS
package/src/index.ts CHANGED
@@ -64,13 +64,21 @@ export type { SecretCipher, SecretEncryptionConfig } from './lib/secrets.ts'
64
64
 
65
65
  export {
66
66
  AppError,
67
+ ExternalSignInError,
67
68
  describeThrown,
68
69
  describeValidationIssue,
69
70
  internalErrorBody,
70
71
  toErrorBody,
71
72
  toErrorDetails,
72
73
  } from './lib/errors.ts'
73
- export type { ErrorBody, ErrorCode, ErrorDetail, ErrorStatus, ValidationIssue } from './lib/errors.ts'
74
+ export type {
75
+ ErrorBody,
76
+ ErrorCode,
77
+ ErrorDetail,
78
+ ErrorStatus,
79
+ ExternalSignInRefusal,
80
+ ValidationIssue,
81
+ } from './lib/errors.ts'
74
82
 
75
83
  export { createIdFactory, idPrefixes } from './lib/ids.ts'
76
84
  export type { IdFactory, ObjectKind } from './lib/ids.ts'
@@ -84,6 +92,9 @@ export {
84
92
  export type { CreateLoggerOptions, LogFields, Logger, LoggingDestination } from './lib/logger.ts'
85
93
 
86
94
  export type {
95
+ CompletedSignIn,
96
+ CompletedSignInAccount,
97
+ ExternalSignInHandler,
87
98
  KelpieModule,
88
99
  McpTool,
89
100
  McpToolDefinition,
@@ -91,6 +102,7 @@ export type {
91
102
  ModuleCatalogEntry,
92
103
  ModuleContext,
93
104
  SchemaContribution,
105
+ VerifiedIdentity,
94
106
  } from './runtime/module.ts'
95
107
 
96
108
  export { ModuleBootError, orderModules } from './runtime/order.ts'
package/src/lib/actor.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { EventActor, MemberRole } from '@kelpie/schemas'
1
+ import type { ApiKeyScope, EventActor, MemberRole } from '@kelpie/schemas'
2
2
 
3
3
  import { AppError } from './errors.ts'
4
4
 
@@ -39,6 +39,8 @@ export interface ApiKeyActor {
39
39
  readonly userId: string | null
40
40
  readonly workspaceId: string
41
41
  readonly role: MemberRole
42
+ /** Empty means full access. Presets and granular tokens may be mixed. */
43
+ readonly scopes: readonly ApiKeyScope[]
42
44
  /** Null for a workspace key: it belongs to the workspace, not to a member. */
43
45
  readonly memberId: string | null
44
46
  }
@@ -0,0 +1,213 @@
1
+ import type { ApiKeyGranularScope, ApiKeyScope } from '@kelpie/schemas'
2
+ import { satisfiesApiKeyScope } from '@kelpie/schemas'
3
+
4
+ import type { Actor } from './actor.ts'
5
+ import { AppError } from './errors.ts'
6
+
7
+ /**
8
+ * REST and MCP scope enforcement for API keys.
9
+ *
10
+ * Sessions are never scoped. An empty scope list on a key means full access.
11
+ */
12
+
13
+ interface RouteScopeRule {
14
+ readonly methods: readonly string[]
15
+ readonly pattern: RegExp
16
+ readonly scope: ApiKeyGranularScope
17
+ }
18
+
19
+ /** Paths that need no scope check (session-only or unauthenticated). */
20
+ function isExempt(method: string, path: string): boolean {
21
+ if (path.startsWith('/v1/public/')) {
22
+ return true
23
+ }
24
+
25
+ if (path.startsWith('/v1/auth/')) {
26
+ return true
27
+ }
28
+
29
+ if (path.startsWith('/v1/account')) {
30
+ return true
31
+ }
32
+
33
+ if (method === 'POST' && path === '/v1/workspaces') {
34
+ return true
35
+ }
36
+
37
+ return method === 'POST' && path === '/v1/invites/accept'
38
+ }
39
+
40
+ const ROUTE_SCOPE_RULES: readonly RouteScopeRule[] = [
41
+ { methods: ['GET'], pattern: /^\/v1\/people(?:\/[^/]+)?$/u, scope: 'people:read' },
42
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/people(?:\/[^/]+)?$/u, scope: 'people:write' },
43
+ { methods: ['GET'], pattern: /^\/v1\/companies(?:\/[^/]+)?$/u, scope: 'companies:read' },
44
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/companies(?:\/[^/]+)?$/u, scope: 'companies:write' },
45
+ { methods: ['GET'], pattern: /^\/v1\/positions(?:\/[^/]+)?$/u, scope: 'positions:read' },
46
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/positions(?:\/[^/]+)?$/u, scope: 'positions:write' },
47
+ { methods: ['GET'], pattern: /^\/v1\/deals(?:\/[^/]+(?:\/convert)?)?$/u, scope: 'deals:read' },
48
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/deals(?:\/[^/]+(?:\/convert)?)?$/u, scope: 'deals:write' },
49
+ { methods: ['GET'], pattern: /^\/v1\/enquiries(?:\/[^/]+(?:\/convert)?)?$/u, scope: 'enquiries:read' },
50
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/enquiries(?:\/[^/]+(?:\/convert)?)?$/u, scope: 'enquiries:write' },
51
+ { methods: ['GET'], pattern: /^\/v1\/opportunities(?:\/[^/]+(?:\/convert)?)?$/u, scope: 'opportunities:read' },
52
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/opportunities(?:\/[^/]+(?:\/convert)?)?$/u, scope: 'opportunities:write' },
53
+ { methods: ['GET'], pattern: /^\/v1\/partnerships(?:\/[^/]+(?:\/convert)?)?$/u, scope: 'partnerships:read' },
54
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/partnerships(?:\/[^/]+(?:\/convert)?)?$/u, scope: 'partnerships:write' },
55
+ { methods: ['GET'], pattern: /^\/v1\/raises(?:\/[^/]+(?:\/convert)?)?$/u, scope: 'raises:read' },
56
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/raises(?:\/[^/]+(?:\/convert)?)?$/u, scope: 'raises:write' },
57
+ { methods: ['GET'], pattern: /^\/v1\/roles(?:\/[^/]+)?$/u, scope: 'roles:read' },
58
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/roles(?:\/[^/]+)?$/u, scope: 'roles:write' },
59
+ { methods: ['GET'], pattern: /^\/v1\/candidates(?:\/[^/]+)?$/u, scope: 'candidates:read' },
60
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/candidates(?:\/[^/]+)?$/u, scope: 'candidates:write' },
61
+ { methods: ['GET'], pattern: /^\/v1\/decisions(?:\/[^/]+)?$/u, scope: 'decisions:read' },
62
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/decisions(?:\/[^/]+)?$/u, scope: 'decisions:write' },
63
+ { methods: ['GET'], pattern: /^\/v1\/plan_items(?:\/[^/]+)?$/u, scope: 'plan_items:read' },
64
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/plan_items(?:\/[^/]+)?$/u, scope: 'plan_items:write' },
65
+ { methods: ['GET'], pattern: /^\/v1\/notes(?:\/[^/]+)?$/u, scope: 'notes:read' },
66
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/notes(?:\/[^/]+)?$/u, scope: 'notes:write' },
67
+ { methods: ['GET'], pattern: /^\/v1\/(?:lists(?:\/[^/]+(?:\/members(?:\/[^/]+)?)?)?|list-memberships)$/u, scope: 'lists:read' },
68
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/(?:lists(?:\/[^/]+(?:\/members(?:\/[^/]+)?)?)?|list-memberships)$/u, scope: 'lists:write' },
69
+ { methods: ['GET'], pattern: /^\/v1\/handbook_pages(?:\/[^/]+)?$/u, scope: 'handbook:read' },
70
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/handbook_pages(?:\/[^/]+)?$/u, scope: 'handbook:write' },
71
+ { methods: ['GET'], pattern: /^\/v1\/(?:forms(?:\/[^/]+(?:\/(?:submissions(?:\/[^/]+)?|embed))?)?|form-submissions)$/u, scope: 'forms:read' },
72
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/forms(?:\/[^/]+)?$/u, scope: 'forms:write' },
73
+ { methods: ['GET'], pattern: /^\/v1\/activities$/u, scope: 'activities:read' },
74
+ { methods: ['GET'], pattern: /^\/v1\/search$/u, scope: 'search:read' },
75
+ { methods: ['GET'], pattern: /^\/v1\/dashboard$/u, scope: 'dashboard:read' },
76
+ { methods: ['GET'], pattern: /^\/v1\/custom_fields(?:\/[^/]+)?$/u, scope: 'custom_fields:read' },
77
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/custom_fields(?:\/[^/]+)?$/u, scope: 'custom_fields:write' },
78
+ { methods: ['GET'], pattern: /^\/v1\/pipeline_stages(?:\/[^/]+)?$/u, scope: 'pipeline_stages:read' },
79
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/pipeline_stages(?:\/[^/]+)?$/u, scope: 'pipeline_stages:write' },
80
+ { methods: ['GET'], pattern: /^\/v1\/consent_purposes(?:\/[^/]+)?$/u, scope: 'consent_purposes:read' },
81
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/consent_purposes(?:\/[^/]+)?$/u, scope: 'consent_purposes:write' },
82
+ { methods: ['GET'], pattern: /^\/v1\/export(?:\/|$)/u, scope: 'import_export:read' },
83
+ { methods: ['GET'], pattern: /^\/v1\/import\/jobs(?:\/[^/]+)?$/u, scope: 'import_export:read' },
84
+ { methods: ['POST', 'DELETE'], pattern: /^\/v1\/import\/jobs(?:\/[^/]+(?:\/commit)?)?$/u, scope: 'import_export:write' },
85
+ { methods: ['GET'], pattern: /^\/v1\/webhooks(?:\/[^/]+(?:\/deliveries)?)?$/u, scope: 'webhooks:read' },
86
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/webhooks(?:\/[^/]+(?:\/rotate_secret)?)?$/u, scope: 'webhooks:write' },
87
+ { methods: ['GET'], pattern: /^\/v1\/api-keys(?:\/[^/]+)?$/u, scope: 'api_keys:read' },
88
+ { methods: ['POST', 'DELETE'], pattern: /^\/v1\/api-keys(?:\/[^/]+)?$/u, scope: 'api_keys:write' },
89
+ { methods: ['GET'], pattern: /^\/v1\/agent-tasks(?:\/[^/]+(?:\/(?:resolve|run))?)?$/u, scope: 'agent_tasks:read' },
90
+ { methods: ['POST'], pattern: /^\/v1\/agent-tasks\/[^/]+(?:\/(?:resolve|run))?$/u, scope: 'agent_tasks:write' },
91
+ { methods: ['GET'], pattern: /^\/v1\/agent-runs(?:\/[^/]+)?$/u, scope: 'agent_runs:read' },
92
+ { methods: ['GET'], pattern: /^\/v1\/agents(?:\/[^/]+)?$/u, scope: 'agents:read' },
93
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/agents(?:\/[^/]+)?$/u, scope: 'agents:write' },
94
+ { methods: ['GET'], pattern: /^\/v1\/workspaces\/[^/]+$/u, scope: 'workspace:read' },
95
+ { methods: ['PATCH'], pattern: /^\/v1\/workspaces\/[^/]+$/u, scope: 'workspace:write' },
96
+ { methods: ['GET'], pattern: /^\/v1\/workspaces\/[^/]+\/members(?:\/[^/]+)?$/u, scope: 'workspace:read' },
97
+ { methods: ['PATCH', 'DELETE'], pattern: /^\/v1\/workspaces\/[^/]+\/members(?:\/[^/]+)?$/u, scope: 'workspace:write' },
98
+ { methods: ['GET'], pattern: /^\/v1\/workspaces\/[^/]+\/invites(?:\/[^/]+(?:\/resend)?)?$/u, scope: 'workspace:read' },
99
+ { methods: ['POST', 'DELETE'], pattern: /^\/v1\/workspaces\/[^/]+\/invites(?:\/[^/]+(?:\/resend)?)?$/u, scope: 'workspace:write' },
100
+ { methods: ['GET'], pattern: /^\/v1\/workspaces\/[^/]+\/modules(?:\/[^/]+)?$/u, scope: 'modules:read' },
101
+ { methods: ['PATCH'], pattern: /^\/v1\/workspaces\/[^/]+\/modules(?:\/[^/]+)?$/u, scope: 'modules:write' },
102
+ { methods: ['POST'], pattern: /^\/v1\/workspaces\/[^/]+\/sample-data$/u, scope: 'sample_data:write' },
103
+ { methods: ['POST'], pattern: /^\/v1\/workspaces\/[^/]+\/relink-email-domains$/u, scope: 'workspace:write' },
104
+ { methods: ['GET'], pattern: /^\/v1\/mcp\/tools$/u, scope: 'search:read' },
105
+ ]
106
+
107
+ function storedScopes(actor: Actor): readonly ApiKeyScope[] {
108
+ if (actor.kind !== 'api_key') {
109
+ return []
110
+ }
111
+
112
+ return actor.scopes
113
+ }
114
+
115
+ export function hasApiKeyScope(actor: Actor, required: ApiKeyGranularScope): boolean {
116
+ if (actor.kind !== 'api_key') {
117
+ return true
118
+ }
119
+
120
+ return satisfiesApiKeyScope(actor.scopes, required)
121
+ }
122
+
123
+ export function requireApiKeyScope(actor: Actor, required: ApiKeyGranularScope): void {
124
+ if (hasApiKeyScope(actor, required)) {
125
+ return
126
+ }
127
+
128
+ throw new AppError('forbidden', `This API key does not have the ${required} scope`)
129
+ }
130
+
131
+ export function resolveRestScope(method: string, path: string): ApiKeyGranularScope | null {
132
+ if (isExempt(method, path)) {
133
+ return null
134
+ }
135
+
136
+ if (method === 'DELETE' && /^\/v1\/workspaces\/[^/]+$/u.test(path)) {
137
+ return 'workspace:write'
138
+ }
139
+
140
+ for (const rule of ROUTE_SCOPE_RULES) {
141
+ if (rule.methods.includes(method) && rule.pattern.test(path)) {
142
+ return rule.scope
143
+ }
144
+ }
145
+
146
+ return null
147
+ }
148
+
149
+ const MCP_READ_VERBS = new Set(['list', 'get', 'query'])
150
+ const MCP_WRITE_VERBS = new Set(['create', 'update', 'delete', 'commit', 'resolve', 'run', 'rotate_secret', 'install'])
151
+
152
+ /** Maps REST resource segments used in MCP tool names to scope resources. */
153
+ const MCP_RESOURCE_ALIASES: Readonly<Record<string, string>> = {
154
+ handbook_pages: 'handbook',
155
+ plan_items: 'plan_items',
156
+ pipeline_stages: 'pipeline_stages',
157
+ custom_fields: 'custom_fields',
158
+ consent_purposes: 'consent_purposes',
159
+ form_submissions: 'forms',
160
+ list_memberships: 'lists',
161
+ agent_tasks: 'agent_tasks',
162
+ agent_runs: 'agent_runs',
163
+ import_jobs: 'import_export',
164
+ export: 'import_export',
165
+ }
166
+
167
+ export function resolveMcpScope(toolName: string): ApiKeyGranularScope | null {
168
+ if (toolName === 'search_query') {
169
+ return 'search:read'
170
+ }
171
+
172
+ const match = /^([a-z0-9_]+)_(list|get|create|update|delete|query|commit|resolve|run|rotate_secret|install)$/u.exec(
173
+ toolName,
174
+ )
175
+
176
+ if (match === null) {
177
+ const parts = toolName.split('_')
178
+ const verb = parts.at(-1)
179
+
180
+ if (verb === undefined) {
181
+ return null
182
+ }
183
+
184
+ const resource = MCP_RESOURCE_ALIASES[parts.slice(0, -1).join('_')] ?? parts.slice(0, -1).join('_')
185
+
186
+ if (MCP_READ_VERBS.has(verb)) {
187
+ return `${resource}:read` as ApiKeyGranularScope
188
+ }
189
+
190
+ if (MCP_WRITE_VERBS.has(verb)) {
191
+ return `${resource}:write` as ApiKeyGranularScope
192
+ }
193
+
194
+ return null
195
+ }
196
+
197
+ const [, rawResource, verb] = match
198
+ const resource = MCP_RESOURCE_ALIASES[rawResource ?? ''] ?? rawResource ?? ''
199
+
200
+ if (MCP_READ_VERBS.has(verb ?? '')) {
201
+ return `${resource}:read` as ApiKeyGranularScope
202
+ }
203
+
204
+ if (MCP_WRITE_VERBS.has(verb ?? '')) {
205
+ return `${resource}:write` as ApiKeyGranularScope
206
+ }
207
+
208
+ return null
209
+ }
210
+
211
+ export function actorHasStoredScopes(actor: Actor): boolean {
212
+ return storedScopes(actor).length > 0
213
+ }
package/src/lib/errors.ts CHANGED
@@ -83,6 +83,36 @@ export class AppError extends Error {
83
83
  }
84
84
  }
85
85
 
86
+ /**
87
+ * Why core refused an identity a module handed to `completeExternalSignIn`.
88
+ *
89
+ * A reason rather than a message, so a provider module can branch on it (to
90
+ * redirect somewhere useful, say) without matching on English text.
91
+ */
92
+ export type ExternalSignInRefusal = 'email_unverified' | 'unknown_identity'
93
+
94
+ /**
95
+ * An external sign-in core would not complete.
96
+ *
97
+ * An `AppError`, so an uncaught one still renders as the `api.md` body. No new
98
+ * `ErrorCode`: refusing an unverified identity is an ordinary `401`, and
99
+ * refusing to provision is an ordinary `403`.
100
+ */
101
+ export class ExternalSignInError extends AppError {
102
+ readonly reason: ExternalSignInRefusal
103
+
104
+ constructor(reason: ExternalSignInRefusal) {
105
+ super(
106
+ reason === 'email_unverified' ? 'unauthorized' : 'forbidden',
107
+ reason === 'email_unverified'
108
+ ? 'The identity provider did not verify this email address'
109
+ : 'No account exists for this identity',
110
+ )
111
+ this.name = 'ExternalSignInError'
112
+ this.reason = reason
113
+ }
114
+ }
115
+
86
116
  /**
87
117
  * Renders an `AppError` into the wire body from `api.md`. `details` is omitted
88
118
  * rather than sent as null when the error carries none.
@@ -193,3 +193,27 @@ export async function deleteForTarget(
193
193
 
194
194
  return deleted.length
195
195
  }
196
+
197
+ /** Moves every activity from one target to another inside a conversion transaction. */
198
+ export async function repointForTarget(
199
+ db: Queryable,
200
+ workspaceId: string,
201
+ fromType: string,
202
+ fromId: string,
203
+ toType: string,
204
+ toId: string,
205
+ ): Promise<number> {
206
+ const updated = await db
207
+ .update(activities)
208
+ .set({ targetType: toType, targetId: toId })
209
+ .where(
210
+ and(
211
+ eq(activities.workspaceId, workspaceId),
212
+ eq(activities.targetType, fromType),
213
+ eq(activities.targetId, fromId),
214
+ ),
215
+ )
216
+ .returning({ id: activities.id })
217
+
218
+ return updated.length
219
+ }
@@ -150,3 +150,27 @@ export async function updateRun(
150
150
  ): Promise<void> {
151
151
  await db.update(agentRuns).set(changes).where(eq(agentRuns.id, id))
152
152
  }
153
+
154
+ /** Moves every agent run from one target to another inside a conversion transaction. */
155
+ export async function repointForTarget(
156
+ db: Queryable,
157
+ workspaceId: string,
158
+ fromType: string,
159
+ fromId: string,
160
+ toType: string,
161
+ toId: string,
162
+ ): Promise<number> {
163
+ const updated = await db
164
+ .update(agentRuns)
165
+ .set({ targetType: toType, targetId: toId })
166
+ .where(
167
+ and(
168
+ eq(agentRuns.workspaceId, workspaceId),
169
+ eq(agentRuns.targetType, fromType),
170
+ eq(agentRuns.targetId, fromId),
171
+ ),
172
+ )
173
+ .returning({ id: agentRuns.id })
174
+
175
+ return updated.length
176
+ }
@@ -1,3 +1,5 @@
1
+ import type { ApiKeyScope } from '@kelpie/schemas'
2
+ import { API_KEY_SCOPES } from '@kelpie/schemas'
1
3
  import type { Context, Hono } from 'hono'
2
4
  import { z } from 'zod'
3
5
 
@@ -13,6 +15,7 @@ import type { ApiKeyService, ApiKeyView, MintedApiKey } from './service.ts'
13
15
  const createBody = z.object({
14
16
  name: z.string().min(1),
15
17
  kind: z.enum(KEY_KINDS),
18
+ scopes: z.array(z.enum(API_KEY_SCOPES)).optional(),
16
19
  })
17
20
 
18
21
  const listQuery = z.object({ kind: z.enum(KEY_KINDS) })
@@ -39,6 +42,7 @@ function keyResponse(key: ApiKeyView): Record<string, unknown> {
39
42
  id: key.id,
40
43
  name: key.name,
41
44
  kind: key.kind,
45
+ scopes: key.scopes,
42
46
  display_prefix: key.displayPrefix,
43
47
  last_used_at: key.lastUsedAt === null ? null : key.lastUsedAt.toISOString(),
44
48
  created_at: key.createdAt.toISOString(),
@@ -55,6 +59,7 @@ export function mountApiKeyRoutes(router: Hono, dependencies: ApiKeyRoutesDepend
55
59
  await requireActor(context),
56
60
  body.name,
57
61
  body.kind,
62
+ body.scopes as ApiKeyScope[] | undefined,
58
63
  )
59
64
 
60
65
  return context.json({ ...keyResponse(minted), secret: minted.secret }, 201)
@@ -20,6 +20,8 @@ export const apiKeys = pgTable(
20
20
  name: text('name').notNull(),
21
21
  secretHash: text('secret_hash').notNull().unique(),
22
22
  displayPrefix: text('display_prefix').notNull(),
23
+ /** Empty array means full access. Presets and granular tokens may be mixed. */
24
+ scopes: text('scopes').array().notNull().default([]),
23
25
  lastUsedAt: moment('last_used_at'),
24
26
  createdAt: createdAt(),
25
27
  updatedAt: updatedAt(),
@@ -0,0 +1,30 @@
1
+ import type { MiddlewareHandler } from 'hono'
2
+
3
+ import { requireApiKeyScope, resolveRestScope } from '../../lib/apiKeyScopes.ts'
4
+ import { resolveActorFrom } from '../auth/credentials.ts'
5
+ import type { CredentialDependencies } from '../auth/credentials.ts'
6
+
7
+ /**
8
+ * Blocks API-key requests that lack the scope a route needs. Sessions pass
9
+ * through unchanged.
10
+ */
11
+ export function createApiKeyScopeMiddleware(
12
+ dependencies: CredentialDependencies,
13
+ ): MiddlewareHandler {
14
+ return async (context, next) => {
15
+ const required = resolveRestScope(context.req.method, context.req.path)
16
+
17
+ if (required === null) {
18
+ await next()
19
+ return
20
+ }
21
+
22
+ const actor = await resolveActorFrom(dependencies, context)
23
+
24
+ if (actor.kind === 'api_key') {
25
+ requireApiKeyScope(actor, required)
26
+ }
27
+
28
+ await next()
29
+ }
30
+ }
@@ -1,5 +1,9 @@
1
+ import type { ApiKeyScope } from '@kelpie/schemas'
2
+ import { API_KEY_SCOPES, dedupeApiKeyScopes } from '@kelpie/schemas'
3
+
1
4
  import type { Database } from '../../lib/database.ts'
2
5
  import { AppError } from '../../lib/errors.ts'
6
+ import { hasApiKeyScope } from '../../lib/apiKeyScopes.ts'
3
7
  import type { IdFactory } from '../../lib/ids.ts'
4
8
  import type { Actor } from '../auth/actor.ts'
5
9
  import { roleAllows } from '../workspace/roles.ts'
@@ -27,6 +31,7 @@ export interface ApiKeyView {
27
31
  readonly id: string
28
32
  readonly name: string
29
33
  readonly kind: KeyKind
34
+ readonly scopes: readonly ApiKeyScope[]
30
35
  readonly displayPrefix: string
31
36
  readonly lastUsedAt: Date | null
32
37
  readonly createdAt: Date
@@ -38,7 +43,7 @@ export interface MintedApiKey extends ApiKeyView {
38
43
  }
39
44
 
40
45
  export interface ApiKeyService {
41
- create(actor: Actor, name: string, kind: KeyKind): Promise<MintedApiKey>
46
+ create(actor: Actor, name: string, kind: KeyKind, scopes?: readonly ApiKeyScope[]): Promise<MintedApiKey>
42
47
  list(actor: Actor, kind: KeyKind): Promise<readonly ApiKeyView[]>
43
48
  revoke(actor: Actor, id: string): Promise<void>
44
49
  }
@@ -48,12 +53,31 @@ function toView(record: repository.ApiKeyRecord): ApiKeyView {
48
53
  id: record.id,
49
54
  name: record.name,
50
55
  kind: record.userId === null ? 'workspace' : 'personal',
56
+ scopes: record.scopes as ApiKeyScope[],
51
57
  displayPrefix: record.displayPrefix,
52
58
  lastUsedAt: record.lastUsedAt,
53
59
  createdAt: record.createdAt,
54
60
  }
55
61
  }
56
62
 
63
+ function parseScopes(scopes: readonly ApiKeyScope[] | undefined): ApiKeyScope[] {
64
+ if (scopes === undefined || scopes.length === 0) {
65
+ return []
66
+ }
67
+
68
+ const deduped = dedupeApiKeyScopes(scopes)
69
+ const allowed = new Set<string>(API_KEY_SCOPES)
70
+ const invalid = deduped.filter((scope) => !allowed.has(scope))
71
+
72
+ if (invalid.length > 0) {
73
+ throw AppError.validationFailed('One or more scopes are not valid', [
74
+ { field: 'scopes', message: `Unknown scopes: ${invalid.join(', ')}` },
75
+ ])
76
+ }
77
+
78
+ return deduped
79
+ }
80
+
57
81
  export function createApiKeyService(dependencies: ApiKeyDependencies): ApiKeyService {
58
82
  /**
59
83
  * Keys are always workspace-bound, so an actor with no workspace cannot have
@@ -92,14 +116,19 @@ export function createApiKeyService(dependencies: ApiKeyDependencies): ApiKeySer
92
116
  }
93
117
 
94
118
  return {
95
- async create(actor, name, kind) {
119
+ async create(actor, name, kind, scopes) {
96
120
  const workspaceId = requireWorkspace(actor)
97
121
  const userId = kind === 'workspace' ? null : requireUser(actor)
122
+ const storedScopes = parseScopes(scopes)
98
123
 
99
124
  if (kind === 'workspace') {
100
125
  requireAdmin(actor)
101
126
  }
102
127
 
128
+ if (actor.kind === 'api_key' && !hasApiKeyScope(actor, 'api_keys:write')) {
129
+ throw new AppError('forbidden', 'This API key does not have the api_keys:write scope')
130
+ }
131
+
103
132
  const minted = mintKey(kind, dependencies.newToken)
104
133
  const record = await repository.insertApiKey(dependencies.db, {
105
134
  id: dependencies.createId('apiKey'),
@@ -108,6 +137,7 @@ export function createApiKeyService(dependencies: ApiKeyDependencies): ApiKeySer
108
137
  name,
109
138
  secretHash: minted.secretHash,
110
139
  displayPrefix: minted.displayPrefix,
140
+ scopes: storedScopes,
111
141
  })
112
142
 
113
143
  // The only time the secret leaves this process. Nothing stores it.
@@ -146,6 +176,10 @@ export function createApiKeyService(dependencies: ApiKeyDependencies): ApiKeySer
146
176
  throw AppError.notFound('API key not found')
147
177
  }
148
178
 
179
+ if (actor.kind === 'api_key' && !hasApiKeyScope(actor, 'api_keys:write')) {
180
+ throw new AppError('forbidden', 'This API key does not have the api_keys:write scope')
181
+ }
182
+
149
183
  await repository.deleteApiKey(dependencies.db, workspaceId, id)
150
184
  },
151
185
  }
@@ -1,6 +1,8 @@
1
1
  import { getCookie } from 'hono/cookie'
2
2
  import type { Context } from 'hono'
3
3
 
4
+ import type { ApiKeyScope } from '@kelpie/schemas'
5
+
4
6
  import type { Database } from '../../lib/database.ts'
5
7
  import { AppError } from '../../lib/errors.ts'
6
8
  import { hashToken } from '../../lib/tokens.ts'
@@ -100,6 +102,7 @@ async function resolveApiKeyActor(
100
102
  userId: null,
101
103
  workspaceId: record.workspaceId,
102
104
  role: WORKSPACE_KEY_ROLE,
105
+ scopes: record.scopes as ApiKeyScope[],
103
106
  memberId: null,
104
107
  }
105
108
  }
@@ -118,6 +121,7 @@ async function resolveApiKeyActor(
118
121
  userId: record.userId,
119
122
  workspaceId: record.workspaceId,
120
123
  role: roleFromMembership(membership.role),
124
+ scopes: record.scopes as ApiKeyScope[],
121
125
  memberId: membership.id,
122
126
  }
123
127
  }