@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,203 @@
1
+ import type { Server } from 'node:http';
2
+ import type { AddressInfo } from 'node:net';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+ import express from 'express';
7
+ import { describe, it, expect, afterEach, vi } from 'vitest';
8
+
9
+ import { AccessControlService } from '../access-control.service.js';
10
+ import { NodeFs } from '../../kb-fs/node-fs.js';
11
+ import type { WorkspaceService } from '../../workspace/workspace.service.js';
12
+ import type { AuthService } from '../../auth/auth.service.js';
13
+ import type { WorkflowService } from '../../workflow/workflow.service.js';
14
+ import type { WorkflowEventBus } from '../../workflow/event-bus.js';
15
+ import { createAccessRoutes } from '../access.routes.js';
16
+ import { usersDbDouble } from './users-db-double.js';
17
+ import { testKbContext } from '../../../__tests__/kb-context.js';
18
+
19
+ /**
20
+ * `GET /api/access/admins`: who the admins are, for anyone signed in. It
21
+ * answers the Admin role's members — through a group too — and the deployment
22
+ * admins, each once, sorted by name, with names from the accounts table; and
23
+ * nothing else about roles. The roster stays admin-only.
24
+ */
25
+
26
+ const KB = 'knowledge-base';
27
+ const MEMBER = { id: 'u-lena', email: 'lena@acme.com', name: 'Lena Park' };
28
+
29
+ const ROLES = `roles:
30
+ Admin:
31
+ - sam.ortiz@acme.com
32
+ - group:Ops
33
+ Sales:
34
+ - felix@acme.com
35
+ `;
36
+
37
+ const GROUPS = `groups:
38
+ Ops:
39
+ - dana@acme.com
40
+ - sam.ortiz@acme.com
41
+ Product:
42
+ - priya@acme.com
43
+ `;
44
+
45
+ const tmpDirs: string[] = [];
46
+
47
+ async function makeHarness(
48
+ opts: {
49
+ roles?: string;
50
+ groups?: string;
51
+ signedIn?: boolean;
52
+ deploymentAdmins?: string[];
53
+ accounts?: { email: string; name?: string; deactivatedAt?: Date | null }[];
54
+ } = {},
55
+ ): Promise<{ server: Server; baseUrl: string }> {
56
+ const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-admins-route-'));
57
+ tmpDirs.push(workspaceDir);
58
+ const repoDir = path.join(workspaceDir, KB);
59
+ await fs.mkdir(repoDir, { recursive: true });
60
+ await fs.writeFile(path.join(repoDir, 'roles.yaml'), opts.roles ?? ROLES, 'utf-8');
61
+ await fs.writeFile(path.join(repoDir, 'groups.yaml'), opts.groups ?? GROUPS, 'utf-8');
62
+
63
+ const workspaceService = {
64
+ getOrCreateForBranch: vi.fn(async () => ({})),
65
+ getWorkspacePath: vi.fn(async () => workspaceDir),
66
+ readFile: vi.fn(async (_id: string, wsRel: string) => fs.readFile(path.join(workspaceDir, wsRel), 'utf-8')),
67
+ } as unknown as WorkspaceService;
68
+ // The real resolver: the admins read is its own membership answer. Nobody
69
+ // signed in here is an Admin, so nobody may read the roster either.
70
+ const deploymentAdmins = opts.deploymentAdmins ?? ['Owner@Acme.com'];
71
+ const accessControl = new AccessControlService(workspaceService, KB, new NodeFs(), deploymentAdmins);
72
+ const authService = { getUserById: vi.fn(async () => MEMBER) } as unknown as AuthService;
73
+ const db = usersDbDouble(
74
+ opts.accounts ?? [
75
+ { email: 'dana@acme.com', name: 'Dana Admin' },
76
+ { email: 'sam.ortiz@acme.com', name: 'Sam Ortiz' },
77
+ { email: 'felix@acme.com', name: 'Felix Sales' },
78
+ { email: 'priya@acme.com', name: 'Priya Product' },
79
+ MEMBER,
80
+ ],
81
+ );
82
+
83
+ const app = express();
84
+ app.use(express.json());
85
+ app.use('/api', (req, _res, next) => {
86
+ if (opts.signedIn !== false) (req as unknown as { userId: string }).userId = MEMBER.id;
87
+ next();
88
+ });
89
+ app.use(
90
+ '/api',
91
+ createAccessRoutes(
92
+ accessControl,
93
+ workspaceService,
94
+ authService,
95
+ authService,
96
+ {} as unknown as WorkflowService,
97
+ { emit: vi.fn() } as unknown as WorkflowEventBus,
98
+ db,
99
+ testKbContext({ kbDirName: KB }),
100
+ deploymentAdmins,
101
+ ),
102
+ );
103
+ const server = await new Promise<Server>((resolve) => {
104
+ const s = app.listen(0, () => resolve(s));
105
+ });
106
+ return { server, baseUrl: `http://127.0.0.1:${(server.address() as AddressInfo).port}` };
107
+ }
108
+
109
+ function close(s: Server): Promise<void> {
110
+ return new Promise((resolve, reject) => s.close((e) => (e ? reject(e) : resolve())));
111
+ }
112
+
113
+ describe('GET /api/access/admins', () => {
114
+ let server: Server | null = null;
115
+ afterEach(async () => {
116
+ if (server) await close(server);
117
+ server = null;
118
+ await Promise.all(tmpDirs.splice(0).map((d) => fs.rm(d, { recursive: true, force: true })));
119
+ });
120
+
121
+ it('tells a Member every admin — direct, through a group, and the deployment admin — once each, sorted by name', async () => {
122
+ const h = await makeHarness();
123
+ server = h.server;
124
+ const res = await fetch(`${h.baseUrl}/api/access/admins`);
125
+ expect(res.status).toBe(200);
126
+ expect(await res.json()).toEqual({
127
+ admins: [
128
+ { name: 'Dana Admin', email: 'dana@acme.com' },
129
+ // No account yet: named by address.
130
+ { name: 'owner@acme.com', email: 'owner@acme.com' },
131
+ { name: 'Sam Ortiz', email: 'sam.ortiz@acme.com' },
132
+ ],
133
+ });
134
+ });
135
+
136
+ it('reveals nothing else: no other role, group or member, and no other field', async () => {
137
+ const h = await makeHarness();
138
+ server = h.server;
139
+ const text = await (await fetch(`${h.baseUrl}/api/access/admins`)).text();
140
+ for (const leak of ['felix', 'priya', 'Sales', 'Product', 'Ops', 'group:', 'lena']) {
141
+ expect(text).not.toContain(leak);
142
+ }
143
+ const body = JSON.parse(text) as { admins: Record<string, unknown>[] };
144
+ expect(Object.keys(body)).toEqual(['admins']);
145
+ for (const admin of body.admins) expect(Object.keys(admin).sort()).toEqual(['email', 'name']);
146
+ });
147
+
148
+ it('answers only the deployment admins when roles.yaml does not parse, as the resolver does', async () => {
149
+ const h = await makeHarness({ roles: 'roles:\n Admin: not-a-list\n' });
150
+ server = h.server;
151
+ const body = (await (await fetch(`${h.baseUrl}/api/access/admins`)).json()) as { admins: unknown[] };
152
+ expect(body.admins).toEqual([{ name: 'owner@acme.com', email: 'owner@acme.com' }]);
153
+ });
154
+
155
+ it('answers only the deployment admins when the resolver rejects roles.yaml the editing model would read', async () => {
156
+ // A role named with the reserved plugin prefix: the resolver refuses the
157
+ // whole file, so nobody in it is an Admin — and nobody is listed as one.
158
+ const h = await makeHarness({
159
+ roles: 'roles:\n Admin:\n - sam.ortiz@acme.com\n - group:Ops\n plugin/x:\n - felix@acme.com\n',
160
+ });
161
+ server = h.server;
162
+ const body = (await (await fetch(`${h.baseUrl}/api/access/admins`)).json()) as { admins: unknown[] };
163
+ expect(body.admins).toEqual([{ name: 'owner@acme.com', email: 'owner@acme.com' }]);
164
+ });
165
+
166
+ it('does not count the members of a GROUP named Admin, as the resolver does not', async () => {
167
+ const h = await makeHarness({ groups: 'groups:\n Admin:\n - priya@acme.com\n' });
168
+ server = h.server;
169
+ const text = await (await fetch(`${h.baseUrl}/api/access/admins`)).text();
170
+ expect(text).not.toContain('priya');
171
+ expect(text).toContain('sam.ortiz@acme.com');
172
+ });
173
+
174
+ it('leaves out a role member whose account is switched off, but not a switched-off deployment admin', async () => {
175
+ const off = new Date('2026-01-01T00:00:00Z');
176
+ const h = await makeHarness({
177
+ accounts: [
178
+ { email: 'dana@acme.com', name: 'Dana Admin', deactivatedAt: off },
179
+ { email: 'sam.ortiz@acme.com', name: 'Sam Ortiz' },
180
+ { email: 'owner@acme.com', name: 'Olive Owner', deactivatedAt: off },
181
+ MEMBER,
182
+ ],
183
+ });
184
+ server = h.server;
185
+ const body = (await (await fetch(`${h.baseUrl}/api/access/admins`)).json()) as { admins: unknown[] };
186
+ expect(body.admins).toEqual([
187
+ { name: 'Olive Owner', email: 'owner@acme.com' },
188
+ { name: 'Sam Ortiz', email: 'sam.ortiz@acme.com' },
189
+ ]);
190
+ });
191
+
192
+ it('refuses a caller who is not signed in', async () => {
193
+ const h = await makeHarness({ signedIn: false });
194
+ server = h.server;
195
+ expect((await fetch(`${h.baseUrl}/api/access/admins`)).status).toBe(401);
196
+ });
197
+
198
+ it('leaves the roles roster admin-only', async () => {
199
+ const h = await makeHarness();
200
+ server = h.server;
201
+ expect((await fetch(`${h.baseUrl}/api/access/roles`)).status).toBe(403);
202
+ });
203
+ });
@@ -28,11 +28,14 @@ interface Harness {
28
28
  baseUrl: string;
29
29
  canWriteBatch: ReturnType<typeof vi.fn>;
30
30
  canOwnerBatch: ReturnType<typeof vi.fn>;
31
+ canReadBatch: ReturnType<typeof vi.fn>;
31
32
  }
32
33
 
33
- // Alice WRITES both skills but is named in the `owner:` grant of one.
34
+ // Alice WRITES both skills but is named in the `owner:` grant of one, and
35
+ // READS a folder she may not write.
34
36
  const WRITABLE = new Set(['Skills/a/SKILL.md', 'Skills/b/SKILL.md']);
35
37
  const OWNED = new Set(['Skills/a/SKILL.md']);
38
+ const READABLE = new Set(['Skills/a/SKILL.md', 'Skills/b/SKILL.md', 'KnowledgeBase/Sales']);
36
39
 
37
40
  const verdicts = (set: Set<string>) =>
38
41
  vi.fn(async (_w: string, _e: string, paths: string[]) => new Map(paths.map((p) => [p, set.has(p)])));
@@ -40,7 +43,8 @@ const verdicts = (set: Set<string>) =>
40
43
  async function makeHarness(): Promise<Harness> {
41
44
  const canWriteBatch = verdicts(WRITABLE);
42
45
  const canOwnerBatch = verdicts(OWNED);
43
- const accessControl = { canWriteBatch, canOwnerBatch } as unknown as IAccessControl;
46
+ const canReadBatch = verdicts(READABLE);
47
+ const accessControl = { canWriteBatch, canOwnerBatch, canReadBatch } as unknown as IAccessControl;
44
48
 
45
49
  const workspaceService = {} as unknown as WorkspaceService;
46
50
  const authService = { getUserById: vi.fn(async () => USER) } as unknown as AuthService;
@@ -57,6 +61,7 @@ async function makeHarness(): Promise<Harness> {
57
61
  accessControl,
58
62
  workspaceService,
59
63
  authService,
64
+ authService,
60
65
  {} as unknown as WorkflowService,
61
66
  { emit: vi.fn() } as unknown as WorkflowEventBus,
62
67
  usersDbDouble(),
@@ -68,7 +73,7 @@ async function makeHarness(): Promise<Harness> {
68
73
  const s = app.listen(0, () => resolve(s));
69
74
  });
70
75
  const addr = server.address() as AddressInfo;
71
- return { server, baseUrl: `http://127.0.0.1:${addr.port}`, canWriteBatch, canOwnerBatch };
76
+ return { server, baseUrl: `http://127.0.0.1:${addr.port}`, canWriteBatch, canOwnerBatch, canReadBatch };
72
77
  }
73
78
 
74
79
  function close(s: Server): Promise<void> {
@@ -119,16 +124,31 @@ describe('POST /access/batch', () => {
119
124
  expect(h.canWriteBatch).not.toHaveBeenCalled();
120
125
  });
121
126
 
127
+ it('answers READ for `verb: read` — a folder the caller may read but not write gets true', async () => {
128
+ h = await makeHarness();
129
+
130
+ const paths = [...PATHS, 'KnowledgeBase/Sales'];
131
+ const res = await post({ paths, verb: 'read' });
132
+ expect(res.status).toBe(200);
133
+ expect(await res.json()).toEqual({
134
+ results: { 'Skills/a/SKILL.md': true, 'Skills/b/SKILL.md': true, 'KnowledgeBase/Sales': true },
135
+ });
136
+ expect(h.canReadBatch).toHaveBeenCalledWith(WS, USER.email, paths);
137
+ expect(h.canWriteBatch).not.toHaveBeenCalled();
138
+ expect(h.canOwnerBatch).not.toHaveBeenCalled();
139
+ });
140
+
122
141
  it('400s a verb it does not dispatch, resolving nothing', async () => {
123
142
  h = await makeHarness();
124
143
 
125
- for (const verb of ['read', 'admin', 42, null]) {
144
+ for (const verb of ['download', 'admin', 42, null]) {
126
145
  const res = await post({ paths: PATHS, verb });
127
146
  expect(res.status).toBe(400);
128
- expect(await res.json()).toEqual({ error: 'verb must be "write" or "owner"' });
147
+ expect(await res.json()).toEqual({ error: 'verb must be "write", "owner" or "read"' });
129
148
  }
130
149
  expect(h.canWriteBatch).not.toHaveBeenCalled();
131
150
  expect(h.canOwnerBatch).not.toHaveBeenCalled();
151
+ expect(h.canReadBatch).not.toHaveBeenCalled();
132
152
  });
133
153
 
134
154
  it('keeps the paths validation ahead of the verb', async () => {
@@ -156,6 +156,7 @@ describe('access mutations on a file that cannot carry frontmatter', () => {
156
156
  accessControl,
157
157
  workspaceService,
158
158
  { getUserById: vi.fn(async () => USER) } as unknown as AuthService,
159
+ { whileSwitchedOn: async (_email: string, write: () => Promise<void>) => write() },
159
160
  workflowService,
160
161
  { emit: vi.fn() } as unknown as WorkflowEventBus,
161
162
  usersDbDouble(),
@@ -88,7 +88,7 @@ async function makeHarness(opts: {
88
88
  (req as unknown as { userId: string }).userId = USER.id;
89
89
  next();
90
90
  });
91
- app.use('/api', createAccessRoutes(accessControl, workspaceService, authService, workflowService, eventBus, db, testKbContext({ kbDirName: KB })));
91
+ app.use('/api', createAccessRoutes(accessControl, workspaceService, authService, authService, workflowService, eventBus, db, testKbContext({ kbDirName: KB })));
92
92
 
93
93
  const server = await new Promise<Server>((resolve) => {
94
94
  const s = app.listen(0, () => resolve(s));
@@ -98,7 +98,7 @@ async function makeHarness(opts: {
98
98
  });
99
99
  app.use(
100
100
  '/api',
101
- createAccessRoutes(accessControl, workspaceService, authService, workflowService, eventBus, db, testKbContext({ kbDirName: KB })),
101
+ createAccessRoutes(accessControl, workspaceService, authService, authService, workflowService, eventBus, db, testKbContext({ kbDirName: KB })),
102
102
  );
103
103
 
104
104
  const server = await new Promise<Server>((resolve) => {
@@ -134,6 +134,7 @@ describe('access routes on a proposal branch (real git)', () => {
134
134
  access,
135
135
  workspaceService,
136
136
  authService,
137
+ authService,
137
138
  workflowService,
138
139
  { emit: () => {} } as unknown as WorkflowEventBus,
139
140
  usersDbDouble(),
@@ -64,6 +64,7 @@ async function makeHarness(opts: { canRead?: boolean } = {}): Promise<Harness> {
64
64
  accessControl,
65
65
  {} as unknown as WorkspaceService,
66
66
  { getUserById: vi.fn(async () => USER) } as unknown as AuthService,
67
+ { whileSwitchedOn: async (_email: string, write: () => Promise<void>) => write() },
67
68
  {} as unknown as WorkflowService,
68
69
  { emit: vi.fn() } as unknown as WorkflowEventBus,
69
70
  {} as unknown as Database,
@@ -134,7 +134,7 @@ async function makeHarness(opts: {
134
134
  (req as unknown as { userId: string }).userId = USER.id;
135
135
  next();
136
136
  });
137
- app.use('/api', createAccessRoutes(accessControl, workspaceService, authService, workflowService, eventBus, db, testKbContext({ kbDirName: KB })));
137
+ app.use('/api', createAccessRoutes(accessControl, workspaceService, authService, authService, workflowService, eventBus, db, testKbContext({ kbDirName: KB })));
138
138
 
139
139
  const server = await new Promise<Server>((resolve) => {
140
140
  const s = app.listen(0, () => resolve(s));
@@ -105,6 +105,7 @@ async function makeHarness(opts: {
105
105
  accessControl,
106
106
  workspaceService,
107
107
  authService,
108
+ authService,
108
109
  workflowService,
109
110
  { emit: vi.fn() } as unknown as WorkflowEventBus,
110
111
  opts.db ?? usersDbDouble(opts.accounts ?? []),
@@ -146,7 +146,7 @@ describe('SECURITY: Admin-role removal takes effect on the next request', () =>
146
146
  (req as unknown as { userEmail?: string }).userEmail = req.header('x-test-email') ?? undefined;
147
147
  next();
148
148
  });
149
- app.use('/api', createAccountRoutes(authService, adminAccess, erasure));
149
+ app.use('/api', createAccountRoutes(authService, adminAccess, erasure, { runAccountInvited: async () => [] }));
150
150
  server = await new Promise<Server>((resolve) => {
151
151
  const s = app.listen(0, () => resolve(s));
152
152
  });
@@ -637,6 +637,18 @@ describe('Admin fixed members — the deployment admin', () => {
637
637
  expect(roster.find((r) => r.canonical === 'sales')!.fixedMembers).toEqual([]);
638
638
  });
639
639
 
640
+ it('sends the note with the roster, and the refusal leads with that same note', async () => {
641
+ // The page prints `fixedMembersNote` as sent, so this is the one copy of
642
+ // the wording: the note and the API refusal cannot drift apart.
643
+ const svc = serviceWith([OWNER]);
644
+ const roster = await svc.getRoster();
645
+ const note = roster.find((r) => r.canonical === 'admin')!.fixedMembersNote;
646
+ expect(note).toBe('Owner; cannot be removed here');
647
+ expect(roster.find((r) => r.canonical === 'sales')!.fixedMembersNote).toBeNull();
648
+ const refusal = await svc.addMember(ADMIN, 'admin', OWNER).catch((e: Error) => e);
649
+ expect((refusal as Error).message.startsWith(`${note}. `)).toBe(true);
650
+ });
651
+
640
652
  it('canonicalises the configured address and drops a blank one', async () => {
641
653
  // The composition root passes `[config.adminEmail]` unconditionally, so an
642
654
  // unset ADMIN_EMAIL arrives as [''] — that must not become a member "".
@@ -665,7 +677,7 @@ describe('Admin fixed members — the deployment admin', () => {
665
677
  status: 422,
666
678
  payload: { kind: 'fixed-deployment-admin' },
667
679
  message: expect.stringContaining(
668
- 'Deployment admin, set in the server configuration; cannot be removed here',
680
+ 'Owner; cannot be removed here',
669
681
  ),
670
682
  });
671
683
  // Nothing written: the file is byte-identical.
@@ -685,7 +697,7 @@ describe('Admin fixed members — the deployment admin', () => {
685
697
  status: 422,
686
698
  payload: { kind: 'fixed-deployment-admin' },
687
699
  message: expect.stringContaining(
688
- 'Deployment admin, set in the server configuration; cannot be removed here',
700
+ 'Owner; cannot be removed here',
689
701
  ),
690
702
  });
691
703
  expect(await fs.readFile(path.join(repo, 'roles.yaml'), 'utf-8')).toContain(OWNER);
@@ -700,6 +712,7 @@ describe('Admin fixed members — the deployment admin', () => {
700
712
  it('with no ADMIN_EMAIL configured, no role has a fixed member', async () => {
701
713
  const roster = await serviceWith([]).getRoster();
702
714
  expect(roster.every((r) => r.fixedMembers.length === 0)).toBe(true);
715
+ expect(roster.every((r) => r.fixedMembersNote === null)).toBe(true);
703
716
  // ...and ordinary Admin membership stays editable.
704
717
  await expect(serviceWith([]).addMember(ADMIN, 'admin', OWNER)).resolves.toBeDefined();
705
718
  });
@@ -4,12 +4,14 @@ import fs from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import os from 'node:os';
6
6
  import express from 'express';
7
+ import { PgDialect } from 'drizzle-orm/pg-core';
8
+ import type { SQL } from 'drizzle-orm';
7
9
  import { describe, it, expect, afterEach, vi } from 'vitest';
8
10
  import { NodeFs } from '../../kb-fs/node-fs.js';
9
11
 
10
12
  import type { IAccessControl } from '../access-control.interface.js';
11
13
  import type { WorkspaceService } from '../../workspace/workspace.service.js';
12
- import type { AuthService } from '../../auth/auth.service.js';
14
+ import { AuthService } from '../../auth/auth.service.js';
13
15
  import type { WorkflowService } from '../../workflow/workflow.service.js';
14
16
  import type { WorkflowEventBus } from '../../workflow/event-bus.js';
15
17
  import type { FileTreeEntry } from '@bevel-software/platform-shared';
@@ -40,7 +42,9 @@ const ROLES = `roles:
40
42
  /** Temp workspace dirs to clean up after each test. */
41
43
  const tmpDirs: string[] = [];
42
44
 
43
- async function makeHarness(opts: { isAdmin?: boolean } = {}): Promise<{ server: Server; baseUrl: string }> {
45
+ async function makeHarness(
46
+ opts: { isAdmin?: boolean; switchOffOnRolesLock?: string } = {},
47
+ ): Promise<{ server: Server; baseUrl: string; txLog: string[] }> {
44
48
  const isAdmin = opts.isAdmin ?? true;
45
49
 
46
50
  // Back the harness with a REAL temp workspace dir: the service's single-file
@@ -103,8 +107,6 @@ async function makeHarness(opts: { isAdmin?: boolean } = {}): Promise<{ server:
103
107
  }),
104
108
  } as unknown as WorkspaceService;
105
109
 
106
- const authService = { getUserById: vi.fn(async () => ADMIN) } as unknown as AuthService;
107
-
108
110
  // Lock holder tracked so the service's getLock pre-check passes; the single-
109
111
  // file write itself lands on disk via LockingFilesystem → LocalFilesystem.
110
112
  // Strict per-path locks — a live row is contended even for the same user,
@@ -119,6 +121,8 @@ async function makeHarness(opts: { isAdmin?: boolean } = {}): Promise<{ server:
119
121
  acquireLock: vi.fn(async (_w: string, _b: string, p: string, user: { id: string; name: string }) => {
120
122
  const h = locks.get(p);
121
123
  if (h) return { acquired: false, lock: lockRow(h) };
124
+ // An account switched off while the promotion waited for the lock.
125
+ if (p.endsWith('roles.yaml')) rolesLocked = true;
122
126
  locks.set(p, user);
123
127
  return { acquired: true, lock: lockRow(user) };
124
128
  }),
@@ -130,7 +134,47 @@ async function makeHarness(opts: { isAdmin?: boolean } = {}): Promise<{ server:
130
134
  } as unknown as WorkflowService;
131
135
 
132
136
  const eventBus = { emit: vi.fn() } as unknown as WorkflowEventBus;
133
- const db = usersDbDouble();
137
+ const accounts = [
138
+ { email: 'off@example.com', deactivatedAt: new Date('2026-09-01T00:00:00Z') },
139
+ 'on@example.com',
140
+ ];
141
+ const before = usersDbDouble(accounts);
142
+ const after = usersDbDouble(
143
+ accounts.map((a) =>
144
+ a === opts.switchOffOnRolesLock ? { email: a, deactivatedAt: new Date('2026-10-10T00:00:00Z') } : a,
145
+ ),
146
+ );
147
+ let rolesLocked = false;
148
+ const current = () => (rolesLocked ? after : before);
149
+ // What happened inside each users transaction, in order: the lock taken
150
+ // with `execute` (rendered), and what roles.yaml held when it ended.
151
+ const txLog: string[] = [];
152
+ const rolesOnDisk = () => fs.readFile(path.join(repoDir, 'roles.yaml'), 'utf-8');
153
+ const db = {
154
+ select: () => current().select(),
155
+ transaction: async <T>(cb: (tx: unknown) => Promise<T>): Promise<T> => {
156
+ txLog.push('begin');
157
+ const tx = {
158
+ execute: async (query: SQL) => {
159
+ const { sql, params } = new PgDialect().sqlToQuery(query);
160
+ txLog.push(`${sql} ${JSON.stringify(params)}`);
161
+ return [];
162
+ },
163
+ select: () => current().select(),
164
+ };
165
+ try {
166
+ return await cb(tx);
167
+ } finally {
168
+ const onDisk = await rolesOnDisk();
169
+ txLog.push(`end: ${['on@example.com', 'new@example.com'].filter((e) => onDisk.includes(e)).join(', ') || 'none'}`);
170
+ }
171
+ },
172
+ } as unknown as typeof before;
173
+
174
+ // The real auth service over the same double, so the promotion's switch
175
+ // lock and read run as they do in production (`whileSwitchedOn`).
176
+ const authService = new AuthService(db, { jwtSecret: 'test', adminEmail: '', adminPassword: '', allowedEmailDomains: [] });
177
+ vi.spyOn(authService, 'getUserById').mockImplementation(async () => ADMIN);
134
178
 
135
179
  const app = express();
136
180
  app.use(express.json());
@@ -138,13 +182,13 @@ async function makeHarness(opts: { isAdmin?: boolean } = {}): Promise<{ server:
138
182
  (req as unknown as { userId: string }).userId = ADMIN.id;
139
183
  next();
140
184
  });
141
- app.use('/api', createAccessRoutes(accessControl, workspaceService, authService, workflowService, eventBus, db, testKbContext({ kbDirName: KB })));
185
+ app.use('/api', createAccessRoutes(accessControl, workspaceService, authService, authService, workflowService, eventBus, db, testKbContext({ kbDirName: KB })));
142
186
 
143
187
  const server = await new Promise<Server>((resolve) => {
144
188
  const s = app.listen(0, () => resolve(s));
145
189
  });
146
190
  const addr = server.address() as AddressInfo;
147
- return { server, baseUrl: `http://127.0.0.1:${addr.port}` };
191
+ return { server, baseUrl: `http://127.0.0.1:${addr.port}`, txLog };
148
192
  }
149
193
 
150
194
  function close(s: Server): Promise<void> {
@@ -258,4 +302,85 @@ describe('/api/access/roles routes', () => {
258
302
  const body = (await add.json()) as { roles: { canonical: string; members: string[] }[] };
259
303
  expect(body.roles.find((r) => r.canonical === 'sales')!.members).toContain('mkt@example.com');
260
304
  });
305
+
306
+ // The invite's promotion (`ifActive`) reads whether the account is switched
307
+ // off inside the roles lock, not from the list the dialog read before the
308
+ // send nor before the lock was waited for.
309
+ async function addAdmin(baseUrl: string, body: unknown) {
310
+ return fetch(`${baseUrl}/api/access/roles/admin/members`, {
311
+ method: 'POST',
312
+ headers: { 'Content-Type': 'application/json' },
313
+ body: JSON.stringify(body),
314
+ });
315
+ }
316
+
317
+ it('ifActive refuses a switched-off account (409 deactivated) and writes nothing', async () => {
318
+ const h = await makeHarness();
319
+ server = h.server;
320
+ const res = await addAdmin(h.baseUrl, { email: 'Off@Example.com', ifActive: true });
321
+ expect(res.status).toBe(409);
322
+ expect(await res.json()).toEqual({ error: 'This account is switched off', kind: 'deactivated' });
323
+ const roster = (await (await fetch(`${h.baseUrl}/api/access/roles`)).json()) as {
324
+ roles: { canonical: string; members: string[] }[];
325
+ };
326
+ expect(roster.roles.find((r) => r.canonical === 'admin')!.members).not.toContain('off@example.com');
327
+ });
328
+
329
+ it('ifActive promotes a switched-on account, and one with no account yet', async () => {
330
+ const h = await makeHarness();
331
+ server = h.server;
332
+ expect((await addAdmin(h.baseUrl, { email: 'on@example.com', ifActive: true })).status).toBe(200);
333
+ const res = await addAdmin(h.baseUrl, { email: 'new@example.com', ifActive: true });
334
+ expect(res.status).toBe(200);
335
+ const body = (await res.json()) as { roles: { canonical: string; members: string[] }[] };
336
+ expect(body.roles.find((r) => r.canonical === 'admin')!.members).toEqual(
337
+ expect.arrayContaining(['on@example.com', 'new@example.com']),
338
+ );
339
+ });
340
+
341
+ it('ifActive reads the account once the roles lock is held: one switched off while it waited is refused', async () => {
342
+ const h = await makeHarness({ switchOffOnRolesLock: 'on@example.com' });
343
+ server = h.server;
344
+ const res = await addAdmin(h.baseUrl, { email: 'on@example.com', ifActive: true });
345
+ expect(res.status).toBe(409);
346
+ expect(await res.json()).toEqual({ error: 'This account is switched off', kind: 'deactivated' });
347
+ const roster = (await (await fetch(`${h.baseUrl}/api/access/roles`)).json()) as {
348
+ roles: { canonical: string; members: string[] }[];
349
+ };
350
+ expect(roster.roles.find((r) => r.canonical === 'admin')!.members).not.toContain('on@example.com');
351
+ });
352
+
353
+ // Every switch-off (and an account created switched off) takes the same
354
+ // lock first; see account-deactivation.test for that side.
355
+ it.each([
356
+ ['an existing account', 'On@Example.com', 'on@example.com'],
357
+ ['an address with no account yet', 'new@example.com', 'new@example.com'],
358
+ ])(
359
+ 'ifActive holds the address switch lock until roles.yaml is written, for %s',
360
+ async (_label, email, canonical) => {
361
+ const h = await makeHarness();
362
+ server = h.server;
363
+ expect((await addAdmin(h.baseUrl, { email, ifActive: true })).status).toBe(200);
364
+ expect(h.txLog).toEqual([
365
+ 'begin',
366
+ `select set_config('lock_timeout', $1, true) ["15000"]`,
367
+ `select pg_advisory_xact_lock(hashtext($1)) ["account-switch:${canonical}"]`,
368
+ `end: ${canonical}`,
369
+ ]);
370
+ },
371
+ );
372
+
373
+ it('without ifActive, adding a switched-off account works as before', async () => {
374
+ const h = await makeHarness();
375
+ server = h.server;
376
+ expect((await addAdmin(h.baseUrl, { email: 'off@example.com' })).status).toBe(200);
377
+ });
378
+
379
+ it('refuses a non-boolean ifActive (400)', async () => {
380
+ const h = await makeHarness();
381
+ server = h.server;
382
+ for (const ifActive of ['true', 1, null]) {
383
+ expect((await addAdmin(h.baseUrl, { email: 'on@example.com', ifActive })).status).toBe(400);
384
+ }
385
+ });
261
386
  });
@@ -329,11 +329,11 @@ describe('UserAccessRemovalService', () => {
329
329
  }
330
330
  });
331
331
 
332
- it('refuses the deployment owner and the last Admin, writing nothing', async () => {
332
+ it('refuses the owner and the last Admin, writing nothing', async () => {
333
333
  const { service, workflow } = build(stubWorkflow(), ['Lee@x.io']);
334
334
  const report = await service.report(LEE);
335
335
  expect(report.removable).toBe(false);
336
- expect(report.blockedReason).toMatch(/deployment owner/);
336
+ expect(report.blockedReason).toBe('The owner stays in roles and access rules.');
337
337
  await expect(service.remove(ADMIN, LEE, 'deleted-1')).rejects.toMatchObject({ status: 409 });
338
338
 
339
339
  const other = build(stubWorkflow(), []);
@@ -48,12 +48,13 @@ function boundValues(node: unknown, depth = 0): string[] {
48
48
  const canonical = (email: string) => email.trim().toLowerCase();
49
49
 
50
50
  export function usersDbDouble(
51
- accounts: readonly (string | { email: string; name?: string })[] = [],
51
+ accounts: readonly (string | { email: string; name?: string; deactivatedAt?: Date | null })[] = [],
52
52
  ): Database {
53
53
  const rows = accounts.map((a) => {
54
54
  const email = typeof a === 'string' ? a : a.email;
55
55
  const name = typeof a === 'string' ? undefined : a.name;
56
- return { id: `u-${email}`, email, name: name ?? email.split('@')[0] };
56
+ const deactivatedAt = typeof a === 'string' ? null : (a.deactivatedAt ?? null);
57
+ return { id: `u-${email}`, email, name: name ?? email.split('@')[0], deactivatedAt };
57
58
  });
58
59
  const from = () => {
59
60
  // Thenable so a bare `await db.select().from(users)` resolves, with
@@ -68,5 +69,13 @@ export function usersDbDouble(
68
69
  };
69
70
  return query;
70
71
  };
71
- return { select: () => ({ from }) } as unknown as Database;
72
+ const db = {
73
+ select: () => ({ from }),
74
+ // One connection, no isolation to model: the callback runs on the same
75
+ // double.
76
+ transaction: async <T>(cb: (tx: unknown) => Promise<T>) => cb(db),
77
+ // Locks taken with `execute` (advisory locks) answer nothing.
78
+ execute: async () => [],
79
+ };
80
+ return db as unknown as Database;
72
81
  }