@bevel-software/platform-core-backend 0.26.0 → 0.28.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 (681) hide show
  1. package/agent-guide/access-control.md +17 -0
  2. package/agent-guide/conventions.md +8 -0
  3. package/agent-guide/html-views.md +83 -0
  4. package/agent-guide/new-knowledge-base.md +24 -0
  5. package/agent-guide/tool-manuals.md +10 -0
  6. package/dist/assets.d.ts +19 -0
  7. package/dist/assets.d.ts.map +1 -1
  8. package/dist/assets.js +26 -2
  9. package/dist/assets.js.map +1 -1
  10. package/dist/core/catalog-revision.d.ts +19 -1
  11. package/dist/core/catalog-revision.d.ts.map +1 -1
  12. package/dist/core/catalog-revision.js +14 -3
  13. package/dist/core/catalog-revision.js.map +1 -1
  14. package/dist/core/create-core-server.d.ts +5 -0
  15. package/dist/core/create-core-server.d.ts.map +1 -1
  16. package/dist/core/create-core-server.js +56 -5
  17. package/dist/core/create-core-server.js.map +1 -1
  18. package/dist/core/create-core-services.d.ts +33 -0
  19. package/dist/core/create-core-services.d.ts.map +1 -1
  20. package/dist/core/create-core-services.js +105 -8
  21. package/dist/core/create-core-services.js.map +1 -1
  22. package/dist/core/lifecycle.d.ts +16 -1
  23. package/dist/core/lifecycle.d.ts.map +1 -1
  24. package/dist/core/lifecycle.js +18 -1
  25. package/dist/core/lifecycle.js.map +1 -1
  26. package/dist/core-config.d.ts +21 -0
  27. package/dist/core-config.d.ts.map +1 -1
  28. package/dist/core-config.js +22 -1
  29. package/dist/core-config.js.map +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/modules/access/access-control.interface.d.ts +17 -0
  35. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  36. package/dist/modules/access/access-control.service.d.ts +2 -0
  37. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  38. package/dist/modules/access/access-control.service.js +31 -0
  39. package/dist/modules/access/access-control.service.js.map +1 -1
  40. package/dist/modules/access/access.routes.d.ts +4 -1
  41. package/dist/modules/access/access.routes.d.ts.map +1 -1
  42. package/dist/modules/access/access.routes.js +66 -7
  43. package/dist/modules/access/access.routes.js.map +1 -1
  44. package/dist/modules/access/directory-sync-bot.js +1 -1
  45. package/dist/modules/access/directory-sync-bot.js.map +1 -1
  46. package/dist/modules/access/roles-admin.service.d.ts +35 -6
  47. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  48. package/dist/modules/access/roles-admin.service.js +36 -9
  49. package/dist/modules/access/roles-admin.service.js.map +1 -1
  50. package/dist/modules/access/user-access-removal.service.js +1 -1
  51. package/dist/modules/access/user-access-removal.service.js.map +1 -1
  52. package/dist/modules/agent-guide/agent-guide.d.ts +6 -0
  53. package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
  54. package/dist/modules/agent-guide/agent-guide.js +8 -0
  55. package/dist/modules/agent-guide/agent-guide.js.map +1 -1
  56. package/dist/modules/agent-guide/index.d.ts +1 -1
  57. package/dist/modules/agent-guide/index.d.ts.map +1 -1
  58. package/dist/modules/agent-guide/index.js +1 -1
  59. package/dist/modules/agent-guide/index.js.map +1 -1
  60. package/dist/modules/agent-instructions/compose.d.ts +5 -0
  61. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  62. package/dist/modules/agent-instructions/compose.js +19 -2
  63. package/dist/modules/agent-instructions/compose.js.map +1 -1
  64. package/dist/modules/agent-instructions/shared-file-rules.js +19 -19
  65. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  66. package/dist/modules/audit/agent-audit.service.d.ts +21 -2
  67. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  68. package/dist/modules/audit/agent-audit.service.js +43 -2
  69. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  70. package/dist/modules/audit/agent-connection.routes.d.ts +34 -0
  71. package/dist/modules/audit/agent-connection.routes.d.ts.map +1 -0
  72. package/dist/modules/audit/agent-connection.routes.js +38 -0
  73. package/dist/modules/audit/agent-connection.routes.js.map +1 -0
  74. package/dist/modules/audit/audit.contract.d.ts +24 -0
  75. package/dist/modules/audit/audit.contract.d.ts.map +1 -1
  76. package/dist/modules/audit/audit.contract.js.map +1 -1
  77. package/dist/modules/audit/index.d.ts +1 -0
  78. package/dist/modules/audit/index.d.ts.map +1 -1
  79. package/dist/modules/audit/index.js +1 -0
  80. package/dist/modules/audit/index.js.map +1 -1
  81. package/dist/modules/auth/account-hooks.d.ts +46 -0
  82. package/dist/modules/auth/account-hooks.d.ts.map +1 -0
  83. package/dist/modules/auth/account-hooks.js +25 -0
  84. package/dist/modules/auth/account-hooks.js.map +1 -0
  85. package/dist/modules/auth/account-switch-guard.d.ts +15 -0
  86. package/dist/modules/auth/account-switch-guard.d.ts.map +1 -0
  87. package/dist/modules/auth/account-switch-guard.js +2 -0
  88. package/dist/modules/auth/account-switch-guard.js.map +1 -0
  89. package/dist/modules/auth/account-switch-lock.d.ts +34 -0
  90. package/dist/modules/auth/account-switch-lock.d.ts.map +1 -0
  91. package/dist/modules/auth/account-switch-lock.js +48 -0
  92. package/dist/modules/auth/account-switch-lock.js.map +1 -0
  93. package/dist/modules/auth/account.routes.d.ts +4 -1
  94. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  95. package/dist/modules/auth/account.routes.js +110 -13
  96. package/dist/modules/auth/account.routes.js.map +1 -1
  97. package/dist/modules/auth/auth.middleware.d.ts +7 -0
  98. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  99. package/dist/modules/auth/auth.middleware.js +1 -1
  100. package/dist/modules/auth/auth.middleware.js.map +1 -1
  101. package/dist/modules/auth/auth.service.d.ts +101 -6
  102. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  103. package/dist/modules/auth/auth.service.js +246 -51
  104. package/dist/modules/auth/auth.service.js.map +1 -1
  105. package/dist/modules/auth/password-hash.d.ts +5 -2
  106. package/dist/modules/auth/password-hash.d.ts.map +1 -1
  107. package/dist/modules/auth/password-hash.js +5 -2
  108. package/dist/modules/auth/password-hash.js.map +1 -1
  109. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  110. package/dist/modules/code-mode/code-mode.tool.js +11 -3
  111. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  112. package/dist/modules/database/core-schema.d.ts +96 -0
  113. package/dist/modules/database/core-schema.d.ts.map +1 -1
  114. package/dist/modules/database/core-schema.js +32 -3
  115. package/dist/modules/database/core-schema.js.map +1 -1
  116. package/dist/modules/database/pg-error.d.ts +9 -0
  117. package/dist/modules/database/pg-error.d.ts.map +1 -0
  118. package/dist/modules/database/pg-error.js +17 -0
  119. package/dist/modules/database/pg-error.js.map +1 -0
  120. package/dist/modules/embed/account-link.service.d.ts +42 -0
  121. package/dist/modules/embed/account-link.service.d.ts.map +1 -0
  122. package/dist/modules/embed/account-link.service.js +50 -0
  123. package/dist/modules/embed/account-link.service.js.map +1 -0
  124. package/dist/modules/embed/embed-link.d.ts +58 -0
  125. package/dist/modules/embed/embed-link.d.ts.map +1 -0
  126. package/dist/modules/embed/embed-link.js +160 -0
  127. package/dist/modules/embed/embed-link.js.map +1 -0
  128. package/dist/modules/embed/embed.errors.d.ts +30 -0
  129. package/dist/modules/embed/embed.errors.d.ts.map +1 -0
  130. package/dist/modules/embed/embed.errors.js +42 -0
  131. package/dist/modules/embed/embed.errors.js.map +1 -0
  132. package/dist/modules/embed/embed.interface.d.ts +204 -0
  133. package/dist/modules/embed/embed.interface.d.ts.map +1 -0
  134. package/dist/modules/embed/embed.interface.js +2 -0
  135. package/dist/modules/embed/embed.interface.js.map +1 -0
  136. package/dist/modules/embed/embed.routes.d.ts +59 -0
  137. package/dist/modules/embed/embed.routes.d.ts.map +1 -0
  138. package/dist/modules/embed/embed.routes.js +343 -0
  139. package/dist/modules/embed/embed.routes.js.map +1 -0
  140. package/dist/modules/embed/embed.service.d.ts +169 -0
  141. package/dist/modules/embed/embed.service.d.ts.map +1 -0
  142. package/dist/modules/embed/embed.service.js +627 -0
  143. package/dist/modules/embed/embed.service.js.map +1 -0
  144. package/dist/modules/embed/embed.tools.d.ts +51 -0
  145. package/dist/modules/embed/embed.tools.d.ts.map +1 -0
  146. package/dist/modules/embed/embed.tools.js +170 -0
  147. package/dist/modules/embed/embed.tools.js.map +1 -0
  148. package/dist/modules/embed/mcp-app.d.ts +64 -0
  149. package/dist/modules/embed/mcp-app.d.ts.map +1 -0
  150. package/dist/modules/embed/mcp-app.js +147 -0
  151. package/dist/modules/embed/mcp-app.js.map +1 -0
  152. package/dist/modules/embed/mcp-app.routes.d.ts +21 -0
  153. package/dist/modules/embed/mcp-app.routes.d.ts.map +1 -0
  154. package/dist/modules/embed/mcp-app.routes.js +36 -0
  155. package/dist/modules/embed/mcp-app.routes.js.map +1 -0
  156. package/dist/modules/github-app/github-app.connection.d.ts +15 -0
  157. package/dist/modules/github-app/github-app.connection.d.ts.map +1 -1
  158. package/dist/modules/github-app/github-app.connection.js +34 -3
  159. package/dist/modules/github-app/github-app.connection.js.map +1 -1
  160. package/dist/modules/kb-fs/locking-filesystem.d.ts +65 -0
  161. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  162. package/dist/modules/kb-fs/locking-filesystem.js +300 -0
  163. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  164. package/dist/modules/kb-sync/kb-sync.interface.d.ts +5 -0
  165. package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
  166. package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
  167. package/dist/modules/kb-sync/kb-sync.service.js +6 -0
  168. package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
  169. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  170. package/dist/modules/mcp/mcp-auth.middleware.js +2 -1
  171. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  172. package/dist/modules/mcp/mcp.service.d.ts +28 -1
  173. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  174. package/dist/modules/mcp/mcp.service.js +64 -8
  175. package/dist/modules/mcp/mcp.service.js.map +1 -1
  176. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -0
  177. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  178. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +32 -4
  179. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  180. package/dist/modules/onboarding/onboarding.contract.d.ts +80 -0
  181. package/dist/modules/onboarding/onboarding.contract.d.ts.map +1 -0
  182. package/dist/modules/onboarding/onboarding.contract.js +7 -0
  183. package/dist/modules/onboarding/onboarding.contract.js.map +1 -0
  184. package/dist/modules/onboarding/onboarding.routes.d.ts +18 -0
  185. package/dist/modules/onboarding/onboarding.routes.d.ts.map +1 -0
  186. package/dist/modules/onboarding/onboarding.routes.js +81 -0
  187. package/dist/modules/onboarding/onboarding.routes.js.map +1 -0
  188. package/dist/modules/onboarding/starter-pack.service.d.ts +213 -0
  189. package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -0
  190. package/dist/modules/onboarding/starter-pack.service.js +492 -0
  191. package/dist/modules/onboarding/starter-pack.service.js.map +1 -0
  192. package/dist/modules/onboarding/starter-packs.d.ts +78 -0
  193. package/dist/modules/onboarding/starter-packs.d.ts.map +1 -0
  194. package/dist/modules/onboarding/starter-packs.js +156 -0
  195. package/dist/modules/onboarding/starter-packs.js.map +1 -0
  196. package/dist/modules/plugins/index.d.ts +1 -1
  197. package/dist/modules/plugins/index.d.ts.map +1 -1
  198. package/dist/modules/plugins/index.js +1 -1
  199. package/dist/modules/plugins/index.js.map +1 -1
  200. package/dist/modules/plugins/plugin-provision.service.d.ts +19 -0
  201. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
  202. package/dist/modules/plugins/plugin-provision.service.js +18 -1
  203. package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
  204. package/dist/modules/plugins/plugins.routes.d.ts +0 -36
  205. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  206. package/dist/modules/plugins/plugins.routes.js +31 -0
  207. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  208. package/dist/modules/settings/deployment-settings.service.d.ts +34 -0
  209. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  210. package/dist/modules/settings/deployment-settings.service.js +134 -1
  211. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  212. package/dist/modules/settings/repository-source.d.ts +1 -0
  213. package/dist/modules/settings/repository-source.d.ts.map +1 -1
  214. package/dist/modules/settings/repository-source.js +3 -3
  215. package/dist/modules/settings/repository-source.js.map +1 -1
  216. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  217. package/dist/modules/skills/skills.tools.js +50 -32
  218. package/dist/modules/skills/skills.tools.js.map +1 -1
  219. package/dist/modules/tool-auth/connection-key-rejection.d.ts +10 -1
  220. package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -1
  221. package/dist/modules/tool-auth/connection-key-rejection.js +11 -3
  222. package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -1
  223. package/dist/modules/tool-auth/external-api-key.interface.d.ts +8 -0
  224. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  225. package/dist/modules/tool-auth/external-api-key.service.d.ts +24 -2
  226. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  227. package/dist/modules/tool-auth/external-api-key.service.js +59 -2
  228. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  229. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  230. package/dist/modules/tool-auth/tool-auth.middleware.js +9 -4
  231. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  232. package/dist/modules/tool-helpers/index.d.ts +28 -1
  233. package/dist/modules/tool-helpers/index.d.ts.map +1 -1
  234. package/dist/modules/tool-helpers/index.js +28 -1
  235. package/dist/modules/tool-helpers/index.js.map +1 -1
  236. package/dist/modules/tool-helpers/route-argument-check.d.ts +10 -0
  237. package/dist/modules/tool-helpers/route-argument-check.d.ts.map +1 -0
  238. package/dist/modules/tool-helpers/route-argument-check.js +132 -0
  239. package/dist/modules/tool-helpers/route-argument-check.js.map +1 -0
  240. package/dist/modules/tool-helpers/route-tool-schemas.d.ts +89 -0
  241. package/dist/modules/tool-helpers/route-tool-schemas.d.ts.map +1 -0
  242. package/dist/modules/tool-helpers/route-tool-schemas.js +113 -0
  243. package/dist/modules/tool-helpers/route-tool-schemas.js.map +1 -0
  244. package/dist/modules/tool-helpers/tool-def.d.ts +71 -0
  245. package/dist/modules/tool-helpers/tool-def.d.ts.map +1 -1
  246. package/dist/modules/tool-helpers/tool-def.js +70 -6
  247. package/dist/modules/tool-helpers/tool-def.js.map +1 -1
  248. package/dist/modules/tool-helpers/tool-handler.d.ts +46 -6
  249. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  250. package/dist/modules/tool-helpers/tool-handler.js +157 -8
  251. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  252. package/dist/modules/tool-helpers/tool.contract.d.ts +9 -0
  253. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  254. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  255. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  256. package/dist/modules/tool-manuals/tool-manuals.tools.js +14 -5
  257. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  258. package/dist/modules/tool-registry/description-length.d.ts +6 -2
  259. package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
  260. package/dist/modules/tool-registry/description-length.js +27 -5
  261. package/dist/modules/tool-registry/description-length.js.map +1 -1
  262. package/dist/modules/tool-registry/guide-first.d.ts +4 -0
  263. package/dist/modules/tool-registry/guide-first.d.ts.map +1 -1
  264. package/dist/modules/tool-registry/guide-first.js +9 -1
  265. package/dist/modules/tool-registry/guide-first.js.map +1 -1
  266. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +53 -0
  267. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -1
  268. package/dist/modules/workflow/agent-tools/change-request-read.tools.js +97 -68
  269. package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -1
  270. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +8 -1
  271. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  272. package/dist/modules/workflow/agent-tools/workflow.tools.js +124 -37
  273. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  274. package/dist/modules/workflow/file-lock.service.d.ts +29 -5
  275. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  276. package/dist/modules/workflow/file-lock.service.js +62 -5
  277. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  278. package/dist/modules/workflow/git/git.service.d.ts +72 -1
  279. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  280. package/dist/modules/workflow/git/git.service.js +172 -36
  281. package/dist/modules/workflow/git/git.service.js.map +1 -1
  282. package/dist/modules/workflow/git/node-git-runner.d.ts +15 -0
  283. package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -1
  284. package/dist/modules/workflow/git/node-git-runner.js +78 -6
  285. package/dist/modules/workflow/git/node-git-runner.js.map +1 -1
  286. package/dist/modules/workflow/pending-commits.service.d.ts +15 -0
  287. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  288. package/dist/modules/workflow/pending-commits.service.js +25 -1
  289. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  290. package/dist/modules/workflow/pending-commits.worker.d.ts +6 -1
  291. package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
  292. package/dist/modules/workflow/pending-commits.worker.js +23 -6
  293. package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
  294. package/dist/modules/workflow/recovery-bot.js +1 -1
  295. package/dist/modules/workflow/recovery-bot.js.map +1 -1
  296. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  297. package/dist/modules/workflow/review-workflow/review-workflow.service.js +2 -1
  298. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  299. package/dist/modules/workflow/sanitize-error.d.ts +16 -0
  300. package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
  301. package/dist/modules/workflow/sanitize-error.js +45 -0
  302. package/dist/modules/workflow/sanitize-error.js.map +1 -1
  303. package/dist/modules/workflow/workflow.service.d.ts +183 -15
  304. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  305. package/dist/modules/workflow/workflow.service.js +807 -107
  306. package/dist/modules/workflow/workflow.service.js.map +1 -1
  307. package/dist/modules/workspace/agent-download.builder.d.ts +105 -0
  308. package/dist/modules/workspace/agent-download.builder.d.ts.map +1 -0
  309. package/dist/modules/workspace/agent-download.builder.js +238 -0
  310. package/dist/modules/workspace/agent-download.builder.js.map +1 -0
  311. package/dist/modules/workspace/agent-download.routes.d.ts +77 -0
  312. package/dist/modules/workspace/agent-download.routes.d.ts.map +1 -0
  313. package/dist/modules/workspace/agent-download.routes.js +156 -0
  314. package/dist/modules/workspace/agent-download.routes.js.map +1 -0
  315. package/dist/modules/workspace/agent-download.store.d.ts +225 -0
  316. package/dist/modules/workspace/agent-download.store.d.ts.map +1 -0
  317. package/dist/modules/workspace/agent-download.store.js +423 -0
  318. package/dist/modules/workspace/agent-download.store.js.map +1 -0
  319. package/dist/modules/workspace/agent-upload.store.d.ts +10 -1
  320. package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -1
  321. package/dist/modules/workspace/agent-upload.store.js +16 -6
  322. package/dist/modules/workspace/agent-upload.store.js.map +1 -1
  323. package/dist/modules/workspace/file-readers/raw-mime.d.ts +6 -0
  324. package/dist/modules/workspace/file-readers/raw-mime.d.ts.map +1 -0
  325. package/dist/modules/workspace/file-readers/raw-mime.js +38 -0
  326. package/dist/modules/workspace/file-readers/raw-mime.js.map +1 -0
  327. package/dist/modules/workspace/file-readers/text-reader.d.ts +5 -5
  328. package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
  329. package/dist/modules/workspace/file-readers/text-reader.js +5 -8
  330. package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
  331. package/dist/modules/workspace/first-run.d.ts +111 -0
  332. package/dist/modules/workspace/first-run.d.ts.map +1 -0
  333. package/dist/modules/workspace/first-run.js +181 -0
  334. package/dist/modules/workspace/first-run.js.map +1 -0
  335. package/dist/modules/workspace/move-links.d.ts +92 -0
  336. package/dist/modules/workspace/move-links.d.ts.map +1 -0
  337. package/dist/modules/workspace/move-links.js +173 -0
  338. package/dist/modules/workspace/move-links.js.map +1 -0
  339. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  340. package/dist/modules/workspace/startup/steps/seed-tree.js +3 -13
  341. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  342. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  343. package/dist/modules/workspace/workspace.routes.js +63 -22
  344. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  345. package/dist/modules/workspace/workspace.service.d.ts +75 -2
  346. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  347. package/dist/modules/workspace/workspace.service.js +197 -24
  348. package/dist/modules/workspace/workspace.service.js.map +1 -1
  349. package/dist/modules/workspace/workspace.tools.d.ts +59 -1
  350. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  351. package/dist/modules/workspace/workspace.tools.js +443 -65
  352. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  353. package/dist/modules/write-access/write-access.d.ts.map +1 -1
  354. package/dist/modules/write-access/write-access.js +9 -0
  355. package/dist/modules/write-access/write-access.js.map +1 -1
  356. package/dist/shared/domain-errors.d.ts +73 -1
  357. package/dist/shared/domain-errors.d.ts.map +1 -1
  358. package/dist/shared/domain-errors.js +86 -12
  359. package/dist/shared/domain-errors.js.map +1 -1
  360. package/dist/shared/git.contract.d.ts +10 -1
  361. package/dist/shared/git.contract.d.ts.map +1 -1
  362. package/dist/shared/git.contract.js.map +1 -1
  363. package/dist/shared/utf8-text.d.ts +15 -0
  364. package/dist/shared/utf8-text.d.ts.map +1 -0
  365. package/dist/shared/utf8-text.js +20 -0
  366. package/dist/shared/utf8-text.js.map +1 -0
  367. package/dist/tenancy/static-tenant-source.d.ts +2 -1
  368. package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
  369. package/dist/tenancy/static-tenant-source.js +9 -2
  370. package/dist/tenancy/static-tenant-source.js.map +1 -1
  371. package/mcp-app/page.html +349 -0
  372. package/migrations/0017_atlassian_account_links.sql +37 -0
  373. package/migrations/meta/0017_snapshot.json +2396 -0
  374. package/migrations/meta/_journal.json +8 -1
  375. package/package.json +5 -3
  376. package/src/__tests__/packaged-assets-ship.test.ts +16 -4
  377. package/src/__tests__/pg-scratch.ts +48 -0
  378. package/src/assets.ts +28 -2
  379. package/src/core/__tests__/catalog-revision.test.ts +40 -0
  380. package/src/core/__tests__/gated-boot.test.ts +1 -0
  381. package/src/core/__tests__/lifecycle.test.ts +14 -0
  382. package/src/core/catalog-revision.ts +15 -2
  383. package/src/core/create-core-server.ts +69 -3
  384. package/src/core/create-core-services.ts +151 -6
  385. package/src/core/lifecycle.ts +28 -2
  386. package/src/core-config.ts +24 -1
  387. package/src/index.ts +2 -2
  388. package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +1 -0
  389. package/src/modules/access/__tests__/access-control.service.test.ts +19 -0
  390. package/src/modules/access/__tests__/access.routes.admins.test.ts +203 -0
  391. package/src/modules/access/__tests__/access.routes.batch.test.ts +25 -5
  392. package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +1 -0
  393. package/src/modules/access/__tests__/access.routes.group-grant.test.ts +1 -1
  394. package/src/modules/access/__tests__/access.routes.overrides.test.ts +1 -1
  395. package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +1 -0
  396. package/src/modules/access/__tests__/access.routes.prospective.test.ts +1 -0
  397. package/src/modules/access/__tests__/access.routes.revoke.test.ts +1 -1
  398. package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +1 -0
  399. package/src/modules/access/__tests__/roles-admin.security.test.ts +1 -1
  400. package/src/modules/access/__tests__/roles-admin.service.test.ts +15 -2
  401. package/src/modules/access/__tests__/roles.routes.test.ts +132 -7
  402. package/src/modules/access/__tests__/user-access-removal.service.test.ts +2 -2
  403. package/src/modules/access/__tests__/users-db-double.ts +12 -3
  404. package/src/modules/access/access-control.interface.ts +23 -0
  405. package/src/modules/access/access-control.service.ts +35 -0
  406. package/src/modules/access/access.routes.ts +67 -6
  407. package/src/modules/access/directory-sync-bot.ts +1 -1
  408. package/src/modules/access/roles-admin.service.ts +51 -11
  409. package/src/modules/access/user-access-removal.service.ts +1 -1
  410. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +58 -0
  411. package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +19 -1
  412. package/src/modules/agent-guide/agent-guide.ts +9 -0
  413. package/src/modules/agent-guide/index.ts +1 -0
  414. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +14 -0
  415. package/src/modules/agent-instructions/compose.ts +17 -2
  416. package/src/modules/agent-instructions/shared-file-rules.ts +19 -19
  417. package/src/modules/audit/__tests__/agent-audit.service.test.ts +64 -4
  418. package/src/modules/audit/__tests__/agent-connection.routes.test.ts +102 -0
  419. package/src/modules/audit/agent-audit.service.ts +58 -3
  420. package/src/modules/audit/agent-connection.routes.ts +56 -0
  421. package/src/modules/audit/audit.contract.ts +26 -0
  422. package/src/modules/audit/index.ts +1 -0
  423. package/src/modules/auth/__tests__/account-deactivation.test.ts +145 -16
  424. package/src/modules/auth/__tests__/account-hooks.test.ts +57 -0
  425. package/src/modules/auth/__tests__/account-switch-lock.pg.test.ts +78 -0
  426. package/src/modules/auth/__tests__/account-switch-lock.test.ts +43 -0
  427. package/src/modules/auth/__tests__/account.routes.test.ts +387 -9
  428. package/src/modules/auth/__tests__/auth.service.created.pg.test.ts +69 -0
  429. package/src/modules/auth/__tests__/auth.service.test.ts +159 -1
  430. package/src/modules/auth/account-hooks.ts +69 -0
  431. package/src/modules/auth/account-switch-guard.ts +14 -0
  432. package/src/modules/auth/account-switch-lock.ts +58 -0
  433. package/src/modules/auth/account.routes.ts +122 -13
  434. package/src/modules/auth/auth.middleware.ts +1 -1
  435. package/src/modules/auth/auth.service.ts +268 -52
  436. package/src/modules/auth/password-hash.ts +5 -2
  437. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +18 -2
  438. package/src/modules/code-mode/code-mode.tool.ts +13 -2
  439. package/src/modules/database/__tests__/pg-error.test.ts +21 -0
  440. package/src/modules/database/__tests__/pii-backfill.pg.test.ts +6 -27
  441. package/src/modules/database/core-schema.ts +33 -3
  442. package/src/modules/database/pg-error.ts +15 -0
  443. package/src/modules/embed/__tests__/account-links.migration.test.ts +98 -0
  444. package/src/modules/embed/__tests__/embed-link.test.ts +122 -0
  445. package/src/modules/embed/__tests__/embed.routes.test.ts +362 -0
  446. package/src/modules/embed/__tests__/embed.service.lock.test.ts +193 -0
  447. package/src/modules/embed/__tests__/embed.service.test.ts +805 -0
  448. package/src/modules/embed/__tests__/embed.tools.test.ts +304 -0
  449. package/src/modules/embed/__tests__/mcp-app.test.ts +207 -0
  450. package/src/modules/embed/account-link.service.ts +76 -0
  451. package/src/modules/embed/embed-link.ts +180 -0
  452. package/src/modules/embed/embed.errors.ts +43 -0
  453. package/src/modules/embed/embed.interface.ts +203 -0
  454. package/src/modules/embed/embed.routes.ts +363 -0
  455. package/src/modules/embed/embed.service.ts +705 -0
  456. package/src/modules/embed/embed.tools.ts +206 -0
  457. package/src/modules/embed/mcp-app.routes.ts +41 -0
  458. package/src/modules/embed/mcp-app.ts +173 -0
  459. package/src/modules/github-app/__tests__/github-app.test.ts +76 -0
  460. package/src/modules/github-app/github-app.connection.ts +38 -4
  461. package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +304 -0
  462. package/src/modules/kb-fs/locking-filesystem.ts +287 -0
  463. package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +35 -0
  464. package/src/modules/kb-sync/kb-sync.interface.ts +2 -0
  465. package/src/modules/kb-sync/kb-sync.service.ts +6 -0
  466. package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +68 -11
  467. package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +37 -2
  468. package/src/modules/mcp/__tests__/mcp.app.e2e.test.ts +200 -0
  469. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +3 -1
  470. package/src/modules/mcp/__tests__/mcp.service.test.ts +23 -13
  471. package/src/modules/mcp/__tests__/tool-call-interface.e2e.test.ts +499 -0
  472. package/src/modules/mcp/mcp-auth.middleware.ts +2 -1
  473. package/src/modules/mcp/mcp.service.ts +81 -12
  474. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +42 -4
  475. package/src/modules/onboarding/__tests__/onboarding.routes.test.ts +163 -0
  476. package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +635 -0
  477. package/src/modules/onboarding/__tests__/starter-packs.test.ts +169 -0
  478. package/src/modules/onboarding/onboarding.contract.ts +85 -0
  479. package/src/modules/onboarding/onboarding.routes.ts +85 -0
  480. package/src/modules/onboarding/starter-pack.service.ts +580 -0
  481. package/src/modules/onboarding/starter-packs.ts +182 -0
  482. package/src/modules/plugins/__tests__/plugins.tools.test.ts +25 -2
  483. package/src/modules/plugins/index.ts +1 -0
  484. package/src/modules/plugins/plugin-provision.service.ts +22 -1
  485. package/src/modules/plugins/plugins.routes.ts +29 -0
  486. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +150 -1
  487. package/src/modules/settings/__tests__/repository-source.test.ts +9 -0
  488. package/src/modules/settings/deployment-settings.service.ts +131 -1
  489. package/src/modules/settings/repository-source.ts +4 -4
  490. package/src/modules/skills/__tests__/skills.tools.description.test.ts +5 -1
  491. package/src/modules/skills/skills.tools.ts +55 -35
  492. package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +32 -0
  493. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +65 -0
  494. package/src/modules/tool-auth/connection-key-rejection.ts +13 -3
  495. package/src/modules/tool-auth/external-api-key.interface.ts +9 -0
  496. package/src/modules/tool-auth/external-api-key.service.ts +61 -4
  497. package/src/modules/tool-auth/tool-auth.middleware.ts +13 -4
  498. package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +502 -0
  499. package/src/modules/tool-helpers/__tests__/route-argument-check.test.ts +337 -0
  500. package/src/modules/tool-helpers/index.ts +34 -1
  501. package/src/modules/tool-helpers/route-argument-check.ts +149 -0
  502. package/src/modules/tool-helpers/route-tool-schemas.ts +171 -0
  503. package/src/modules/tool-helpers/tool-def.ts +132 -7
  504. package/src/modules/tool-helpers/tool-handler.ts +196 -8
  505. package/src/modules/tool-helpers/tool.contract.ts +9 -0
  506. package/src/modules/tool-manuals/tool-manuals.tools.ts +15 -5
  507. package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +14 -1
  508. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +23 -10
  509. package/src/modules/tool-registry/description-length.ts +34 -7
  510. package/src/modules/tool-registry/guide-first.ts +8 -1
  511. package/src/modules/workflow/__tests__/file-lock.service.deletion-gate.test.ts +104 -0
  512. package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +66 -0
  513. package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +31 -2
  514. package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +110 -6
  515. package/src/modules/workflow/__tests__/sanitize-error.test.ts +89 -1
  516. package/src/modules/workflow/__tests__/workflow.routes.push-refused.test.ts +202 -0
  517. package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +6 -6
  518. package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +1026 -0
  519. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +468 -10
  520. package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +4 -0
  521. package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +5 -4
  522. package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +54 -2
  523. package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +41 -5
  524. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +127 -2
  525. package/src/modules/workflow/agent-tools/change-request-read.tools.ts +157 -122
  526. package/src/modules/workflow/agent-tools/workflow.tools.ts +129 -35
  527. package/src/modules/workflow/file-lock.service.ts +67 -5
  528. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +164 -0
  529. package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +48 -1
  530. package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +231 -2
  531. package/src/modules/workflow/git/git.service.ts +176 -38
  532. package/src/modules/workflow/git/node-git-runner.ts +82 -7
  533. package/src/modules/workflow/pending-commits.service.ts +29 -1
  534. package/src/modules/workflow/pending-commits.worker.ts +24 -6
  535. package/src/modules/workflow/recovery-bot.ts +1 -1
  536. package/src/modules/workflow/review-workflow/review-workflow.service.ts +2 -1
  537. package/src/modules/workflow/sanitize-error.ts +50 -0
  538. package/src/modules/workflow/workflow.service.ts +881 -113
  539. package/src/modules/workspace/__tests__/agent-downloads.test.ts +823 -0
  540. package/src/modules/workspace/__tests__/first-run.test.ts +234 -0
  541. package/src/modules/workspace/__tests__/git-internals.security.test.ts +5 -2
  542. package/src/modules/workspace/__tests__/md-links.test.ts +353 -0
  543. package/src/modules/workspace/__tests__/move-links.test.ts +261 -0
  544. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +219 -0
  545. package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +3 -0
  546. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +102 -0
  547. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +52 -5
  548. package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +190 -0
  549. package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +8 -4
  550. package/src/modules/workspace/__tests__/workspace.service.test.ts +50 -8
  551. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +44 -1
  552. package/src/modules/workspace/__tests__/workspace.tools.test.ts +409 -12
  553. package/src/modules/workspace/agent-download.builder.ts +308 -0
  554. package/src/modules/workspace/agent-download.routes.ts +183 -0
  555. package/src/modules/workspace/agent-download.store.ts +533 -0
  556. package/src/modules/workspace/agent-upload.store.ts +23 -7
  557. package/src/modules/workspace/file-readers/raw-mime.ts +40 -0
  558. package/src/modules/workspace/file-readers/text-reader.ts +5 -8
  559. package/src/modules/workspace/first-run.ts +222 -0
  560. package/src/modules/workspace/move-links.ts +237 -0
  561. package/src/modules/workspace/startup/steps/seed-tree.ts +3 -12
  562. package/src/modules/workspace/workspace.routes.ts +64 -22
  563. package/src/modules/workspace/workspace.service.ts +202 -25
  564. package/src/modules/workspace/workspace.tools.ts +492 -63
  565. package/src/modules/write-access/__tests__/write-access.test.ts +13 -0
  566. package/src/modules/write-access/write-access.ts +9 -0
  567. package/src/shared/domain-errors.ts +108 -11
  568. package/src/shared/git.contract.ts +8 -1
  569. package/src/shared/utf8-text.ts +21 -0
  570. package/src/tenancy/__tests__/static-tenant-source.test.ts +1 -0
  571. package/src/tenancy/static-tenant-source.ts +10 -2
  572. package/starter-packs/README.md +103 -0
  573. package/starter-packs/engineering/KnowledgeBase/About us.md +9 -0
  574. package/starter-packs/engineering/KnowledgeBase/Architecture.md +9 -0
  575. package/starter-packs/engineering/KnowledgeBase/Glossary.md +9 -0
  576. package/starter-packs/engineering/KnowledgeBase/How we ship.md +9 -0
  577. package/starter-packs/engineering/Plugins/engineering-starter/LICENSE +21 -0
  578. package/starter-packs/engineering/Plugins/engineering-starter/SOURCE.md +20 -0
  579. package/starter-packs/engineering/Plugins/engineering-starter/access.md +19 -0
  580. package/starter-packs/engineering/Plugins/engineering-starter/plugin.json +10 -0
  581. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/SKILL.md +171 -0
  582. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2a-startup-diagnostic.md +157 -0
  583. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2b-builder-brainstorm.md +39 -0
  584. package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-ceo-review/SKILL.md +144 -0
  585. package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-eng-review/SKILL.md +144 -0
  586. package/starter-packs/engineering/Plugins/engineering-starter/skills/review/SKILL.md +80 -0
  587. package/starter-packs/engineering/Plugins/engineering-starter/skills/review/checklist.md +184 -0
  588. package/starter-packs/engineering/pack.yaml +11 -0
  589. package/starter-packs/general/KnowledgeBase/About us.md +9 -0
  590. package/starter-packs/general/KnowledgeBase/Glossary.md +9 -0
  591. package/starter-packs/general/KnowledgeBase/How we work.md +9 -0
  592. package/starter-packs/general/Plugins/general-starter/LICENSE +202 -0
  593. package/starter-packs/general/Plugins/general-starter/SOURCE.md +10 -0
  594. package/starter-packs/general/Plugins/general-starter/access.md +19 -0
  595. package/starter-packs/general/Plugins/general-starter/plugin.json +10 -0
  596. package/starter-packs/general/Plugins/general-starter/skills/memory-management/SKILL.md +332 -0
  597. package/starter-packs/general/Plugins/general-starter/skills/productivity-start/SKILL.md +163 -0
  598. package/starter-packs/general/Plugins/general-starter/skills/productivity-update/SKILL.md +167 -0
  599. package/starter-packs/general/Plugins/general-starter/skills/task-management/SKILL.md +83 -0
  600. package/starter-packs/general/pack.yaml +10 -0
  601. package/starter-packs/operations/KnowledgeBase/Glossary.md +9 -0
  602. package/starter-packs/operations/KnowledgeBase/How we work.md +9 -0
  603. package/starter-packs/operations/KnowledgeBase/Processes.md +9 -0
  604. package/starter-packs/operations/KnowledgeBase/Tools we use.md +9 -0
  605. package/starter-packs/operations/KnowledgeBase/Vendors.md +9 -0
  606. package/starter-packs/operations/Plugins/operations-starter/LICENSE +202 -0
  607. package/starter-packs/operations/Plugins/operations-starter/SOURCE.md +10 -0
  608. package/starter-packs/operations/Plugins/operations-starter/access.md +19 -0
  609. package/starter-packs/operations/Plugins/operations-starter/plugin.json +10 -0
  610. package/starter-packs/operations/Plugins/operations-starter/skills/capacity-plan/SKILL.md +111 -0
  611. package/starter-packs/operations/Plugins/operations-starter/skills/change-request/SKILL.md +119 -0
  612. package/starter-packs/operations/Plugins/operations-starter/skills/compliance-tracking/SKILL.md +44 -0
  613. package/starter-packs/operations/Plugins/operations-starter/skills/process-doc/SKILL.md +80 -0
  614. package/starter-packs/operations/Plugins/operations-starter/skills/process-optimization/SKILL.md +39 -0
  615. package/starter-packs/operations/Plugins/operations-starter/skills/risk-assessment/SKILL.md +40 -0
  616. package/starter-packs/operations/Plugins/operations-starter/skills/runbook/SKILL.md +85 -0
  617. package/starter-packs/operations/Plugins/operations-starter/skills/status-report/SKILL.md +75 -0
  618. package/starter-packs/operations/Plugins/operations-starter/skills/vendor-review/SKILL.md +102 -0
  619. package/starter-packs/operations/pack.yaml +12 -0
  620. package/starter-packs/sales/KnowledgeBase/About us.md +9 -0
  621. package/starter-packs/sales/KnowledgeBase/Customers.md +9 -0
  622. package/starter-packs/sales/KnowledgeBase/Glossary.md +9 -0
  623. package/starter-packs/sales/KnowledgeBase/How we sell.md +9 -0
  624. package/starter-packs/sales/KnowledgeBase/Objections.md +9 -0
  625. package/starter-packs/sales/KnowledgeBase/Pricing.md +9 -0
  626. package/starter-packs/sales/Plugins/sales-starter/LICENSE +202 -0
  627. package/starter-packs/sales/Plugins/sales-starter/SOURCE.md +10 -0
  628. package/starter-packs/sales/Plugins/sales-starter/access.md +19 -0
  629. package/starter-packs/sales/Plugins/sales-starter/plugin.json +10 -0
  630. package/starter-packs/sales/Plugins/sales-starter/skills/account-context/SKILL.md +94 -0
  631. package/starter-packs/sales/Plugins/sales-starter/skills/account-plan/SKILL.md +95 -0
  632. package/starter-packs/sales/Plugins/sales-starter/skills/account-research/SKILL.md +99 -0
  633. package/starter-packs/sales/Plugins/sales-starter/skills/account-tiering/SKILL.md +101 -0
  634. package/starter-packs/sales/Plugins/sales-starter/skills/call-prep/SKILL.md +111 -0
  635. package/starter-packs/sales/Plugins/sales-starter/skills/call-summary/SKILL.md +111 -0
  636. package/starter-packs/sales/Plugins/sales-starter/skills/close-plan/SKILL.md +106 -0
  637. package/starter-packs/sales/Plugins/sales-starter/skills/competitive-intelligence/SKILL.md +102 -0
  638. package/starter-packs/sales/Plugins/sales-starter/skills/create-an-asset/SKILL.md +105 -0
  639. package/starter-packs/sales/Plugins/sales-starter/skills/crm-hygiene-check/SKILL.md +103 -0
  640. package/starter-packs/sales/Plugins/sales-starter/skills/customer-health/SKILL.md +108 -0
  641. package/starter-packs/sales/Plugins/sales-starter/skills/customer-voice/SKILL.md +94 -0
  642. package/starter-packs/sales/Plugins/sales-starter/skills/daily-briefing/SKILL.md +61 -0
  643. package/starter-packs/sales/Plugins/sales-starter/skills/deal-advance-gap/SKILL.md +102 -0
  644. package/starter-packs/sales/Plugins/sales-starter/skills/deal-review/SKILL.md +105 -0
  645. package/starter-packs/sales/Plugins/sales-starter/skills/deal-signals/SKILL.md +96 -0
  646. package/starter-packs/sales/Plugins/sales-starter/skills/deal-slip-scenario/SKILL.md +104 -0
  647. package/starter-packs/sales/Plugins/sales-starter/skills/draft-outreach/SKILL.md +116 -0
  648. package/starter-packs/sales/Plugins/sales-starter/skills/end-of-day/SKILL.md +114 -0
  649. package/starter-packs/sales/Plugins/sales-starter/skills/expansion-whitespace/SKILL.md +97 -0
  650. package/starter-packs/sales/Plugins/sales-starter/skills/forecast/SKILL.md +100 -0
  651. package/starter-packs/sales/Plugins/sales-starter/skills/handle-objection/SKILL.md +93 -0
  652. package/starter-packs/sales/Plugins/sales-starter/skills/inbox-sweep/SKILL.md +114 -0
  653. package/starter-packs/sales/Plugins/sales-starter/skills/lead-triage/SKILL.md +106 -0
  654. package/starter-packs/sales/Plugins/sales-starter/skills/log-activity/SKILL.md +111 -0
  655. package/starter-packs/sales/Plugins/sales-starter/skills/pipeline-review/SKILL.md +97 -0
  656. package/starter-packs/sales/Plugins/sales-starter/skills/renewal-radar/SKILL.md +95 -0
  657. package/starter-packs/sales/Plugins/sales-starter/skills/rep-context/SKILL.md +90 -0
  658. package/starter-packs/sales/Plugins/sales-starter/skills/route-lead/SKILL.md +111 -0
  659. package/starter-packs/sales/Plugins/sales-starter/skills/sales-setup/SKILL.md +200 -0
  660. package/starter-packs/sales/Plugins/sales-starter/skills/schedule-meeting/SKILL.md +99 -0
  661. package/starter-packs/sales/Plugins/sales-starter/skills/stakeholder-map/SKILL.md +107 -0
  662. package/starter-packs/sales/Plugins/sales-starter/skills/team-pipeline/SKILL.md +105 -0
  663. package/starter-packs/sales/Plugins/sales-starter/skills/update-opportunity/SKILL.md +112 -0
  664. package/starter-packs/sales/Plugins/sales-starter/skills/weekly-wrap/SKILL.md +90 -0
  665. package/starter-packs/sales/Plugins/sales-starter/skills/win-loss-review/SKILL.md +88 -0
  666. package/starter-packs/sales/pack.yaml +13 -0
  667. package/starter-packs/support/KnowledgeBase/Escalations.md +9 -0
  668. package/starter-packs/support/KnowledgeBase/Glossary.md +9 -0
  669. package/starter-packs/support/KnowledgeBase/Known issues.md +9 -0
  670. package/starter-packs/support/KnowledgeBase/Products.md +9 -0
  671. package/starter-packs/support/KnowledgeBase/Tone of voice.md +9 -0
  672. package/starter-packs/support/Plugins/support-starter/LICENSE +202 -0
  673. package/starter-packs/support/Plugins/support-starter/SOURCE.md +10 -0
  674. package/starter-packs/support/Plugins/support-starter/access.md +19 -0
  675. package/starter-packs/support/Plugins/support-starter/plugin.json +10 -0
  676. package/starter-packs/support/Plugins/support-starter/skills/customer-escalation/SKILL.md +246 -0
  677. package/starter-packs/support/Plugins/support-starter/skills/customer-research/SKILL.md +250 -0
  678. package/starter-packs/support/Plugins/support-starter/skills/draft-response/SKILL.md +416 -0
  679. package/starter-packs/support/Plugins/support-starter/skills/kb-article/SKILL.md +350 -0
  680. package/starter-packs/support/Plugins/support-starter/skills/ticket-triage/SKILL.md +273 -0
  681. package/starter-packs/support/pack.yaml +12 -0
@@ -1,6 +1,11 @@
1
1
  import type { Database } from '../database/connection.js';
2
2
  import type { AuthUser } from '@bevel-software/platform-shared';
3
3
  import { type IAccountAdmission } from './account-admission.js';
4
+ import type { IAccountSwitchGuard } from './account-switch-guard.js';
5
+ /** What an admin is told when they try to switch an owner off. */
6
+ export declare const OWNER_SWITCH_OFF_REFUSAL = "The owner can't be switched off.";
7
+ /** What an admin is told when deleting an owner's account would leave them no way to sign in. */
8
+ export declare const OWNER_DELETE_REFUSAL = "The owner's account can't be deleted: they would have no way to sign in.";
4
9
  /**
5
10
  * What this service needs from the deployment's configuration, and nothing
6
11
  * more. Narrowed from `CoreConfig` so the caller can supply a value that is
@@ -39,7 +44,7 @@ export interface SsoLoginOptions {
39
44
  */
40
45
  admittedByProvider?: boolean;
41
46
  }
42
- export declare class AuthService {
47
+ export declare class AuthService implements IAccountSwitchGuard {
43
48
  private readonly db;
44
49
  private readonly config;
45
50
  /**
@@ -115,7 +120,10 @@ export declare class AuthService {
115
120
  * Password sign-in refuses it until a password is set. For an address that
116
121
  * already has an account it changes nothing but an explicitly given name.
117
122
  *
118
- * WITH a password, the deployment admin is the one target this refuses, for the reason
123
+ * WITH a password, this refuses two kinds of target. The accounts the
124
+ * platform runs its own work as are refused first
125
+ * ({@link assertNotSystemAccount}): a password would make them able to sign
126
+ * in. The deployment admin is refused for the reason
119
127
  * {@link changePassword} refuses it: that account's password is the
120
128
  * environment's, so a stored hash would not replace it but ADD a second
121
129
  * credential — one that keeps signing in after `ADMIN_PASSWORD` is rotated,
@@ -124,6 +132,40 @@ export declare class AuthService {
124
132
  * with an arbitrary email.
125
133
  */
126
134
  createAccount(email: string, name: string | undefined, password?: string): Promise<AuthUser>;
135
+ /**
136
+ * An account for single sign-on, or for a password somebody sets later:
137
+ * created without one, and for an address that already has an account,
138
+ * nothing changes but an explicitly given name. `created` says which
139
+ * happened, from the one statement that did it (see {@link CREATED}): an
140
+ * invite is only an invite when the account is new.
141
+ */
142
+ createAccountWithoutPassword(email: string, name: string | undefined): Promise<{
143
+ user: AuthUser;
144
+ created: boolean;
145
+ }>;
146
+ /**
147
+ * An invite's starting password: create the account with it, or give it to
148
+ * an existing account that is switched on and has no password yet — never
149
+ * to one that has a password of its own or is switched off. The test and
150
+ * the write are ONE statement (the upsert's `setWhere`), so a password the
151
+ * person sets between the admin reading the account list and sending the
152
+ * invite is never overwritten; `passwordSet` says which happened, and
153
+ * `created` whether the account is new (see {@link CREATED}).
154
+ *
155
+ * The deployment admin has its own password (the environment's): it is
156
+ * left as it is, as {@link createAccount} would refuse to store one. The
157
+ * accounts the platform runs its own work as are refused outright: a
158
+ * password would make them able to sign in.
159
+ *
160
+ * When nothing was written, `deactivated` tells a switched-off account
161
+ * apart from one that has a password of its own.
162
+ */
163
+ createAccountWithStartingPassword(email: string, password: string): Promise<{
164
+ user: AuthUser;
165
+ created: boolean;
166
+ passwordSet: boolean;
167
+ deactivated: boolean;
168
+ }>;
127
169
  /**
128
170
  * Is `email` the deployment admin — the account whose password is set in
129
171
  * the deployment environment (`ADMIN_EMAIL` while `ADMIN_PASSWORD` is set)
@@ -139,6 +181,39 @@ export declare class AuthService {
139
181
  * client is TOLD does follow the switch: {@link reportsAsEnvAdmin}.
140
182
  */
141
183
  private isEnvAdminEmail;
184
+ /**
185
+ * Is `email` an owner of this deployment — an address it is configured
186
+ * with as its owner (`ADMIN_EMAIL`; on Hexis by Bevel cloud, whoever
187
+ * created the workspace)? Unlike {@link isEnvAdminEmail} this asks nothing
188
+ * of `ADMIN_PASSWORD`: the cloud never sets one, and its owner must be
189
+ * protected all the same. Behind {@link deactivate} (which always refuses
190
+ * an owner) and {@link assertDeletable} (which refuses one who could not
191
+ * sign back in). Canonicalises both sides, so `email` may be raw.
192
+ */
193
+ isOwnerEmail(email: string): boolean;
194
+ /**
195
+ * An owner is never switched off, but an earlier version did switch off an
196
+ * owner without a server-held password, and no admin can undo that for a
197
+ * workspace's only owner. So wherever an owner's row is read to let them
198
+ * in, a switch-off left on it is cleared — on the row itself, so that the
199
+ * credentials that read the column directly (API keys, MCP tokens) are
200
+ * honoured again too. Returns `row` as it now stands; anyone else's row
201
+ * unchanged.
202
+ */
203
+ private clearOwnerSwitchOff;
204
+ /**
205
+ * The configured owner addresses, canonical, blanks dropped. One today
206
+ * (`ADMIN_EMAIL` is a single string); a list so that every owner is
207
+ * treated alike should the configuration ever carry more.
208
+ */
209
+ private ownerEmails;
210
+ /**
211
+ * Refuse to delete `email`'s account when it is an owner who could not
212
+ * sign back in afterwards: only an owner whose password the server holds,
213
+ * with password sign-in on (exactly {@link reportsAsEnvAdmin}), still has a
214
+ * way in once the row is gone. Anyone else's account passes.
215
+ */
216
+ assertDeletable(email: string): void;
142
217
  /**
143
218
  * Whether a client is told `email` is the deployment admin — behind
144
219
  * {@link toClientUser} and {@link listAccounts}. The identity is
@@ -189,12 +264,18 @@ export declare class AuthService {
189
264
  name: string;
190
265
  hasPassword: boolean;
191
266
  isEnvAdmin: boolean;
267
+ /** One of the deployment's owners (`ADMIN_EMAIL`), with or without a server-held password. */
268
+ isOwner: boolean;
269
+ /** An owner whose account may be deleted, since they can sign back in (see {@link assertDeletable}); false for everyone else. */
270
+ ownerCanBeDeleted: boolean;
192
271
  /** When an admin switched the account off; null while it is on. */
193
272
  deactivatedAt: Date | null;
194
273
  /** One of the accounts the platform runs its own work as: never switched off, nobody signs in with it. */
195
274
  isSystem: boolean;
196
275
  createdAt: Date;
197
276
  }>>;
277
+ /** Every password write calls this first: the system accounts never get one. */
278
+ private assertNotSystemAccount;
198
279
  private assertPasswordPolicy;
199
280
  private signToken;
200
281
  /**
@@ -245,18 +326,32 @@ export declare class AuthService {
245
326
  * environment password signs it in regardless (see `loginWithPassword`),
246
327
  * and a session that sign-in mints must then be usable too, or the way
247
328
  * back into a deployment would lead nowhere. {@link deactivate} refuses
248
- * the account, but a host may write the column itself.
329
+ * the account, but a host may write the column itself. Any owner's
330
+ * leftover switch-off is cleared here, as at sign-in
331
+ * ({@link clearOwnerSwitchOff}).
249
332
  */
250
333
  isActive(userId: string): Promise<boolean>;
334
+ /**
335
+ * Run `write` only while `email` is not switched off, and keep it so until
336
+ * `write` has finished: a transaction takes the address's switch lock (see
337
+ * account-switch-lock), reads the account and stays open across `write`.
338
+ * Every write that switches an account off, or creates one switched off,
339
+ * takes the same lock first, so it either committed before the read (and
340
+ * `write` is refused) or waits until `write` is done. An address with no
341
+ * account yet counts as switched on. A switched-off account throws a 409
342
+ * `deactivated` and `write` never runs.
343
+ */
344
+ whileSwitchedOn(email: string, write: () => Promise<void>): Promise<void>;
251
345
  /**
252
346
  * Switch an account off. Its row, history and place in roles and groups
253
347
  * stay; every credential it holds stops being honoured (see
254
348
  * {@link isActive}). Idempotent: an account already off keeps the moment
255
349
  * it was first switched off.
256
350
  *
257
- * The deployment admin is refused: the environment password is the way
258
- * back into a deployment whose every other admin is gone, and switching
259
- * it off would close that door from inside. So are the machine accounts
351
+ * An owner is refused, on every deployment, whether or not the server
352
+ * holds a password for them: switching the owner off locks them out of
353
+ * their own workspace (and, where `ADMIN_PASSWORD` is set, closes the
354
+ * way back into a deployment whose every other admin is gone). So are the machine accounts
260
355
  * core runs its own work as. Refusing an admin's own account is the
261
356
  * route's concern, since it knows who is asking.
262
357
  *
@@ -1 +1 @@
1
- {"version":3,"file":"auth.service.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/auth.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.service.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/auth.service.ts"],"names":[],"mappings":"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"}
@@ -1,11 +1,13 @@
1
1
  import jwt from 'jsonwebtoken';
2
- import { and, eq, isNotNull, isNull } from 'drizzle-orm';
2
+ import { and, eq, getTableColumns, isNotNull, isNull, sql } from 'drizzle-orm';
3
3
  import { users } from '../database/schema.js';
4
4
  import { canonicalEmail, hashEmail } from '../../shared/email-identity.js';
5
5
  import { AccountAdmissionRefusedError, AccountChangeRefusedError, AccountDeactivatedError, AuthBackendError, admitEveryone, } from './account-admission.js';
6
6
  import { hashPassword, verifyPassword, timingSafeStringEqual, MIN_PASSWORD_LENGTH, } from './password-hash.js';
7
7
  import { RECOVERY_BOT_EMAIL } from '../workflow/recovery-bot.js';
8
8
  import { DIRECTORY_SYNC_BOT_EMAIL } from '../access/directory-sync-bot.js';
9
+ import { takeAccountSwitchLock } from './account-switch-lock.js';
10
+ import { WorkflowDomainError } from '../../shared/domain-errors.js';
9
11
  const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
10
12
  /**
11
13
  * How long {@link AuthService.isActive} trusts what it last read. Every
@@ -16,6 +18,20 @@ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
16
18
  const ACTIVE_CACHE_MS = 30_000;
17
19
  /** The accounts core runs its own work as. Nobody signs in with them, and nobody switches them off. */
18
20
  const SYSTEM_ACCOUNT_EMAILS = [RECOVERY_BOT_EMAIL, DIRECTORY_SYNC_BOT_EMAIL];
21
+ /**
22
+ * Whether an upsert's returned row was INSERTED rather than updated, from the
23
+ * one statement that did it: Postgres leaves `xmax` at zero on a row the
24
+ * statement inserted and sets it on one its `ON CONFLICT DO UPDATE` touched.
25
+ * So "is this account new" is answered by the write itself, never by a read
26
+ * before it that a concurrent create could make a lie of.
27
+ */
28
+ const CREATED = sql `(xmax = 0)`;
29
+ /**
30
+ * What every password write refuses for the accounts the platform runs its own
31
+ * work as: a stored hash would make them able to sign in. One guard,
32
+ * {@link AuthService.assertNotSystemAccount}, on every path that writes one.
33
+ */
34
+ const SYSTEM_ACCOUNT_PASSWORD_REFUSAL = 'System accounts cannot be given a password';
19
35
  /**
20
36
  * What every caller is told when it tries to give the deployment admin a
21
37
  * stored password — the Account page's own change and an admin's "Set
@@ -23,6 +39,10 @@ const SYSTEM_ACCOUNT_EMAILS = [RECOVERY_BOT_EMAIL, DIRECTORY_SYNC_BOT_EMAIL];
23
39
  * two surfaces cannot end up explaining the same rule differently.
24
40
  */
25
41
  const ENV_ADMIN_PASSWORD_REFUSAL = "This account's password is set in the deployment environment and cannot be changed here";
42
+ /** What an admin is told when they try to switch an owner off. */
43
+ export const OWNER_SWITCH_OFF_REFUSAL = "The owner can't be switched off.";
44
+ /** What an admin is told when deleting an owner's account would leave them no way to sign in. */
45
+ export const OWNER_DELETE_REFUSAL = "The owner's account can't be deleted: they would have no way to sign in.";
26
46
  /**
27
47
  * Decoy hash verified when the email is unknown or has no password set, so
28
48
  * those paths cost the same scrypt work as a real wrong-password attempt —
@@ -87,16 +107,20 @@ export class AuthService {
87
107
  // is put on file, switched off, for an admin to switch on — and is still
88
108
  // refused now, with the port's words. Only for single sign-on, the one
89
109
  // provisioning nobody asked for (see `AccountAdmissionVerdict`).
110
+ // Created switched off, so under the switch lock (see account-switch-lock).
90
111
  if (verdict.waitForAdmin && reason === 'sso') {
91
- await this.db
92
- .insert(users)
93
- .values({
94
- email: normalizedEmail,
95
- emailBidx: normalizedEmail,
96
- name: normalizedEmail.split('@')[0] || normalizedEmail,
97
- deactivatedAt: new Date(),
98
- })
99
- .onConflictDoNothing({ target: users.emailBidx });
112
+ await this.db.transaction(async (tx) => {
113
+ await takeAccountSwitchLock(tx, normalizedEmail);
114
+ await tx
115
+ .insert(users)
116
+ .values({
117
+ email: normalizedEmail,
118
+ emailBidx: normalizedEmail,
119
+ name: normalizedEmail.split('@')[0] || normalizedEmail,
120
+ deactivatedAt: new Date(),
121
+ })
122
+ .onConflictDoNothing({ target: users.emailBidx });
123
+ });
100
124
  throw new AccountAdmissionRefusedError(verdict.message, { waitingForAdmin: true });
101
125
  }
102
126
  throw new AccountAdmissionRefusedError(verdict.message);
@@ -165,10 +189,12 @@ export class AuthService {
165
189
  }
166
190
  // Never refused for being switched off: the environment password is
167
191
  // the deployment's way back in, and {@link deactivate} refuses this
168
- // account for that reason.
192
+ // account for that reason. A switch-off left on the row by an earlier
193
+ // version is cleared here too, so the credentials that read the column
194
+ // directly come back with this sign-in ({@link clearOwnerSwitchOff}).
169
195
  const defaultName = normalizedEmail.split('@')[0] || normalizedEmail;
170
196
  await this.assertAdmitted(normalizedEmail, 'bootstrap');
171
- const admin = await this.upsertUserByEmail(normalizedEmail, defaultName);
197
+ const admin = await this.clearOwnerSwitchOff(await this.upsertUserByEmail(normalizedEmail, defaultName));
172
198
  return { token: this.signToken(admin.id, admin.email), user: this.toClientUser(admin) };
173
199
  }
174
200
  // Always run one scrypt verification — against the stored hash when there
@@ -182,9 +208,10 @@ export class AuthService {
182
208
  }
183
209
  // Only now, with the password proven: telling a stranger that an address
184
210
  // is switched off would tell them it has an account.
185
- if (user.deactivatedAt)
211
+ const signedIn = await this.clearOwnerSwitchOff(user);
212
+ if (signedIn.deactivatedAt)
186
213
  throw new AccountDeactivatedError();
187
- return { token: this.signToken(user.id, user.email), user: this.toClientUser(user) };
214
+ return { token: this.signToken(signedIn.id, signedIn.email), user: this.toClientUser(signedIn) };
188
215
  }
189
216
  /**
190
217
  * Establish a session for a user whose identity an SSO provider (the
@@ -210,7 +237,7 @@ export class AuthService {
210
237
  this.assertAllowedDomain(normalizedEmail);
211
238
  await this.assertAdmitted(normalizedEmail, 'sso');
212
239
  const displayName = (name ?? '').trim() || normalizedEmail.split('@')[0] || normalizedEmail;
213
- const user = await this.upsertUserByEmail(normalizedEmail, displayName);
240
+ const user = await this.clearOwnerSwitchOff(await this.upsertUserByEmail(normalizedEmail, displayName));
214
241
  if (user.deactivatedAt)
215
242
  throw new AccountDeactivatedError();
216
243
  return { token: this.signToken(user.id, user.email), user: this.toClientUser(user) };
@@ -227,7 +254,10 @@ export class AuthService {
227
254
  * Password sign-in refuses it until a password is set. For an address that
228
255
  * already has an account it changes nothing but an explicitly given name.
229
256
  *
230
- * WITH a password, the deployment admin is the one target this refuses, for the reason
257
+ * WITH a password, this refuses two kinds of target. The accounts the
258
+ * platform runs its own work as are refused first
259
+ * ({@link assertNotSystemAccount}): a password would make them able to sign
260
+ * in. The deployment admin is refused for the reason
231
261
  * {@link changePassword} refuses it: that account's password is the
232
262
  * environment's, so a stored hash would not replace it but ADD a second
233
263
  * credential — one that keeps signing in after `ADMIN_PASSWORD` is rotated,
@@ -242,20 +272,11 @@ export class AuthService {
242
272
  }
243
273
  const suppliedName = (name ?? '').trim();
244
274
  const displayName = suppliedName || normalizedEmail.split('@')[0] || normalizedEmail;
245
- if (!password) {
246
- await this.assertAdmitted(normalizedEmail, 'admin-create');
247
- const [created] = await this.db
248
- .insert(users)
249
- .values({ email: normalizedEmail, emailBidx: normalizedEmail, name: displayName })
250
- .onConflictDoUpdate({
251
- target: users.emailBidx,
252
- set: suppliedName ? { name: suppliedName, updatedAt: new Date() } : { updatedAt: new Date() },
253
- })
254
- .returning();
255
- return this.toClientUser(created);
256
- }
275
+ if (!password)
276
+ return (await this.createAccountWithoutPassword(email, name)).user;
257
277
  // Before the policy check, so the refusal names the real reason rather
258
278
  // than sending the admin off to pick a longer password first.
279
+ this.assertNotSystemAccount(normalizedEmail);
259
280
  if (this.isEnvAdminEmail(normalizedEmail)) {
260
281
  throw new Error(ENV_ADMIN_PASSWORD_REFUSAL);
261
282
  }
@@ -278,6 +299,82 @@ export class AuthService {
278
299
  .returning();
279
300
  return this.toClientUser(row);
280
301
  }
302
+ /**
303
+ * An account for single sign-on, or for a password somebody sets later:
304
+ * created without one, and for an address that already has an account,
305
+ * nothing changes but an explicitly given name. `created` says which
306
+ * happened, from the one statement that did it (see {@link CREATED}): an
307
+ * invite is only an invite when the account is new.
308
+ */
309
+ async createAccountWithoutPassword(email, name) {
310
+ const normalizedEmail = canonicalEmail(email ?? '');
311
+ if (!EMAIL_REGEX.test(normalizedEmail)) {
312
+ throw new Error('Invalid email');
313
+ }
314
+ const suppliedName = (name ?? '').trim();
315
+ const displayName = suppliedName || normalizedEmail.split('@')[0] || normalizedEmail;
316
+ await this.assertAdmitted(normalizedEmail, 'admin-create');
317
+ const [row] = await this.db
318
+ .insert(users)
319
+ .values({ email: normalizedEmail, emailBidx: normalizedEmail, name: displayName })
320
+ .onConflictDoUpdate({
321
+ target: users.emailBidx,
322
+ set: suppliedName ? { name: suppliedName, updatedAt: new Date() } : { updatedAt: new Date() },
323
+ })
324
+ .returning({ ...getTableColumns(users), created: CREATED });
325
+ return { user: this.toClientUser(row), created: row.created === true };
326
+ }
327
+ /**
328
+ * An invite's starting password: create the account with it, or give it to
329
+ * an existing account that is switched on and has no password yet — never
330
+ * to one that has a password of its own or is switched off. The test and
331
+ * the write are ONE statement (the upsert's `setWhere`), so a password the
332
+ * person sets between the admin reading the account list and sending the
333
+ * invite is never overwritten; `passwordSet` says which happened, and
334
+ * `created` whether the account is new (see {@link CREATED}).
335
+ *
336
+ * The deployment admin has its own password (the environment's): it is
337
+ * left as it is, as {@link createAccount} would refuse to store one. The
338
+ * accounts the platform runs its own work as are refused outright: a
339
+ * password would make them able to sign in.
340
+ *
341
+ * When nothing was written, `deactivated` tells a switched-off account
342
+ * apart from one that has a password of its own.
343
+ */
344
+ async createAccountWithStartingPassword(email, password) {
345
+ const normalizedEmail = canonicalEmail(email ?? '');
346
+ if (!EMAIL_REGEX.test(normalizedEmail)) {
347
+ throw new Error('Invalid email');
348
+ }
349
+ this.assertNotSystemAccount(normalizedEmail);
350
+ if (this.isEnvAdminEmail(normalizedEmail)) {
351
+ const [existing] = await this.db.select().from(users).where(eq(users.emailBidx, normalizedEmail)).limit(1);
352
+ if (existing) {
353
+ return { user: this.toClientUser(existing), created: false, passwordSet: false, deactivated: existing.deactivatedAt != null };
354
+ }
355
+ throw new Error(ENV_ADMIN_PASSWORD_REFUSAL);
356
+ }
357
+ this.assertPasswordPolicy(password);
358
+ await this.assertAdmitted(normalizedEmail, 'admin-create');
359
+ const passwordHash = await hashPassword(password);
360
+ const displayName = normalizedEmail.split('@')[0] || normalizedEmail;
361
+ const [row] = await this.db
362
+ .insert(users)
363
+ .values({ email: normalizedEmail, emailBidx: normalizedEmail, name: displayName, passwordHash })
364
+ .onConflictDoUpdate({
365
+ target: users.emailBidx,
366
+ set: { passwordHash, updatedAt: new Date() },
367
+ setWhere: and(isNull(users.passwordHash), isNull(users.deactivatedAt)),
368
+ })
369
+ .returning({ ...getTableColumns(users), created: CREATED });
370
+ if (row)
371
+ return { user: this.toClientUser(row), created: row.created === true, passwordSet: true, deactivated: false };
372
+ // The conflicting account has a password or is switched off: untouched.
373
+ const [existing] = await this.db.select().from(users).where(eq(users.emailBidx, normalizedEmail)).limit(1);
374
+ if (!existing)
375
+ throw new Error('Could not set the password');
376
+ return { user: this.toClientUser(existing), created: false, passwordSet: false, deactivated: existing.deactivatedAt != null };
377
+ }
281
378
  /**
282
379
  * Is `email` the deployment admin — the account whose password is set in
283
380
  * the deployment environment (`ADMIN_EMAIL` while `ADMIN_PASSWORD` is set)
@@ -297,6 +394,59 @@ export class AuthService {
297
394
  this.config.adminPassword.length > 0 &&
298
395
  email === this.config.adminEmail);
299
396
  }
397
+ /**
398
+ * Is `email` an owner of this deployment — an address it is configured
399
+ * with as its owner (`ADMIN_EMAIL`; on Hexis by Bevel cloud, whoever
400
+ * created the workspace)? Unlike {@link isEnvAdminEmail} this asks nothing
401
+ * of `ADMIN_PASSWORD`: the cloud never sets one, and its owner must be
402
+ * protected all the same. Behind {@link deactivate} (which always refuses
403
+ * an owner) and {@link assertDeletable} (which refuses one who could not
404
+ * sign back in). Canonicalises both sides, so `email` may be raw.
405
+ */
406
+ isOwnerEmail(email) {
407
+ // Not trusted to be text: the accounts API passes a request body's value
408
+ // in before validating it, and a non-string is simply not an owner.
409
+ const candidate = typeof email === 'string' ? canonicalEmail(email) : '';
410
+ return candidate.length > 0 && this.ownerEmails().includes(candidate);
411
+ }
412
+ /**
413
+ * An owner is never switched off, but an earlier version did switch off an
414
+ * owner without a server-held password, and no admin can undo that for a
415
+ * workspace's only owner. So wherever an owner's row is read to let them
416
+ * in, a switch-off left on it is cleared — on the row itself, so that the
417
+ * credentials that read the column directly (API keys, MCP tokens) are
418
+ * honoured again too. Returns `row` as it now stands; anyone else's row
419
+ * unchanged.
420
+ */
421
+ async clearOwnerSwitchOff(row) {
422
+ if (!row.deactivatedAt || !this.isOwnerEmail(row.email))
423
+ return row;
424
+ await this.db
425
+ .update(users)
426
+ .set({ deactivatedAt: null, updatedAt: new Date() })
427
+ .where(and(eq(users.id, row.id), isNotNull(users.deactivatedAt)));
428
+ this.activeCache.delete(row.id);
429
+ return { ...row, deactivatedAt: null };
430
+ }
431
+ /**
432
+ * The configured owner addresses, canonical, blanks dropped. One today
433
+ * (`ADMIN_EMAIL` is a single string); a list so that every owner is
434
+ * treated alike should the configuration ever carry more.
435
+ */
436
+ ownerEmails() {
437
+ return [this.config.adminEmail].map((e) => canonicalEmail(e ?? '')).filter((e) => e.length > 0);
438
+ }
439
+ /**
440
+ * Refuse to delete `email`'s account when it is an owner who could not
441
+ * sign back in afterwards: only an owner whose password the server holds,
442
+ * with password sign-in on (exactly {@link reportsAsEnvAdmin}), still has a
443
+ * way in once the row is gone. Anyone else's account passes.
444
+ */
445
+ assertDeletable(email) {
446
+ if (this.isOwnerEmail(email) && !this.reportsAsEnvAdmin(canonicalEmail(email ?? ''))) {
447
+ throw new AccountChangeRefusedError(OWNER_DELETE_REFUSAL);
448
+ }
449
+ }
300
450
  /**
301
451
  * Whether a client is told `email` is the deployment admin — behind
302
452
  * {@link toClientUser} and {@link listAccounts}. The identity is
@@ -343,6 +493,7 @@ export class AuthService {
343
493
  throw new Error('User not found');
344
494
  // Before the policy check, so the deployment admin is told the real reason
345
495
  // rather than being sent to fix a password that would be refused anyway.
496
+ this.assertNotSystemAccount(user.email);
346
497
  if (this.isEnvAdminEmail(user.email)) {
347
498
  throw new Error(ENV_ADMIN_PASSWORD_REFUSAL);
348
499
  }
@@ -370,18 +521,30 @@ export class AuthService {
370
521
  // would sort by IV noise. The table is one row per team member.
371
522
  const rows = await this.db.select().from(users);
372
523
  return rows
373
- .map((row) => ({
374
- id: row.id,
375
- email: row.email,
376
- name: row.name,
377
- hasPassword: row.passwordHash != null,
378
- isEnvAdmin: this.reportsAsEnvAdmin(row.email),
379
- deactivatedAt: row.deactivatedAt,
380
- isSystem: SYSTEM_ACCOUNT_EMAILS.includes(row.email),
381
- createdAt: row.createdAt,
382
- }))
524
+ .map((row) => {
525
+ const isEnvAdmin = this.reportsAsEnvAdmin(row.email);
526
+ const isOwner = this.isOwnerEmail(row.email);
527
+ return {
528
+ id: row.id,
529
+ email: row.email,
530
+ name: row.name,
531
+ hasPassword: row.passwordHash != null,
532
+ isEnvAdmin,
533
+ isOwner,
534
+ ownerCanBeDeleted: isOwner && isEnvAdmin,
535
+ deactivatedAt: row.deactivatedAt,
536
+ isSystem: SYSTEM_ACCOUNT_EMAILS.includes(row.email),
537
+ createdAt: row.createdAt,
538
+ };
539
+ })
383
540
  .sort((a, b) => a.email.localeCompare(b.email));
384
541
  }
542
+ /** Every password write calls this first: the system accounts never get one. */
543
+ assertNotSystemAccount(normalizedEmail) {
544
+ if (SYSTEM_ACCOUNT_EMAILS.includes(normalizedEmail)) {
545
+ throw new Error(SYSTEM_ACCOUNT_PASSWORD_REFUSAL);
546
+ }
547
+ }
385
548
  assertPasswordPolicy(password) {
386
549
  if ((password ?? '').length < MIN_PASSWORD_LENGTH) {
387
550
  throw new Error(`Password must be at least ${MIN_PASSWORD_LENGTH} characters`);
@@ -404,7 +567,7 @@ export class AuthService {
404
567
  return null;
405
568
  await this.assertAdmitted(normalizedEmail, 'embed');
406
569
  const displayName = (name ?? '').trim() || normalizedEmail.split('@')[0] || normalizedEmail;
407
- const user = await this.upsertUserByEmail(normalizedEmail, displayName);
570
+ const user = await this.clearOwnerSwitchOff(await this.upsertUserByEmail(normalizedEmail, displayName));
408
571
  if (user.deactivatedAt)
409
572
  throw new AccountDeactivatedError();
410
573
  return { id: user.id, email: user.email, name: user.name };
@@ -462,31 +625,58 @@ export class AuthService {
462
625
  * environment password signs it in regardless (see `loginWithPassword`),
463
626
  * and a session that sign-in mints must then be usable too, or the way
464
627
  * back into a deployment would lead nowhere. {@link deactivate} refuses
465
- * the account, but a host may write the column itself.
628
+ * the account, but a host may write the column itself. Any owner's
629
+ * leftover switch-off is cleared here, as at sign-in
630
+ * ({@link clearOwnerSwitchOff}).
466
631
  */
467
632
  async isActive(userId) {
468
633
  const now = Date.now();
469
634
  const hit = this.activeCache.get(userId);
470
635
  if (hit && now - hit.at < ACTIVE_CACHE_MS)
471
636
  return hit.active;
472
- const [row] = await this.db
473
- .select({ email: users.email, deactivatedAt: users.deactivatedAt })
637
+ const [found] = await this.db
638
+ .select({ id: users.id, email: users.email, deactivatedAt: users.deactivatedAt })
474
639
  .from(users)
475
640
  .where(eq(users.id, userId))
476
641
  .limit(1);
642
+ const row = found && (await this.clearOwnerSwitchOff(found));
477
643
  const active = row !== undefined && (!row.deactivatedAt || this.isEnvAdminEmail(row.email ?? ''));
478
644
  this.activeCache.set(userId, { active, at: now });
479
645
  return active;
480
646
  }
647
+ /**
648
+ * Run `write` only while `email` is not switched off, and keep it so until
649
+ * `write` has finished: a transaction takes the address's switch lock (see
650
+ * account-switch-lock), reads the account and stays open across `write`.
651
+ * Every write that switches an account off, or creates one switched off,
652
+ * takes the same lock first, so it either committed before the read (and
653
+ * `write` is refused) or waits until `write` is done. An address with no
654
+ * account yet counts as switched on. A switched-off account throws a 409
655
+ * `deactivated` and `write` never runs.
656
+ */
657
+ async whileSwitchedOn(email, write) {
658
+ await this.db.transaction(async (tx) => {
659
+ await takeAccountSwitchLock(tx, email);
660
+ const [account] = await tx
661
+ .select({ deactivatedAt: users.deactivatedAt })
662
+ .from(users)
663
+ .where(eq(users.emailBidx, canonicalEmail(email)));
664
+ if (account?.deactivatedAt) {
665
+ throw new WorkflowDomainError('This account is switched off', 409, { kind: 'deactivated' });
666
+ }
667
+ await write();
668
+ });
669
+ }
481
670
  /**
482
671
  * Switch an account off. Its row, history and place in roles and groups
483
672
  * stay; every credential it holds stops being honoured (see
484
673
  * {@link isActive}). Idempotent: an account already off keeps the moment
485
674
  * it was first switched off.
486
675
  *
487
- * The deployment admin is refused: the environment password is the way
488
- * back into a deployment whose every other admin is gone, and switching
489
- * it off would close that door from inside. So are the machine accounts
676
+ * An owner is refused, on every deployment, whether or not the server
677
+ * holds a password for them: switching the owner off locks them out of
678
+ * their own workspace (and, where `ADMIN_PASSWORD` is set, closes the
679
+ * way back into a deployment whose every other admin is gone). So are the machine accounts
490
680
  * core runs its own work as. Refusing an admin's own account is the
491
681
  * route's concern, since it knows who is asking.
492
682
  *
@@ -496,16 +686,21 @@ export class AuthService {
496
686
  const [row] = await this.db.select().from(users).where(eq(users.id, userId)).limit(1);
497
687
  if (!row)
498
688
  return false;
499
- if (this.isEnvAdminEmail(row.email)) {
500
- throw new AccountChangeRefusedError('The deployment admin cannot be switched off: its password in the environment is the way back in.');
689
+ if (this.isOwnerEmail(row.email)) {
690
+ throw new AccountChangeRefusedError(OWNER_SWITCH_OFF_REFUSAL);
501
691
  }
502
692
  if (SYSTEM_ACCOUNT_EMAILS.includes(row.email)) {
503
693
  throw new AccountChangeRefusedError('This account belongs to the platform itself and cannot be switched off.');
504
694
  }
505
- await this.db
506
- .update(users)
507
- .set({ deactivatedAt: new Date(), updatedAt: new Date() })
508
- .where(and(eq(users.id, userId), isNull(users.deactivatedAt)));
695
+ // Under the switch lock, so a promotion that checked this account was
696
+ // on is written before this lands (see account-switch-lock).
697
+ await this.db.transaction(async (tx) => {
698
+ await takeAccountSwitchLock(tx, row.email);
699
+ await tx
700
+ .update(users)
701
+ .set({ deactivatedAt: new Date(), updatedAt: new Date() })
702
+ .where(and(eq(users.id, userId), isNull(users.deactivatedAt)));
703
+ });
509
704
  this.activeCache.delete(userId);
510
705
  return true;
511
706
  }