@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
@@ -0,0 +1,69 @@
1
+ import { afterAll, describe, expect, it } from 'vitest';
2
+ import { randomBytes } from 'node:crypto';
3
+ import { closeDb, createDb, type Database } from '../../database/connection.js';
4
+ import { runCoreMigrations } from '../../database/migrate.js';
5
+ import { coreMigrationsDir } from '../../../assets.js';
6
+ import { PG_ADMIN_URL, PG_TEST_TIMEOUT, dropScratchDatabases, scratchDatabaseUrl } from '../../../__tests__/pg-scratch.js';
7
+ import { AuthService } from '../auth.service.js';
8
+
9
+ /**
10
+ * Whether an account is NEW is answered by the upsert's own returned row,
11
+ * through Postgres's `xmax = 0` (see `CREATED` in auth.service.ts): zero on
12
+ * a row the statement inserted, set on one its `ON CONFLICT DO UPDATE`
13
+ * touched. No stub can stand in for that, so this crosses a real Postgres
14
+ * (see `pg-scratch.ts`): the invite's notification hangs on the answer, and
15
+ * a wrong one would either tell nobody or tell the same person twice.
16
+ */
17
+ const KEY = randomBytes(32).toString('base64');
18
+ const open: Database[] = [];
19
+
20
+ async function migratedDatabase(): Promise<Database> {
21
+ const db = createDb(await scratchDatabaseUrl('hexis_created'), { piiKey: KEY });
22
+ open.push(db);
23
+ await runCoreMigrations(db, coreMigrationsDir());
24
+ return db;
25
+ }
26
+
27
+ describe.skipIf(!PG_ADMIN_URL)('whether an account is new, on a real Postgres', () => {
28
+ afterAll(async () => {
29
+ for (const db of open) await closeDb(db);
30
+ await dropScratchDatabases();
31
+ }, PG_TEST_TIMEOUT);
32
+
33
+ it(
34
+ 'says created once per address: the insert, and never the conflict-update after it',
35
+ async () => {
36
+ const db = await migratedDatabase();
37
+ const auth = new AuthService(db, { jwtSecret: 'test-jwt-secret', adminEmail: '', adminPassword: '', allowedEmailDomains: [] });
38
+
39
+ // Without a password: the first write inserts, the second only updates.
40
+ const first = await auth.createAccountWithoutPassword('Lena@Acme.test', 'Lena');
41
+ expect(first.created).toBe(true);
42
+ expect(first.user).toMatchObject({ email: 'lena@acme.test', name: 'Lena' });
43
+ const again = await auth.createAccountWithoutPassword('lena@acme.test', 'Lena Park');
44
+ expect(again.created).toBe(false);
45
+ expect(again.user).toMatchObject({ id: first.user.id, name: 'Lena Park' });
46
+
47
+ // With a starting password: a new address is created with it...
48
+ const tom = await auth.createAccountWithStartingPassword('tom@acme.test', 'welcome-to-acme');
49
+ expect(tom).toMatchObject({ created: true, passwordSet: true, deactivated: false });
50
+ // ...an account that has a password keeps it, and is not new...
51
+ const tomAgain = await auth.createAccountWithStartingPassword('tom@acme.test', 'another-password');
52
+ expect(tomAgain).toMatchObject({ created: false, passwordSet: false, deactivated: false });
53
+ expect(tomAgain.user.id).toBe(tom.user.id);
54
+ // ...and an existing account with none gets it, through the conflict
55
+ // update: a password was set, and the account is still not new.
56
+ const lenaPassword = await auth.createAccountWithStartingPassword('lena@acme.test', 'welcome-to-acme');
57
+ expect(lenaPassword).toMatchObject({ created: false, passwordSet: true, deactivated: false });
58
+ expect(lenaPassword.user.id).toBe(first.user.id);
59
+
60
+ // What the rows say agrees: two accounts, both with a password now.
61
+ const accounts = await auth.listAccounts();
62
+ expect(accounts.map((a) => [a.email, a.hasPassword]).sort()).toEqual([
63
+ ['lena@acme.test', true],
64
+ ['tom@acme.test', true],
65
+ ]);
66
+ },
67
+ PG_TEST_TIMEOUT,
68
+ );
69
+ });
@@ -1,9 +1,13 @@
1
1
  import { describe, expect, it, vi } from 'vitest';
2
+ import { PgDialect } from 'drizzle-orm/pg-core';
3
+ import type { SQL } from 'drizzle-orm';
2
4
  import type { Database } from '../../database/connection.js';
3
5
  import type { CoreConfig } from '../../../core-config.js';
4
6
  import { AuthService } from '../auth.service.js';
5
7
  import { AccountAdmissionRefusedError, type IAccountAdmission } from '../account-admission.js';
6
8
  import { hashPassword, verifyPassword } from '../password-hash.js';
9
+ import { RECOVERY_BOT_EMAIL } from '../../workflow/recovery-bot.js';
10
+ import { DIRECTORY_SYNC_BOT_EMAIL } from '../../access/directory-sync-bot.js';
7
11
 
8
12
  /**
9
13
  * Minimal drizzle chain stub (same idiom as the secrets-vault tests): each
@@ -291,6 +295,102 @@ describe('AuthService.createAccount / changePassword', () => {
291
295
  const set = captured.set[0] as { passwordHash: string };
292
296
  expect(set.passwordHash.startsWith('scrypt:')).toBe(true);
293
297
  });
298
+
299
+ // The system-account guard sits on EVERY password write, not only the
300
+ // invite's starting password: a bot holding a hash could sign in.
301
+ it.each([RECOVERY_BOT_EMAIL, DIRECTORY_SYNC_BOT_EMAIL])(
302
+ 'createAccount refuses a password for the platform account %s, writing nothing',
303
+ async (email) => {
304
+ const { db } = makeFakeDb([]);
305
+ await expect(
306
+ new AuthService(db, makeConfig()).createAccount(email, undefined, 'brand-new-pass'),
307
+ ).rejects.toThrow('System accounts cannot be given a password');
308
+ expect(db.insert).not.toHaveBeenCalled();
309
+ },
310
+ );
311
+
312
+ it.each([RECOVERY_BOT_EMAIL, DIRECTORY_SYNC_BOT_EMAIL])(
313
+ 'changePassword refuses the platform account %s, writing nothing',
314
+ async (email) => {
315
+ const { db } = makeFakeDb([[{ ...ROW, email, passwordHash: null }]]);
316
+ await expect(
317
+ new AuthService(db, makeConfig()).changePassword('user-1', undefined, 'first-password'),
318
+ ).rejects.toThrow('System accounts cannot be given a password');
319
+ expect(db.update).not.toHaveBeenCalled();
320
+ },
321
+ );
322
+ });
323
+
324
+ describe('AuthService.createAccountWithStartingPassword — an invite never replaces a password', () => {
325
+ it('sets it in ONE upsert that writes only over an account with no password that is switched on', async () => {
326
+ const { db, captured } = makeFakeDb([[{ ...ROW, passwordHash: 'scrypt:x' }]]);
327
+ const result = await new AuthService(db, makeConfig()).createAccountWithStartingPassword(
328
+ 'Alice@Example.com',
329
+ 'starting-pass-1',
330
+ );
331
+ expect(result.passwordSet).toBe(true);
332
+ expect(result.user.email).toBe('alice@example.com');
333
+ const conflict = captured.conflict[0] as { set: { passwordHash: string; name?: string }; setWhere: unknown };
334
+ expect(conflict.set.passwordHash.startsWith('scrypt:')).toBe(true);
335
+ // The test and the write are one statement: the guard rides on the upsert,
336
+ // and writes only where there is no password AND the account is on.
337
+ const guard = new PgDialect().sqlToQuery(conflict.setWhere as SQL);
338
+ expect(guard.sql).toBe('("users"."password_hash" is null and "users"."deactivated_at" is null)');
339
+ expect(guard.params).toEqual([]);
340
+ expect(conflict.set.name).toBeUndefined();
341
+ expect(db.select).not.toHaveBeenCalled();
342
+ });
343
+
344
+ it('leaves an account the guard refused as it was, and says so', async () => {
345
+ // The upsert's guard refused the row (it has a password now): nothing returned, the row is read back.
346
+ const { db, captured } = makeFakeDb([[], [{ ...ROW, passwordHash: 'scrypt:own', deactivatedAt: null }]]);
347
+ const result = await new AuthService(db, makeConfig()).createAccountWithStartingPassword(
348
+ 'alice@example.com',
349
+ 'starting-pass-1',
350
+ );
351
+ expect(result.passwordSet).toBe(false);
352
+ expect(result.deactivated).toBe(false);
353
+ expect(result.user.email).toBe('alice@example.com');
354
+ expect(captured.set).toEqual([]);
355
+ });
356
+
357
+ it('tells a switched-off account apart from one with its own password', async () => {
358
+ const { db } = makeFakeDb([[], [{ ...ROW, passwordHash: null, deactivatedAt: new Date('2026-09-01T00:00:00Z') }]]);
359
+ const result = await new AuthService(db, makeConfig()).createAccountWithStartingPassword(
360
+ 'alice@example.com',
361
+ 'starting-pass-1',
362
+ );
363
+ expect(result).toMatchObject({ passwordSet: false, deactivated: true });
364
+ });
365
+
366
+ it.each([RECOVERY_BOT_EMAIL, DIRECTORY_SYNC_BOT_EMAIL])(
367
+ 'refuses the platform account %s, writing nothing',
368
+ async (email) => {
369
+ const { db } = makeFakeDb([]);
370
+ await expect(
371
+ new AuthService(db, makeConfig()).createAccountWithStartingPassword(email, 'starting-pass-1'),
372
+ ).rejects.toThrow('System accounts cannot be given a password');
373
+ expect(db.insert).not.toHaveBeenCalled();
374
+ expect(db.select).not.toHaveBeenCalled();
375
+ },
376
+ );
377
+
378
+ it('leaves the deployment admin to the environment password, writing nothing', async () => {
379
+ const { db } = makeFakeDb([[{ ...ROW, email: 'root@example.com' }]]);
380
+ const result = await new AuthService(
381
+ db,
382
+ makeConfig({ adminEmail: 'root@example.com', adminPassword: 'sup3r-secret' }),
383
+ ).createAccountWithStartingPassword('root@example.com', 'starting-pass-1');
384
+ expect(result.passwordSet).toBe(false);
385
+ expect(db.insert).not.toHaveBeenCalled();
386
+ });
387
+
388
+ it('enforces the password policy', async () => {
389
+ const { db } = makeFakeDb([]);
390
+ await expect(
391
+ new AuthService(db, makeConfig()).createAccountWithStartingPassword('alice@example.com', 'short'),
392
+ ).rejects.toThrow(/at least/);
393
+ });
294
394
  });
295
395
 
296
396
  /**
@@ -539,7 +639,7 @@ describe('AuthService.listAccounts', () => {
539
639
  expect(json).not.toContain('sup3r-secret');
540
640
  for (const account of [...withHash, ...withoutHash]) {
541
641
  expect(Object.keys(account).sort()).toEqual(
542
- ['createdAt', 'deactivatedAt', 'email', 'hasPassword', 'id', 'isEnvAdmin', 'isSystem', 'name'],
642
+ ['createdAt', 'deactivatedAt', 'email', 'hasPassword', 'id', 'isEnvAdmin', 'isOwner', 'isSystem', 'name', 'ownerCanBeDeleted'],
543
643
  );
544
644
  }
545
645
  });
@@ -552,6 +652,64 @@ describe('AuthService.listAccounts', () => {
552
652
  });
553
653
  });
554
654
 
655
+ describe('AuthService — the owner', () => {
656
+ // A deployment with a server-held owner password (self-hosted) and one
657
+ // without (the cloud's configuration: `adminPassword: ''`).
658
+ const WITH_PASSWORD = makeConfig({ adminEmail: 'root@example.com', adminPassword: 'sup3r-secret' });
659
+ const WITHOUT_PASSWORD = makeConfig({ adminEmail: 'root@example.com', adminPassword: '' });
660
+
661
+ it('recognises the owner by address alone, with or without ADMIN_PASSWORD, compared canonically', () => {
662
+ for (const config of [WITH_PASSWORD, WITHOUT_PASSWORD]) {
663
+ const svc = new AuthService(makeFakeDb([]).db, config);
664
+ expect(svc.isOwnerEmail('root@example.com')).toBe(true);
665
+ expect(svc.isOwnerEmail(' Root@Example.COM ')).toBe(true);
666
+ expect(svc.isOwnerEmail('alice@example.com')).toBe(false);
667
+ expect(svc.isOwnerEmail('')).toBe(false);
668
+ expect(svc.isOwnerEmail({ evil: true } as unknown as string)).toBe(false);
669
+ expect(svc.isOwnerEmail(undefined as unknown as string)).toBe(false);
670
+ }
671
+ });
672
+
673
+ it('has no owner when ADMIN_EMAIL is unset, so a blank address is never one', () => {
674
+ const svc = new AuthService(makeFakeDb([]).db, makeConfig({ adminEmail: '', adminPassword: '' }));
675
+ expect(svc.isOwnerEmail('')).toBe(false);
676
+ expect(svc.isOwnerEmail('root@example.com')).toBe(false);
677
+ });
678
+
679
+ it("refuses to delete the owner's account unless they can sign back in with the server's password", () => {
680
+ const refusal = "The owner's account can't be deleted: they would have no way to sign in.";
681
+ // The cloud: no server-held password.
682
+ expect(() => new AuthService(makeFakeDb([]).db, WITHOUT_PASSWORD).assertDeletable('root@example.com')).toThrow(refusal);
683
+ // A password the server holds, but password sign-in is switched off.
684
+ const passwordOff = makeConfig({ adminEmail: 'root@example.com', adminPassword: 'sup3r-secret', loginPasswordEnabled: false });
685
+ expect(() => new AuthService(makeFakeDb([]).db, passwordOff).assertDeletable('Root@example.com')).toThrow(refusal);
686
+ // Self-hosted with ADMIN_PASSWORD and password sign-in on: allowed, as today.
687
+ expect(() => new AuthService(makeFakeDb([]).db, WITH_PASSWORD).assertDeletable('root@example.com')).not.toThrow();
688
+ // Anyone else, on either deployment.
689
+ for (const config of [WITH_PASSWORD, WITHOUT_PASSWORD]) {
690
+ expect(() => new AuthService(makeFakeDb([]).db, config).assertDeletable('alice@example.com')).not.toThrow();
691
+ }
692
+ });
693
+
694
+ it('the accounts list says who the owner is and whether their account may be deleted', async () => {
695
+ const rows = [
696
+ { ...ROW, email: 'root@example.com', passwordHash: null, createdAt: new Date() },
697
+ { ...ROW, id: 'user-2', email: 'alice@example.com', passwordHash: null, createdAt: new Date() },
698
+ ];
699
+ const pick = (a: { email: string; isOwner: boolean; ownerCanBeDeleted: boolean }) => [a.email, a.isOwner, a.ownerCanBeDeleted];
700
+ const withPassword = await new AuthService(makeFakeDb([rows]).db, WITH_PASSWORD).listAccounts();
701
+ expect(withPassword.map(pick)).toEqual([
702
+ ['alice@example.com', false, false],
703
+ ['root@example.com', true, true],
704
+ ]);
705
+ const withoutPassword = await new AuthService(makeFakeDb([rows]).db, WITHOUT_PASSWORD).listAccounts();
706
+ expect(withoutPassword.map(pick)).toEqual([
707
+ ['alice@example.com', false, false],
708
+ ['root@example.com', true, false],
709
+ ]);
710
+ });
711
+ });
712
+
555
713
  /**
556
714
  * `ALLOWED_EMAIL_DOMAINS` is the SSO allow-list, and only that.
557
715
  *
@@ -0,0 +1,69 @@
1
+ import type { AuthUser } from '@bevel-software/platform-shared';
2
+ import { logger } from '../../shared/logging.js';
3
+
4
+ const log = logger('accounts');
5
+
6
+ /**
7
+ * An admin invited somebody: a NEW account was made for the address. Never
8
+ * raised for an account that was already there (an invite that only set a
9
+ * starting password, a password reset, a rename), and never for a first
10
+ * single sign-on, which provisions by itself: the person is already in.
11
+ */
12
+ export interface AccountInvitedEvent {
13
+ /** The new account, as the admin's answer carries it. */
14
+ account: Pick<AuthUser, 'id' | 'email' | 'name'>;
15
+ /** The admin who invited them. */
16
+ invitedBy: Pick<AuthUser, 'id' | 'email' | 'name'>;
17
+ /**
18
+ * Whether the account was created with a starting password the admin
19
+ * typed. The password itself never travels: a listener that writes to the
20
+ * person says it comes from the admin separately.
21
+ */
22
+ passwordSet: boolean;
23
+ }
24
+
25
+ /**
26
+ * A listener answers how it reached the person — a non-empty channel name,
27
+ * `'email'` say — or null when it did not (nothing configured, the send
28
+ * failed). It is awaited: the admin's answer says who was notified,
29
+ * truthfully, so a listener bounds its own wait. A throw is logged and
30
+ * counts as null; it never fails the invite. An empty name is no channel and
31
+ * counts as null too.
32
+ */
33
+ export type AccountInvitedHook = (event: AccountInvitedEvent) => Promise<string | null>;
34
+
35
+ /**
36
+ * Core's seam for "an account was invited": a deployment (the cloud with its
37
+ * mail, an enterprise overlay) registers how to tell the person, and core
38
+ * says in its answer whether somebody did. Core registers nothing, so a
39
+ * core-only deployment notifies nobody and the admin is handed the link and
40
+ * the message to send, as before.
41
+ */
42
+ export interface IAccountHooks {
43
+ onAccountInvited(hook: AccountInvitedHook): void;
44
+ /** Run every listener in registration order; the channels that reached the person, in that order. */
45
+ runAccountInvited(event: AccountInvitedEvent): Promise<string[]>;
46
+ }
47
+
48
+ export class AccountHooks implements IAccountHooks {
49
+ private readonly invited: AccountInvitedHook[] = [];
50
+
51
+ onAccountInvited(hook: AccountInvitedHook): void {
52
+ this.invited.push(hook);
53
+ }
54
+
55
+ async runAccountInvited(event: AccountInvitedEvent): Promise<string[]> {
56
+ const notified: string[] = [];
57
+ for (const hook of this.invited) {
58
+ try {
59
+ const channel = await hook(event);
60
+ if (channel !== null && channel !== '') notified.push(channel);
61
+ } catch (err) {
62
+ // The account exists whether or not the person was told: the admin's
63
+ // answer says they were not, and the admin sends the link themselves.
64
+ log.error('an account-invited listener failed; the person was not notified by it:', { err });
65
+ }
66
+ }
67
+ return notified;
68
+ }
69
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Auth's contract for a write that must not land on a switched-off account:
3
+ * `whileSwitchedOn` runs `write` only while `email` is not switched off, and
4
+ * keeps it so until `write` has finished. A switch-off either committed
5
+ * before the check (`write` is refused with a 409 `deactivated` and never
6
+ * runs) or waits until `write` is done. An address with no account yet counts
7
+ * as switched on.
8
+ *
9
+ * Implemented by `AuthService`; other modules take this contract, not the
10
+ * service, so how auth coordinates switch-offs stays its own.
11
+ */
12
+ export interface IAccountSwitchGuard {
13
+ whileSwitchedOn(email: string, write: () => Promise<void>): Promise<void>;
14
+ }
@@ -0,0 +1,58 @@
1
+ import { sql } from 'drizzle-orm';
2
+ import type { Database } from '../database/connection.js';
3
+ import { canonicalEmail } from '../../shared/email-identity.js';
4
+ import { WorkflowDomainError } from '../../shared/domain-errors.js';
5
+ import { pgErrorCode } from '../database/pg-error.js';
6
+
7
+ /** The handle `db.transaction` hands its callback. */
8
+ type Tx = Parameters<Parameters<Database['transaction']>[0]>[0];
9
+
10
+ /**
11
+ * How long a switch-off or a promotion waits for the address's lock. The
12
+ * holder may be a promotion committing `roles.yaml` (a git commit and push,
13
+ * seconds), so the wait is generous; it is bounded all the same, for the
14
+ * reason `database/advisory-lock.ts` gives: a waiter blocked for good sits
15
+ * on a pooled connection, and a wedged holder would take the pool with it.
16
+ */
17
+ export const ACCOUNT_SWITCH_LOCK_WAIT_MS = 15_000;
18
+
19
+ /** Postgres raises this when `lock_timeout` expires while waiting for a lock. */
20
+ const LOCK_NOT_AVAILABLE = '55P03';
21
+
22
+ /** What a caller is told when the wait ran out: a 409, to try again shortly. */
23
+ export const ACCOUNT_BUSY_MESSAGE = 'Another change to this account is still being saved; try again in a moment.';
24
+
25
+ /**
26
+ * Hold, for the rest of `tx`, the one lock that orders an address's switch-off
27
+ * against a write that must not land on a switched-off account (the invite's
28
+ * Admin promotion).
29
+ *
30
+ * A row lock cannot do it: an address with no account yet has no row, and an
31
+ * account can be created switched off (single sign-on waiting for an admin)
32
+ * or created and switched off while the promotion is being written. So the
33
+ * lock is a transaction-scoped advisory lock on the canonical address, taken
34
+ * by every write that switches an account off and by the promotion, before
35
+ * either reads the row. Whichever comes second waits for the first to commit,
36
+ * then reads what it committed (READ COMMITTED takes a fresh snapshot per
37
+ * statement). A different address that hashes to the same key only waits.
38
+ *
39
+ * The wait is bounded by a transaction-local `lock_timeout`
40
+ * ({@link ACCOUNT_SWITCH_LOCK_WAIT_MS}), which the transaction's end reverts
41
+ * with everything else; running out of it is answered as a 409 `account-busy`.
42
+ */
43
+ export async function takeAccountSwitchLock(
44
+ tx: Pick<Tx, 'execute'>,
45
+ email: string,
46
+ waitMs: number = ACCOUNT_SWITCH_LOCK_WAIT_MS,
47
+ ): Promise<void> {
48
+ await tx.execute(sql`select set_config('lock_timeout', ${String(waitMs)}, true)`);
49
+ try {
50
+ await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${`account-switch:${canonicalEmail(email)}`}))`);
51
+ } catch (err) {
52
+ // Drizzle wraps the driver's error, so the SQLSTATE is on its cause.
53
+ if (pgErrorCode(err) === LOCK_NOT_AVAILABLE) {
54
+ throw new WorkflowDomainError(ACCOUNT_BUSY_MESSAGE, 409, { kind: 'account-busy' });
55
+ }
56
+ throw err;
57
+ }
58
+ }
@@ -6,12 +6,14 @@ import { printable } from '../../shared/printable.js';
6
6
  const log = logger('accounts');
7
7
  import type { AuthUser } from '@bevel-software/platform-shared';
8
8
  import type { AuthService } from './auth.service.js';
9
+ import type { IAccountHooks } from './account-hooks.js';
9
10
  import { AccountAdmissionRefusedError, AccountChangeRefusedError } from './account-admission.js';
10
11
  import { erasedAccountId, type IAccountErasureService } from './account-erasure.service.js';
11
12
  import type { IAdminAccessService } from '../admin/admin.interface.js';
12
13
  import type { UserAccessRemovalService } from '../access/user-access-removal.service.js';
13
14
  import { sendError } from '../access/admin-route-helpers.js';
14
15
  import { WorkflowDomainError } from '../../shared/domain-errors.js';
16
+ import { canonicalEmail } from '../../shared/email-identity.js';
15
17
  import './auth.middleware.js'; // Express Request augmentation
16
18
 
17
19
  /**
@@ -26,9 +28,22 @@ import './auth.middleware.js'; // Express Request augmentation
26
28
  * bootstrap admin (always-admin, see AdminAccessService).
27
29
  */
28
30
  export function createAccountRoutes(
29
- authService: Pick<AuthService, 'listAccounts' | 'createAccount' | 'getUserById' | 'deactivate' | 'reactivate'>,
31
+ authService: Pick<
32
+ AuthService,
33
+ | 'listAccounts'
34
+ | 'createAccount'
35
+ | 'createAccountWithoutPassword'
36
+ | 'createAccountWithStartingPassword'
37
+ | 'getUserById'
38
+ | 'deactivate'
39
+ | 'reactivate'
40
+ | 'isOwnerEmail'
41
+ | 'assertDeletable'
42
+ >,
30
43
  adminAccess: IAdminAccessService,
31
44
  accountErasure: Pick<IAccountErasureService, 'eraseUser'>,
45
+ /** Who tells an invited person (see {@link IAccountHooks}); core registers nobody. */
46
+ accountHooks: Pick<IAccountHooks, 'runAccountInvited'>,
32
47
  accessRemoval?: Pick<UserAccessRemovalService, 'report' | 'assertRemovable' | 'remove' | 'filesNaming'>,
33
48
  ): express.Router {
34
49
  const router = express.Router();
@@ -42,7 +57,8 @@ export function createAccountRoutes(
42
57
  };
43
58
 
44
59
  // GET /api/admin/accounts — id, email, name, whether a password hash is
45
- // stored, and whether the account is the env bootstrap admin.
60
+ // stored, whether the account is the env bootstrap admin, and whether it is
61
+ // an owner (and, if so, whether it may be deleted).
46
62
  router.get('/admin/accounts', requireAdmin, async (_req, res) => {
47
63
  res.json({ accounts: await authService.listAccounts() });
48
64
  });
@@ -51,11 +67,16 @@ export function createAccountRoutes(
51
67
  // (or reset the password of an existing one; upsert-by-email is deliberate,
52
68
  // see AuthService.createAccount). Without a password the account is for
53
69
  // single sign-on: the person finds it waiting when they first sign in.
70
+ // With `keepExistingPassword: true` (an invite's starting password) the
71
+ // password goes only to a new account or a switched-on one with none; the
72
+ // reply's `passwordSet` says whether it did, and `deactivated` whether a
73
+ // refusal was because the account is switched off.
54
74
  router.post('/admin/accounts', requireAdmin, async (req, res) => {
55
- const { email, name, password } = req.body as {
75
+ const { email, name, password, keepExistingPassword } = req.body as {
56
76
  email?: string;
57
77
  name?: string;
58
78
  password?: unknown;
79
+ keepExistingPassword?: unknown;
59
80
  };
60
81
  if (!email) {
61
82
  res.status(400).json({ error: 'email is required' });
@@ -66,14 +87,80 @@ export function createAccountRoutes(
66
87
  res.status(400).json({ error: 'password must be a string' });
67
88
  return;
68
89
  }
90
+ // Fail closed: anything but a boolean is a mistake, never "overwrite".
91
+ if (keepExistingPassword !== undefined && typeof keepExistingPassword !== 'boolean') {
92
+ res.status(400).json({ error: 'keepExistingPassword must be a boolean' });
93
+ return;
94
+ }
95
+ if (keepExistingPassword === true && typeof password !== 'string') {
96
+ res.status(400).json({ error: 'keepExistingPassword needs a password' });
97
+ return;
98
+ }
99
+ // Another admin setting an owner's password could then sign in as the
100
+ // owner. The owner sets their own on the Account page; an owner account
101
+ // without a password (restoring one, say; an empty one counts as none,
102
+ // as in createAccount) is still created here. An invite's starting
103
+ // password is a password too: the owner is invited by nobody.
104
+ if (
105
+ password &&
106
+ authService.isOwnerEmail(email) &&
107
+ canonicalEmail(email) !== canonicalEmail(req.userEmail ?? '')
108
+ ) {
109
+ res.status(400).json({ error: "The owner's password can't be set by another admin; the owner sets it on their Account page." });
110
+ return;
111
+ }
69
112
  try {
113
+ // A NEW account is an invitation, and the deployment's listeners say
114
+ // how the person was told (`notified`): the answer the admin reads is
115
+ // true to what happened, so the dialog can say "emailed" or hand over
116
+ // the link. An account that was already there is not invited again.
117
+ const invited = async (account: AuthUser, created: boolean, passwordSet: boolean): Promise<string[]> => {
118
+ if (!created) return [];
119
+ // The inviting admin, by name when their account can be read. The
120
+ // account is already made: a failed read falls back to the identity
121
+ // the request carries rather than answering an error a retry could
122
+ // not make good (the account is not new the second time).
123
+ const requester = { id: req.userId ?? '', email: req.userEmail ?? '', name: '' };
124
+ const by = req.userId
125
+ ? await authService.getUserById(req.userId).catch((err: unknown) => {
126
+ log.warn('the inviting admin could not be read; the invite names them by address:', { err });
127
+ return null;
128
+ })
129
+ : null;
130
+ return accountHooks.runAccountInvited({
131
+ account: { id: account.id, email: account.email, name: account.name },
132
+ invitedBy: by ?? requester,
133
+ passwordSet,
134
+ });
135
+ };
136
+ if (keepExistingPassword === true && typeof password === 'string') {
137
+ const { user, created, passwordSet, deactivated } = await authService.createAccountWithStartingPassword(email, password);
138
+ const notified = await invited(user, created, passwordSet);
139
+ res.status(201).json({ ...user, passwordSet, deactivated, notified });
140
+ return;
141
+ }
142
+ if (!password) {
143
+ const { user, created } = await authService.createAccountWithoutPassword(email, name);
144
+ const notified = await invited(user, created, false);
145
+ res.status(201).json({ ...user, notified });
146
+ return;
147
+ }
148
+ // Set or reset password: an account that is here already, or one an
149
+ // admin makes with a password from the API. Not an invitation the
150
+ // listeners are told of: the dialog that invites never sends this form.
70
151
  const user = await authService.createAccount(email, name, password);
71
152
  res.status(201).json(user);
72
153
  } catch (error) {
73
154
  const msg = error instanceof Error ? error.message : 'Unknown error';
74
155
  // The deployment has no place for the account (a seat limit, say): the
75
156
  // port's own words, as a refusal rather than a malformed request.
76
- res.status(error instanceof AccountAdmissionRefusedError ? 403 : 400).json({ error: msg });
157
+ // `kind` tells it apart from `requireAdmin`'s 403, which a caller must
158
+ // not read as "no seat left".
159
+ if (error instanceof AccountAdmissionRefusedError) {
160
+ res.status(403).json({ error: msg, kind: 'admission' });
161
+ return;
162
+ }
163
+ res.status(400).json({ error: msg });
77
164
  }
78
165
  });
79
166
 
@@ -100,6 +187,14 @@ export function createAccountRoutes(
100
187
  res.status(400).json({ error: error.message });
101
188
  return;
102
189
  }
190
+ // The address's switch lock is held by a change still being saved (an
191
+ // invite's promotion committing roles.yaml, say): the 409 the lock
192
+ // promises, with its words, so the admin tries again rather than
193
+ // reading an outage.
194
+ if (error instanceof WorkflowDomainError) {
195
+ res.status(error.status).json({ error: error.message, ...(error.payload ?? {}) });
196
+ return;
197
+ }
103
198
  log.error('switching an account off failed:', { err: error });
104
199
  res.status(500).json({ error: 'Could not switch this account off' });
105
200
  }
@@ -107,7 +202,8 @@ export function createAccountRoutes(
107
202
 
108
203
  // POST /api/admin/accounts/:userId/reactivate — switch it back on, if the
109
204
  // deployment has room: the admission port is asked, as for a new account
110
- // (a host that sells seats answers from its plan; 403 with its words).
205
+ // (a host that sells seats answers from its plan; 403 with its words and
206
+ // `kind: 'admission'`).
111
207
  router.post('/admin/accounts/:userId/reactivate', requireAdmin, async (req, res) => {
112
208
  const userId = String(req.params.userId);
113
209
  try {
@@ -119,7 +215,7 @@ export function createAccountRoutes(
119
215
  res.status(204).end();
120
216
  } catch (error) {
121
217
  if (error instanceof AccountAdmissionRefusedError) {
122
- res.status(403).json({ error: error.message });
218
+ res.status(403).json({ error: error.message, kind: 'admission' });
123
219
  return;
124
220
  }
125
221
  log.error('switching an account on failed:', { err: error });
@@ -174,14 +270,27 @@ export function createAccountRoutes(
174
270
  // for the removal below — never logged, never returned.
175
271
  let email: string | null = null;
176
272
  let actor: AuthUser | null = null;
177
- if (removeFromAccess) {
273
+ try {
274
+ const user = await authService.getUserById(userId);
275
+ if (!user) {
276
+ res.status(404).json({ error: 'No such user' });
277
+ return;
278
+ }
279
+ // An owner who could not sign back in afterwards is refused for every
280
+ // caller, whether or not the access option is ticked.
281
+ authService.assertDeletable(user.email);
282
+ if (removeFromAccess) email = user.email;
283
+ } catch (err) {
284
+ if (err instanceof AccountChangeRefusedError) {
285
+ res.status(400).json({ error: err.message });
286
+ return;
287
+ }
288
+ log.error('looking up the account to erase failed:', { err });
289
+ res.status(500).json({ error: 'Failed to erase user' });
290
+ return;
291
+ }
292
+ if (removeFromAccess && email !== null) {
178
293
  try {
179
- const user = await authService.getUserById(userId);
180
- if (!user) {
181
- res.status(404).json({ error: 'No such user' });
182
- return;
183
- }
184
- email = user.email;
185
294
  // The existing guards: the deployment owner and the last Admin are
186
295
  // refused up front, before anything is erased.
187
296
  await accessRemoval!.assertRemovable(email);
@@ -30,7 +30,7 @@ export const AUTH_COOKIE_NAME = 'bevel_token';
30
30
  * beats the dep surface of a middleware. Returns `null` if the cookie
31
31
  * is absent or empty.
32
32
  */
33
- function readAuthCookie(req: Request): string | null {
33
+ export function readAuthCookie(req: Request): string | null {
34
34
  const header = req.headers.cookie;
35
35
  if (!header) return null;
36
36
  for (const segment of header.split(';')) {