@bevel-software/platform-core-backend 0.26.0 → 0.27.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 (613) 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 +54 -3
  17. package/dist/core/create-core-server.js.map +1 -1
  18. package/dist/core/create-core-services.d.ts +26 -0
  19. package/dist/core/create-core-services.d.ts.map +1 -1
  20. package/dist/core/create-core-services.js +100 -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 +8 -0
  35. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  36. package/dist/modules/access/access-control.service.d.ts +1 -0
  37. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  38. package/dist/modules/access/access-control.service.js +11 -0
  39. package/dist/modules/access/access-control.service.js.map +1 -1
  40. package/dist/modules/access/directory-sync-bot.js +1 -1
  41. package/dist/modules/access/directory-sync-bot.js.map +1 -1
  42. package/dist/modules/agent-guide/agent-guide.d.ts +6 -0
  43. package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
  44. package/dist/modules/agent-guide/agent-guide.js +8 -0
  45. package/dist/modules/agent-guide/agent-guide.js.map +1 -1
  46. package/dist/modules/agent-guide/index.d.ts +1 -1
  47. package/dist/modules/agent-guide/index.d.ts.map +1 -1
  48. package/dist/modules/agent-guide/index.js +1 -1
  49. package/dist/modules/agent-guide/index.js.map +1 -1
  50. package/dist/modules/agent-instructions/compose.d.ts +5 -0
  51. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  52. package/dist/modules/agent-instructions/compose.js +19 -2
  53. package/dist/modules/agent-instructions/compose.js.map +1 -1
  54. package/dist/modules/agent-instructions/shared-file-rules.js +19 -19
  55. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  56. package/dist/modules/audit/agent-audit.service.d.ts +21 -2
  57. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  58. package/dist/modules/audit/agent-audit.service.js +43 -2
  59. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  60. package/dist/modules/audit/agent-connection.routes.d.ts +34 -0
  61. package/dist/modules/audit/agent-connection.routes.d.ts.map +1 -0
  62. package/dist/modules/audit/agent-connection.routes.js +38 -0
  63. package/dist/modules/audit/agent-connection.routes.js.map +1 -0
  64. package/dist/modules/audit/audit.contract.d.ts +24 -0
  65. package/dist/modules/audit/audit.contract.d.ts.map +1 -1
  66. package/dist/modules/audit/audit.contract.js.map +1 -1
  67. package/dist/modules/audit/index.d.ts +1 -0
  68. package/dist/modules/audit/index.d.ts.map +1 -1
  69. package/dist/modules/audit/index.js +1 -0
  70. package/dist/modules/audit/index.js.map +1 -1
  71. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  72. package/dist/modules/auth/account.routes.js +10 -3
  73. package/dist/modules/auth/account.routes.js.map +1 -1
  74. package/dist/modules/auth/auth.middleware.d.ts +7 -0
  75. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  76. package/dist/modules/auth/auth.middleware.js +1 -1
  77. package/dist/modules/auth/auth.middleware.js.map +1 -1
  78. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  79. package/dist/modules/code-mode/code-mode.tool.js +11 -3
  80. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  81. package/dist/modules/database/core-schema.d.ts +96 -0
  82. package/dist/modules/database/core-schema.d.ts.map +1 -1
  83. package/dist/modules/database/core-schema.js +32 -3
  84. package/dist/modules/database/core-schema.js.map +1 -1
  85. package/dist/modules/embed/account-link.service.d.ts +42 -0
  86. package/dist/modules/embed/account-link.service.d.ts.map +1 -0
  87. package/dist/modules/embed/account-link.service.js +50 -0
  88. package/dist/modules/embed/account-link.service.js.map +1 -0
  89. package/dist/modules/embed/embed-link.d.ts +58 -0
  90. package/dist/modules/embed/embed-link.d.ts.map +1 -0
  91. package/dist/modules/embed/embed-link.js +160 -0
  92. package/dist/modules/embed/embed-link.js.map +1 -0
  93. package/dist/modules/embed/embed.errors.d.ts +30 -0
  94. package/dist/modules/embed/embed.errors.d.ts.map +1 -0
  95. package/dist/modules/embed/embed.errors.js +42 -0
  96. package/dist/modules/embed/embed.errors.js.map +1 -0
  97. package/dist/modules/embed/embed.interface.d.ts +204 -0
  98. package/dist/modules/embed/embed.interface.d.ts.map +1 -0
  99. package/dist/modules/embed/embed.interface.js +2 -0
  100. package/dist/modules/embed/embed.interface.js.map +1 -0
  101. package/dist/modules/embed/embed.routes.d.ts +59 -0
  102. package/dist/modules/embed/embed.routes.d.ts.map +1 -0
  103. package/dist/modules/embed/embed.routes.js +343 -0
  104. package/dist/modules/embed/embed.routes.js.map +1 -0
  105. package/dist/modules/embed/embed.service.d.ts +169 -0
  106. package/dist/modules/embed/embed.service.d.ts.map +1 -0
  107. package/dist/modules/embed/embed.service.js +627 -0
  108. package/dist/modules/embed/embed.service.js.map +1 -0
  109. package/dist/modules/embed/embed.tools.d.ts +51 -0
  110. package/dist/modules/embed/embed.tools.d.ts.map +1 -0
  111. package/dist/modules/embed/embed.tools.js +170 -0
  112. package/dist/modules/embed/embed.tools.js.map +1 -0
  113. package/dist/modules/embed/mcp-app.d.ts +64 -0
  114. package/dist/modules/embed/mcp-app.d.ts.map +1 -0
  115. package/dist/modules/embed/mcp-app.js +147 -0
  116. package/dist/modules/embed/mcp-app.js.map +1 -0
  117. package/dist/modules/embed/mcp-app.routes.d.ts +21 -0
  118. package/dist/modules/embed/mcp-app.routes.d.ts.map +1 -0
  119. package/dist/modules/embed/mcp-app.routes.js +36 -0
  120. package/dist/modules/embed/mcp-app.routes.js.map +1 -0
  121. package/dist/modules/github-app/github-app.connection.d.ts +15 -0
  122. package/dist/modules/github-app/github-app.connection.d.ts.map +1 -1
  123. package/dist/modules/github-app/github-app.connection.js +34 -3
  124. package/dist/modules/github-app/github-app.connection.js.map +1 -1
  125. package/dist/modules/kb-fs/locking-filesystem.d.ts +65 -0
  126. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  127. package/dist/modules/kb-fs/locking-filesystem.js +300 -0
  128. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  129. package/dist/modules/kb-sync/kb-sync.interface.d.ts +5 -0
  130. package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
  131. package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
  132. package/dist/modules/kb-sync/kb-sync.service.js +6 -0
  133. package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
  134. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  135. package/dist/modules/mcp/mcp-auth.middleware.js +2 -1
  136. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  137. package/dist/modules/mcp/mcp.service.d.ts +28 -1
  138. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  139. package/dist/modules/mcp/mcp.service.js +64 -8
  140. package/dist/modules/mcp/mcp.service.js.map +1 -1
  141. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -0
  142. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  143. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +32 -4
  144. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  145. package/dist/modules/onboarding/onboarding.contract.d.ts +80 -0
  146. package/dist/modules/onboarding/onboarding.contract.d.ts.map +1 -0
  147. package/dist/modules/onboarding/onboarding.contract.js +7 -0
  148. package/dist/modules/onboarding/onboarding.contract.js.map +1 -0
  149. package/dist/modules/onboarding/onboarding.routes.d.ts +18 -0
  150. package/dist/modules/onboarding/onboarding.routes.d.ts.map +1 -0
  151. package/dist/modules/onboarding/onboarding.routes.js +81 -0
  152. package/dist/modules/onboarding/onboarding.routes.js.map +1 -0
  153. package/dist/modules/onboarding/starter-pack.service.d.ts +213 -0
  154. package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -0
  155. package/dist/modules/onboarding/starter-pack.service.js +492 -0
  156. package/dist/modules/onboarding/starter-pack.service.js.map +1 -0
  157. package/dist/modules/onboarding/starter-packs.d.ts +78 -0
  158. package/dist/modules/onboarding/starter-packs.d.ts.map +1 -0
  159. package/dist/modules/onboarding/starter-packs.js +156 -0
  160. package/dist/modules/onboarding/starter-packs.js.map +1 -0
  161. package/dist/modules/plugins/index.d.ts +1 -1
  162. package/dist/modules/plugins/index.d.ts.map +1 -1
  163. package/dist/modules/plugins/index.js +1 -1
  164. package/dist/modules/plugins/index.js.map +1 -1
  165. package/dist/modules/plugins/plugin-provision.service.d.ts +19 -0
  166. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
  167. package/dist/modules/plugins/plugin-provision.service.js +18 -1
  168. package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
  169. package/dist/modules/plugins/plugins.routes.d.ts +0 -36
  170. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  171. package/dist/modules/plugins/plugins.routes.js +31 -0
  172. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  173. package/dist/modules/settings/deployment-settings.service.d.ts +34 -0
  174. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  175. package/dist/modules/settings/deployment-settings.service.js +134 -1
  176. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  177. package/dist/modules/settings/repository-source.d.ts +1 -0
  178. package/dist/modules/settings/repository-source.d.ts.map +1 -1
  179. package/dist/modules/settings/repository-source.js +3 -3
  180. package/dist/modules/settings/repository-source.js.map +1 -1
  181. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  182. package/dist/modules/skills/skills.tools.js +50 -32
  183. package/dist/modules/skills/skills.tools.js.map +1 -1
  184. package/dist/modules/tool-auth/connection-key-rejection.d.ts +10 -1
  185. package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -1
  186. package/dist/modules/tool-auth/connection-key-rejection.js +11 -3
  187. package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -1
  188. package/dist/modules/tool-auth/external-api-key.interface.d.ts +8 -0
  189. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  190. package/dist/modules/tool-auth/external-api-key.service.d.ts +24 -2
  191. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  192. package/dist/modules/tool-auth/external-api-key.service.js +59 -2
  193. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  194. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  195. package/dist/modules/tool-auth/tool-auth.middleware.js +9 -4
  196. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  197. package/dist/modules/tool-helpers/index.d.ts +28 -1
  198. package/dist/modules/tool-helpers/index.d.ts.map +1 -1
  199. package/dist/modules/tool-helpers/index.js +28 -1
  200. package/dist/modules/tool-helpers/index.js.map +1 -1
  201. package/dist/modules/tool-helpers/route-argument-check.d.ts +10 -0
  202. package/dist/modules/tool-helpers/route-argument-check.d.ts.map +1 -0
  203. package/dist/modules/tool-helpers/route-argument-check.js +132 -0
  204. package/dist/modules/tool-helpers/route-argument-check.js.map +1 -0
  205. package/dist/modules/tool-helpers/route-tool-schemas.d.ts +89 -0
  206. package/dist/modules/tool-helpers/route-tool-schemas.d.ts.map +1 -0
  207. package/dist/modules/tool-helpers/route-tool-schemas.js +113 -0
  208. package/dist/modules/tool-helpers/route-tool-schemas.js.map +1 -0
  209. package/dist/modules/tool-helpers/tool-def.d.ts +71 -0
  210. package/dist/modules/tool-helpers/tool-def.d.ts.map +1 -1
  211. package/dist/modules/tool-helpers/tool-def.js +70 -6
  212. package/dist/modules/tool-helpers/tool-def.js.map +1 -1
  213. package/dist/modules/tool-helpers/tool-handler.d.ts +46 -6
  214. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  215. package/dist/modules/tool-helpers/tool-handler.js +157 -8
  216. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  217. package/dist/modules/tool-helpers/tool.contract.d.ts +9 -0
  218. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  219. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  220. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  221. package/dist/modules/tool-manuals/tool-manuals.tools.js +14 -5
  222. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  223. package/dist/modules/tool-registry/description-length.d.ts +6 -2
  224. package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
  225. package/dist/modules/tool-registry/description-length.js +27 -5
  226. package/dist/modules/tool-registry/description-length.js.map +1 -1
  227. package/dist/modules/tool-registry/guide-first.d.ts +4 -0
  228. package/dist/modules/tool-registry/guide-first.d.ts.map +1 -1
  229. package/dist/modules/tool-registry/guide-first.js +9 -1
  230. package/dist/modules/tool-registry/guide-first.js.map +1 -1
  231. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +53 -0
  232. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -1
  233. package/dist/modules/workflow/agent-tools/change-request-read.tools.js +97 -68
  234. package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -1
  235. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +8 -1
  236. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  237. package/dist/modules/workflow/agent-tools/workflow.tools.js +124 -37
  238. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  239. package/dist/modules/workflow/file-lock.service.d.ts +29 -5
  240. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  241. package/dist/modules/workflow/file-lock.service.js +62 -5
  242. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  243. package/dist/modules/workflow/git/git.service.d.ts +72 -1
  244. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  245. package/dist/modules/workflow/git/git.service.js +172 -36
  246. package/dist/modules/workflow/git/git.service.js.map +1 -1
  247. package/dist/modules/workflow/git/node-git-runner.d.ts +15 -0
  248. package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -1
  249. package/dist/modules/workflow/git/node-git-runner.js +78 -6
  250. package/dist/modules/workflow/git/node-git-runner.js.map +1 -1
  251. package/dist/modules/workflow/pending-commits.service.d.ts +15 -0
  252. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  253. package/dist/modules/workflow/pending-commits.service.js +25 -1
  254. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  255. package/dist/modules/workflow/pending-commits.worker.d.ts +6 -1
  256. package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
  257. package/dist/modules/workflow/pending-commits.worker.js +23 -6
  258. package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
  259. package/dist/modules/workflow/recovery-bot.js +1 -1
  260. package/dist/modules/workflow/recovery-bot.js.map +1 -1
  261. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  262. package/dist/modules/workflow/review-workflow/review-workflow.service.js +2 -1
  263. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  264. package/dist/modules/workflow/sanitize-error.d.ts +16 -0
  265. package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
  266. package/dist/modules/workflow/sanitize-error.js +45 -0
  267. package/dist/modules/workflow/sanitize-error.js.map +1 -1
  268. package/dist/modules/workflow/workflow.service.d.ts +183 -15
  269. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  270. package/dist/modules/workflow/workflow.service.js +805 -106
  271. package/dist/modules/workflow/workflow.service.js.map +1 -1
  272. package/dist/modules/workspace/agent-download.builder.d.ts +105 -0
  273. package/dist/modules/workspace/agent-download.builder.d.ts.map +1 -0
  274. package/dist/modules/workspace/agent-download.builder.js +238 -0
  275. package/dist/modules/workspace/agent-download.builder.js.map +1 -0
  276. package/dist/modules/workspace/agent-download.routes.d.ts +77 -0
  277. package/dist/modules/workspace/agent-download.routes.d.ts.map +1 -0
  278. package/dist/modules/workspace/agent-download.routes.js +156 -0
  279. package/dist/modules/workspace/agent-download.routes.js.map +1 -0
  280. package/dist/modules/workspace/agent-download.store.d.ts +225 -0
  281. package/dist/modules/workspace/agent-download.store.d.ts.map +1 -0
  282. package/dist/modules/workspace/agent-download.store.js +423 -0
  283. package/dist/modules/workspace/agent-download.store.js.map +1 -0
  284. package/dist/modules/workspace/agent-upload.store.d.ts +10 -1
  285. package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -1
  286. package/dist/modules/workspace/agent-upload.store.js +16 -6
  287. package/dist/modules/workspace/agent-upload.store.js.map +1 -1
  288. package/dist/modules/workspace/file-readers/raw-mime.d.ts +6 -0
  289. package/dist/modules/workspace/file-readers/raw-mime.d.ts.map +1 -0
  290. package/dist/modules/workspace/file-readers/raw-mime.js +38 -0
  291. package/dist/modules/workspace/file-readers/raw-mime.js.map +1 -0
  292. package/dist/modules/workspace/file-readers/text-reader.d.ts +5 -5
  293. package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
  294. package/dist/modules/workspace/file-readers/text-reader.js +5 -8
  295. package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
  296. package/dist/modules/workspace/first-run.d.ts +111 -0
  297. package/dist/modules/workspace/first-run.d.ts.map +1 -0
  298. package/dist/modules/workspace/first-run.js +181 -0
  299. package/dist/modules/workspace/first-run.js.map +1 -0
  300. package/dist/modules/workspace/move-links.d.ts +92 -0
  301. package/dist/modules/workspace/move-links.d.ts.map +1 -0
  302. package/dist/modules/workspace/move-links.js +173 -0
  303. package/dist/modules/workspace/move-links.js.map +1 -0
  304. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  305. package/dist/modules/workspace/startup/steps/seed-tree.js +3 -13
  306. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  307. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  308. package/dist/modules/workspace/workspace.routes.js +63 -22
  309. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  310. package/dist/modules/workspace/workspace.service.d.ts +75 -2
  311. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  312. package/dist/modules/workspace/workspace.service.js +197 -24
  313. package/dist/modules/workspace/workspace.service.js.map +1 -1
  314. package/dist/modules/workspace/workspace.tools.d.ts +59 -1
  315. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  316. package/dist/modules/workspace/workspace.tools.js +443 -65
  317. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  318. package/dist/modules/write-access/write-access.d.ts.map +1 -1
  319. package/dist/modules/write-access/write-access.js +9 -0
  320. package/dist/modules/write-access/write-access.js.map +1 -1
  321. package/dist/shared/domain-errors.d.ts +73 -1
  322. package/dist/shared/domain-errors.d.ts.map +1 -1
  323. package/dist/shared/domain-errors.js +86 -12
  324. package/dist/shared/domain-errors.js.map +1 -1
  325. package/dist/shared/git.contract.d.ts +10 -1
  326. package/dist/shared/git.contract.d.ts.map +1 -1
  327. package/dist/shared/git.contract.js.map +1 -1
  328. package/dist/shared/utf8-text.d.ts +15 -0
  329. package/dist/shared/utf8-text.d.ts.map +1 -0
  330. package/dist/shared/utf8-text.js +20 -0
  331. package/dist/shared/utf8-text.js.map +1 -0
  332. package/dist/tenancy/static-tenant-source.d.ts +2 -1
  333. package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
  334. package/dist/tenancy/static-tenant-source.js +9 -2
  335. package/dist/tenancy/static-tenant-source.js.map +1 -1
  336. package/mcp-app/page.html +349 -0
  337. package/migrations/0017_atlassian_account_links.sql +37 -0
  338. package/migrations/meta/0017_snapshot.json +2396 -0
  339. package/migrations/meta/_journal.json +8 -1
  340. package/package.json +5 -3
  341. package/src/__tests__/packaged-assets-ship.test.ts +16 -4
  342. package/src/assets.ts +28 -2
  343. package/src/core/__tests__/catalog-revision.test.ts +40 -0
  344. package/src/core/__tests__/gated-boot.test.ts +1 -0
  345. package/src/core/__tests__/lifecycle.test.ts +14 -0
  346. package/src/core/catalog-revision.ts +15 -2
  347. package/src/core/create-core-server.ts +67 -3
  348. package/src/core/create-core-services.ts +140 -6
  349. package/src/core/lifecycle.ts +28 -2
  350. package/src/core-config.ts +24 -1
  351. package/src/index.ts +2 -2
  352. package/src/modules/access/__tests__/access-control.service.test.ts +19 -0
  353. package/src/modules/access/access-control.interface.ts +13 -0
  354. package/src/modules/access/access-control.service.ts +16 -0
  355. package/src/modules/access/directory-sync-bot.ts +1 -1
  356. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +58 -0
  357. package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +19 -1
  358. package/src/modules/agent-guide/agent-guide.ts +9 -0
  359. package/src/modules/agent-guide/index.ts +1 -0
  360. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +14 -0
  361. package/src/modules/agent-instructions/compose.ts +17 -2
  362. package/src/modules/agent-instructions/shared-file-rules.ts +19 -19
  363. package/src/modules/audit/__tests__/agent-audit.service.test.ts +64 -4
  364. package/src/modules/audit/__tests__/agent-connection.routes.test.ts +102 -0
  365. package/src/modules/audit/agent-audit.service.ts +58 -3
  366. package/src/modules/audit/agent-connection.routes.ts +56 -0
  367. package/src/modules/audit/audit.contract.ts +26 -0
  368. package/src/modules/audit/index.ts +1 -0
  369. package/src/modules/auth/__tests__/account.routes.test.ts +5 -3
  370. package/src/modules/auth/account.routes.ts +10 -3
  371. package/src/modules/auth/auth.middleware.ts +1 -1
  372. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +18 -2
  373. package/src/modules/code-mode/code-mode.tool.ts +13 -2
  374. package/src/modules/database/core-schema.ts +33 -3
  375. package/src/modules/embed/__tests__/account-links.migration.test.ts +98 -0
  376. package/src/modules/embed/__tests__/embed-link.test.ts +122 -0
  377. package/src/modules/embed/__tests__/embed.routes.test.ts +362 -0
  378. package/src/modules/embed/__tests__/embed.service.lock.test.ts +193 -0
  379. package/src/modules/embed/__tests__/embed.service.test.ts +805 -0
  380. package/src/modules/embed/__tests__/embed.tools.test.ts +304 -0
  381. package/src/modules/embed/__tests__/mcp-app.test.ts +207 -0
  382. package/src/modules/embed/account-link.service.ts +76 -0
  383. package/src/modules/embed/embed-link.ts +180 -0
  384. package/src/modules/embed/embed.errors.ts +43 -0
  385. package/src/modules/embed/embed.interface.ts +203 -0
  386. package/src/modules/embed/embed.routes.ts +363 -0
  387. package/src/modules/embed/embed.service.ts +705 -0
  388. package/src/modules/embed/embed.tools.ts +206 -0
  389. package/src/modules/embed/mcp-app.routes.ts +41 -0
  390. package/src/modules/embed/mcp-app.ts +173 -0
  391. package/src/modules/github-app/__tests__/github-app.test.ts +76 -0
  392. package/src/modules/github-app/github-app.connection.ts +38 -4
  393. package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +304 -0
  394. package/src/modules/kb-fs/locking-filesystem.ts +287 -0
  395. package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +35 -0
  396. package/src/modules/kb-sync/kb-sync.interface.ts +2 -0
  397. package/src/modules/kb-sync/kb-sync.service.ts +6 -0
  398. package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +68 -11
  399. package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +37 -2
  400. package/src/modules/mcp/__tests__/mcp.app.e2e.test.ts +200 -0
  401. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +3 -1
  402. package/src/modules/mcp/__tests__/mcp.service.test.ts +23 -13
  403. package/src/modules/mcp/__tests__/tool-call-interface.e2e.test.ts +499 -0
  404. package/src/modules/mcp/mcp-auth.middleware.ts +2 -1
  405. package/src/modules/mcp/mcp.service.ts +81 -12
  406. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +42 -4
  407. package/src/modules/onboarding/__tests__/onboarding.routes.test.ts +163 -0
  408. package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +635 -0
  409. package/src/modules/onboarding/__tests__/starter-packs.test.ts +169 -0
  410. package/src/modules/onboarding/onboarding.contract.ts +85 -0
  411. package/src/modules/onboarding/onboarding.routes.ts +85 -0
  412. package/src/modules/onboarding/starter-pack.service.ts +580 -0
  413. package/src/modules/onboarding/starter-packs.ts +182 -0
  414. package/src/modules/plugins/__tests__/plugins.tools.test.ts +25 -2
  415. package/src/modules/plugins/index.ts +1 -0
  416. package/src/modules/plugins/plugin-provision.service.ts +22 -1
  417. package/src/modules/plugins/plugins.routes.ts +29 -0
  418. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +150 -1
  419. package/src/modules/settings/__tests__/repository-source.test.ts +9 -0
  420. package/src/modules/settings/deployment-settings.service.ts +131 -1
  421. package/src/modules/settings/repository-source.ts +4 -4
  422. package/src/modules/skills/__tests__/skills.tools.description.test.ts +5 -1
  423. package/src/modules/skills/skills.tools.ts +55 -35
  424. package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +32 -0
  425. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +65 -0
  426. package/src/modules/tool-auth/connection-key-rejection.ts +13 -3
  427. package/src/modules/tool-auth/external-api-key.interface.ts +9 -0
  428. package/src/modules/tool-auth/external-api-key.service.ts +61 -4
  429. package/src/modules/tool-auth/tool-auth.middleware.ts +13 -4
  430. package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +502 -0
  431. package/src/modules/tool-helpers/__tests__/route-argument-check.test.ts +337 -0
  432. package/src/modules/tool-helpers/index.ts +34 -1
  433. package/src/modules/tool-helpers/route-argument-check.ts +149 -0
  434. package/src/modules/tool-helpers/route-tool-schemas.ts +171 -0
  435. package/src/modules/tool-helpers/tool-def.ts +132 -7
  436. package/src/modules/tool-helpers/tool-handler.ts +196 -8
  437. package/src/modules/tool-helpers/tool.contract.ts +9 -0
  438. package/src/modules/tool-manuals/tool-manuals.tools.ts +15 -5
  439. package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +14 -1
  440. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +23 -10
  441. package/src/modules/tool-registry/description-length.ts +34 -7
  442. package/src/modules/tool-registry/guide-first.ts +8 -1
  443. package/src/modules/workflow/__tests__/file-lock.service.deletion-gate.test.ts +104 -0
  444. package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +66 -0
  445. package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +31 -2
  446. package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +110 -6
  447. package/src/modules/workflow/__tests__/sanitize-error.test.ts +89 -1
  448. package/src/modules/workflow/__tests__/workflow.routes.push-refused.test.ts +202 -0
  449. package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +6 -6
  450. package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +1026 -0
  451. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +468 -10
  452. package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +4 -0
  453. package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +5 -4
  454. package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +54 -2
  455. package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +41 -5
  456. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +127 -2
  457. package/src/modules/workflow/agent-tools/change-request-read.tools.ts +157 -122
  458. package/src/modules/workflow/agent-tools/workflow.tools.ts +129 -35
  459. package/src/modules/workflow/file-lock.service.ts +67 -5
  460. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +164 -0
  461. package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +48 -1
  462. package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +231 -2
  463. package/src/modules/workflow/git/git.service.ts +176 -38
  464. package/src/modules/workflow/git/node-git-runner.ts +82 -7
  465. package/src/modules/workflow/pending-commits.service.ts +29 -1
  466. package/src/modules/workflow/pending-commits.worker.ts +24 -6
  467. package/src/modules/workflow/recovery-bot.ts +1 -1
  468. package/src/modules/workflow/review-workflow/review-workflow.service.ts +2 -1
  469. package/src/modules/workflow/sanitize-error.ts +50 -0
  470. package/src/modules/workflow/workflow.service.ts +879 -112
  471. package/src/modules/workspace/__tests__/agent-downloads.test.ts +823 -0
  472. package/src/modules/workspace/__tests__/first-run.test.ts +234 -0
  473. package/src/modules/workspace/__tests__/git-internals.security.test.ts +5 -2
  474. package/src/modules/workspace/__tests__/md-links.test.ts +353 -0
  475. package/src/modules/workspace/__tests__/move-links.test.ts +261 -0
  476. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +219 -0
  477. package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +3 -0
  478. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +102 -0
  479. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +52 -5
  480. package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +190 -0
  481. package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +8 -4
  482. package/src/modules/workspace/__tests__/workspace.service.test.ts +50 -8
  483. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +44 -1
  484. package/src/modules/workspace/__tests__/workspace.tools.test.ts +409 -12
  485. package/src/modules/workspace/agent-download.builder.ts +308 -0
  486. package/src/modules/workspace/agent-download.routes.ts +183 -0
  487. package/src/modules/workspace/agent-download.store.ts +533 -0
  488. package/src/modules/workspace/agent-upload.store.ts +23 -7
  489. package/src/modules/workspace/file-readers/raw-mime.ts +40 -0
  490. package/src/modules/workspace/file-readers/text-reader.ts +5 -8
  491. package/src/modules/workspace/first-run.ts +222 -0
  492. package/src/modules/workspace/move-links.ts +237 -0
  493. package/src/modules/workspace/startup/steps/seed-tree.ts +3 -12
  494. package/src/modules/workspace/workspace.routes.ts +64 -22
  495. package/src/modules/workspace/workspace.service.ts +202 -25
  496. package/src/modules/workspace/workspace.tools.ts +492 -63
  497. package/src/modules/write-access/__tests__/write-access.test.ts +13 -0
  498. package/src/modules/write-access/write-access.ts +9 -0
  499. package/src/shared/domain-errors.ts +108 -11
  500. package/src/shared/git.contract.ts +8 -1
  501. package/src/shared/utf8-text.ts +21 -0
  502. package/src/tenancy/__tests__/static-tenant-source.test.ts +1 -0
  503. package/src/tenancy/static-tenant-source.ts +10 -2
  504. package/starter-packs/README.md +103 -0
  505. package/starter-packs/engineering/KnowledgeBase/About us.md +9 -0
  506. package/starter-packs/engineering/KnowledgeBase/Architecture.md +9 -0
  507. package/starter-packs/engineering/KnowledgeBase/Glossary.md +9 -0
  508. package/starter-packs/engineering/KnowledgeBase/How we ship.md +9 -0
  509. package/starter-packs/engineering/Plugins/engineering-starter/LICENSE +21 -0
  510. package/starter-packs/engineering/Plugins/engineering-starter/SOURCE.md +20 -0
  511. package/starter-packs/engineering/Plugins/engineering-starter/access.md +19 -0
  512. package/starter-packs/engineering/Plugins/engineering-starter/plugin.json +10 -0
  513. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/SKILL.md +171 -0
  514. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2a-startup-diagnostic.md +157 -0
  515. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2b-builder-brainstorm.md +39 -0
  516. package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-ceo-review/SKILL.md +144 -0
  517. package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-eng-review/SKILL.md +144 -0
  518. package/starter-packs/engineering/Plugins/engineering-starter/skills/review/SKILL.md +80 -0
  519. package/starter-packs/engineering/Plugins/engineering-starter/skills/review/checklist.md +184 -0
  520. package/starter-packs/engineering/pack.yaml +11 -0
  521. package/starter-packs/general/KnowledgeBase/About us.md +9 -0
  522. package/starter-packs/general/KnowledgeBase/Glossary.md +9 -0
  523. package/starter-packs/general/KnowledgeBase/How we work.md +9 -0
  524. package/starter-packs/general/Plugins/general-starter/LICENSE +202 -0
  525. package/starter-packs/general/Plugins/general-starter/SOURCE.md +10 -0
  526. package/starter-packs/general/Plugins/general-starter/access.md +19 -0
  527. package/starter-packs/general/Plugins/general-starter/plugin.json +10 -0
  528. package/starter-packs/general/Plugins/general-starter/skills/memory-management/SKILL.md +332 -0
  529. package/starter-packs/general/Plugins/general-starter/skills/productivity-start/SKILL.md +163 -0
  530. package/starter-packs/general/Plugins/general-starter/skills/productivity-update/SKILL.md +167 -0
  531. package/starter-packs/general/Plugins/general-starter/skills/task-management/SKILL.md +83 -0
  532. package/starter-packs/general/pack.yaml +10 -0
  533. package/starter-packs/operations/KnowledgeBase/Glossary.md +9 -0
  534. package/starter-packs/operations/KnowledgeBase/How we work.md +9 -0
  535. package/starter-packs/operations/KnowledgeBase/Processes.md +9 -0
  536. package/starter-packs/operations/KnowledgeBase/Tools we use.md +9 -0
  537. package/starter-packs/operations/KnowledgeBase/Vendors.md +9 -0
  538. package/starter-packs/operations/Plugins/operations-starter/LICENSE +202 -0
  539. package/starter-packs/operations/Plugins/operations-starter/SOURCE.md +10 -0
  540. package/starter-packs/operations/Plugins/operations-starter/access.md +19 -0
  541. package/starter-packs/operations/Plugins/operations-starter/plugin.json +10 -0
  542. package/starter-packs/operations/Plugins/operations-starter/skills/capacity-plan/SKILL.md +111 -0
  543. package/starter-packs/operations/Plugins/operations-starter/skills/change-request/SKILL.md +119 -0
  544. package/starter-packs/operations/Plugins/operations-starter/skills/compliance-tracking/SKILL.md +44 -0
  545. package/starter-packs/operations/Plugins/operations-starter/skills/process-doc/SKILL.md +80 -0
  546. package/starter-packs/operations/Plugins/operations-starter/skills/process-optimization/SKILL.md +39 -0
  547. package/starter-packs/operations/Plugins/operations-starter/skills/risk-assessment/SKILL.md +40 -0
  548. package/starter-packs/operations/Plugins/operations-starter/skills/runbook/SKILL.md +85 -0
  549. package/starter-packs/operations/Plugins/operations-starter/skills/status-report/SKILL.md +75 -0
  550. package/starter-packs/operations/Plugins/operations-starter/skills/vendor-review/SKILL.md +102 -0
  551. package/starter-packs/operations/pack.yaml +12 -0
  552. package/starter-packs/sales/KnowledgeBase/About us.md +9 -0
  553. package/starter-packs/sales/KnowledgeBase/Customers.md +9 -0
  554. package/starter-packs/sales/KnowledgeBase/Glossary.md +9 -0
  555. package/starter-packs/sales/KnowledgeBase/How we sell.md +9 -0
  556. package/starter-packs/sales/KnowledgeBase/Objections.md +9 -0
  557. package/starter-packs/sales/KnowledgeBase/Pricing.md +9 -0
  558. package/starter-packs/sales/Plugins/sales-starter/LICENSE +202 -0
  559. package/starter-packs/sales/Plugins/sales-starter/SOURCE.md +10 -0
  560. package/starter-packs/sales/Plugins/sales-starter/access.md +19 -0
  561. package/starter-packs/sales/Plugins/sales-starter/plugin.json +10 -0
  562. package/starter-packs/sales/Plugins/sales-starter/skills/account-context/SKILL.md +94 -0
  563. package/starter-packs/sales/Plugins/sales-starter/skills/account-plan/SKILL.md +95 -0
  564. package/starter-packs/sales/Plugins/sales-starter/skills/account-research/SKILL.md +99 -0
  565. package/starter-packs/sales/Plugins/sales-starter/skills/account-tiering/SKILL.md +101 -0
  566. package/starter-packs/sales/Plugins/sales-starter/skills/call-prep/SKILL.md +111 -0
  567. package/starter-packs/sales/Plugins/sales-starter/skills/call-summary/SKILL.md +111 -0
  568. package/starter-packs/sales/Plugins/sales-starter/skills/close-plan/SKILL.md +106 -0
  569. package/starter-packs/sales/Plugins/sales-starter/skills/competitive-intelligence/SKILL.md +102 -0
  570. package/starter-packs/sales/Plugins/sales-starter/skills/create-an-asset/SKILL.md +105 -0
  571. package/starter-packs/sales/Plugins/sales-starter/skills/crm-hygiene-check/SKILL.md +103 -0
  572. package/starter-packs/sales/Plugins/sales-starter/skills/customer-health/SKILL.md +108 -0
  573. package/starter-packs/sales/Plugins/sales-starter/skills/customer-voice/SKILL.md +94 -0
  574. package/starter-packs/sales/Plugins/sales-starter/skills/daily-briefing/SKILL.md +61 -0
  575. package/starter-packs/sales/Plugins/sales-starter/skills/deal-advance-gap/SKILL.md +102 -0
  576. package/starter-packs/sales/Plugins/sales-starter/skills/deal-review/SKILL.md +105 -0
  577. package/starter-packs/sales/Plugins/sales-starter/skills/deal-signals/SKILL.md +96 -0
  578. package/starter-packs/sales/Plugins/sales-starter/skills/deal-slip-scenario/SKILL.md +104 -0
  579. package/starter-packs/sales/Plugins/sales-starter/skills/draft-outreach/SKILL.md +116 -0
  580. package/starter-packs/sales/Plugins/sales-starter/skills/end-of-day/SKILL.md +114 -0
  581. package/starter-packs/sales/Plugins/sales-starter/skills/expansion-whitespace/SKILL.md +97 -0
  582. package/starter-packs/sales/Plugins/sales-starter/skills/forecast/SKILL.md +100 -0
  583. package/starter-packs/sales/Plugins/sales-starter/skills/handle-objection/SKILL.md +93 -0
  584. package/starter-packs/sales/Plugins/sales-starter/skills/inbox-sweep/SKILL.md +114 -0
  585. package/starter-packs/sales/Plugins/sales-starter/skills/lead-triage/SKILL.md +106 -0
  586. package/starter-packs/sales/Plugins/sales-starter/skills/log-activity/SKILL.md +111 -0
  587. package/starter-packs/sales/Plugins/sales-starter/skills/pipeline-review/SKILL.md +97 -0
  588. package/starter-packs/sales/Plugins/sales-starter/skills/renewal-radar/SKILL.md +95 -0
  589. package/starter-packs/sales/Plugins/sales-starter/skills/rep-context/SKILL.md +90 -0
  590. package/starter-packs/sales/Plugins/sales-starter/skills/route-lead/SKILL.md +111 -0
  591. package/starter-packs/sales/Plugins/sales-starter/skills/sales-setup/SKILL.md +200 -0
  592. package/starter-packs/sales/Plugins/sales-starter/skills/schedule-meeting/SKILL.md +99 -0
  593. package/starter-packs/sales/Plugins/sales-starter/skills/stakeholder-map/SKILL.md +107 -0
  594. package/starter-packs/sales/Plugins/sales-starter/skills/team-pipeline/SKILL.md +105 -0
  595. package/starter-packs/sales/Plugins/sales-starter/skills/update-opportunity/SKILL.md +112 -0
  596. package/starter-packs/sales/Plugins/sales-starter/skills/weekly-wrap/SKILL.md +90 -0
  597. package/starter-packs/sales/Plugins/sales-starter/skills/win-loss-review/SKILL.md +88 -0
  598. package/starter-packs/sales/pack.yaml +13 -0
  599. package/starter-packs/support/KnowledgeBase/Escalations.md +9 -0
  600. package/starter-packs/support/KnowledgeBase/Glossary.md +9 -0
  601. package/starter-packs/support/KnowledgeBase/Known issues.md +9 -0
  602. package/starter-packs/support/KnowledgeBase/Products.md +9 -0
  603. package/starter-packs/support/KnowledgeBase/Tone of voice.md +9 -0
  604. package/starter-packs/support/Plugins/support-starter/LICENSE +202 -0
  605. package/starter-packs/support/Plugins/support-starter/SOURCE.md +10 -0
  606. package/starter-packs/support/Plugins/support-starter/access.md +19 -0
  607. package/starter-packs/support/Plugins/support-starter/plugin.json +10 -0
  608. package/starter-packs/support/Plugins/support-starter/skills/customer-escalation/SKILL.md +246 -0
  609. package/starter-packs/support/Plugins/support-starter/skills/customer-research/SKILL.md +250 -0
  610. package/starter-packs/support/Plugins/support-starter/skills/draft-response/SKILL.md +416 -0
  611. package/starter-packs/support/Plugins/support-starter/skills/kb-article/SKILL.md +350 -0
  612. package/starter-packs/support/Plugins/support-starter/skills/ticket-triage/SKILL.md +273 -0
  613. package/starter-packs/support/pack.yaml +12 -0
@@ -49,6 +49,8 @@ interface Harness {
49
49
  server: Server;
50
50
  baseUrl: string;
51
51
  canDownload: ReturnType<typeof vi.fn>;
52
+ canReadBatch: ReturnType<typeof vi.fn>;
53
+ canDownloadBatch: ReturnType<typeof vi.fn>;
52
54
  canWrite: ReturnType<typeof vi.fn>;
53
55
  createFolderZip: ReturnType<typeof vi.fn>;
54
56
  }
@@ -70,14 +72,21 @@ async function makeHarness(opts: {
70
72
  return opts.canDownload;
71
73
  });
72
74
  const canWrite = vi.fn();
75
+ // The /file/raw route now read-gates before the download gate; these tests
76
+ // exercise the download verb, so reads pass.
77
+ const canReadBatch = vi.fn(async (_w: string, _e: string, paths: string[]) => new Map(paths.map((p) => [p, true])));
78
+ // Every entry of a folder is judged for download too; by default each gets
79
+ // the folder's own verdict.
80
+ const canDownloadBatch = vi.fn(async (_w: string, _e: string, paths: string[]) =>
81
+ new Map(paths.map((p) => [p, opts.canDownload])),
82
+ );
73
83
  const accessControl: IAccessControl = {
74
84
  canWrite,
75
85
  canWriteBatch: vi.fn(),
76
- // The /file/raw route now read-gates before the download gate; these tests
77
- // exercise the download verb, so reads pass.
78
86
  canRead: vi.fn(async () => true),
79
- canReadBatch: vi.fn(async (_w: string, _e: string, paths: string[]) => new Map(paths.map((p) => [p, true]))),
87
+ canReadBatch,
80
88
  canDownload,
89
+ canDownloadBatch,
81
90
  eligibleWriters: vi.fn(),
82
91
  eligibleWriterEmails: vi.fn(),
83
92
  invalidate: vi.fn(),
@@ -152,7 +161,7 @@ async function makeHarness(opts: {
152
161
  throw new Error(`Unexpected server.address() shape: ${JSON.stringify(addr)}`);
153
162
  }
154
163
  const port = (addr as AddressInfo).port;
155
- return { server, baseUrl: `http://127.0.0.1:${port}`, canDownload, canWrite, createFolderZip };
164
+ return { server, baseUrl: `http://127.0.0.1:${port}`, canDownload, canReadBatch, canDownloadBatch, canWrite, createFolderZip };
156
165
  }
157
166
 
158
167
  async function closeServer(server: Server): Promise<void> {
@@ -338,6 +347,8 @@ describe('GET /workspace/:id/folder/zip — gated on Download role', () => {
338
347
  expect(res.status).toBe(200);
339
348
  expect(res.headers.get('content-type')).toBe('application/zip');
340
349
  expect(res.headers.get('x-content-type-options')).toBe('nosniff');
350
+ // The archive holds one caller's verdicts, so a shared cache never keeps it.
351
+ expect(res.headers.get('cache-control')).toBe('private, no-store');
341
352
  const dispo = res.headers.get('content-disposition');
342
353
  expect(dispo).toBeTruthy();
343
354
  expect(dispo).toContain('attachment');
@@ -349,7 +360,43 @@ describe('GET /workspace/:id/folder/zip — gated on Download role', () => {
349
360
  // verb is resolved on its repo-relative form, and the service is handed the
350
361
  // repository path it was judged as.
351
362
  expect(h.canDownload).toHaveBeenCalledWith(WORKSPACE_ID, USER.email, 'Knowledge/Sales');
352
- expect(h.createFolderZip).toHaveBeenCalledWith(WORKSPACE_ID, `${KB}/Knowledge/Sales`);
363
+ expect(h.createFolderZip.mock.calls[0]!.slice(0, 2)).toEqual([WORKSPACE_ID, `${KB}/Knowledge/Sales`]);
364
+ });
365
+
366
+ it('judges every file the zip would pack: unreadable ones and undownloadable ones are left out', async () => {
367
+ h = await makeHarness({ canDownload: true });
368
+ // What the service finds under the folder; the route's filter decides.
369
+ const found = ['Knowledge/Sales/open.md', 'Knowledge/Sales/hidden.md', 'Knowledge/Sales/no-save.md'];
370
+ h.canReadBatch.mockImplementation(async (_w: string, _e: string, paths: string[]) =>
371
+ new Map(paths.map((p) => [p, p !== 'Knowledge/Sales/hidden.md'])),
372
+ );
373
+ h.canDownloadBatch.mockImplementation(async (_w: string, _e: string, paths: string[]) =>
374
+ new Map(paths.map((p) => [p, p !== 'Knowledge/Sales/no-save.md'])),
375
+ );
376
+ let packed: ReadonlySet<string> | null = null;
377
+ h.createFolderZip.mockImplementation(
378
+ async (_id: string, _p: string, include: (paths: string[]) => Promise<ReadonlySet<string>>) => {
379
+ packed = await include(found);
380
+ return ZIP_BYTES;
381
+ },
382
+ );
383
+
384
+ const res = await fetch(
385
+ `${h.baseUrl}/api/workspace/${WORKSPACE_ID}/folder/zip?path=${encodeURIComponent('Knowledge/Sales')}&download=1`,
386
+ );
387
+
388
+ expect(res.status).toBe(200);
389
+ expect([...packed!]).toEqual(['Knowledge/Sales/open.md']);
390
+ // Read first, then download on what is readable: a file the caller may
391
+ // not see is never asked about for download, so nothing about it is
392
+ // computed or counted.
393
+ expect(h.canReadBatch).toHaveBeenCalledWith(WORKSPACE_ID, USER.email, found);
394
+ expect(h.canDownloadBatch).toHaveBeenCalledWith(WORKSPACE_ID, USER.email, [
395
+ 'Knowledge/Sales/open.md',
396
+ 'Knowledge/Sales/no-save.md',
397
+ ]);
398
+ // The count covers the readable file left out, not the hidden one.
399
+ expect(res.headers.get('x-withheld-files')).toBe('1');
353
400
  });
354
401
 
355
402
  it('user without Download role gets 403 and no zip is built', async () => {
@@ -0,0 +1,190 @@
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 AdmZip from 'adm-zip';
8
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
9
+ import { testKbContext } from '../../../__tests__/kb-context.js';
10
+ import { NodeFs } from '../../kb-fs/node-fs.js';
11
+ import type { IWorkflowService } from '@bevel-software/platform-shared';
12
+ import type { ICreatorAccess } from '../../access-model/creator.js';
13
+ import type { IAdminAccessService } from '../../admin/admin.interface.js';
14
+ import type { WorkflowEventBus } from '../../workflow/event-bus.js';
15
+ import type { AuthService } from '../../auth/auth.service.js';
16
+ import { AccessControlService } from '../../access/access-control.service.js';
17
+ import { createWorkspaceRoutes } from '../workspace.routes.js';
18
+ import { WorkspaceService } from '../workspace.service.js';
19
+
20
+ /**
21
+ * The folder download over the REAL resolver and the REAL zip builder.
22
+ *
23
+ * `download` on a folder let the caller ask for its zip, and the zip then
24
+ * packed every file under the folder: no file was judged against its own
25
+ * rules, so a caller got files whose frontmatter denied them `download`, files
26
+ * whose frontmatter denied them `read`, and whole sub-folders a nested
27
+ * access.md closed to them — files the per-file route refused and the file
28
+ * tree never showed (found 2026-10-07). The four cases below are the ones
29
+ * that were verified leaking; each must now be absent from the archive, and
30
+ * the one the caller may download must still be in it.
31
+ */
32
+
33
+ const KB = 'knowledge-base';
34
+ const WORKSPACE_ID = 'target-company-state';
35
+
36
+ const stubCreatorAccess: ICreatorAccess = {
37
+ planForCreate: async () => null,
38
+ grantInExtractedFile: async () => null,
39
+ noteAccessFileWritten: () => {},
40
+ };
41
+
42
+ let caller = { id: 'user-1', email: 'ana@x.io', name: 'Ana' };
43
+
44
+ const ROLES_YAML = `roles:
45
+ Admin:
46
+ - admin@x.io
47
+ `;
48
+
49
+ /** The folder Ana may download, with the files the rules below decide over. */
50
+ const FILES: Record<string, string> = {
51
+ 'Shared/access.md': '---\n---\ndownload:\n - Ana <ana@x.io>\n',
52
+ 'Shared/Open.md': '# open\n',
53
+ 'Shared/Node-Deny-Download.md': '---\ndownload:\n - deny Ana <ana@x.io>\n---\n# no save\n',
54
+ 'Shared/Node-Deny-Read.md': '---\nread:\n - deny Ana <ana@x.io>\n---\n# hidden\n',
55
+ 'Shared/Inner/access.md': '---\n---\nread:\n - deny Ana <ana@x.io>\ndownload:\n - deny Ana <ana@x.io>\n',
56
+ 'Shared/Inner/Plan.md': '# hidden plan\n',
57
+ };
58
+
59
+ interface Harness {
60
+ server: Server;
61
+ baseUrl: string;
62
+ }
63
+
64
+ describe('GET /folder/zip judges every file it packs (real resolver, real zip)', () => {
65
+ let root: string;
66
+ let h: Harness | null = null;
67
+
68
+ beforeEach(async () => {
69
+ root = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-zip-access-'));
70
+ caller = { id: 'user-1', email: 'ana@x.io', name: 'Ana' };
71
+ });
72
+
73
+ afterEach(async () => {
74
+ if (h) {
75
+ await new Promise<void>((resolve, reject) => h!.server.close((e) => (e ? reject(e) : resolve())));
76
+ h = null;
77
+ }
78
+ await fs.rm(root, { recursive: true, force: true });
79
+ });
80
+
81
+ async function makeHarness(files: Record<string, string>): Promise<Harness> {
82
+ const workspaceDir = path.join(root, WORKSPACE_ID);
83
+ const repo = path.join(workspaceDir, KB);
84
+ // A `.git` folder is what makes the service adopt the clone without cloning.
85
+ await fs.mkdir(path.join(repo, '.git'), { recursive: true });
86
+ for (const [rel, contents] of Object.entries({ 'roles.yaml': ROLES_YAML, ...files })) {
87
+ const abs = path.join(repo, rel);
88
+ await fs.mkdir(path.dirname(abs), { recursive: true });
89
+ await fs.writeFile(abs, contents);
90
+ }
91
+
92
+ const workspaceService = new WorkspaceService(
93
+ root,
94
+ 'https://example.invalid/kb.git',
95
+ testKbContext({ kbDirName: KB }),
96
+ new NodeFs(),
97
+ );
98
+ const accessControl = new AccessControlService(workspaceService, KB, new NodeFs());
99
+ const authService = { getUserById: async () => caller } as unknown as AuthService;
100
+
101
+ const app = express();
102
+ app.use(express.json());
103
+ app.use('/api', (req, _res, next) => {
104
+ (req as unknown as { userId: string }).userId = caller.id;
105
+ next();
106
+ });
107
+ app.use(
108
+ '/api',
109
+ createWorkspaceRoutes(
110
+ workspaceService,
111
+ authService,
112
+ {} as unknown as IWorkflowService,
113
+ {} as unknown as WorkflowEventBus,
114
+ accessControl,
115
+ testKbContext({ kbDirName: KB }),
116
+ stubCreatorAccess,
117
+ { isAdmin: async () => false } as unknown as IAdminAccessService,
118
+ new NodeFs(),
119
+ ),
120
+ );
121
+ const server = await new Promise<Server>((resolve) => {
122
+ const s = app.listen(0, () => resolve(s));
123
+ });
124
+ const addr = server.address() as AddressInfo;
125
+ return { server, baseUrl: `http://127.0.0.1:${addr.port}` };
126
+ }
127
+
128
+ const zipOf = (base: string, folder: string) =>
129
+ fetch(`${base}/api/workspace/${WORKSPACE_ID}/folder/zip?path=${encodeURIComponent(folder)}&download=1`);
130
+
131
+ const entriesOf = async (res: Response) =>
132
+ new AdmZip(Buffer.from(await res.arrayBuffer()))
133
+ .getEntries()
134
+ .map((e) => e.entryName)
135
+ .sort();
136
+
137
+ it('packs the file the caller may download, and none of the ones their own rules withhold', async () => {
138
+ h = await makeHarness(FILES);
139
+
140
+ const res = await zipOf(h.baseUrl, `${KB}/Shared`);
141
+
142
+ expect(res.status).toBe(200);
143
+ expect(res.headers.get('content-type')).toBe('application/zip');
144
+ // Ana's archive, with Ana's verdicts: never served to anyone else from a cache.
145
+ expect(res.headers.get('cache-control')).toBe('private, no-store');
146
+ expect(await entriesOf(res)).toEqual(['Shared/Open.md', 'Shared/access.md']);
147
+ // One file Ana can see but not save is counted; the two she cannot see
148
+ // and the closed folder are not — the count says nothing the tree does not.
149
+ expect(res.headers.get('x-withheld-files')).toBe('1');
150
+ });
151
+
152
+ it('the per-file route agrees on every one of them', async () => {
153
+ h = await makeHarness(FILES);
154
+ const raw = (p: string) =>
155
+ fetch(`${h!.baseUrl}/api/workspace/${WORKSPACE_ID}/file/raw?path=${encodeURIComponent(`${KB}/${p}`)}&download=1`);
156
+
157
+ expect((await raw('Shared/Open.md')).status).toBe(200);
158
+ expect((await raw('Shared/Node-Deny-Download.md')).status).toBe(403);
159
+ expect((await raw('Shared/Node-Deny-Read.md')).status).toBe(403);
160
+ expect((await raw('Shared/Inner/Plan.md')).status).toBe(403);
161
+ });
162
+
163
+ it('still refuses the folder itself to someone with no download grant', async () => {
164
+ h = await makeHarness(FILES);
165
+ caller = { id: 'user-2', email: 'mallory@x.io', name: 'Mallory' };
166
+
167
+ const res = await zipOf(h.baseUrl, `${KB}/Shared`);
168
+
169
+ expect(res.status).toBe(403);
170
+ });
171
+
172
+ // `access.md` itself stays in HERE: this fixture's declares nothing for
173
+ // itself (its frontmatter is empty), so the folder's rules — which admit
174
+ // the caller — admit the file too. That is not a guarantee: an `access.md`
175
+ // is judged like any file, by its own frontmatter and the folder chain, and
176
+ // one whose frontmatter denies the caller its read is left out of the
177
+ // archive exactly as the single-file route would refuse it.
178
+ it('answers an archive holding only the grant file, not an error, when every content file is withheld', async () => {
179
+ h = await makeHarness({
180
+ 'Shared/access.md': '---\n---\ndownload:\n - Ana <ana@x.io>\n',
181
+ 'Shared/Only.md': '---\ndownload:\n - deny Ana <ana@x.io>\n---\n# no save\n',
182
+ });
183
+
184
+ const res = await zipOf(h.baseUrl, `${KB}/Shared`);
185
+
186
+ expect(res.status).toBe(200);
187
+ expect(await entriesOf(res)).toEqual(['Shared/access.md']);
188
+ expect(res.headers.get('x-withheld-files')).toBe('1');
189
+ });
190
+ });
@@ -127,6 +127,13 @@ describe('a bootstrap in flight when the working copy is taken away', () => {
127
127
  const svc = service(runner);
128
128
  // Nothing on disk: this is a first clone, which is what takes seconds.
129
129
  const opening = svc.getOrCreateForBranch(BRANCH);
130
+ // Told, rather than handed a cached path to a working copy of the
131
+ // repository that was replaced — which every later read would ENOENT on,
132
+ // with nothing left to re-clone it. The expectation is attached NOW: the
133
+ // refusal lands somewhere inside the timer turns below, and a rejection
134
+ // with no handler on it across a turn is what Vitest reports as unhandled
135
+ // — a flake that failed the suite on CI with every test green.
136
+ const refused = expect(opening).rejects.toThrow(/replaced while the "main" working copy was being created/);
130
137
  await vi.waitFor(() => expect(calls.length).toBeGreaterThan(0));
131
138
 
132
139
  // The replacement lands while the clone runs.
@@ -139,9 +146,6 @@ describe('a bootstrap in flight when the working copy is taken away', () => {
139
146
  await new Promise((r) => setTimeout(r, 0));
140
147
  }
141
148
 
142
- // Told, rather than handed a cached path to a working copy of the
143
- // repository that was replaced — which every later read would ENOENT on,
144
- // with nothing left to re-clone it.
145
- await expect(opening).rejects.toThrow(/replaced while the "main" working copy was being created/);
149
+ await refused;
146
150
  });
147
151
  });
@@ -698,13 +698,55 @@ describe('WorkspaceService.createFolderZip', () => {
698
698
  }));
699
699
  }
700
700
 
701
+ /** The filter a caller with every permission passes: pack everything found. */
702
+ const ALL = async (paths: string[]) => new Set(paths);
703
+
704
+ it('packs only what the filter allows, and asks it once with every file as a repository path', async () => {
705
+ const dir = path.join(repoDir, 'judged');
706
+ await fs.mkdir(path.join(dir, 'inner'), { recursive: true });
707
+ await fs.writeFile(path.join(dir, 'open.md'), 'open');
708
+ await fs.writeFile(path.join(dir, 'withheld.md'), 'withheld');
709
+ await fs.writeFile(path.join(dir, 'inner', 'deep.md'), 'deep');
710
+ const asked: string[][] = [];
711
+ const include = async (paths: string[]) => {
712
+ asked.push([...paths].sort());
713
+ return new Set(paths.filter((p) => p !== 'judged/withheld.md'));
714
+ };
715
+
716
+ const buf = await svc.createFolderZip(workspaceId, 'judged', include);
717
+
718
+ // Asked once, with every file the walk found, keyed the way the access
719
+ // rules are: relative to the repository, not to the folder or the workspace.
720
+ expect(asked).toEqual([['judged/inner/deep.md', 'judged/open.md', 'judged/withheld.md']]);
721
+ const names = (await unzipEntries(buf)).map((e) => e.name).sort();
722
+ expect(names).toEqual(['judged/inner/deep.md', 'judged/open.md']);
723
+ });
724
+
725
+ it('keys the files the same way whether the folder is written with a trailing slash or not', async () => {
726
+ const dir = path.join(repoDir, 'slashed');
727
+ await fs.mkdir(dir, { recursive: true });
728
+ await fs.writeFile(path.join(dir, 'a.md'), 'a');
729
+ const asked: string[][] = [];
730
+ const include = async (paths: string[]) => {
731
+ asked.push(paths);
732
+ return new Set(paths);
733
+ };
734
+
735
+ await svc.createFolderZip(workspaceId, 'slashed/', include);
736
+ await svc.createFolderZip(workspaceId, `${'knowledge-base'}/slashed//`, include);
737
+
738
+ // A `slashed//a.md` key would match no verdict, and the filter would
739
+ // withhold the whole folder without a word.
740
+ expect(asked).toEqual([['slashed/a.md'], ['slashed/a.md']]);
741
+ });
742
+
701
743
  it('zips a folder prefixing entries with the folder name', async () => {
702
744
  const dir = path.join(repoDir, 'docs');
703
745
  await fs.mkdir(dir, { recursive: true });
704
746
  await fs.writeFile(path.join(dir, 'a.md'), 'alpha');
705
747
  await fs.writeFile(path.join(dir, 'b.md'), 'beta');
706
748
 
707
- const buf = await svc.createFolderZip(workspaceId, 'docs');
749
+ const buf = await svc.createFolderZip(workspaceId, 'docs', ALL);
708
750
  const entries = await unzipEntries(buf);
709
751
  const names = entries.map((e) => e.name).sort();
710
752
  expect(names).toEqual(['docs/a.md', 'docs/b.md']);
@@ -719,7 +761,7 @@ describe('WorkspaceService.createFolderZip', () => {
719
761
  await fs.writeFile(path.join(dir, 'nested', 'mid.md'), 'mid');
720
762
  await fs.writeFile(path.join(dir, 'nested', 'deep', 'leaf.md'), 'leaf');
721
763
 
722
- const buf = await svc.createFolderZip(workspaceId, 'tree');
764
+ const buf = await svc.createFolderZip(workspaceId, 'tree', ALL);
723
765
  const names = (await unzipEntries(buf)).map((e) => e.name).sort();
724
766
  expect(names).toEqual([
725
767
  'tree/nested/deep/leaf.md',
@@ -736,7 +778,7 @@ describe('WorkspaceService.createFolderZip', () => {
736
778
  await fs.writeFile(path.join(dir, '.gitkeep'), '');
737
779
  await fs.writeFile(path.join(dir, 'real.md'), 'real');
738
780
 
739
- const buf = await svc.createFolderZip(workspaceId, 'mixed');
781
+ const buf = await svc.createFolderZip(workspaceId, 'mixed', ALL);
740
782
  const names = (await unzipEntries(buf)).map((e) => e.name).sort();
741
783
  expect(names).toEqual(['mixed/real.md']);
742
784
  });
@@ -748,7 +790,7 @@ describe('WorkspaceService.createFolderZip', () => {
748
790
  await fs.writeFile(path.join(dir, 'public.md'), 'pub');
749
791
  await fs.writeFile(path.join(dir, 'secret.md'), 'shh');
750
792
 
751
- const buf = await svc.createFolderZip(workspaceId, 'with-ignore');
793
+ const buf = await svc.createFolderZip(workspaceId, 'with-ignore', ALL);
752
794
  const names = (await unzipEntries(buf)).map((e) => e.name).sort();
753
795
  expect(names).toContain('with-ignore/public.md');
754
796
  expect(names).not.toContain('with-ignore/secret.md');
@@ -756,17 +798,17 @@ describe('WorkspaceService.createFolderZip', () => {
756
798
 
757
799
  it('refuses to zip a file (not a directory)', async () => {
758
800
  await fs.writeFile(path.join(repoDir, 'lone.md'), 'one');
759
- await expect(svc.createFolderZip(workspaceId, 'lone.md')).rejects.toThrow('Not a directory');
801
+ await expect(svc.createFolderZip(workspaceId, 'lone.md', ALL)).rejects.toThrow('Not a directory');
760
802
  });
761
803
 
762
804
  it('refuses a traversing path, and reads an unprefixed one as the repository folder', async () => {
763
- await expect(svc.createFolderZip(workspaceId, '../escape')).rejects.toThrow(
805
+ await expect(svc.createFolderZip(workspaceId, '../escape', ALL)).rejects.toThrow(
764
806
  'is outside the knowledge base repository',
765
807
  );
766
808
  // The unprefixed spelling is the repository's own folder, so it zips.
767
809
  await fs.mkdir(path.join(repoDir, 'unprefixed'), { recursive: true });
768
810
  await fs.writeFile(path.join(repoDir, 'unprefixed', 'a.md'), 'a');
769
- const buf = await svc.createFolderZip(workspaceId, 'unprefixed');
811
+ const buf = await svc.createFolderZip(workspaceId, 'unprefixed', ALL);
770
812
  expect((await unzipEntries(buf)).map((e) => e.name)).toEqual(['unprefixed/a.md']);
771
813
  });
772
814
 
@@ -796,7 +838,7 @@ describe('WorkspaceService.createFolderZip', () => {
796
838
  return result;
797
839
  });
798
840
  try {
799
- await expect(svc.createFolderZip(workspaceId, 'too-big'))
841
+ await expect(svc.createFolderZip(workspaceId, 'too-big', ALL))
800
842
  .rejects.toBeInstanceOf(FolderTooLargeError);
801
843
  } finally {
802
844
  statSpy.mockRestore();
@@ -9,6 +9,7 @@ import { afterEach, describe, expect, it } from 'vitest';
9
9
  import '@utcp/http'; // registers the http CallTemplate serializer + protocol
10
10
  import { UtcpManualSerializer } from '@utcp/sdk';
11
11
  import { HttpCallTemplateSerializer } from '@utcp/http';
12
+ import { exampleArguments } from '@bevel-software/platform-mcp-core';
12
13
  import { CodeModeUtcpClient } from '../../code-mode/index.js';
13
14
  import { testKbContext } from '../../../__tests__/kb-context.js';
14
15
  import { NodeFs } from '../../kb-fs/node-fs.js';
@@ -167,7 +168,12 @@ describe('workspace tools — a branch that cannot be opened', () => {
167
168
  } as ToolAuth;
168
169
  next();
169
170
  },
170
- createToolHandlerFactory(resolve),
171
+ // Wired as the composition root wires it, so a missing branch is
172
+ // answered before the tool runs, as it is in production.
173
+ createToolHandlerFactory(resolve, undefined, {
174
+ defaultBranch: () => 'target-company-state',
175
+ isMissing: (branch) => workspaces!.isBranchMissing(branch),
176
+ }),
171
177
  new SpillStore(spillRoot),
172
178
  new DocExtractService(docCacheDir),
173
179
  allowAll,
@@ -272,6 +278,24 @@ describe('workspace tools — a branch that cannot be opened', () => {
272
278
  });
273
279
  }
274
280
 
281
+ /**
282
+ * The platform's file tools keep the refusal they had: `read_file` is
283
+ * still `required`, answered the same sentence by the tool handler that
284
+ * now answers it for every tool that takes a branch.
285
+ */
286
+ it('read_file refuses a call without a branch as before', async () => {
287
+ const base = await start();
288
+
289
+ const res = await callTool(base, 'read_file', { path: 'knowledge-base/a.md' });
290
+
291
+ expect(res.status).toBe(400);
292
+ expect(res.body).toEqual({
293
+ kind: 'branch-required',
294
+ error: '`branch` is required: pass the branch (draft) you are working on.',
295
+ });
296
+ expect(await workspaceDirs()).toEqual([]);
297
+ });
298
+
275
299
  it('never writes `undefined` or `null` as a branch name into the answer', async () => {
276
300
  const base = await start();
277
301
 
@@ -407,6 +431,25 @@ describe('workspace tools — a branch that cannot be opened', () => {
407
431
  expect(await workspaceDirs()).toEqual([]);
408
432
  });
409
433
 
434
+ it('404s a branch that does not exist on every mounted tool that takes one, before the tool runs', async () => {
435
+ const base = await start({ source: 'internal', scope: 'write' });
436
+ const takesBranch = internalTools
437
+ .map((t) => ({ name: t.name, body: (t.inputs as { properties?: { body?: { required?: string[] } } }).properties?.body }))
438
+ .filter(({ body }) => (body?.required ?? []).includes('branch'))
439
+ // Resolves its own branch, and validates it itself first.
440
+ .filter(({ name }) => name !== 'execute_command');
441
+
442
+ for (const { name: tool, body } of takesBranch) {
443
+ // Every other argument the tool requires, so the call matches the tool
444
+ // and it is the branch, not a missing argument, that is refused.
445
+ const args = { ...exampleArguments(body), branch: 'nobody/never-made-this' };
446
+ const res = await callTool(base, tool, args);
447
+ expect(res.status, `${tool} must 404 a branch that does not exist`).toBe(404);
448
+ expect(res.body, tool).toMatchObject({ kind: 'branch-not-found', branch: 'nobody/never-made-this' });
449
+ }
450
+ expect(await workspaceDirs()).toEqual([]);
451
+ });
452
+
410
453
  it('410s a branch a listing showed us and origin no longer has', async () => {
411
454
  const base = await start();
412
455
  workspaces!.noteBranchesListed(['target-company-state', 'alice/draft']);