@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,78 @@
1
+ import { afterAll, describe, expect, it } from 'vitest';
2
+ import { randomBytes } from 'node:crypto';
3
+ import pg from 'pg';
4
+ import { closeDb, createDb, type Database } from '../../database/connection.js';
5
+ import { runCoreMigrations } from '../../database/migrate.js';
6
+ import { coreMigrationsDir } from '../../../assets.js';
7
+ import { PG_ADMIN_URL, PG_TEST_TIMEOUT, dropScratchDatabases, scratchDatabaseUrl } from '../../../__tests__/pg-scratch.js';
8
+ import { WorkflowDomainError } from '../../../shared/domain-errors.js';
9
+ import { AuthService } from '../auth.service.js';
10
+ import { ACCOUNT_BUSY_MESSAGE, takeAccountSwitchLock } from '../account-switch-lock.js';
11
+
12
+ /**
13
+ * The account switch lock's promise, against a real Postgres (see
14
+ * `pg-scratch.ts`): a wait that runs out is a 409 `account-busy`, never the
15
+ * driver's error. Drizzle wraps that error (the SQLSTATE `55P03` is on its
16
+ * cause), which no stub of the driver shows, so the contention is real here:
17
+ * another connection holds the address's lock while the lock is asked for.
18
+ */
19
+ const KEY = randomBytes(32).toString('base64');
20
+ const open: Database[] = [];
21
+ const holders: pg.Client[] = [];
22
+
23
+ async function migratedDatabase(): Promise<{ db: Database; url: string }> {
24
+ const url = await scratchDatabaseUrl('hexis_switch');
25
+ const db = createDb(url, { piiKey: KEY });
26
+ open.push(db);
27
+ await runCoreMigrations(db, coreMigrationsDir());
28
+ return { db, url };
29
+ }
30
+
31
+ /** Another connection holding `email`'s switch lock in an open transaction, as a promotion mid-write does. */
32
+ async function holdLock(url: string, email: string): Promise<pg.Client> {
33
+ const holder = new pg.Client({ connectionString: url });
34
+ await holder.connect();
35
+ holders.push(holder);
36
+ await holder.query('begin');
37
+ await holder.query('select pg_advisory_xact_lock(hashtext($1))', [`account-switch:${email}`]);
38
+ return holder;
39
+ }
40
+
41
+ describe.skipIf(!PG_ADMIN_URL)('the account switch lock under contention, on a real Postgres', () => {
42
+ afterAll(async () => {
43
+ for (const holder of holders) await holder.end().catch(() => undefined);
44
+ for (const db of open) await closeDb(db);
45
+ await dropScratchDatabases();
46
+ }, PG_TEST_TIMEOUT);
47
+
48
+ it(
49
+ "answers a wait that runs out as the 409 account-busy, through Drizzle's wrapping",
50
+ async () => {
51
+ const { db, url } = await migratedDatabase();
52
+ const holder = await holdLock(url, 'alice@acme.test');
53
+ const refusal = db.transaction((tx) => takeAccountSwitchLock(tx, 'Alice@Acme.test', 200));
54
+ await expect(refusal).rejects.toBeInstanceOf(WorkflowDomainError);
55
+ await expect(refusal).rejects.toMatchObject({ status: 409, message: ACCOUNT_BUSY_MESSAGE, payload: { kind: 'account-busy' } });
56
+ // Released, the lock is taken at once.
57
+ await holder.query('commit');
58
+ await expect(db.transaction((tx) => takeAccountSwitchLock(tx, 'alice@acme.test', 200))).resolves.toBeUndefined();
59
+ },
60
+ PG_TEST_TIMEOUT,
61
+ );
62
+
63
+ it(
64
+ 'switching an account off while a promotion holds its lock is refused as account-busy, and changes nothing',
65
+ async () => {
66
+ const { db, url } = await migratedDatabase();
67
+ const auth = new AuthService(db, { jwtSecret: 'test-jwt-secret', adminEmail: '', adminPassword: '', allowedEmailDomains: [] });
68
+ const { user } = await auth.createAccountWithoutPassword('lee@acme.test', 'Lee');
69
+ const holder = await holdLock(url, 'lee@acme.test');
70
+ // The real wait (15 s): what an admin's switch-off meets.
71
+ await expect(auth.deactivate(user.id)).rejects.toMatchObject({ status: 409, payload: { kind: 'account-busy' } });
72
+ await holder.query('commit');
73
+ const [lee] = (await auth.listAccounts()).filter((a) => a.email === 'lee@acme.test');
74
+ expect(lee?.deactivatedAt).toBeNull();
75
+ },
76
+ PG_TEST_TIMEOUT,
77
+ );
78
+ });
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { PgDialect } from 'drizzle-orm/pg-core';
3
+ import type { SQL } from 'drizzle-orm';
4
+ import { WorkflowDomainError } from '../../../shared/domain-errors.js';
5
+ import { ACCOUNT_BUSY_MESSAGE, ACCOUNT_SWITCH_LOCK_WAIT_MS, takeAccountSwitchLock } from '../account-switch-lock.js';
6
+
7
+ /** A transaction handle that records what it ran, and fails the lock statement when told to. */
8
+ function fakeTx(failLockWith?: { code: string; message: string }) {
9
+ const dialect = new PgDialect();
10
+ const ran: string[] = [];
11
+ const execute = vi.fn(async (query: SQL) => {
12
+ const { sql, params } = dialect.sqlToQuery(query);
13
+ ran.push(`${sql} ${JSON.stringify(params)}`);
14
+ if (failLockWith && sql.includes('pg_advisory_xact_lock')) {
15
+ throw Object.assign(new Error(failLockWith.message), { code: failLockWith.code });
16
+ }
17
+ return [];
18
+ });
19
+ return { tx: { execute }, ran };
20
+ }
21
+
22
+ describe('takeAccountSwitchLock', () => {
23
+ it('bounds the wait with a transaction-local lock_timeout, set before the lock is asked for', async () => {
24
+ const { tx, ran } = fakeTx();
25
+ await takeAccountSwitchLock(tx, 'Alice@Example.com');
26
+ expect(ran).toEqual([
27
+ `select set_config('lock_timeout', $1, true) ["${ACCOUNT_SWITCH_LOCK_WAIT_MS}"]`,
28
+ 'select pg_advisory_xact_lock(hashtext($1)) ["account-switch:alice@example.com"]',
29
+ ]);
30
+ });
31
+
32
+ it('answers a wait that ran out as a 409 account-busy, in the words the admin is told', async () => {
33
+ const { tx } = fakeTx({ code: '55P03', message: 'canceling statement due to lock timeout' });
34
+ const refusal = takeAccountSwitchLock(tx, 'alice@example.com');
35
+ await expect(refusal).rejects.toBeInstanceOf(WorkflowDomainError);
36
+ await expect(refusal).rejects.toMatchObject({ status: 409, message: ACCOUNT_BUSY_MESSAGE, payload: { kind: 'account-busy' } });
37
+ });
38
+
39
+ it('lets any other failure of the lock through unchanged', async () => {
40
+ const { tx } = fakeTx({ code: '57P01', message: 'terminating connection due to administrator command' });
41
+ await expect(takeAccountSwitchLock(tx, 'alice@example.com')).rejects.toThrow('terminating connection');
42
+ });
43
+ });
@@ -2,8 +2,10 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
2
2
  import express from 'express';
3
3
  import type { Server } from 'node:http';
4
4
  import { createAccountRoutes } from '../account.routes.js';
5
+ import type { AccountInvitedEvent } from '../account-hooks.js';
5
6
  import { AuthService } from '../auth.service.js';
6
7
  import { AccountAdmissionRefusedError, AccountChangeRefusedError } from '../account-admission.js';
8
+ import { WorkflowDomainError } from '../../../shared/domain-errors.js';
7
9
  import { hashPassword } from '../password-hash.js';
8
10
  import type { Database } from '../../database/connection.js';
9
11
  import type { IAdminAccessService } from '../../admin/admin.interface.js';
@@ -13,8 +15,18 @@ const authService = {
13
15
  { id: 'u1', email: 'a@example.com', name: 'A', hasPassword: true, createdAt: new Date() },
14
16
  ]),
15
17
  createAccount: vi.fn(async (email: string) => ({ id: 'u2', email, name: 'B' })),
18
+ createAccountWithoutPassword: vi.fn(async (email: string) => ({ user: { id: 'u2', email, name: 'B' }, created: true })),
19
+ createAccountWithStartingPassword: vi.fn(async (email: string) => ({
20
+ user: { id: 'u2', email, name: 'B' },
21
+ created: false,
22
+ passwordSet: false,
23
+ deactivated: false,
24
+ })),
16
25
  deactivate: vi.fn(async (userId: string) => userId !== 'missing'),
17
26
  reactivate: vi.fn(async (userId: string) => userId !== 'missing'),
27
+ getUserById: vi.fn(async (id: string) => (id === 'missing' ? null : { id, email: `${id}@example.com`, name: id })),
28
+ isOwnerEmail: vi.fn((email: string) => email.trim().toLowerCase() === 'root@example.com'),
29
+ assertDeletable: vi.fn(() => {}),
18
30
  } as unknown as AuthService;
19
31
 
20
32
  // Satisfies the route's narrow Pick<IAccountErasureService, 'eraseUser'>
@@ -23,6 +35,11 @@ const accountErasure = {
23
35
  eraseUser: vi.fn<(userId: string, opts?: { erasureId?: string }) => Promise<boolean>>(async () => true),
24
36
  };
25
37
 
38
+ /** The invite listeners: nobody registered unless a test says so (see "an invite tells the listeners"). */
39
+ const accountHooks = {
40
+ runAccountInvited: vi.fn<(event: AccountInvitedEvent) => Promise<string[]>>(async () => []),
41
+ };
42
+
26
43
  function makeApp(opts: { admin: boolean; email?: string }) {
27
44
  const adminAccess: IAdminAccessService = {
28
45
  isAdmin: vi.fn(async () => opts.admin),
@@ -36,7 +53,7 @@ function makeApp(opts: { admin: boolean; email?: string }) {
36
53
  req.userEmail = opts.email ?? 'caller@example.com';
37
54
  next();
38
55
  });
39
- app.use('/api', createAccountRoutes(authService, adminAccess, accountErasure));
56
+ app.use('/api', createAccountRoutes(authService, adminAccess, accountErasure, accountHooks));
40
57
  return app;
41
58
  }
42
59
 
@@ -67,6 +84,8 @@ describe('account routes — admin gate', () => {
67
84
  body: JSON.stringify({ email: 'x@example.com', password: 'long-enough-pw' }),
68
85
  });
69
86
  expect(create.status).toBe(403);
87
+ // Not an admission refusal: nothing here may read as "no seat left".
88
+ expect(await create.json()).toEqual({ error: 'Admins only' });
70
89
  expect(authService.createAccount).not.toHaveBeenCalled();
71
90
  });
72
91
 
@@ -86,6 +105,155 @@ describe('account routes — admin gate', () => {
86
105
  expect(authService.createAccount).toHaveBeenCalledWith('b@example.com', 'B', 'long-enough-pw');
87
106
  });
88
107
 
108
+ it('gives an invite\'s starting password through the keep-existing path, and says whether it was set', async () => {
109
+ const base = await listen(makeApp({ admin: true }));
110
+ const create = await fetch(`${base}/api/admin/accounts`, {
111
+ method: 'POST',
112
+ headers: { 'Content-Type': 'application/json' },
113
+ body: JSON.stringify({ email: 'b@example.com', password: 'long-enough-pw', keepExistingPassword: true }),
114
+ });
115
+ expect(create.status).toBe(201);
116
+ expect(await create.json()).toEqual({
117
+ id: 'u2',
118
+ email: 'b@example.com',
119
+ name: 'B',
120
+ passwordSet: false,
121
+ deactivated: false,
122
+ notified: [],
123
+ });
124
+ expect(authService.createAccountWithStartingPassword).toHaveBeenCalledWith('b@example.com', 'long-enough-pw');
125
+ expect(authService.createAccount).not.toHaveBeenCalled();
126
+ // The account was already there: nobody is told of an invitation.
127
+ expect(accountHooks.runAccountInvited).not.toHaveBeenCalled();
128
+
129
+ const without = await fetch(`${base}/api/admin/accounts`, {
130
+ method: 'POST',
131
+ headers: { 'Content-Type': 'application/json' },
132
+ body: JSON.stringify({ email: 'b@example.com', keepExistingPassword: true }),
133
+ });
134
+ expect(without.status).toBe(400);
135
+
136
+ // Anything but a boolean fails closed — never the overwriting path.
137
+ for (const keepExistingPassword of ['true', 1, null, {}]) {
138
+ const odd = await fetch(`${base}/api/admin/accounts`, {
139
+ method: 'POST',
140
+ headers: { 'Content-Type': 'application/json' },
141
+ body: JSON.stringify({ email: 'b@example.com', password: 'long-enough-pw', keepExistingPassword }),
142
+ });
143
+ expect(odd.status).toBe(400);
144
+ expect(await odd.json()).toEqual({ error: 'keepExistingPassword must be a boolean' });
145
+ }
146
+ expect(authService.createAccount).not.toHaveBeenCalled();
147
+ });
148
+
149
+ describe('an invite tells the listeners, and the answer says who was told', () => {
150
+ afterEach(() => {
151
+ vi.mocked(accountHooks.runAccountInvited).mockReset().mockResolvedValue([]);
152
+ vi.mocked(authService.createAccountWithStartingPassword).mockClear();
153
+ vi.mocked(authService.createAccountWithoutPassword).mockClear();
154
+ });
155
+
156
+ it('a NEW account with a starting password: once, with the account, the admin and the fact of the password, never the password', async () => {
157
+ vi.mocked(authService.createAccountWithStartingPassword).mockResolvedValueOnce({
158
+ user: { id: 'u-new', email: 'new@example.com', name: 'new' },
159
+ created: true,
160
+ passwordSet: true,
161
+ deactivated: false,
162
+ });
163
+ vi.mocked(accountHooks.runAccountInvited).mockResolvedValueOnce(['email']);
164
+ const base = await listen(makeApp({ admin: true }));
165
+ const res = await fetch(`${base}/api/admin/accounts`, {
166
+ method: 'POST',
167
+ headers: { 'Content-Type': 'application/json' },
168
+ body: JSON.stringify({ email: 'new@example.com', password: 'long-enough-pw', keepExistingPassword: true }),
169
+ });
170
+ expect(res.status).toBe(201);
171
+ expect(await res.json()).toEqual({
172
+ id: 'u-new',
173
+ email: 'new@example.com',
174
+ name: 'new',
175
+ passwordSet: true,
176
+ deactivated: false,
177
+ notified: ['email'],
178
+ });
179
+ expect(accountHooks.runAccountInvited).toHaveBeenCalledTimes(1);
180
+ const [event] = vi.mocked(accountHooks.runAccountInvited).mock.calls[0]!;
181
+ expect(event).toEqual({
182
+ account: { id: 'u-new', email: 'new@example.com', name: 'new' },
183
+ // The caller, as the route knows them: `getUserById(req.userId)`.
184
+ invitedBy: { id: 'u1', email: 'u1@example.com', name: 'u1' },
185
+ passwordSet: true,
186
+ });
187
+ expect(JSON.stringify(event)).not.toContain('long-enough-pw');
188
+ });
189
+
190
+ it('when the inviting admin cannot be read, the invite still goes out, naming them by address', async () => {
191
+ vi.mocked(authService.getUserById).mockRejectedValueOnce(new Error('users table is away'));
192
+ vi.mocked(accountHooks.runAccountInvited).mockResolvedValueOnce(['email']);
193
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
194
+ try {
195
+ const base = await listen(makeApp({ admin: true }));
196
+ const res = await fetch(`${base}/api/admin/accounts`, {
197
+ method: 'POST',
198
+ headers: { 'Content-Type': 'application/json' },
199
+ body: JSON.stringify({ email: 'b@example.com' }),
200
+ });
201
+ expect(res.status).toBe(201);
202
+ expect(await res.json()).toEqual({ id: 'u2', email: 'b@example.com', name: 'B', notified: ['email'] });
203
+ expect(accountHooks.runAccountInvited).toHaveBeenCalledWith(
204
+ expect.objectContaining({ invitedBy: { id: 'u1', email: 'caller@example.com', name: '' } }),
205
+ );
206
+ } finally {
207
+ warn.mockRestore();
208
+ }
209
+ });
210
+
211
+ it('a NEW account for single sign-on (no password) goes through the same seam, with passwordSet false', async () => {
212
+ vi.mocked(accountHooks.runAccountInvited).mockResolvedValueOnce(['email', 'chat']);
213
+ const base = await listen(makeApp({ admin: true }));
214
+ const res = await fetch(`${base}/api/admin/accounts`, {
215
+ method: 'POST',
216
+ headers: { 'Content-Type': 'application/json' },
217
+ body: JSON.stringify({ email: 'b@example.com', name: 'B' }),
218
+ });
219
+ expect(res.status).toBe(201);
220
+ expect(await res.json()).toEqual({ id: 'u2', email: 'b@example.com', name: 'B', notified: ['email', 'chat'] });
221
+ expect(authService.createAccountWithoutPassword).toHaveBeenCalledWith('b@example.com', 'B');
222
+ expect(authService.createAccount).not.toHaveBeenCalled();
223
+ expect(accountHooks.runAccountInvited).toHaveBeenCalledWith(
224
+ expect.objectContaining({ account: { id: 'u2', email: 'b@example.com', name: 'B' }, passwordSet: false }),
225
+ );
226
+ });
227
+
228
+ it('an account that was already there is not an invitation: the listeners are not run', async () => {
229
+ vi.mocked(authService.createAccountWithoutPassword).mockResolvedValueOnce({
230
+ user: { id: 'u2', email: 'b@example.com', name: 'B' },
231
+ created: false,
232
+ });
233
+ const base = await listen(makeApp({ admin: true }));
234
+ const res = await fetch(`${base}/api/admin/accounts`, {
235
+ method: 'POST',
236
+ headers: { 'Content-Type': 'application/json' },
237
+ body: JSON.stringify({ email: 'b@example.com' }),
238
+ });
239
+ expect(res.status).toBe(201);
240
+ expect(await res.json()).toEqual({ id: 'u2', email: 'b@example.com', name: 'B', notified: [] });
241
+ expect(accountHooks.runAccountInvited).not.toHaveBeenCalled();
242
+ });
243
+
244
+ it('a set or reset password (a password without keepExistingPassword) is not an invitation either', async () => {
245
+ const base = await listen(makeApp({ admin: true }));
246
+ const res = await fetch(`${base}/api/admin/accounts`, {
247
+ method: 'POST',
248
+ headers: { 'Content-Type': 'application/json' },
249
+ body: JSON.stringify({ email: 'b@example.com', password: 'long-enough-pw' }),
250
+ });
251
+ expect(res.status).toBe(201);
252
+ expect(await res.json()).toEqual({ id: 'u2', email: 'b@example.com', name: 'B' });
253
+ expect(accountHooks.runAccountInvited).not.toHaveBeenCalled();
254
+ });
255
+ });
256
+
89
257
  it('list reports hasPassword + isEnvAdmin and never carries a hash or password', async () => {
90
258
  const passwordHash = await hashPassword('stored-password-1');
91
259
  const rows = [
@@ -111,7 +279,7 @@ describe('account routes — admin gate', () => {
111
279
  req.userEmail = 'caller@example.com';
112
280
  next();
113
281
  });
114
- app.use('/api', createAccountRoutes(realAuth, { isAdmin: async () => true }, accountErasure));
282
+ app.use('/api', createAccountRoutes(realAuth, { isAdmin: async () => true }, accountErasure, accountHooks));
115
283
  const base = await listen(app);
116
284
 
117
285
  const res = await fetch(`${base}/api/admin/accounts`);
@@ -151,7 +319,7 @@ describe('account routes — admin gate', () => {
151
319
  expect(accountErasure.eraseUser).toHaveBeenLastCalledWith('u2');
152
320
  });
153
321
 
154
- it('an account the admission port refuses → 403 carrying the port\'s own words', async () => {
322
+ it('an account the admission port refuses → 403 carrying the port\'s own words, marked as an admission refusal', async () => {
155
323
  const base = await listen(makeApp({ admin: true }));
156
324
  vi.mocked(authService.createAccount).mockRejectedValueOnce(
157
325
  new AccountAdmissionRefusedError('No seat left on this plan'),
@@ -162,7 +330,7 @@ describe('account routes — admin gate', () => {
162
330
  body: JSON.stringify({ email: 'b@example.com', password: 'long-enough-pw' }),
163
331
  });
164
332
  expect(refused.status).toBe(403);
165
- expect(((await refused.json()) as { error: string }).error).toBe('No seat left on this plan');
333
+ expect(await refused.json()).toEqual({ error: 'No seat left on this plan', kind: 'admission' });
166
334
  });
167
335
 
168
336
  it('erasure failure → 500 with a generic body (no internal error text)', async () => {
@@ -231,7 +399,7 @@ describe('account routes — removing the erased address from access files', ()
231
399
  req.userEmail = 'caller@example.com';
232
400
  next();
233
401
  });
234
- app.use('/api', createAccountRoutes(auth, { isAdmin: async () => true }, accountErasure, accessRemoval));
402
+ app.use('/api', createAccountRoutes(auth, { isAdmin: async () => true }, accountErasure, accountHooks, accessRemoval));
235
403
  return { app, accessRemoval };
236
404
  }
237
405
 
@@ -370,15 +538,30 @@ describe('account routes — switching an account off and on', () => {
370
538
  const base = await listen(makeApp({ admin: true }));
371
539
  const res = await post(base, 'u2/reactivate');
372
540
  expect(res.status).toBe(403);
373
- expect(((await res.json()) as { error: string }).error).toBe('All 3 seats are taken');
541
+ expect(await res.json()).toEqual({ error: 'All 3 seats are taken', kind: 'admission' });
542
+ });
543
+
544
+ it('answers a switch lock that timed out with the 409 account-busy the lock promises, not a 500', async () => {
545
+ vi.mocked(authService.deactivate).mockRejectedValueOnce(
546
+ new WorkflowDomainError('Another change to this account is still being saved; try again in a moment.', 409, {
547
+ kind: 'account-busy',
548
+ }),
549
+ );
550
+ const base = await listen(makeApp({ admin: true }));
551
+ const res = await post(base, 'u2/deactivate');
552
+ expect(res.status).toBe(409);
553
+ expect(await res.json()).toEqual({
554
+ error: 'Another change to this account is still being saved; try again in a moment.',
555
+ kind: 'account-busy',
556
+ });
374
557
  });
375
558
 
376
- it('answers a refused deactivation (the deployment admin, say) with 400 and the reason', async () => {
377
- vi.mocked(authService.deactivate).mockRejectedValueOnce(new AccountChangeRefusedError('The deployment admin cannot be switched off'));
559
+ it('answers a refused deactivation (the owner, say) with 400 and the reason', async () => {
560
+ vi.mocked(authService.deactivate).mockRejectedValueOnce(new AccountChangeRefusedError("The owner can't be switched off."));
378
561
  const base = await listen(makeApp({ admin: true }));
379
562
  const res = await post(base, 'u2/deactivate');
380
563
  expect(res.status).toBe(400);
381
- expect(((await res.json()) as { error: string }).error).toContain('deployment admin');
564
+ expect(((await res.json()) as { error: string }).error).toBe("The owner can't be switched off.");
382
565
  });
383
566
 
384
567
  it('answers an unexpected failure with a 500 that names nothing of it', async () => {
@@ -407,3 +590,198 @@ describe('account routes — switching an account off and on', () => {
407
590
  expect(authService.createAccount).not.toHaveBeenCalled();
408
591
  });
409
592
  });
593
+
594
+ /**
595
+ * The owner (`ADMIN_EMAIL`) through the accounts API, against a real
596
+ * AuthService, on a deployment with a server-held owner password
597
+ * (self-hosted) and one without (the cloud's configuration). The refusals
598
+ * live on the server, so they hold for every caller, not only the page.
599
+ */
600
+ describe('account routes — the owner cannot be locked out', () => {
601
+ const OWNER_ROW = {
602
+ id: 'owner',
603
+ email: 'root@example.com',
604
+ name: 'Root',
605
+ avatarUrl: null,
606
+ onboardingDone: true,
607
+ passwordHash: null,
608
+ deactivatedAt: null,
609
+ createdAt: new Date(),
610
+ };
611
+ const OTHER_ROW = { ...OWNER_ROW, id: 'u2', email: 'lee@example.com', name: 'Lee' };
612
+
613
+ /**
614
+ * Every read answers with `row`; every write is recorded. A switch-off runs
615
+ * in a transaction that first takes the address's switch lock (see
616
+ * account-switch-lock), so the handle a transaction hands out reads, locks
617
+ * and writes like the plain one.
618
+ */
619
+ function fakeDb(row: typeof OWNER_ROW) {
620
+ const updates: unknown[] = [];
621
+ const chain = (result: unknown): Record<string, unknown> => {
622
+ const c: Record<string, unknown> = {};
623
+ for (const k of ['from', 'where', 'limit', 'set', 'values', 'returning', 'onConflictDoUpdate']) c[k] = () => c;
624
+ c.then = (ok: (v: unknown) => unknown, ko: (e: unknown) => unknown) => Promise.resolve(result).then(ok, ko);
625
+ return c;
626
+ };
627
+ const handle = {
628
+ select: () => chain([row]),
629
+ update: () => {
630
+ updates.push(row.id);
631
+ return chain([]);
632
+ },
633
+ execute: async () => [],
634
+ };
635
+ const db = {
636
+ ...handle,
637
+ transaction: async <T,>(cb: (tx: typeof handle) => Promise<T>) => cb(handle),
638
+ } as unknown as Database;
639
+ return { db, updates };
640
+ }
641
+
642
+ function makeOwnerApp(adminPassword: string, row: typeof OWNER_ROW, opts: { loginPasswordEnabled?: boolean } = {}) {
643
+ const { db, updates } = fakeDb(row);
644
+ const realAuth = new AuthService(db, {
645
+ jwtSecret: 'test-jwt-secret',
646
+ adminEmail: 'root@example.com',
647
+ adminPassword,
648
+ allowedEmailDomains: [],
649
+ loginPasswordEnabled: opts.loginPasswordEnabled,
650
+ });
651
+ const app = express();
652
+ app.use(express.json());
653
+ app.use((req, _res, next) => {
654
+ req.userId = 'second-admin';
655
+ req.userEmail = 'second-admin@example.com';
656
+ next();
657
+ });
658
+ app.use('/api', createAccountRoutes(realAuth, { isAdmin: async () => true }, accountErasure, accountHooks));
659
+ return { app, updates };
660
+ }
661
+
662
+ const CONFIGS = [
663
+ ['with a server-held owner password', 'sup3r-secret'],
664
+ ['without one (the cloud)', ''],
665
+ ] as const;
666
+
667
+ for (const [label, adminPassword] of CONFIGS) {
668
+ it(`refuses to switch the owner off, ${label}, and says why`, async () => {
669
+ const { app, updates } = makeOwnerApp(adminPassword, OWNER_ROW);
670
+ const base = await listen(app);
671
+ const res = await fetch(`${base}/api/admin/accounts/owner/deactivate`, { method: 'POST' });
672
+ expect(res.status).toBe(400);
673
+ expect(await res.json()).toEqual({ error: "The owner can't be switched off." });
674
+ expect(updates).toEqual([]);
675
+ });
676
+
677
+ it(`switches off an account that is not the owner as today, ${label}`, async () => {
678
+ const { app, updates } = makeOwnerApp(adminPassword, OTHER_ROW);
679
+ const base = await listen(app);
680
+ const res = await fetch(`${base}/api/admin/accounts/u2/deactivate`, { method: 'POST' });
681
+ expect(res.status).toBe(204);
682
+ expect(updates).toEqual(['u2']);
683
+ });
684
+
685
+ it(`deletes an account that is not the owner as today, ${label}`, async () => {
686
+ const { app } = makeOwnerApp(adminPassword, OTHER_ROW);
687
+ const base = await listen(app);
688
+ const res = await fetch(`${base}/api/admin/accounts/u2`, { method: 'DELETE' });
689
+ expect(res.status).toBe(204);
690
+ expect(accountErasure.eraseUser).toHaveBeenLastCalledWith('u2');
691
+ });
692
+
693
+ it(`lists the owner as one, ${label}`, async () => {
694
+ const { app } = makeOwnerApp(adminPassword, OWNER_ROW);
695
+ const base = await listen(app);
696
+ const body = (await (await fetch(`${base}/api/admin/accounts`)).json()) as {
697
+ accounts: Array<{ isOwner: boolean; ownerCanBeDeleted: boolean }>;
698
+ };
699
+ expect(body.accounts[0]).toMatchObject({ isOwner: true, ownerCanBeDeleted: adminPassword !== '' });
700
+ });
701
+
702
+ it(`refuses another admin setting the owner's password, ${label}`, async () => {
703
+ const { app } = makeOwnerApp(adminPassword, OWNER_ROW);
704
+ const base = await listen(app);
705
+ const res = await fetch(`${base}/api/admin/accounts`, {
706
+ method: 'POST',
707
+ headers: { 'Content-Type': 'application/json' },
708
+ body: JSON.stringify({ email: 'Root@Example.com', password: 'long-enough-pw' }),
709
+ });
710
+ expect(res.status).toBe(400);
711
+ expect(((await res.json()) as { error: string }).error).toMatch(/^The owner's password can't be set by another admin/);
712
+ });
713
+
714
+ it(`answers an email that is not text with a 400, not a crash, ${label}`, async () => {
715
+ const { app } = makeOwnerApp(adminPassword, OWNER_ROW);
716
+ const base = await listen(app);
717
+ const res = await fetch(`${base}/api/admin/accounts`, {
718
+ method: 'POST',
719
+ headers: { 'Content-Type': 'application/json' },
720
+ body: JSON.stringify({ email: { evil: true }, password: 'long-enough-pw' }),
721
+ });
722
+ expect(res.status).toBe(400);
723
+ });
724
+ }
725
+
726
+ it("lets another admin create the owner's account with an empty password, which means none", async () => {
727
+ const base = await listen(makeApp({ admin: true }));
728
+ const res = await fetch(`${base}/api/admin/accounts`, {
729
+ method: 'POST',
730
+ headers: { 'Content-Type': 'application/json' },
731
+ body: JSON.stringify({ email: 'root@example.com', password: '' }),
732
+ });
733
+ expect(res.status).toBe(201);
734
+ expect(authService.createAccountWithoutPassword).toHaveBeenCalledWith('root@example.com', undefined);
735
+ });
736
+
737
+ it("refuses to delete the owner's account where they could not sign back in (the cloud): nothing is erased", async () => {
738
+ const { app } = makeOwnerApp('', OWNER_ROW);
739
+ const base = await listen(app);
740
+ for (const query of ['', '?removeFromAccess=1']) {
741
+ const res = await fetch(`${base}/api/admin/accounts/owner${query}`, { method: 'DELETE' });
742
+ expect(res.status).toBe(400);
743
+ expect(await res.json()).toEqual({ error: "The owner's account can't be deleted: they would have no way to sign in." });
744
+ }
745
+ expect(accountErasure.eraseUser).not.toHaveBeenCalled();
746
+ });
747
+
748
+ it('refuses it too where the server holds a password but password sign-in is off', async () => {
749
+ const { app } = makeOwnerApp('sup3r-secret', OWNER_ROW, { loginPasswordEnabled: false });
750
+ const base = await listen(app);
751
+ const res = await fetch(`${base}/api/admin/accounts/owner`, { method: 'DELETE' });
752
+ expect(res.status).toBe(400);
753
+ expect(accountErasure.eraseUser).not.toHaveBeenCalled();
754
+ });
755
+
756
+ it("deletes the owner's account as today where they can sign back in with the server's password", async () => {
757
+ const { app } = makeOwnerApp('sup3r-secret', OWNER_ROW);
758
+ const base = await listen(app);
759
+ const res = await fetch(`${base}/api/admin/accounts/owner`, { method: 'DELETE' });
760
+ expect(res.status).toBe(204);
761
+ expect(accountErasure.eraseUser).toHaveBeenLastCalledWith('owner');
762
+ });
763
+
764
+ it("still lets the owner's account be (re)created without a password, so an admin can restore it", async () => {
765
+ vi.mocked(authService.createAccountWithoutPassword).mockClear();
766
+ const base = await listen(makeApp({ admin: true }));
767
+ const res = await fetch(`${base}/api/admin/accounts`, {
768
+ method: 'POST',
769
+ headers: { 'Content-Type': 'application/json' },
770
+ body: JSON.stringify({ email: 'root@example.com', name: 'Root' }),
771
+ });
772
+ expect(res.status).toBe(201);
773
+ expect(authService.createAccountWithoutPassword).toHaveBeenCalledWith('root@example.com', 'Root');
774
+ });
775
+
776
+ it('leaves the owner setting their own password through the same call to the service', async () => {
777
+ vi.mocked(authService.createAccount).mockClear();
778
+ const base = await listen(makeApp({ admin: true, email: 'root@example.com' }));
779
+ const res = await fetch(`${base}/api/admin/accounts`, {
780
+ method: 'POST',
781
+ headers: { 'Content-Type': 'application/json' },
782
+ body: JSON.stringify({ email: 'root@example.com', password: 'long-enough-pw' }),
783
+ });
784
+ expect(res.status).toBe(201);
785
+ expect(authService.createAccount).toHaveBeenCalledWith('root@example.com', undefined, 'long-enough-pw');
786
+ });
787
+ });