@bevel-software/platform-core-backend 0.15.1 → 0.20.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 (1385) hide show
  1. package/THIRD-PARTY-NOTICES.md +4 -4
  2. package/dist/core/catalog-cache-invalidation.d.ts +16 -0
  3. package/dist/core/catalog-cache-invalidation.d.ts.map +1 -1
  4. package/dist/core/catalog-cache-invalidation.js +6 -1
  5. package/dist/core/catalog-cache-invalidation.js.map +1 -1
  6. package/dist/core/catalog-revision.d.ts +39 -0
  7. package/dist/core/catalog-revision.d.ts.map +1 -0
  8. package/dist/core/catalog-revision.js +94 -0
  9. package/dist/core/catalog-revision.js.map +1 -0
  10. package/dist/core/create-core-server.d.ts +7 -0
  11. package/dist/core/create-core-server.d.ts.map +1 -1
  12. package/dist/core/create-core-server.js +221 -75
  13. package/dist/core/create-core-server.js.map +1 -1
  14. package/dist/core/create-core-services.d.ts +52 -2
  15. package/dist/core/create-core-services.d.ts.map +1 -1
  16. package/dist/core/create-core-services.js +222 -87
  17. package/dist/core/create-core-services.js.map +1 -1
  18. package/dist/core/lifecycle.d.ts +138 -0
  19. package/dist/core/lifecycle.d.ts.map +1 -0
  20. package/dist/core/lifecycle.js +218 -0
  21. package/dist/core/lifecycle.js.map +1 -0
  22. package/dist/core/public-config.d.ts +82 -0
  23. package/dist/core/public-config.d.ts.map +1 -0
  24. package/dist/core/public-config.js +78 -0
  25. package/dist/core/public-config.js.map +1 -0
  26. package/dist/core/readiness.d.ts +86 -0
  27. package/dist/core/readiness.d.ts.map +1 -0
  28. package/dist/core/readiness.js +56 -0
  29. package/dist/core/readiness.js.map +1 -0
  30. package/dist/core-config.d.ts +30 -0
  31. package/dist/core-config.d.ts.map +1 -1
  32. package/dist/core-config.js +64 -2
  33. package/dist/core-config.js.map +1 -1
  34. package/dist/index.d.ts +4 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +6 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/modules/access/access-control.interface.d.ts +215 -11
  39. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  40. package/dist/modules/access/access-control.service.d.ts +57 -11
  41. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  42. package/dist/modules/access/access-control.service.js +616 -155
  43. package/dist/modules/access/access-control.service.js.map +1 -1
  44. package/dist/modules/access/access-mutation.service.d.ts +71 -5
  45. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  46. package/dist/modules/access/access-mutation.service.js +161 -13
  47. package/dist/modules/access/access-mutation.service.js.map +1 -1
  48. package/dist/modules/access/access-view.d.ts +147 -0
  49. package/dist/modules/access/access-view.d.ts.map +1 -0
  50. package/dist/modules/access/access-view.js +211 -0
  51. package/dist/modules/access/access-view.js.map +1 -0
  52. package/dist/modules/access/access.routes.d.ts.map +1 -1
  53. package/dist/modules/access/access.routes.js +255 -79
  54. package/dist/modules/access/access.routes.js.map +1 -1
  55. package/dist/modules/access/admin-locked-commit.d.ts +2 -3
  56. package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
  57. package/dist/modules/access/admin-locked-commit.js +9 -8
  58. package/dist/modules/access/admin-locked-commit.js.map +1 -1
  59. package/dist/modules/access/admin-route-helpers.d.ts.map +1 -1
  60. package/dist/modules/access/admin-route-helpers.js +4 -4
  61. package/dist/modules/access/admin-route-helpers.js.map +1 -1
  62. package/dist/modules/access/change-read-gate.d.ts +53 -0
  63. package/dist/modules/access/change-read-gate.d.ts.map +1 -0
  64. package/dist/modules/access/change-read-gate.js +134 -0
  65. package/dist/modules/access/change-read-gate.js.map +1 -0
  66. package/dist/modules/access/creator-access.d.ts +33 -39
  67. package/dist/modules/access/creator-access.d.ts.map +1 -1
  68. package/dist/modules/access/creator-access.js +92 -119
  69. package/dist/modules/access/creator-access.js.map +1 -1
  70. package/dist/modules/access/directory-sync-bot.d.ts.map +1 -1
  71. package/dist/modules/access/directory-sync-bot.js +3 -1
  72. package/dist/modules/access/directory-sync-bot.js.map +1 -1
  73. package/dist/modules/access/groups-admin.service.d.ts +3 -3
  74. package/dist/modules/access/groups-admin.service.js +2 -2
  75. package/dist/modules/access/reference-scan.d.ts +1 -1
  76. package/dist/modules/access/reference-scan.d.ts.map +1 -1
  77. package/dist/modules/access/reference-scan.js +5 -4
  78. package/dist/modules/access/reference-scan.js.map +1 -1
  79. package/dist/modules/access/roles-admin.service.d.ts +72 -13
  80. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  81. package/dist/modules/access/roles-admin.service.js +71 -13
  82. package/dist/modules/access/roles-admin.service.js.map +1 -1
  83. package/dist/modules/access/synced-groups-committer.d.ts.map +1 -1
  84. package/dist/modules/access/synced-groups-committer.js +8 -6
  85. package/dist/modules/access/synced-groups-committer.js.map +1 -1
  86. package/dist/modules/access/synced-groups-writer.d.ts.map +1 -1
  87. package/dist/modules/access/synced-groups-writer.js +3 -8
  88. package/dist/modules/access/synced-groups-writer.js.map +1 -1
  89. package/dist/modules/access/user-access-removal.service.d.ts +209 -0
  90. package/dist/modules/access/user-access-removal.service.d.ts.map +1 -0
  91. package/dist/modules/access/user-access-removal.service.js +662 -0
  92. package/dist/modules/access/user-access-removal.service.js.map +1 -0
  93. package/dist/modules/access-model/access-errors.d.ts +32 -1
  94. package/dist/modules/access-model/access-errors.d.ts.map +1 -1
  95. package/dist/modules/access-model/access-errors.js +38 -11
  96. package/dist/modules/access-model/access-errors.js.map +1 -1
  97. package/dist/modules/access-model/access-grammar.d.ts +49 -24
  98. package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
  99. package/dist/modules/access-model/access-grammar.js +68 -48
  100. package/dist/modules/access-model/access-grammar.js.map +1 -1
  101. package/dist/modules/access-model/access-splice.d.ts.map +1 -1
  102. package/dist/modules/access-model/access-splice.js +18 -18
  103. package/dist/modules/access-model/access-splice.js.map +1 -1
  104. package/dist/modules/access-model/access-template.d.ts +22 -0
  105. package/dist/modules/access-model/access-template.d.ts.map +1 -0
  106. package/dist/modules/access-model/access-template.js +42 -0
  107. package/dist/modules/access-model/access-template.js.map +1 -0
  108. package/dist/modules/access-model/change-gate.d.ts +65 -0
  109. package/dist/modules/access-model/change-gate.d.ts.map +1 -0
  110. package/dist/modules/access-model/change-gate.js +25 -0
  111. package/dist/modules/access-model/change-gate.js.map +1 -0
  112. package/dist/modules/access-model/creator.d.ts +13 -16
  113. package/dist/modules/access-model/creator.d.ts.map +1 -1
  114. package/dist/modules/access-model/creator.js.map +1 -1
  115. package/dist/modules/access-model/frontmatter-lines.d.ts +27 -0
  116. package/dist/modules/access-model/frontmatter-lines.d.ts.map +1 -0
  117. package/dist/modules/access-model/frontmatter-lines.js +74 -0
  118. package/dist/modules/access-model/frontmatter-lines.js.map +1 -0
  119. package/dist/modules/access-model/plugin-principals.d.ts +5 -3
  120. package/dist/modules/access-model/plugin-principals.d.ts.map +1 -1
  121. package/dist/modules/access-model/plugin-principals.js +11 -17
  122. package/dist/modules/access-model/plugin-principals.js.map +1 -1
  123. package/dist/modules/access-model/roles-yaml-guard.d.ts +73 -2
  124. package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -1
  125. package/dist/modules/access-model/roles-yaml-guard.js +153 -3
  126. package/dist/modules/access-model/roles-yaml-guard.js.map +1 -1
  127. package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
  128. package/dist/modules/admin/admin-access.service.js +5 -2
  129. package/dist/modules/admin/admin-access.service.js.map +1 -1
  130. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +21 -0
  131. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -0
  132. package/dist/modules/agent-instructions/agent-instructions.routes.js +38 -0
  133. package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -0
  134. package/dist/modules/agent-instructions/compose.d.ts +71 -0
  135. package/dist/modules/agent-instructions/compose.d.ts.map +1 -0
  136. package/dist/modules/agent-instructions/compose.js +234 -0
  137. package/dist/modules/agent-instructions/compose.js.map +1 -0
  138. package/dist/modules/agent-instructions/index.d.ts +4 -0
  139. package/dist/modules/agent-instructions/index.d.ts.map +1 -0
  140. package/dist/modules/agent-instructions/index.js +4 -0
  141. package/dist/modules/agent-instructions/index.js.map +1 -0
  142. package/dist/modules/agent-instructions/read-preamble.d.ts +37 -0
  143. package/dist/modules/agent-instructions/read-preamble.d.ts.map +1 -0
  144. package/dist/modules/agent-instructions/read-preamble.js +81 -0
  145. package/dist/modules/agent-instructions/read-preamble.js.map +1 -0
  146. package/dist/modules/audit/agent-audit.service.d.ts +54 -0
  147. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -0
  148. package/dist/modules/audit/agent-audit.service.js +290 -0
  149. package/dist/modules/audit/agent-audit.service.js.map +1 -0
  150. package/dist/modules/audit/agent-rest-audit.middleware.d.ts +40 -0
  151. package/dist/modules/audit/agent-rest-audit.middleware.d.ts.map +1 -0
  152. package/dist/modules/audit/agent-rest-audit.middleware.js +75 -0
  153. package/dist/modules/audit/agent-rest-audit.middleware.js.map +1 -0
  154. package/dist/modules/audit/audit.contract.d.ts +145 -0
  155. package/dist/modules/audit/audit.contract.d.ts.map +1 -0
  156. package/dist/modules/audit/audit.contract.js +35 -0
  157. package/dist/modules/audit/audit.contract.js.map +1 -0
  158. package/dist/modules/audit/audit.routes.d.ts +21 -0
  159. package/dist/modules/audit/audit.routes.d.ts.map +1 -0
  160. package/dist/modules/audit/audit.routes.js +157 -0
  161. package/dist/modules/audit/audit.routes.js.map +1 -0
  162. package/dist/modules/audit/event-classifier.d.ts +52 -0
  163. package/dist/modules/audit/event-classifier.d.ts.map +1 -0
  164. package/dist/modules/audit/event-classifier.js +123 -0
  165. package/dist/modules/audit/event-classifier.js.map +1 -0
  166. package/dist/modules/audit/index.d.ts +7 -0
  167. package/dist/modules/audit/index.d.ts.map +1 -0
  168. package/dist/modules/audit/index.js +7 -0
  169. package/dist/modules/audit/index.js.map +1 -0
  170. package/dist/modules/audit/request-audit.d.ts +57 -0
  171. package/dist/modules/audit/request-audit.d.ts.map +1 -0
  172. package/dist/modules/audit/request-audit.js +117 -0
  173. package/dist/modules/audit/request-audit.js.map +1 -0
  174. package/dist/modules/auth/account-erasure.service.d.ts +30 -4
  175. package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
  176. package/dist/modules/auth/account-erasure.service.js +96 -10
  177. package/dist/modules/auth/account-erasure.service.js.map +1 -1
  178. package/dist/modules/auth/account.routes.d.ts +6 -3
  179. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  180. package/dist/modules/auth/account.routes.js +109 -10
  181. package/dist/modules/auth/account.routes.js.map +1 -1
  182. package/dist/modules/auth/auth.routes.d.ts +10 -3
  183. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  184. package/dist/modules/auth/auth.routes.js +9 -4
  185. package/dist/modules/auth/auth.routes.js.map +1 -1
  186. package/dist/modules/auth/auth.service.d.ts +89 -5
  187. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  188. package/dist/modules/auth/auth.service.js +160 -25
  189. package/dist/modules/auth/auth.service.js.map +1 -1
  190. package/dist/modules/auth/oidc-auth-provider.d.ts +46 -3
  191. package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
  192. package/dist/modules/auth/oidc-auth-provider.js +99 -21
  193. package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
  194. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  195. package/dist/modules/code-mode/code-mode.tool.js +14 -2
  196. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  197. package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
  198. package/dist/modules/connection-probe/connection-probe.service.js +56 -7
  199. package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
  200. package/dist/modules/database/advisory-lock.d.ts +103 -0
  201. package/dist/modules/database/advisory-lock.d.ts.map +1 -0
  202. package/dist/modules/database/advisory-lock.js +320 -0
  203. package/dist/modules/database/advisory-lock.js.map +1 -0
  204. package/dist/modules/database/connection.d.ts.map +1 -1
  205. package/dist/modules/database/connection.js +9 -1
  206. package/dist/modules/database/connection.js.map +1 -1
  207. package/dist/modules/database/core-schema.d.ts +859 -99
  208. package/dist/modules/database/core-schema.d.ts.map +1 -1
  209. package/dist/modules/database/core-schema.js +218 -0
  210. package/dist/modules/database/core-schema.js.map +1 -1
  211. package/dist/modules/database/migrate.d.ts.map +1 -1
  212. package/dist/modules/database/migrate.js +26 -6
  213. package/dist/modules/database/migrate.js.map +1 -1
  214. package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
  215. package/dist/modules/declared-variables/declared-variables.routes.js +4 -2
  216. package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
  217. package/dist/modules/diff/diff.routes.d.ts.map +1 -1
  218. package/dist/modules/diff/diff.routes.js +2 -4
  219. package/dist/modules/diff/diff.routes.js.map +1 -1
  220. package/dist/modules/diff/diff.service.d.ts +4 -1
  221. package/dist/modules/diff/diff.service.d.ts.map +1 -1
  222. package/dist/modules/diff/diff.service.js +84 -81
  223. package/dist/modules/diff/diff.service.js.map +1 -1
  224. package/dist/modules/{workspace → kb-fs}/bevel-ignore.d.ts +14 -9
  225. package/dist/modules/kb-fs/bevel-ignore.d.ts.map +1 -0
  226. package/dist/modules/{workspace → kb-fs}/bevel-ignore.js +24 -14
  227. package/dist/modules/kb-fs/bevel-ignore.js.map +1 -0
  228. package/dist/modules/kb-fs/file-change-notifier.d.ts.map +1 -1
  229. package/dist/modules/kb-fs/file-change-notifier.js +3 -1
  230. package/dist/modules/kb-fs/file-change-notifier.js.map +1 -1
  231. package/dist/modules/kb-fs/git-guarded-filesystem.d.ts +45 -0
  232. package/dist/modules/kb-fs/git-guarded-filesystem.d.ts.map +1 -0
  233. package/dist/modules/kb-fs/git-guarded-filesystem.js +117 -0
  234. package/dist/modules/kb-fs/git-guarded-filesystem.js.map +1 -0
  235. package/dist/modules/kb-fs/locking-filesystem.d.ts +113 -18
  236. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  237. package/dist/modules/kb-fs/locking-filesystem.js +353 -76
  238. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  239. package/dist/modules/kb-fs/node-fs.d.ts +23 -0
  240. package/dist/modules/kb-fs/node-fs.d.ts.map +1 -0
  241. package/dist/modules/kb-fs/node-fs.js +158 -0
  242. package/dist/modules/kb-fs/node-fs.js.map +1 -0
  243. package/dist/modules/kb-fs/read-only-filesystem.d.ts +10 -9
  244. package/dist/modules/kb-fs/read-only-filesystem.d.ts.map +1 -1
  245. package/dist/modules/kb-fs/read-only-filesystem.js +18 -9
  246. package/dist/modules/kb-fs/read-only-filesystem.js.map +1 -1
  247. package/dist/modules/kb-fs/repo-path.d.ts +100 -3
  248. package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
  249. package/dist/modules/kb-fs/repo-path.js +195 -3
  250. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  251. package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
  252. package/dist/modules/kb-sync/kb-sync.routes.js +4 -2
  253. package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
  254. package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
  255. package/dist/modules/kb-sync/kb-sync.service.js +7 -5
  256. package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
  257. package/dist/modules/marketplace/git-http.routes.d.ts.map +1 -1
  258. package/dist/modules/marketplace/git-http.routes.js +6 -4
  259. package/dist/modules/marketplace/git-http.routes.js.map +1 -1
  260. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts +12 -2
  261. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts.map +1 -1
  262. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js +32 -3
  263. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js.map +1 -1
  264. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts +27 -0
  265. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts.map +1 -1
  266. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js +45 -0
  267. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js.map +1 -1
  268. package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts.map +1 -1
  269. package/dist/modules/marketplace/github-facade/github-facade.routes.js +30 -6
  270. package/dist/modules/marketplace/github-facade/github-facade.routes.js.map +1 -1
  271. package/dist/modules/marketplace/github-facade/github-facade.service.d.ts +21 -9
  272. package/dist/modules/marketplace/github-facade/github-facade.service.d.ts.map +1 -1
  273. package/dist/modules/marketplace/github-facade/github-facade.service.js +60 -19
  274. package/dist/modules/marketplace/github-facade/github-facade.service.js.map +1 -1
  275. package/dist/modules/marketplace/github-facade/index.d.ts +1 -1
  276. package/dist/modules/marketplace/github-facade/index.d.ts.map +1 -1
  277. package/dist/modules/marketplace/github-facade/index.js +1 -1
  278. package/dist/modules/marketplace/github-facade/index.js.map +1 -1
  279. package/dist/modules/marketplace/marketplace-repo.service.d.ts +14 -1
  280. package/dist/modules/marketplace/marketplace-repo.service.d.ts.map +1 -1
  281. package/dist/modules/marketplace/marketplace-repo.service.js +26 -8
  282. package/dist/modules/marketplace/marketplace-repo.service.js.map +1 -1
  283. package/dist/modules/mcp/downstream-pool.d.ts +107 -0
  284. package/dist/modules/mcp/downstream-pool.d.ts.map +1 -0
  285. package/dist/modules/mcp/downstream-pool.js +221 -0
  286. package/dist/modules/mcp/downstream-pool.js.map +1 -0
  287. package/dist/modules/mcp/downstream-token-refresh.d.ts +77 -0
  288. package/dist/modules/mcp/downstream-token-refresh.d.ts.map +1 -0
  289. package/dist/modules/mcp/downstream-token-refresh.js +166 -0
  290. package/dist/modules/mcp/downstream-token-refresh.js.map +1 -0
  291. package/dist/modules/mcp/first-call-probe.cli-args.d.ts +78 -0
  292. package/dist/modules/mcp/first-call-probe.cli-args.d.ts.map +1 -0
  293. package/dist/modules/mcp/first-call-probe.cli-args.js +151 -0
  294. package/dist/modules/mcp/first-call-probe.cli-args.js.map +1 -0
  295. package/dist/modules/mcp/first-call-probe.cli.d.ts +2 -0
  296. package/dist/modules/mcp/first-call-probe.cli.d.ts.map +1 -0
  297. package/dist/modules/mcp/first-call-probe.cli.js +62 -0
  298. package/dist/modules/mcp/first-call-probe.cli.js.map +1 -0
  299. package/dist/modules/mcp/first-call-probe.d.ts +165 -0
  300. package/dist/modules/mcp/first-call-probe.d.ts.map +1 -0
  301. package/dist/modules/mcp/first-call-probe.js +298 -0
  302. package/dist/modules/mcp/first-call-probe.js.map +1 -0
  303. package/dist/modules/mcp/manual-failure-memo.d.ts +42 -17
  304. package/dist/modules/mcp/manual-failure-memo.d.ts.map +1 -1
  305. package/dist/modules/mcp/manual-failure-memo.js +79 -23
  306. package/dist/modules/mcp/manual-failure-memo.js.map +1 -1
  307. package/dist/modules/mcp/mcp-auth.middleware.d.ts +10 -5
  308. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  309. package/dist/modules/mcp/mcp-auth.middleware.js +56 -16
  310. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  311. package/dist/modules/mcp/mcp.routes.d.ts +4 -4
  312. package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
  313. package/dist/modules/mcp/mcp.routes.js +91 -180
  314. package/dist/modules/mcp/mcp.routes.js.map +1 -1
  315. package/dist/modules/mcp/mcp.service.d.ts +226 -61
  316. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  317. package/dist/modules/mcp/mcp.service.js +758 -161
  318. package/dist/modules/mcp/mcp.service.js.map +1 -1
  319. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +59 -0
  320. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  321. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +184 -10
  322. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  323. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
  324. package/dist/modules/mcp/oauth/oauth-consent.routes.js +3 -1
  325. package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
  326. package/dist/modules/mcp/surface-log-throttle.d.ts +59 -0
  327. package/dist/modules/mcp/surface-log-throttle.d.ts.map +1 -0
  328. package/dist/modules/mcp/surface-log-throttle.js +94 -0
  329. package/dist/modules/mcp/surface-log-throttle.js.map +1 -0
  330. package/dist/modules/plugins/compile/compile-marketplace.d.ts +13 -6
  331. package/dist/modules/plugins/compile/compile-marketplace.d.ts.map +1 -1
  332. package/dist/modules/plugins/compile/compile-marketplace.js +119 -36
  333. package/dist/modules/plugins/compile/compile-marketplace.js.map +1 -1
  334. package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts +8 -1
  335. package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts.map +1 -1
  336. package/dist/modules/plugins/compile/marketplace-compiler.service.js +9 -6
  337. package/dist/modules/plugins/compile/marketplace-compiler.service.js.map +1 -1
  338. package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts.map +1 -1
  339. package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js +32 -9
  340. package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js.map +1 -1
  341. package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts +26 -6
  342. package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts.map +1 -1
  343. package/dist/modules/plugins/discovery/bundle-dialect/registry.js +71 -14
  344. package/dist/modules/plugins/discovery/bundle-dialect/registry.js.map +1 -1
  345. package/dist/modules/plugins/discovery/kb-plugin-source.d.ts +4 -2
  346. package/dist/modules/plugins/discovery/kb-plugin-source.d.ts.map +1 -1
  347. package/dist/modules/plugins/discovery/kb-plugin-source.js +9 -6
  348. package/dist/modules/plugins/discovery/kb-plugin-source.js.map +1 -1
  349. package/dist/modules/plugins/discovery/native.source.d.ts +2 -1
  350. package/dist/modules/plugins/discovery/native.source.d.ts.map +1 -1
  351. package/dist/modules/plugins/discovery/native.source.js +22 -4
  352. package/dist/modules/plugins/discovery/native.source.js.map +1 -1
  353. package/dist/modules/plugins/discovery/plugin-source.d.ts +17 -3
  354. package/dist/modules/plugins/discovery/plugin-source.d.ts.map +1 -1
  355. package/dist/modules/plugins/index.d.ts +6 -3
  356. package/dist/modules/plugins/index.d.ts.map +1 -1
  357. package/dist/modules/plugins/index.js +6 -3
  358. package/dist/modules/plugins/index.js.map +1 -1
  359. package/dist/modules/plugins/join-request-jobs.service.d.ts +250 -0
  360. package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -0
  361. package/dist/modules/plugins/join-request-jobs.service.js +602 -0
  362. package/dist/modules/plugins/join-request-jobs.service.js.map +1 -0
  363. package/dist/modules/plugins/join-request-records.store.d.ts +161 -0
  364. package/dist/modules/plugins/join-request-records.store.d.ts.map +1 -0
  365. package/dist/modules/plugins/join-request-records.store.js +179 -0
  366. package/dist/modules/plugins/join-request-records.store.js.map +1 -0
  367. package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
  368. package/dist/modules/plugins/join-requests.service.js +4 -2
  369. package/dist/modules/plugins/join-requests.service.js.map +1 -1
  370. package/dist/modules/plugins/plugin-links.d.ts +4 -1
  371. package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
  372. package/dist/modules/plugins/plugin-links.js +6 -3
  373. package/dist/modules/plugins/plugin-links.js.map +1 -1
  374. package/dist/modules/plugins/plugin-links.service.d.ts +2 -5
  375. package/dist/modules/plugins/plugin-links.service.d.ts.map +1 -1
  376. package/dist/modules/plugins/plugin-links.service.js +4 -13
  377. package/dist/modules/plugins/plugin-links.service.js.map +1 -1
  378. package/dist/modules/plugins/plugin-provision.service.d.ts +91 -19
  379. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
  380. package/dist/modules/plugins/plugin-provision.service.js +183 -38
  381. package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
  382. package/dist/modules/plugins/plugin-rename.service.d.ts +3 -1
  383. package/dist/modules/plugins/plugin-rename.service.d.ts.map +1 -1
  384. package/dist/modules/plugins/plugin-rename.service.js +20 -11
  385. package/dist/modules/plugins/plugin-rename.service.js.map +1 -1
  386. package/dist/modules/plugins/plugins.contract.d.ts +52 -4
  387. package/dist/modules/plugins/plugins.contract.d.ts.map +1 -1
  388. package/dist/modules/plugins/plugins.routes.d.ts +38 -5
  389. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  390. package/dist/modules/plugins/plugins.routes.js +190 -116
  391. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  392. package/dist/modules/plugins/plugins.service.d.ts +38 -6
  393. package/dist/modules/plugins/plugins.service.d.ts.map +1 -1
  394. package/dist/modules/plugins/plugins.service.js +86 -17
  395. package/dist/modules/plugins/plugins.service.js.map +1 -1
  396. package/dist/modules/plugins/plugins.tools.d.ts +25 -0
  397. package/dist/modules/plugins/plugins.tools.d.ts.map +1 -0
  398. package/dist/modules/plugins/plugins.tools.js +82 -0
  399. package/dist/modules/plugins/plugins.tools.js.map +1 -0
  400. package/dist/modules/plugins/teams.routes.d.ts +42 -0
  401. package/dist/modules/plugins/teams.routes.d.ts.map +1 -0
  402. package/dist/modules/plugins/teams.routes.js +79 -0
  403. package/dist/modules/plugins/teams.routes.js.map +1 -0
  404. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +40 -1
  405. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
  406. package/dist/modules/secrets-vault/db-secrets-vault.service.js +202 -37
  407. package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
  408. package/dist/modules/secrets-vault/index.d.ts +1 -1
  409. package/dist/modules/secrets-vault/index.d.ts.map +1 -1
  410. package/dist/modules/secrets-vault/index.js.map +1 -1
  411. package/dist/modules/secrets-vault/mcp-oauth-discovery.service.d.ts.map +1 -1
  412. package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js +5 -3
  413. package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js.map +1 -1
  414. package/dist/modules/secrets-vault/secrets-variable-loader.d.ts.map +1 -1
  415. package/dist/modules/secrets-vault/secrets-variable-loader.js +3 -1
  416. package/dist/modules/secrets-vault/secrets-variable-loader.js.map +1 -1
  417. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +78 -0
  418. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
  419. package/dist/modules/secrets-vault/secrets-vault.contract.js +40 -0
  420. package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
  421. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
  422. package/dist/modules/secrets-vault/secrets-vault.routes.js +106 -52
  423. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  424. package/dist/modules/settings/connection-check.d.ts +118 -0
  425. package/dist/modules/settings/connection-check.d.ts.map +1 -0
  426. package/dist/modules/settings/connection-check.js +287 -0
  427. package/dist/modules/settings/connection-check.js.map +1 -0
  428. package/dist/modules/settings/deployment-settings.service.d.ts +162 -8
  429. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  430. package/dist/modules/settings/deployment-settings.service.js +375 -52
  431. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  432. package/dist/modules/settings/git-root-folders.d.ts +54 -0
  433. package/dist/modules/settings/git-root-folders.d.ts.map +1 -0
  434. package/dist/modules/settings/git-root-folders.js +130 -0
  435. package/dist/modules/settings/git-root-folders.js.map +1 -0
  436. package/dist/modules/settings/oidc-check.d.ts +85 -0
  437. package/dist/modules/settings/oidc-check.d.ts.map +1 -0
  438. package/dist/modules/settings/oidc-check.js +172 -0
  439. package/dist/modules/settings/oidc-check.js.map +1 -0
  440. package/dist/modules/settings/setup.routes.d.ts +31 -1
  441. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  442. package/dist/modules/settings/setup.routes.js +400 -108
  443. package/dist/modules/settings/setup.routes.js.map +1 -1
  444. package/dist/modules/skills/allowed-tools-check.d.ts +132 -0
  445. package/dist/modules/skills/allowed-tools-check.d.ts.map +1 -0
  446. package/dist/modules/skills/allowed-tools-check.js +260 -0
  447. package/dist/modules/skills/allowed-tools-check.js.map +1 -0
  448. package/dist/modules/skills/index.d.ts +3 -1
  449. package/dist/modules/skills/index.d.ts.map +1 -1
  450. package/dist/modules/skills/index.js +1 -0
  451. package/dist/modules/skills/index.js.map +1 -1
  452. package/dist/modules/skills/pending-skills.service.d.ts +6 -11
  453. package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
  454. package/dist/modules/skills/pending-skills.service.js +18 -70
  455. package/dist/modules/skills/pending-skills.service.js.map +1 -1
  456. package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -1
  457. package/dist/modules/skills/skill-access-requests.routes.js +9 -6
  458. package/dist/modules/skills/skill-access-requests.routes.js.map +1 -1
  459. package/dist/modules/skills/skills.contract.d.ts +44 -10
  460. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  461. package/dist/modules/skills/skills.routes.d.ts +8 -1
  462. package/dist/modules/skills/skills.routes.d.ts.map +1 -1
  463. package/dist/modules/skills/skills.routes.js +16 -2
  464. package/dist/modules/skills/skills.routes.js.map +1 -1
  465. package/dist/modules/skills/skills.service.d.ts +31 -7
  466. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  467. package/dist/modules/skills/skills.service.js +191 -81
  468. package/dist/modules/skills/skills.service.js.map +1 -1
  469. package/dist/modules/skills/skills.tools.d.ts +4 -1
  470. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  471. package/dist/modules/skills/skills.tools.js +41 -6
  472. package/dist/modules/skills/skills.tools.js.map +1 -1
  473. package/dist/modules/tool-auth/connection-key-rejection.d.ts +18 -0
  474. package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -0
  475. package/dist/modules/tool-auth/connection-key-rejection.js +20 -0
  476. package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -0
  477. package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts +14 -0
  478. package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts.map +1 -0
  479. package/dist/modules/tool-auth/connection-keys-admin.routes.js +67 -0
  480. package/dist/modules/tool-auth/connection-keys-admin.routes.js.map +1 -0
  481. package/dist/modules/tool-auth/external-api-key.interface.d.ts +72 -8
  482. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  483. package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
  484. package/dist/modules/tool-auth/external-api-key.service.d.ts +17 -6
  485. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  486. package/dist/modules/tool-auth/external-api-key.service.js +83 -27
  487. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  488. package/dist/modules/tool-auth/internal-token.service.d.ts +15 -2
  489. package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
  490. package/dist/modules/tool-auth/internal-token.service.js +6 -2
  491. package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
  492. package/dist/modules/tool-auth/key-or-session.middleware.d.ts +23 -0
  493. package/dist/modules/tool-auth/key-or-session.middleware.d.ts.map +1 -0
  494. package/dist/modules/tool-auth/key-or-session.middleware.js +39 -0
  495. package/dist/modules/tool-auth/key-or-session.middleware.js.map +1 -0
  496. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +15 -1
  497. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  498. package/dist/modules/tool-auth/tool-auth.middleware.js +17 -6
  499. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  500. package/dist/modules/tool-helpers/tool-context.d.ts +14 -0
  501. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
  502. package/dist/modules/tool-helpers/tool-context.js +28 -6
  503. package/dist/modules/tool-helpers/tool-context.js.map +1 -1
  504. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  505. package/dist/modules/tool-helpers/tool-handler.js +22 -5
  506. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  507. package/dist/modules/tool-helpers/tool.contract.d.ts +6 -1
  508. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  509. package/dist/modules/tool-helpers/tool.contract.js +7 -1
  510. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  511. package/dist/modules/tool-manuals/index.d.ts +2 -1
  512. package/dist/modules/tool-manuals/index.d.ts.map +1 -1
  513. package/dist/modules/tool-manuals/index.js +1 -0
  514. package/dist/modules/tool-manuals/index.js.map +1 -1
  515. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
  516. package/dist/modules/tool-manuals/mcp-json-discovery.js +8 -6
  517. package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
  518. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +10 -2
  519. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -1
  520. package/dist/modules/tool-manuals/mcp-server-edit.service.js +12 -15
  521. package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
  522. package/dist/modules/tool-manuals/pending-tools.service.d.ts +50 -0
  523. package/dist/modules/tool-manuals/pending-tools.service.d.ts.map +1 -0
  524. package/dist/modules/tool-manuals/pending-tools.service.js +149 -0
  525. package/dist/modules/tool-manuals/pending-tools.service.js.map +1 -0
  526. package/dist/modules/tool-manuals/tool-delete.service.d.ts +135 -0
  527. package/dist/modules/tool-manuals/tool-delete.service.d.ts.map +1 -0
  528. package/dist/modules/tool-manuals/tool-delete.service.js +303 -0
  529. package/dist/modules/tool-manuals/tool-delete.service.js.map +1 -0
  530. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +161 -0
  531. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  532. package/dist/modules/tool-manuals/tool-manuals.contract.js +9 -0
  533. package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -1
  534. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +19 -2
  535. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  536. package/dist/modules/tool-manuals/tool-manuals.routes.js +127 -57
  537. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  538. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +119 -5
  539. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  540. package/dist/modules/tool-manuals/tool-manuals.service.js +404 -74
  541. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  542. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  543. package/dist/modules/tool-manuals/tool-manuals.tools.js +78 -6
  544. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  545. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  546. package/dist/modules/workflow/agent-tools/workflow.tools.js +88 -54
  547. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  548. package/dist/modules/workflow/event-bus.d.ts +10 -4
  549. package/dist/modules/workflow/event-bus.d.ts.map +1 -1
  550. package/dist/modules/workflow/event-bus.js +14 -11
  551. package/dist/modules/workflow/event-bus.js.map +1 -1
  552. package/dist/modules/workflow/events.routes.d.ts +17 -2
  553. package/dist/modules/workflow/events.routes.d.ts.map +1 -1
  554. package/dist/modules/workflow/events.routes.js +31 -12
  555. package/dist/modules/workflow/events.routes.js.map +1 -1
  556. package/dist/modules/workflow/file-lock.service.d.ts +22 -4
  557. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  558. package/dist/modules/workflow/file-lock.service.js +27 -4
  559. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  560. package/dist/modules/workflow/git/change-request-link.d.ts +20 -0
  561. package/dist/modules/workflow/git/change-request-link.d.ts.map +1 -0
  562. package/dist/modules/workflow/git/change-request-link.js +34 -0
  563. package/dist/modules/workflow/git/change-request-link.js.map +1 -0
  564. package/dist/modules/workflow/git/git-version.d.ts.map +1 -1
  565. package/dist/modules/workflow/git/git-version.js +3 -1
  566. package/dist/modules/workflow/git/git-version.js.map +1 -1
  567. package/dist/modules/workflow/git/git.service.d.ts +263 -4
  568. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  569. package/dist/modules/workflow/git/git.service.js +528 -118
  570. package/dist/modules/workflow/git/git.service.js.map +1 -1
  571. package/dist/modules/workflow/git/node-git-runner.d.ts +36 -0
  572. package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -0
  573. package/dist/modules/workflow/git/node-git-runner.js +273 -0
  574. package/dist/modules/workflow/git/node-git-runner.js.map +1 -0
  575. package/dist/modules/workflow/git/pull-request.service.d.ts +51 -2
  576. package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
  577. package/dist/modules/workflow/git/pull-request.service.js +157 -25
  578. package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
  579. package/dist/modules/workflow/pending-commits.service.d.ts +11 -0
  580. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  581. package/dist/modules/workflow/pending-commits.service.js +24 -4
  582. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  583. package/dist/modules/workflow/pending-commits.worker.d.ts +15 -8
  584. package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
  585. package/dist/modules/workflow/pending-commits.worker.js +107 -58
  586. package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
  587. package/dist/modules/workflow/recovery-bot.d.ts.map +1 -1
  588. package/dist/modules/workflow/recovery-bot.js +3 -1
  589. package/dist/modules/workflow/recovery-bot.js.map +1 -1
  590. package/dist/modules/workflow/review-workflow/approval-lock.d.ts +60 -0
  591. package/dist/modules/workflow/review-workflow/approval-lock.d.ts.map +1 -0
  592. package/dist/modules/workflow/review-workflow/approval-lock.js +116 -0
  593. package/dist/modules/workflow/review-workflow/approval-lock.js.map +1 -0
  594. package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts +54 -7
  595. package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts.map +1 -1
  596. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +64 -0
  597. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  598. package/dist/modules/workflow/review-workflow/review-workflow.service.js +269 -90
  599. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  600. package/dist/modules/workflow/sanitize-error.d.ts +5 -2
  601. package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
  602. package/dist/modules/workflow/sanitize-error.js +8 -4
  603. package/dist/modules/workflow/sanitize-error.js.map +1 -1
  604. package/dist/modules/workflow/workflow.routes.d.ts +2 -0
  605. package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
  606. package/dist/modules/workflow/workflow.routes.js +259 -29
  607. package/dist/modules/workflow/workflow.routes.js.map +1 -1
  608. package/dist/modules/workflow/workflow.service.d.ts +166 -14
  609. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  610. package/dist/modules/workflow/workflow.service.js +906 -106
  611. package/dist/modules/workflow/workflow.service.js.map +1 -1
  612. package/dist/modules/workspace/empty-dirs.d.ts +15 -0
  613. package/dist/modules/workspace/empty-dirs.d.ts.map +1 -0
  614. package/dist/modules/workspace/empty-dirs.js +90 -0
  615. package/dist/modules/workspace/empty-dirs.js.map +1 -0
  616. package/dist/modules/workspace/file-readers/content-mode.d.ts +50 -0
  617. package/dist/modules/workspace/file-readers/content-mode.d.ts.map +1 -0
  618. package/dist/modules/workspace/file-readers/content-mode.js +55 -0
  619. package/dist/modules/workspace/file-readers/content-mode.js.map +1 -0
  620. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +4 -1
  621. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
  622. package/dist/modules/workspace/file-readers/doc-extract.service.js +4 -1
  623. package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
  624. package/dist/modules/workspace/file-readers/doc-extract.types.d.ts +5 -2
  625. package/dist/modules/workspace/file-readers/doc-extract.types.d.ts.map +1 -1
  626. package/dist/modules/workspace/file-readers/doc-extract.types.js +7 -4
  627. package/dist/modules/workspace/file-readers/doc-extract.types.js.map +1 -1
  628. package/dist/modules/workspace/file-readers/document-reader.d.ts +2 -0
  629. package/dist/modules/workspace/file-readers/document-reader.d.ts.map +1 -1
  630. package/dist/modules/workspace/file-readers/document-reader.js +5 -0
  631. package/dist/modules/workspace/file-readers/document-reader.js.map +1 -1
  632. package/dist/modules/workspace/file-readers/extract-docx.d.ts +6 -0
  633. package/dist/modules/workspace/file-readers/extract-docx.d.ts.map +1 -1
  634. package/dist/modules/workspace/file-readers/extract-docx.js +6 -0
  635. package/dist/modules/workspace/file-readers/extract-docx.js.map +1 -1
  636. package/dist/modules/workspace/file-readers/file-reader.d.ts +24 -5
  637. package/dist/modules/workspace/file-readers/file-reader.d.ts.map +1 -1
  638. package/dist/modules/workspace/file-readers/file-reader.js +11 -6
  639. package/dist/modules/workspace/file-readers/file-reader.js.map +1 -1
  640. package/dist/modules/workspace/file-readers/file-reader.registry.d.ts.map +1 -1
  641. package/dist/modules/workspace/file-readers/file-reader.registry.js +6 -1
  642. package/dist/modules/workspace/file-readers/file-reader.registry.js.map +1 -1
  643. package/dist/modules/workspace/file-readers/image-reader.d.ts +7 -5
  644. package/dist/modules/workspace/file-readers/image-reader.d.ts.map +1 -1
  645. package/dist/modules/workspace/file-readers/image-reader.js +9 -5
  646. package/dist/modules/workspace/file-readers/image-reader.js.map +1 -1
  647. package/dist/modules/workspace/file-readers/ooxml-text.d.ts +3 -0
  648. package/dist/modules/workspace/file-readers/ooxml-text.d.ts.map +1 -1
  649. package/dist/modules/workspace/file-readers/ooxml-text.js +39 -2
  650. package/dist/modules/workspace/file-readers/ooxml-text.js.map +1 -1
  651. package/dist/modules/workspace/file-readers/text-reader.d.ts +29 -1
  652. package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
  653. package/dist/modules/workspace/file-readers/text-reader.js +57 -3
  654. package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
  655. package/dist/modules/workspace/git-internals.middleware.d.ts +28 -0
  656. package/dist/modules/workspace/git-internals.middleware.d.ts.map +1 -0
  657. package/dist/modules/workspace/git-internals.middleware.js +93 -0
  658. package/dist/modules/workspace/git-internals.middleware.js.map +1 -0
  659. package/dist/modules/workspace/not-found.d.ts +80 -0
  660. package/dist/modules/workspace/not-found.d.ts.map +1 -0
  661. package/dist/modules/workspace/not-found.js +112 -0
  662. package/dist/modules/workspace/not-found.js.map +1 -0
  663. package/dist/modules/workspace/session-ontology.gate.d.ts +1 -1
  664. package/dist/modules/workspace/session-ontology.gate.d.ts.map +1 -1
  665. package/dist/modules/workspace/session-ontology.gate.js +1 -1
  666. package/dist/modules/workspace/session-ontology.gate.js.map +1 -1
  667. package/dist/modules/workspace/startup/beside-checkout.d.ts +42 -0
  668. package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -0
  669. package/dist/modules/workspace/startup/beside-checkout.js +110 -0
  670. package/dist/modules/workspace/startup/beside-checkout.js.map +1 -0
  671. package/dist/modules/workspace/startup/kb-git.d.ts +7 -12
  672. package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
  673. package/dist/modules/workspace/startup/kb-git.js +34 -35
  674. package/dist/modules/workspace/startup/kb-git.js.map +1 -1
  675. package/dist/modules/workspace/startup/kb-startup-runner.d.ts +95 -0
  676. package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
  677. package/dist/modules/workspace/startup/kb-startup-runner.js +257 -55
  678. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
  679. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts +45 -0
  680. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts.map +1 -1
  681. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +417 -385
  682. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -1
  683. package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts +48 -0
  684. package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts.map +1 -0
  685. package/dist/modules/workspace/startup/steps/personal-spaces.step.js +183 -0
  686. package/dist/modules/workspace/startup/steps/personal-spaces.step.js.map +1 -0
  687. package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts +51 -0
  688. package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts.map +1 -0
  689. package/dist/modules/workspace/startup/steps/plugin-display-names.step.js +158 -0
  690. package/dist/modules/workspace/startup/steps/plugin-display-names.step.js.map +1 -0
  691. package/dist/modules/workspace/startup/steps/plugin-layout.d.ts +64 -0
  692. package/dist/modules/workspace/startup/steps/plugin-layout.d.ts.map +1 -0
  693. package/dist/modules/workspace/startup/steps/plugin-layout.js +97 -0
  694. package/dist/modules/workspace/startup/steps/plugin-layout.js.map +1 -0
  695. package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts +7 -1
  696. package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts.map +1 -1
  697. package/dist/modules/workspace/startup/steps/plugin-manifests.step.js +45 -81
  698. package/dist/modules/workspace/startup/steps/plugin-manifests.step.js.map +1 -1
  699. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +3 -1
  700. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts.map +1 -1
  701. package/dist/modules/workspace/startup/steps/roles-yaml.step.js +4 -12
  702. package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -1
  703. package/dist/modules/workspace/startup/steps/seed-tree.d.ts +6 -1
  704. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  705. package/dist/modules/workspace/startup/steps/seed-tree.js +154 -78
  706. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  707. package/dist/modules/workspace/startup/steps/template-files.step.d.ts +96 -24
  708. package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
  709. package/dist/modules/workspace/startup/steps/template-files.step.js +446 -138
  710. package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
  711. package/dist/modules/workspace/startup/steps/template-source.d.ts +94 -0
  712. package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -0
  713. package/dist/modules/workspace/startup/steps/template-source.js +160 -0
  714. package/dist/modules/workspace/startup/steps/template-source.js.map +1 -0
  715. package/dist/modules/workspace/workspace.routes.d.ts +14 -2
  716. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  717. package/dist/modules/workspace/workspace.routes.js +649 -238
  718. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  719. package/dist/modules/workspace/workspace.service.d.ts +298 -12
  720. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  721. package/dist/modules/workspace/workspace.service.js +933 -269
  722. package/dist/modules/workspace/workspace.service.js.map +1 -1
  723. package/dist/modules/workspace/workspace.tools.d.ts +42 -1
  724. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  725. package/dist/modules/workspace/workspace.tools.js +1626 -146
  726. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  727. package/dist/modules/workspace/write-denial.d.ts +76 -0
  728. package/dist/modules/workspace/write-denial.d.ts.map +1 -0
  729. package/dist/modules/workspace/write-denial.js +138 -0
  730. package/dist/modules/workspace/write-denial.js.map +1 -0
  731. package/dist/shared/canonical-file-identity.d.ts +64 -0
  732. package/dist/shared/canonical-file-identity.d.ts.map +1 -0
  733. package/dist/shared/canonical-file-identity.js +92 -0
  734. package/dist/shared/canonical-file-identity.js.map +1 -0
  735. package/dist/shared/domain-errors.d.ts +108 -0
  736. package/dist/shared/domain-errors.d.ts.map +1 -1
  737. package/dist/shared/domain-errors.js +137 -0
  738. package/dist/shared/domain-errors.js.map +1 -1
  739. package/dist/shared/email-identity.d.ts +32 -0
  740. package/dist/shared/email-identity.d.ts.map +1 -0
  741. package/dist/shared/email-identity.js +37 -0
  742. package/dist/shared/email-identity.js.map +1 -0
  743. package/dist/shared/fs.contract.d.ts +206 -0
  744. package/dist/shared/fs.contract.d.ts.map +1 -0
  745. package/dist/shared/fs.contract.js +79 -0
  746. package/dist/shared/fs.contract.js.map +1 -0
  747. package/dist/shared/git-failure.d.ts +80 -0
  748. package/dist/shared/git-failure.d.ts.map +1 -0
  749. package/dist/shared/git-failure.js +139 -0
  750. package/dist/shared/git-failure.js.map +1 -0
  751. package/dist/shared/git-internals.d.ts +23 -0
  752. package/dist/shared/git-internals.d.ts.map +1 -0
  753. package/dist/shared/git-internals.js +181 -0
  754. package/dist/shared/git-internals.js.map +1 -0
  755. package/dist/shared/git.contract.d.ts +157 -0
  756. package/dist/shared/git.contract.d.ts.map +1 -0
  757. package/dist/shared/git.contract.js +103 -0
  758. package/dist/shared/git.contract.js.map +1 -0
  759. package/dist/shared/http-errors.d.ts +33 -0
  760. package/dist/shared/http-errors.d.ts.map +1 -0
  761. package/dist/shared/http-errors.js +35 -0
  762. package/dist/shared/http-errors.js.map +1 -0
  763. package/dist/shared/logger.contract.d.ts +63 -0
  764. package/dist/shared/logger.contract.d.ts.map +1 -0
  765. package/dist/shared/logger.contract.js +126 -0
  766. package/dist/shared/logger.contract.js.map +1 -0
  767. package/dist/shared/logging.d.ts +31 -0
  768. package/dist/shared/logging.d.ts.map +1 -0
  769. package/dist/shared/logging.js +92 -0
  770. package/dist/shared/logging.js.map +1 -0
  771. package/dist/shared/path-containment.d.ts +21 -0
  772. package/dist/shared/path-containment.d.ts.map +1 -0
  773. package/dist/shared/path-containment.js +34 -0
  774. package/dist/shared/path-containment.js.map +1 -0
  775. package/dist/shared/pending-proposals.d.ts +71 -0
  776. package/dist/shared/pending-proposals.d.ts.map +1 -0
  777. package/dist/shared/pending-proposals.js +98 -0
  778. package/dist/shared/pending-proposals.js.map +1 -0
  779. package/dist/shared/printable.d.ts +22 -0
  780. package/dist/shared/printable.d.ts.map +1 -0
  781. package/dist/shared/printable.js +65 -0
  782. package/dist/shared/printable.js.map +1 -0
  783. package/dist/shared/redact-secret.d.ts +30 -0
  784. package/dist/shared/redact-secret.d.ts.map +1 -0
  785. package/dist/shared/redact-secret.js +114 -0
  786. package/dist/shared/redact-secret.js.map +1 -0
  787. package/dist/shared/rename-no-replace.d.ts +119 -0
  788. package/dist/shared/rename-no-replace.d.ts.map +1 -0
  789. package/dist/shared/rename-no-replace.js +323 -0
  790. package/dist/shared/rename-no-replace.js.map +1 -0
  791. package/dist/shared/ssrf.d.ts.map +1 -1
  792. package/dist/shared/ssrf.js +49 -18
  793. package/dist/shared/ssrf.js.map +1 -1
  794. package/dist/version.d.ts +10 -6
  795. package/dist/version.d.ts.map +1 -1
  796. package/dist/version.js +10 -6
  797. package/dist/version.js.map +1 -1
  798. package/kb-template/.bevelignore +4 -3
  799. package/kb-template/AGENTS.md +281 -36
  800. package/kb-template/KnowledgeBase/How to get started.md +1 -1
  801. package/kb-template/access.md +5 -3
  802. package/kb-template/mcp-description.md +27 -0
  803. package/migrations/0006_api_tokens_revoked_by.sql +1 -0
  804. package/migrations/0007_github_facade_registered.sql +1 -0
  805. package/migrations/0008_change_request_apply_failure.sql +4 -0
  806. package/migrations/0009_change_request_apply_failure_kind.sql +13 -0
  807. package/migrations/0010_plugin_join_requests.sql +15 -0
  808. package/migrations/0011_plugin_join_request_claim.sql +11 -0
  809. package/migrations/0012_agent_audit.sql +41 -0
  810. package/migrations/0013_agent_identity_and_key_soft_delete.sql +29 -0
  811. package/migrations/meta/0006_snapshot.json +1723 -0
  812. package/migrations/meta/0007_snapshot.json +1729 -0
  813. package/migrations/meta/0008_snapshot.json +1753 -0
  814. package/migrations/meta/0009_snapshot.json +1759 -0
  815. package/migrations/meta/0010_snapshot.json +1872 -0
  816. package/migrations/meta/0011_snapshot.json +1884 -0
  817. package/migrations/meta/0012_snapshot.json +2247 -0
  818. package/migrations/meta/0013_snapshot.json +2259 -0
  819. package/migrations/meta/_journal.json +56 -0
  820. package/package.json +7 -6
  821. package/src/__tests__/core-config.domain.test.ts +67 -2
  822. package/src/__tests__/kb-layout-config.test.ts +186 -1
  823. package/src/__tests__/open-change-gate.ts +15 -0
  824. package/src/core/__tests__/catalog-cache-invalidation.test.ts +83 -3
  825. package/src/core/__tests__/catalog-revision.test.ts +190 -0
  826. package/src/core/__tests__/lifecycle.test.ts +369 -0
  827. package/src/core/__tests__/public-config.test.ts +30 -0
  828. package/src/core/__tests__/readiness.test.ts +109 -0
  829. package/src/core/catalog-cache-invalidation.ts +20 -1
  830. package/src/core/catalog-revision.ts +110 -0
  831. package/src/core/create-core-server.ts +286 -79
  832. package/src/core/create-core-services.ts +315 -81
  833. package/src/core/lifecycle.ts +312 -0
  834. package/src/core/public-config.ts +86 -0
  835. package/src/core/readiness.ts +123 -0
  836. package/src/core-config.ts +70 -4
  837. package/src/index.ts +15 -0
  838. package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +362 -0
  839. package/src/modules/access/__tests__/access-control.atref-batch.test.ts +2 -1
  840. package/src/modules/access/__tests__/access-control.atref-cache.test.ts +46 -22
  841. package/src/modules/access/__tests__/access-control.deny-dependents.test.ts +215 -0
  842. package/src/modules/access/__tests__/access-control.download-read.test.ts +268 -0
  843. package/src/modules/access/__tests__/access-control.everyone-read.test.ts +145 -0
  844. package/src/modules/access/__tests__/access-control.group-read.test.ts +163 -0
  845. package/src/modules/access/__tests__/access-control.invalidate-race.test.ts +2 -0
  846. package/src/modules/access/__tests__/access-control.platform-restore.test.ts +152 -0
  847. package/src/modules/access/__tests__/access-control.prospective.test.ts +189 -0
  848. package/src/modules/access/__tests__/access-control.read-batch.test.ts +2 -1
  849. package/src/modules/access/__tests__/access-control.service.test.ts +123 -78
  850. package/src/modules/access/__tests__/access-groups.test.ts +47 -14
  851. package/src/modules/access/__tests__/access-md-format.test.ts +2 -1
  852. package/src/modules/access/__tests__/access-mutation.service.test.ts +52 -4
  853. package/src/modules/access/__tests__/access-own-read-grant.test.ts +2 -0
  854. package/src/modules/access/__tests__/access-personal-plugin.test.ts +153 -0
  855. package/src/modules/access/__tests__/access-plugin-principals.test.ts +18 -1
  856. package/src/modules/access/__tests__/access-restrict-here.test.ts +450 -0
  857. package/src/modules/access/__tests__/access.routes.batch.test.ts +139 -0
  858. package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +394 -0
  859. package/src/modules/access/__tests__/access.routes.group-grant.test.ts +6 -2
  860. package/src/modules/access/__tests__/access.routes.overrides.test.ts +3 -2
  861. package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +234 -0
  862. package/src/modules/access/__tests__/access.routes.prospective.test.ts +216 -0
  863. package/src/modules/access/__tests__/access.routes.revoke.test.ts +7 -2
  864. package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +360 -0
  865. package/src/modules/access/__tests__/change-read-gate.test.ts +272 -0
  866. package/src/modules/access/__tests__/creator-access.test.ts +81 -83
  867. package/src/modules/access/__tests__/grant-sources.test.ts +3 -2
  868. package/src/modules/access/__tests__/groups-admin.service.test.ts +6 -5
  869. package/src/modules/access/__tests__/roles-admin.security.test.ts +196 -0
  870. package/src/modules/access/__tests__/roles-admin.service.test.ts +127 -2
  871. package/src/modules/access/__tests__/roles-capabilities.test.ts +5 -4
  872. package/src/modules/access/__tests__/roles.routes.test.ts +5 -2
  873. package/src/modules/access/__tests__/user-access-removal.service.test.ts +402 -0
  874. package/src/modules/access/__tests__/users-db-double.ts +63 -0
  875. package/src/modules/access/access-control.interface.ts +251 -11
  876. package/src/modules/access/access-control.service.ts +719 -164
  877. package/src/modules/access/access-mutation.service.ts +182 -13
  878. package/src/modules/access/access-view.ts +316 -0
  879. package/src/modules/access/access.routes.ts +293 -95
  880. package/src/modules/access/admin-locked-commit.ts +12 -14
  881. package/src/modules/access/admin-route-helpers.ts +4 -4
  882. package/src/modules/access/change-read-gate.ts +148 -0
  883. package/src/modules/access/creator-access.ts +89 -124
  884. package/src/modules/access/directory-sync-bot.ts +4 -1
  885. package/src/modules/access/groups-admin.service.ts +3 -3
  886. package/src/modules/access/reference-scan.ts +5 -5
  887. package/src/modules/access/roles-admin.service.ts +94 -15
  888. package/src/modules/access/synced-groups-committer.ts +10 -15
  889. package/src/modules/access/synced-groups-writer.ts +3 -12
  890. package/src/modules/access/user-access-removal.service.ts +711 -0
  891. package/src/modules/access-model/__tests__/access-splice.test.ts +63 -1
  892. package/src/modules/access-model/__tests__/frontmatter-fences.test.ts +71 -0
  893. package/src/modules/access-model/__tests__/roles-yaml-guard.test.ts +131 -0
  894. package/src/modules/access-model/access-errors.ts +56 -15
  895. package/src/modules/access-model/access-grammar.ts +85 -48
  896. package/src/modules/access-model/access-splice.ts +18 -18
  897. package/src/modules/access-model/access-template.ts +44 -0
  898. package/src/modules/access-model/change-gate.ts +80 -0
  899. package/src/modules/access-model/creator.ts +18 -21
  900. package/src/modules/access-model/frontmatter-lines.ts +98 -0
  901. package/src/modules/access-model/plugin-principals.ts +12 -19
  902. package/src/modules/access-model/roles-yaml-guard.ts +182 -4
  903. package/src/modules/admin/admin-access.service.ts +6 -2
  904. package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +100 -0
  905. package/src/modules/agent-instructions/__tests__/compose.test.ts +256 -0
  906. package/src/modules/agent-instructions/__tests__/read-preamble.test.ts +135 -0
  907. package/src/modules/agent-instructions/agent-instructions.routes.ts +41 -0
  908. package/src/modules/agent-instructions/compose.ts +271 -0
  909. package/src/modules/agent-instructions/index.ts +14 -0
  910. package/src/modules/agent-instructions/read-preamble.ts +88 -0
  911. package/src/modules/audit/__tests__/agent-audit.service.test.ts +370 -0
  912. package/src/modules/audit/__tests__/agent-rest-audit.middleware.test.ts +113 -0
  913. package/src/modules/audit/__tests__/audit.routes.test.ts +201 -0
  914. package/src/modules/audit/__tests__/event-classifier.test.ts +121 -0
  915. package/src/modules/audit/agent-audit.service.ts +316 -0
  916. package/src/modules/audit/agent-rest-audit.middleware.ts +83 -0
  917. package/src/modules/audit/audit.contract.ts +164 -0
  918. package/src/modules/audit/audit.routes.ts +168 -0
  919. package/src/modules/audit/event-classifier.ts +155 -0
  920. package/src/modules/audit/index.ts +14 -0
  921. package/src/modules/audit/request-audit.ts +141 -0
  922. package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +209 -0
  923. package/src/modules/auth/__tests__/account.routes.test.ts +178 -2
  924. package/src/modules/auth/__tests__/auth.middleware.test.ts +78 -0
  925. package/src/modules/auth/__tests__/auth.service.test.ts +338 -5
  926. package/src/modules/auth/__tests__/oidc-auth-provider.test.ts +352 -12
  927. package/src/modules/auth/__tests__/onboarding-done.route.test.ts +3 -1
  928. package/src/modules/auth/account-erasure.service.ts +117 -10
  929. package/src/modules/auth/account.routes.ts +115 -14
  930. package/src/modules/auth/auth.routes.ts +20 -7
  931. package/src/modules/auth/auth.service.ts +187 -28
  932. package/src/modules/auth/oidc-auth-provider.ts +120 -22
  933. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +109 -1
  934. package/src/modules/code-mode/code-mode.tool.ts +13 -2
  935. package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +78 -0
  936. package/src/modules/connection-probe/connection-probe.service.ts +59 -7
  937. package/src/modules/database/__tests__/advisory-lease.test.ts +152 -0
  938. package/src/modules/database/__tests__/advisory-lock.test.ts +147 -0
  939. package/src/modules/database/advisory-lock.ts +346 -0
  940. package/src/modules/database/connection.ts +9 -1
  941. package/src/modules/database/core-schema.ts +227 -0
  942. package/src/modules/database/migrate.ts +27 -6
  943. package/src/modules/declared-variables/declared-variables.routes.ts +6 -3
  944. package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +3 -0
  945. package/src/modules/diff/__tests__/diff.service.test.ts +3 -0
  946. package/src/modules/diff/diff.routes.ts +2 -4
  947. package/src/modules/diff/diff.service.ts +80 -79
  948. package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +472 -9
  949. package/src/modules/kb-fs/__tests__/node-fs.test.ts +409 -0
  950. package/src/modules/kb-fs/__tests__/repo-path.test.ts +210 -1
  951. package/src/modules/{workspace → kb-fs}/bevel-ignore.ts +22 -15
  952. package/src/modules/kb-fs/file-change-notifier.ts +4 -1
  953. package/src/modules/kb-fs/git-guarded-filesystem.ts +136 -0
  954. package/src/modules/kb-fs/locking-filesystem.ts +399 -109
  955. package/src/modules/kb-fs/node-fs.ts +165 -0
  956. package/src/modules/kb-fs/read-only-filesystem.ts +18 -9
  957. package/src/modules/kb-fs/repo-path.ts +213 -3
  958. package/src/modules/kb-sync/kb-sync.routes.ts +5 -2
  959. package/src/modules/kb-sync/kb-sync.service.ts +8 -5
  960. package/src/modules/marketplace/__tests__/github-facade.test.ts +134 -2
  961. package/src/modules/marketplace/__tests__/marketplace-git.test.ts +2 -1
  962. package/src/modules/marketplace/__tests__/marketplace-repo.head.test.ts +47 -0
  963. package/src/modules/marketplace/git-http.routes.ts +7 -4
  964. package/src/modules/marketplace/github-facade/github-facade-admin.routes.ts +35 -3
  965. package/src/modules/marketplace/github-facade/github-facade-credentials.service.ts +55 -0
  966. package/src/modules/marketplace/github-facade/github-facade.routes.ts +32 -6
  967. package/src/modules/marketplace/github-facade/github-facade.service.ts +80 -19
  968. package/src/modules/marketplace/github-facade/index.ts +1 -0
  969. package/src/modules/marketplace/marketplace-repo.service.ts +23 -8
  970. package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +183 -13
  971. package/src/modules/mcp/__tests__/downstream-pool.test.ts +278 -0
  972. package/src/modules/mcp/__tests__/downstream-token-refresh.test.ts +178 -0
  973. package/src/modules/mcp/__tests__/fake-downstream-mcp-server.ts +156 -0
  974. package/src/modules/mcp/__tests__/first-call-probe.cli-args.test.ts +178 -0
  975. package/src/modules/mcp/__tests__/first-call-probe.test.ts +414 -0
  976. package/src/modules/mcp/__tests__/manual-failure-memo.test.ts +98 -6
  977. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +68 -7
  978. package/src/modules/mcp/__tests__/mcp-routes-harness.ts +1 -3
  979. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +1055 -59
  980. package/src/modules/mcp/__tests__/mcp.routes.local-token.test.ts +24 -4
  981. package/src/modules/mcp/__tests__/mcp.routes.stateless.test.ts +182 -0
  982. package/src/modules/mcp/__tests__/mcp.service.test.ts +391 -6
  983. package/src/modules/mcp/__tests__/surface-log-throttle.test.ts +169 -0
  984. package/src/modules/mcp/downstream-pool.ts +256 -0
  985. package/src/modules/mcp/downstream-token-refresh.ts +170 -0
  986. package/src/modules/mcp/first-call-probe.cli-args.ts +159 -0
  987. package/src/modules/mcp/first-call-probe.cli.ts +60 -0
  988. package/src/modules/mcp/first-call-probe.ts +418 -0
  989. package/src/modules/mcp/manual-failure-memo.ts +81 -23
  990. package/src/modules/mcp/mcp-auth.middleware.ts +56 -16
  991. package/src/modules/mcp/mcp.routes.ts +386 -482
  992. package/src/modules/mcp/mcp.service.ts +905 -177
  993. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +189 -8
  994. package/src/modules/mcp/oauth/oauth-consent.routes.ts +4 -4
  995. package/src/modules/mcp/surface-log-throttle.ts +120 -0
  996. package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +10 -7
  997. package/src/modules/plugins/__tests__/bundle-dialect.test.ts +165 -0
  998. package/src/modules/plugins/__tests__/compile-marketplace.test.ts +139 -10
  999. package/src/modules/plugins/__tests__/fake-join-request-store.ts +189 -0
  1000. package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +608 -0
  1001. package/src/modules/plugins/__tests__/plugin-creation-display-name.test.ts +241 -0
  1002. package/src/modules/plugins/__tests__/plugin-index.service.test.ts +148 -7
  1003. package/src/modules/plugins/__tests__/plugin-links.service.test.ts +10 -13
  1004. package/src/modules/plugins/__tests__/plugin-names.test.ts +77 -0
  1005. package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +129 -19
  1006. package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +91 -29
  1007. package/src/modules/plugins/__tests__/plugins.routes.test.ts +370 -19
  1008. package/src/modules/plugins/__tests__/plugins.tools.test.ts +131 -0
  1009. package/src/modules/plugins/__tests__/teams.routes.test.ts +248 -0
  1010. package/src/modules/plugins/compile/compile-marketplace.ts +143 -46
  1011. package/src/modules/plugins/compile/marketplace-compiler.service.ts +8 -7
  1012. package/src/modules/plugins/discovery/__tests__/kb-plugin-source.test.ts +56 -20
  1013. package/src/modules/plugins/discovery/bundle-dialect/bundle.source.ts +30 -9
  1014. package/src/modules/plugins/discovery/bundle-dialect/registry.ts +93 -18
  1015. package/src/modules/plugins/discovery/kb-plugin-source.ts +10 -7
  1016. package/src/modules/plugins/discovery/native.source.ts +21 -3
  1017. package/src/modules/plugins/discovery/plugin-source.ts +17 -3
  1018. package/src/modules/plugins/index.ts +16 -3
  1019. package/src/modules/plugins/join-request-jobs.service.ts +675 -0
  1020. package/src/modules/plugins/join-request-records.store.ts +353 -0
  1021. package/src/modules/plugins/join-requests.service.ts +7 -4
  1022. package/src/modules/plugins/plugin-links.service.ts +4 -17
  1023. package/src/modules/plugins/plugin-links.ts +6 -3
  1024. package/src/modules/plugins/plugin-provision.service.ts +209 -37
  1025. package/src/modules/plugins/plugin-rename.service.ts +21 -9
  1026. package/src/modules/plugins/plugins.contract.ts +52 -4
  1027. package/src/modules/plugins/plugins.routes.ts +204 -124
  1028. package/src/modules/plugins/plugins.service.ts +92 -18
  1029. package/src/modules/plugins/plugins.tools.ts +88 -0
  1030. package/src/modules/plugins/teams.routes.ts +129 -0
  1031. package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +359 -15
  1032. package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +248 -0
  1033. package/src/modules/secrets-vault/__tests__/namespace-secrets.test.ts +202 -0
  1034. package/src/modules/secrets-vault/db-secrets-vault.service.ts +217 -35
  1035. package/src/modules/secrets-vault/index.ts +1 -0
  1036. package/src/modules/secrets-vault/mcp-oauth-discovery.service.ts +6 -3
  1037. package/src/modules/secrets-vault/secrets-variable-loader.ts +4 -4
  1038. package/src/modules/secrets-vault/secrets-vault.contract.ts +93 -0
  1039. package/src/modules/secrets-vault/secrets-vault.routes.ts +99 -38
  1040. package/src/modules/settings/__tests__/connection-check.test.ts +233 -0
  1041. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +223 -6
  1042. package/src/modules/settings/__tests__/git-root-folders.test.ts +247 -0
  1043. package/src/modules/settings/__tests__/oidc-check.test.ts +185 -0
  1044. package/src/modules/settings/__tests__/setup.routes.boot-failure.test.ts +119 -0
  1045. package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +170 -0
  1046. package/src/modules/settings/__tests__/setup.routes.oidc.test.ts +555 -0
  1047. package/src/modules/settings/__tests__/setup.routes.test.ts +667 -16
  1048. package/src/modules/settings/connection-check.ts +351 -0
  1049. package/src/modules/settings/deployment-settings.service.ts +444 -59
  1050. package/src/modules/settings/git-root-folders.ts +171 -0
  1051. package/src/modules/settings/oidc-check.ts +229 -0
  1052. package/src/modules/settings/setup.routes.ts +456 -110
  1053. package/src/modules/skills/__tests__/allowed-tools-check.test.ts +224 -0
  1054. package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +210 -0
  1055. package/src/modules/skills/__tests__/pending-skills.service.test.ts +3 -3
  1056. package/src/modules/skills/__tests__/skills.routes.warnings.test.ts +91 -0
  1057. package/src/modules/skills/__tests__/skills.service.test.ts +223 -2
  1058. package/src/modules/skills/allowed-tools-check.ts +352 -0
  1059. package/src/modules/skills/index.ts +4 -0
  1060. package/src/modules/skills/pending-skills.service.ts +21 -82
  1061. package/src/modules/skills/skill-access-requests.routes.ts +10 -6
  1062. package/src/modules/skills/skills.contract.ts +43 -11
  1063. package/src/modules/skills/skills.routes.ts +16 -1
  1064. package/src/modules/skills/skills.service.ts +214 -84
  1065. package/src/modules/skills/skills.tools.ts +43 -5
  1066. package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +192 -0
  1067. package/src/modules/tool-auth/__tests__/connection-keys-admin.routes.test.ts +116 -0
  1068. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +153 -8
  1069. package/src/modules/tool-auth/__tests__/internal-token.service.test.ts +6 -0
  1070. package/src/modules/tool-auth/connection-key-rejection.ts +25 -0
  1071. package/src/modules/tool-auth/connection-keys-admin.routes.ts +76 -0
  1072. package/src/modules/tool-auth/external-api-key.interface.ts +78 -8
  1073. package/src/modules/tool-auth/external-api-key.service.ts +98 -33
  1074. package/src/modules/tool-auth/internal-token.service.ts +16 -2
  1075. package/src/modules/tool-auth/key-or-session.middleware.ts +45 -0
  1076. package/src/modules/tool-auth/tool-auth.middleware.ts +33 -8
  1077. package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +228 -0
  1078. package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +33 -4
  1079. package/src/modules/tool-helpers/tool-context.ts +46 -7
  1080. package/src/modules/tool-helpers/tool-handler.ts +23 -5
  1081. package/src/modules/tool-helpers/tool.contract.ts +5 -0
  1082. package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +2 -1
  1083. package/src/modules/tool-manuals/__tests__/pending-tools.service.test.ts +411 -0
  1084. package/src/modules/tool-manuals/__tests__/tool-delete.route.test.ts +133 -0
  1085. package/src/modules/tool-manuals/__tests__/tool-delete.service.test.ts +469 -0
  1086. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +21 -2
  1087. package/src/modules/tool-manuals/__tests__/tool-manuals.cli.test.ts +9 -4
  1088. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +6 -1
  1089. package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.routes.test.ts +254 -0
  1090. package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.test.ts +577 -0
  1091. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +6 -1
  1092. package/src/modules/tool-manuals/__tests__/tool-manuals.pending.route.test.ts +102 -0
  1093. package/src/modules/tool-manuals/__tests__/tool-manuals.restart.test.ts +285 -0
  1094. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +178 -2
  1095. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +114 -2
  1096. package/src/modules/tool-manuals/index.ts +3 -0
  1097. package/src/modules/tool-manuals/mcp-json-discovery.ts +13 -10
  1098. package/src/modules/tool-manuals/mcp-server-edit.service.ts +11 -14
  1099. package/src/modules/tool-manuals/pending-tools.service.ts +179 -0
  1100. package/src/modules/tool-manuals/tool-delete.service.ts +401 -0
  1101. package/src/modules/tool-manuals/tool-manuals.contract.ts +165 -0
  1102. package/src/modules/tool-manuals/tool-manuals.routes.ts +138 -54
  1103. package/src/modules/tool-manuals/tool-manuals.service.ts +432 -87
  1104. package/src/modules/tool-manuals/tool-manuals.tools.ts +81 -6
  1105. package/src/modules/workflow/__tests__/apply-failure.test.ts +1038 -0
  1106. package/src/modules/workflow/__tests__/event-bus.test.ts +61 -9
  1107. package/src/modules/workflow/__tests__/fake-file-lock-db.ts +199 -0
  1108. package/src/modules/workflow/__tests__/file-lock.service.path-identity.test.ts +237 -0
  1109. package/src/modules/workflow/__tests__/format-affected-owners.test.ts +18 -0
  1110. package/src/modules/workflow/__tests__/merge-announces-tree.test.ts +220 -0
  1111. package/src/modules/workflow/__tests__/pending-commits.worker.concurrency.test.ts +187 -0
  1112. package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +2 -0
  1113. package/src/modules/workflow/__tests__/sanitize-error.test.ts +10 -0
  1114. package/src/modules/workflow/__tests__/workflow.routes.folder-proposals.test.ts +128 -0
  1115. package/src/modules/workflow/__tests__/workflow.routes.fork-point-file.test.ts +149 -0
  1116. package/src/modules/workflow/__tests__/workflow.routes.lock-path.test.ts +305 -0
  1117. package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +2 -0
  1118. package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +2 -0
  1119. package/src/modules/workflow/__tests__/workflow.service.deleted-branch-sweep.test.ts +2 -0
  1120. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +294 -25
  1121. package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +564 -0
  1122. package/src/modules/workflow/__tests__/workflow.service.platform-restore.test.ts +135 -0
  1123. package/src/modules/workflow/__tests__/workflow.service.read-gate.test.ts +172 -0
  1124. package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +14 -0
  1125. package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +5 -0
  1126. package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +281 -0
  1127. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +158 -7
  1128. package/src/modules/workflow/agent-tools/workflow.tools.ts +93 -64
  1129. package/src/modules/workflow/event-bus.ts +26 -28
  1130. package/src/modules/workflow/events.routes.ts +56 -21
  1131. package/src/modules/workflow/file-lock.service.ts +27 -4
  1132. package/src/modules/workflow/git/__tests__/change-request-link.test.ts +43 -0
  1133. package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +1 -0
  1134. package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +238 -1
  1135. package/src/modules/workflow/git/__tests__/git.service.forkPoint.test.ts +245 -0
  1136. package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +35 -0
  1137. package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +149 -0
  1138. package/src/modules/workflow/git/__tests__/git.service.revertPathsAndPush.test.ts +177 -0
  1139. package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +114 -0
  1140. package/src/modules/workflow/git/__tests__/pull-request.service.getPrDetail.test.ts +69 -2
  1141. package/src/modules/workflow/git/__tests__/pull-request.service.list-fetch.test.ts +121 -0
  1142. package/src/modules/workflow/git/__tests__/pull-request.service.placeholder.test.ts +79 -0
  1143. package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
  1144. package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +1 -1
  1145. package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-update.test.ts +111 -0
  1146. package/src/modules/workflow/git/change-request-link.ts +35 -0
  1147. package/src/modules/workflow/git/git-version.ts +5 -2
  1148. package/src/modules/workflow/git/git.service.ts +684 -144
  1149. package/src/modules/workflow/git/node-git-runner.ts +315 -0
  1150. package/src/modules/workflow/git/pull-request.service.ts +179 -27
  1151. package/src/modules/workflow/pending-commits.service.ts +26 -7
  1152. package/src/modules/workflow/pending-commits.worker.ts +114 -69
  1153. package/src/modules/workflow/recovery-bot.ts +4 -1
  1154. package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +180 -1
  1155. package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
  1156. package/src/modules/workflow/review-workflow/__tests__/carry-approvals-forward.test.ts +520 -0
  1157. package/src/modules/workflow/review-workflow/__tests__/erase-approver.test.ts +84 -0
  1158. package/src/modules/workflow/review-workflow/__tests__/merge-gate.test.ts +89 -77
  1159. package/src/modules/workflow/review-workflow/approval-lock.ts +122 -0
  1160. package/src/modules/workflow/review-workflow/review-workflow.interface.ts +59 -7
  1161. package/src/modules/workflow/review-workflow/review-workflow.service.ts +309 -105
  1162. package/src/modules/workflow/sanitize-error.ts +9 -4
  1163. package/src/modules/workflow/workflow.routes.ts +273 -29
  1164. package/src/modules/workflow/workflow.service.ts +1087 -171
  1165. package/src/modules/workspace/__tests__/empty-dirs.test.ts +44 -0
  1166. package/src/modules/workspace/__tests__/file-stat-access.test.ts +301 -0
  1167. package/src/modules/workspace/__tests__/git-internals.security.test.ts +883 -0
  1168. package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +464 -0
  1169. package/src/modules/workspace/__tests__/workspace.routes.branch-errors.test.ts +139 -0
  1170. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +189 -37
  1171. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +51 -11
  1172. package/src/modules/workspace/__tests__/workspace.routes.download-implies-read.test.ts +161 -0
  1173. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +109 -10
  1174. package/src/modules/workspace/__tests__/workspace.routes.file-writes.test.ts +624 -0
  1175. package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +409 -0
  1176. package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +327 -0
  1177. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +25 -6
  1178. package/src/modules/workspace/__tests__/workspace.service.ensure-remotes-fetched.test.ts +109 -0
  1179. package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +6 -5
  1180. package/src/modules/workspace/__tests__/workspace.service.move-never-overwrites.test.ts +218 -0
  1181. package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +56 -1
  1182. package/src/modules/workspace/__tests__/workspace.service.test.ts +811 -42
  1183. package/src/modules/workspace/__tests__/workspace.service.unknown-branch.test.ts +197 -0
  1184. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +128 -0
  1185. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +212 -0
  1186. package/src/modules/workspace/__tests__/workspace.tools.test.ts +2632 -110
  1187. package/src/modules/workspace/__tests__/write-denial.test.ts +75 -0
  1188. package/src/modules/workspace/empty-dirs.ts +79 -0
  1189. package/src/modules/workspace/file-readers/README.md +32 -0
  1190. package/src/modules/workspace/file-readers/__tests__/doc-extract.test.ts +59 -2
  1191. package/src/modules/workspace/file-readers/__tests__/file-reader.registry.test.ts +133 -4
  1192. package/src/modules/workspace/file-readers/__tests__/fixtures/soft-break.docx +0 -0
  1193. package/src/modules/workspace/file-readers/__tests__/fixtures/tab.docx +0 -0
  1194. package/src/modules/workspace/file-readers/content-mode.ts +87 -0
  1195. package/src/modules/workspace/file-readers/doc-extract.service.ts +4 -1
  1196. package/src/modules/workspace/file-readers/doc-extract.types.ts +8 -4
  1197. package/src/modules/workspace/file-readers/document-reader.ts +6 -0
  1198. package/src/modules/workspace/file-readers/extract-docx.ts +6 -0
  1199. package/src/modules/workspace/file-readers/file-reader.registry.ts +9 -1
  1200. package/src/modules/workspace/file-readers/file-reader.ts +27 -6
  1201. package/src/modules/workspace/file-readers/image-reader.ts +10 -5
  1202. package/src/modules/workspace/file-readers/ooxml-text.ts +38 -2
  1203. package/src/modules/workspace/file-readers/text-reader.ts +61 -4
  1204. package/src/modules/workspace/git-internals.middleware.ts +86 -0
  1205. package/src/modules/workspace/not-found.ts +119 -0
  1206. package/src/modules/workspace/session-ontology.gate.ts +1 -1
  1207. package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +181 -0
  1208. package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +361 -0
  1209. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +53 -16
  1210. package/src/modules/workspace/startup/beside-checkout.ts +112 -0
  1211. package/src/modules/workspace/startup/kb-git.ts +43 -36
  1212. package/src/modules/workspace/startup/kb-startup-runner.ts +303 -63
  1213. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +1215 -73
  1214. package/src/modules/workspace/startup/steps/groups-to-plugins.step.ts +460 -417
  1215. package/src/modules/workspace/startup/steps/personal-spaces.step.ts +191 -0
  1216. package/src/modules/workspace/startup/steps/plugin-display-names.step.ts +169 -0
  1217. package/src/modules/workspace/startup/steps/plugin-layout.ts +110 -0
  1218. package/src/modules/workspace/startup/steps/plugin-manifests.step.ts +46 -85
  1219. package/src/modules/workspace/startup/steps/roles-yaml.step.ts +6 -12
  1220. package/src/modules/workspace/startup/steps/seed-tree.ts +159 -78
  1221. package/src/modules/workspace/startup/steps/template-files.step.ts +492 -141
  1222. package/src/modules/workspace/startup/steps/template-source.ts +168 -0
  1223. package/src/modules/workspace/workspace.routes.ts +687 -257
  1224. package/src/modules/workspace/workspace.service.ts +1000 -303
  1225. package/src/modules/workspace/workspace.tools.ts +1823 -180
  1226. package/src/modules/workspace/write-denial.ts +189 -0
  1227. package/src/shared/__tests__/canonical-file-identity.test.ts +154 -0
  1228. package/src/shared/__tests__/email-identity.test.ts +35 -0
  1229. package/src/shared/__tests__/filename.test.ts +0 -0
  1230. package/src/shared/__tests__/frontmatter-id.test.ts +5 -0
  1231. package/src/shared/__tests__/git-failure.test.ts +198 -0
  1232. package/src/shared/__tests__/git-internals.test.ts +156 -0
  1233. package/src/shared/__tests__/http-errors.test.ts +42 -0
  1234. package/src/shared/__tests__/logging.test.ts +185 -0
  1235. package/src/shared/__tests__/path-containment.test.ts +60 -0
  1236. package/src/shared/__tests__/pending-proposals.readAt.test.ts +102 -0
  1237. package/src/shared/__tests__/printable.test.ts +64 -0
  1238. package/src/shared/__tests__/redact-secret.test.ts +122 -0
  1239. package/src/shared/__tests__/rename-no-replace.test.ts +466 -0
  1240. package/src/shared/__tests__/ssrf.test.ts +20 -0
  1241. package/src/shared/canonical-file-identity.ts +92 -0
  1242. package/src/shared/domain-errors.ts +149 -0
  1243. package/src/shared/email-identity.ts +38 -0
  1244. package/src/shared/fs.contract.ts +225 -0
  1245. package/src/shared/git-failure.ts +196 -0
  1246. package/src/shared/git-internals.ts +179 -0
  1247. package/src/shared/git.contract.ts +170 -0
  1248. package/src/shared/http-errors.ts +36 -0
  1249. package/src/shared/logger.contract.ts +141 -0
  1250. package/src/shared/logging.ts +96 -0
  1251. package/src/shared/path-containment.ts +34 -0
  1252. package/src/shared/pending-proposals.ts +161 -0
  1253. package/src/shared/printable.ts +68 -0
  1254. package/src/shared/redact-secret.ts +118 -0
  1255. package/src/shared/rename-no-replace.ts +386 -0
  1256. package/src/shared/ssrf.ts +44 -16
  1257. package/src/version.ts +10 -6
  1258. package/dist/modules/access/access-errors.d.ts +0 -34
  1259. package/dist/modules/access/access-errors.d.ts.map +0 -1
  1260. package/dist/modules/access/access-errors.js +0 -40
  1261. package/dist/modules/access/access-errors.js.map +0 -1
  1262. package/dist/modules/access/access-splice.d.ts +0 -140
  1263. package/dist/modules/access/access-splice.d.ts.map +0 -1
  1264. package/dist/modules/access/access-splice.js +0 -389
  1265. package/dist/modules/access/access-splice.js.map +0 -1
  1266. package/dist/modules/access/group-files.d.ts +0 -83
  1267. package/dist/modules/access/group-files.d.ts.map +0 -1
  1268. package/dist/modules/access/group-files.js +0 -167
  1269. package/dist/modules/access/group-files.js.map +0 -1
  1270. package/dist/modules/access/kb-read-filter.d.ts +0 -41
  1271. package/dist/modules/access/kb-read-filter.d.ts.map +0 -1
  1272. package/dist/modules/access/kb-read-filter.js +0 -60
  1273. package/dist/modules/access/kb-read-filter.js.map +0 -1
  1274. package/dist/modules/access/render-roles-yaml.d.ts +0 -22
  1275. package/dist/modules/access/render-roles-yaml.d.ts.map +0 -1
  1276. package/dist/modules/access/render-roles-yaml.js +0 -56
  1277. package/dist/modules/access/render-roles-yaml.js.map +0 -1
  1278. package/dist/modules/access/roles-yaml-guard.d.ts +0 -56
  1279. package/dist/modules/access/roles-yaml-guard.d.ts.map +0 -1
  1280. package/dist/modules/access/roles-yaml-guard.js +0 -79
  1281. package/dist/modules/access/roles-yaml-guard.js.map +0 -1
  1282. package/dist/modules/diff/diff-paths.d.ts +0 -2
  1283. package/dist/modules/diff/diff-paths.d.ts.map +0 -1
  1284. package/dist/modules/diff/diff-paths.js +0 -9
  1285. package/dist/modules/diff/diff-paths.js.map +0 -1
  1286. package/dist/modules/groups/group-provision.service.d.ts +0 -130
  1287. package/dist/modules/groups/group-provision.service.d.ts.map +0 -1
  1288. package/dist/modules/groups/group-provision.service.js +0 -288
  1289. package/dist/modules/groups/group-provision.service.js.map +0 -1
  1290. package/dist/modules/groups/groups.contract.d.ts +0 -106
  1291. package/dist/modules/groups/groups.contract.d.ts.map +0 -1
  1292. package/dist/modules/groups/groups.contract.js +0 -36
  1293. package/dist/modules/groups/groups.contract.js.map +0 -1
  1294. package/dist/modules/groups/groups.routes.d.ts +0 -42
  1295. package/dist/modules/groups/groups.routes.d.ts.map +0 -1
  1296. package/dist/modules/groups/groups.routes.js +0 -379
  1297. package/dist/modules/groups/groups.routes.js.map +0 -1
  1298. package/dist/modules/groups/groups.service.d.ts +0 -60
  1299. package/dist/modules/groups/groups.service.d.ts.map +0 -1
  1300. package/dist/modules/groups/groups.service.js +0 -172
  1301. package/dist/modules/groups/groups.service.js.map +0 -1
  1302. package/dist/modules/groups/index.d.ts +0 -7
  1303. package/dist/modules/groups/index.d.ts.map +0 -1
  1304. package/dist/modules/groups/index.js +0 -6
  1305. package/dist/modules/groups/index.js.map +0 -1
  1306. package/dist/modules/groups/join-proposals.d.ts +0 -53
  1307. package/dist/modules/groups/join-proposals.d.ts.map +0 -1
  1308. package/dist/modules/groups/join-proposals.js +0 -67
  1309. package/dist/modules/groups/join-proposals.js.map +0 -1
  1310. package/dist/modules/groups/join-requests.service.d.ts +0 -81
  1311. package/dist/modules/groups/join-requests.service.d.ts.map +0 -1
  1312. package/dist/modules/groups/join-requests.service.js +0 -135
  1313. package/dist/modules/groups/join-requests.service.js.map +0 -1
  1314. package/dist/modules/mcp/mcp-session-store.d.ts +0 -74
  1315. package/dist/modules/mcp/mcp-session-store.d.ts.map +0 -1
  1316. package/dist/modules/mcp/mcp-session-store.js +0 -131
  1317. package/dist/modules/mcp/mcp-session-store.js.map +0 -1
  1318. package/dist/modules/workflow/file-change-notifier.d.ts +0 -38
  1319. package/dist/modules/workflow/file-change-notifier.d.ts.map +0 -1
  1320. package/dist/modules/workflow/file-change-notifier.js +0 -22
  1321. package/dist/modules/workflow/file-change-notifier.js.map +0 -1
  1322. package/dist/modules/workflow/git/branch-name.d.ts +0 -10
  1323. package/dist/modules/workflow/git/branch-name.d.ts.map +0 -1
  1324. package/dist/modules/workflow/git/branch-name.js +0 -76
  1325. package/dist/modules/workflow/git/branch-name.js.map +0 -1
  1326. package/dist/modules/workflow/git/clone-config.d.ts +0 -61
  1327. package/dist/modules/workflow/git/clone-config.d.ts.map +0 -1
  1328. package/dist/modules/workflow/git/clone-config.js +0 -69
  1329. package/dist/modules/workflow/git/clone-config.js.map +0 -1
  1330. package/dist/modules/workflow/git/mutex.d.ts +0 -11
  1331. package/dist/modules/workflow/git/mutex.d.ts.map +0 -1
  1332. package/dist/modules/workflow/git/mutex.js +0 -23
  1333. package/dist/modules/workflow/git/mutex.js.map +0 -1
  1334. package/dist/modules/workflow/locking-filesystem.d.ts +0 -137
  1335. package/dist/modules/workflow/locking-filesystem.d.ts.map +0 -1
  1336. package/dist/modules/workflow/locking-filesystem.js +0 -553
  1337. package/dist/modules/workflow/locking-filesystem.js.map +0 -1
  1338. package/dist/modules/workflow/read-only-filesystem.d.ts +0 -24
  1339. package/dist/modules/workflow/read-only-filesystem.d.ts.map +0 -1
  1340. package/dist/modules/workflow/read-only-filesystem.js +0 -39
  1341. package/dist/modules/workflow/read-only-filesystem.js.map +0 -1
  1342. package/dist/modules/workflow/workflow.errors.d.ts +0 -197
  1343. package/dist/modules/workflow/workflow.errors.d.ts.map +0 -1
  1344. package/dist/modules/workflow/workflow.errors.js +0 -298
  1345. package/dist/modules/workflow/workflow.errors.js.map +0 -1
  1346. package/dist/modules/workspace/bevel-ignore.d.ts.map +0 -1
  1347. package/dist/modules/workspace/bevel-ignore.js.map +0 -1
  1348. package/dist/modules/workspace/kb-seed.interface.d.ts +0 -36
  1349. package/dist/modules/workspace/kb-seed.interface.d.ts.map +0 -1
  1350. package/dist/modules/workspace/kb-seed.interface.js +0 -2
  1351. package/dist/modules/workspace/kb-seed.interface.js.map +0 -1
  1352. package/dist/modules/workspace/kb-seed.service.d.ts +0 -125
  1353. package/dist/modules/workspace/kb-seed.service.d.ts.map +0 -1
  1354. package/dist/modules/workspace/kb-seed.service.js +0 -535
  1355. package/dist/modules/workspace/kb-seed.service.js.map +0 -1
  1356. package/dist/modules/workspace/plugins-migration.d.ts +0 -50
  1357. package/dist/modules/workspace/plugins-migration.d.ts.map +0 -1
  1358. package/dist/modules/workspace/plugins-migration.js +0 -379
  1359. package/dist/modules/workspace/plugins-migration.js.map +0 -1
  1360. package/dist/shared/fs-errors.d.ts +0 -8
  1361. package/dist/shared/fs-errors.d.ts.map +0 -1
  1362. package/dist/shared/fs-errors.js +0 -11
  1363. package/dist/shared/fs-errors.js.map +0 -1
  1364. package/dist/shared/fs-walk.d.ts +0 -23
  1365. package/dist/shared/fs-walk.d.ts.map +0 -1
  1366. package/dist/shared/fs-walk.js +0 -42
  1367. package/dist/shared/fs-walk.js.map +0 -1
  1368. package/dist/shared/hash-email.d.ts +0 -11
  1369. package/dist/shared/hash-email.d.ts.map +0 -1
  1370. package/dist/shared/hash-email.js +0 -14
  1371. package/dist/shared/hash-email.js.map +0 -1
  1372. package/dist/shared/kb-walk.d.ts +0 -52
  1373. package/dist/shared/kb-walk.d.ts.map +0 -1
  1374. package/dist/shared/kb-walk.js +0 -76
  1375. package/dist/shared/kb-walk.js.map +0 -1
  1376. package/src/modules/diff/diff-paths.ts +0 -9
  1377. package/src/modules/mcp/__tests__/mcp-session-store.test.ts +0 -151
  1378. package/src/modules/mcp/__tests__/mcp.routes.session.test.ts +0 -226
  1379. package/src/modules/mcp/mcp-session-store.ts +0 -162
  1380. package/src/shared/__tests__/fs-walk.test.ts +0 -63
  1381. package/src/shared/__tests__/kb-walk.test.ts +0 -107
  1382. package/src/shared/fs-errors.ts +0 -10
  1383. package/src/shared/fs-walk.ts +0 -45
  1384. package/src/shared/hash-email.ts +0 -14
  1385. package/src/shared/kb-walk.ts +0 -95
@@ -1,47 +1,85 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import type { Server as HttpServer } from 'node:http';
3
+ import type { AddressInfo } from 'node:net';
2
4
  import type { RequestHandler } from 'express';
3
5
  import express from 'express';
4
- import { afterEach, describe, expect, it } from 'vitest';
6
+ import { afterEach, describe, expect, it, vi } from 'vitest';
5
7
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
6
8
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
9
+ import { CallTemplateSerializer, UtcpClientConfigSerializer } from '@utcp/sdk';
10
+ import { CodeModeUtcpClient } from '@utcp/code-mode';
7
11
  import { tmpdir } from 'node:os';
8
12
  import { join } from 'node:path';
9
13
  import { createMcpRoutes } from '../mcp.routes.js';
10
14
  import { McpService } from '../mcp.service.js';
11
- import { McpSessionStore } from '../mcp-session-store.js';
12
15
  import { SpillStore } from '../../workspace/spill-store.js';
13
16
  import { createManualRoutes } from '../../tool-registry/manual.routes.js';
14
17
  import { ToolRegistry } from '../../tool-registry/tool-registry.js';
15
18
  import { toolDef } from '../../tool-helpers/tool-def.js';
19
+ import { PLATFORM_HEADER } from '../../agent-instructions/index.js';
20
+ import { startFakeDownstreamMcpServer, type FakeDownstreamMcpServer } from './fake-downstream-mcp-server.js';
21
+ import { registerBevelSecretsVariableLoader } from '../../secrets-vault/secrets-variable-loader.js';
22
+ import type { ForcedRefreshOutcome, ISecretsVaultService } from '../../secrets-vault/secrets-vault.contract.js';
23
+ import type { IToolManualService } from '../../tool-manuals/tool-manuals.contract.js';
24
+ import { setLogger } from '../../../shared/logging.js';
25
+ import type { ILogger } from '../../../shared/logger.contract.js';
26
+ import { formatProbeReport, probeFirstCall } from '../first-call-probe.js';
27
+ import { printable } from '../../../shared/printable.js';
16
28
 
17
29
  /**
18
- * True end-to-end test over the REAL Streamable-HTTP MCP transport. A real MCP
19
- * `Client` talks to the real `createMcpRoutes` HTTP surface, which drives the
20
- * real `McpService` proxy, which discovers and dispatches against the real
21
- * `createExternalToolsRoutes` surface over loopback — all on one express app.
22
- * Nothing is faked except the two auth middlewares (we're testing the transport
23
- * + proxy wiring, not credential verification). This is the closest automated
24
- * coverage to a production MCP client connecting in.
30
+ * True end-to-end test over the REAL Streamable-HTTP MCP transport. Real MCP
31
+ * clients (the official SDK `Client`, and `@utcp/mcp` — the client `hexis-mcp`
32
+ * pins) talk to the real `createMcpRoutes` HTTP surface, which drives the real
33
+ * `McpService` proxy, which discovers and dispatches against real tool routes
34
+ * over loopback — all on one express app per "platform process". Nothing is
35
+ * faked except credential verification (a bearer → user map).
36
+ *
37
+ * The endpoint is STATELESS, so the suite's centre of gravity is what that
38
+ * buys: no session id ever issued, every request served on its own, and a
39
+ * platform restart between two requests of one client invisible to it.
25
40
  */
26
41
 
27
- const TEST_BEARER = 'bevel_e2e_testkey';
42
+ const KEY_A = 'bevel_key_user_a';
43
+ const KEY_B = 'bevel_key_user_b';
44
+ const KEY_FORGER = 'bevel_key_user_forger';
45
+ /**
46
+ * A user id carrying a whole forged log line — and one character from every
47
+ * class `printable` escapes: a C0 newline, a C1 control (U+009B, the one-byte
48
+ * CSI that opens an ANSI sequence), and a Unicode line separator (U+2028).
49
+ * `JSON.stringify` escapes only the first, so the last two are what keep the
50
+ * escaping test honest about what it pins.
51
+ */
52
+ const FORGER_ID =
53
+ 'user-C\ninfo: downstream token refresh: manual=notion user=root outcome=refreshed\u009b31m\u2028tail';
54
+ /** An INTERACTIVE caller (OAuth/JWT): no connection-key id, so it gets the full tool listing. */
55
+ const KEY_INTERACTIVE = 'bevel_key_user_interactive';
56
+ const USERS: Record<string, { userId: string; tokenId?: string }> = {
57
+ [KEY_A]: { userId: 'user-A', tokenId: 'tok-A' },
58
+ [KEY_B]: { userId: 'user-B', tokenId: 'tok-B' },
59
+ [KEY_FORGER]: { userId: FORGER_ID, tokenId: 'tok-C' },
60
+ [KEY_INTERACTIVE]: { userId: 'user-I' },
61
+ };
28
62
 
29
- let httpServer: HttpServer | undefined;
30
- const cleanups: Array<() => Promise<void>> = [];
63
+ const bearerOf = (req: express.Request) => (req.headers.authorization ?? '').replace(/^Bearer\s+/i, '');
31
64
 
32
- // Stand-in auth: bind a user + token like the real middlewares do, accepting
33
- // the test bearer. Used for the /mcp surface; the loopback tool endpoints are
34
- // hosted directly below.
35
- const fakeAuth: RequestHandler = (req, _res, next) => {
36
- req.userId = 'user-A';
37
- req.externalApiKeyId = 'tok-1';
65
+ // Stand-in auth: bind the user + connection-key id for the bearer, as the real
66
+ // middleware does — per request, from that request's own header.
67
+ const fakeAuth: RequestHandler = (req, res, next) => {
68
+ const user = USERS[bearerOf(req)];
69
+ if (!user) {
70
+ res.status(401).json({ error: 'unauthorized' });
71
+ return;
72
+ }
73
+ req.userId = user.userId;
74
+ req.externalApiKeyId = user.tokenId;
38
75
  next();
39
76
  };
40
77
 
41
- async function connectClient(): Promise<Client> {
78
+ const passthrough: RequestHandler = (_req, _res, next) => next();
79
+
80
+ /** A tool registry: the echo `ask` + erroring `boom`, plus `extra` tools only this catalog has. */
81
+ function registryWith(extra: string[]): ToolRegistry {
42
82
  const registry = new ToolRegistry();
43
- // Echo `ask` (reflects the received sessionId so continuity is observable) +
44
- // an erroring `boom`, registered as defs and hosted as endpoints.
45
83
  registry.registerExternalTool(
46
84
  toolDef({
47
85
  name: 'ask',
@@ -57,96 +95,1054 @@ async function connectClient(): Promise<Client> {
57
95
  registry.registerExternalTool(
58
96
  toolDef({ name: 'boom', description: 'always errors', path: '/api/agent/tools/boom', inputs: { type: 'object', properties: {} } }),
59
97
  );
98
+ for (const name of extra) {
99
+ registry.registerExternalTool(
100
+ toolDef({ name, description: name, path: `/api/agent/tools/${name}`, inputs: { type: 'object', properties: {} } }),
101
+ );
102
+ }
103
+ return registry;
104
+ }
105
+
106
+ interface Platform {
107
+ baseUrl: string;
108
+ port: number;
109
+ service: McpService;
110
+ /** Loopback `ask` calls, with the bearer each one carried. */
111
+ askCalls: Array<{ bearer: string; prompt?: string; sessionId?: string }>;
112
+ stop(): Promise<void>;
113
+ }
114
+
115
+ interface PlatformOptions {
116
+ /** Bind this port — how a "restarted" platform comes back at the same address. */
117
+ port?: number;
118
+ readAgentPreamble?: () => Promise<string | null>;
119
+ /** Extra manuals the catalog serves each caller (beyond the KB manual). */
120
+ manualsFor?: (bearer: string) => unknown[];
121
+ poolNow?: () => number;
122
+ /** The vault + manual catalog behind per-user credentials; absent ⇒ the proxy skips credential handling. */
123
+ secretsVault?: ConstructorParameters<typeof McpService>[1];
124
+ toolManuals?: ConstructorParameters<typeof McpService>[2];
125
+ /**
126
+ * Extra KB-manual tools in this platform's catalog. Empty for every suite
127
+ * that asserts on the catalog's exact contents; the first-call reproduction
128
+ * adds `start_session` so it can call the tool the ticket is about.
129
+ */
130
+ extraTools?: string[];
131
+ }
132
+
133
+ const platforms: Platform[] = [];
134
+ const cleanups: Array<() => Promise<void>> = [];
135
+
136
+ /**
137
+ * One "platform process": its own express app, its own `McpService`, nothing
138
+ * shared with any earlier instance — which is exactly what a restart leaves.
139
+ */
140
+ async function startPlatform(opts: PlatformOptions = {}): Promise<Platform> {
141
+ const askCalls: Platform['askCalls'] = [];
142
+ // Per-user catalogs: user B can see `b_only`, user A cannot.
143
+ const routesA = createManualRoutes(registryWith(opts.extraTools ?? []), passthrough);
144
+ const routesB = createManualRoutes(registryWith(['b_only']), passthrough);
60
145
 
61
146
  const app = express();
62
147
  app.use(express.json());
63
148
  app.post('/api/agent/tools/ask', (req, res) => {
64
149
  const b = (req.body ?? {}) as { prompt?: string; sessionId?: string };
150
+ askCalls.push({ bearer: bearerOf(req), prompt: b.prompt, sessionId: b.sessionId });
65
151
  const incoming = typeof b.sessionId === 'string' ? b.sessionId : undefined;
66
152
  res.json({ text: `echo: ${b.prompt} sid=${incoming ?? 'NONE'}`, sessionId: incoming ?? 'sess-1' });
67
153
  });
68
154
  app.post('/api/agent/tools/boom', (_req, res) => res.status(500).json({ error: 'kaboom' }));
155
+ // The real `start_session` behind the core `UuidSessionSink`: a fresh id and
156
+ // no I/O whatsoever. Mounted on every platform, but reachable only through a
157
+ // catalog that lists the tool (see `extraTools`).
158
+ app.post('/api/agent/tools/start_session', (_req, res) => res.json({ sessionId: randomUUID() }));
159
+ app.get('/api/agent/all-tools', (req, res) => {
160
+ res.json({
161
+ manuals: [
162
+ {
163
+ name: 'KNOWLEDGE_BASE',
164
+ call_template_type: 'http',
165
+ http_method: 'GET',
166
+ url: '${API_URL}/api/agent/utcp',
167
+ content_type: 'application/json',
168
+ headers: { Authorization: 'Bearer ${CONNECTION_KEY}' },
169
+ },
170
+ ...(opts.manualsFor?.(bearerOf(req)) ?? []),
171
+ ],
172
+ });
173
+ });
69
174
 
70
- // Listen first so we know the port, then construct the proxy pointed at our
71
- // own loopback address and mount the routes (Express allows post-listen use).
72
- httpServer = await new Promise<HttpServer>((resolve) => {
73
- const s = app.listen(0, () => resolve(s));
175
+ const httpServer = await new Promise<HttpServer>((resolve) => {
176
+ const s = app.listen(opts.port ?? 0, '127.0.0.1', () => resolve(s));
74
177
  });
75
- const port = (httpServer.address() as { port: number }).port;
178
+ const { port } = httpServer.address() as AddressInfo;
76
179
  const baseUrl = `http://127.0.0.1:${port}`;
77
180
 
78
- const mcpService = new McpService(new McpSessionStore(), {
181
+ const service = new McpService({
79
182
  loopbackBaseUrl: baseUrl,
80
183
  manualName: 'KNOWLEDGE_BASE',
81
184
  spillStore: new SpillStore(join(tmpdir(), 'bevel-test-spills')),
82
185
  publicFrontendUrl: 'http://localhost:5173',
83
- });
186
+ readAgentPreamble: opts.readAgentPreamble,
187
+ downstreamPool: opts.poolNow ? { now: opts.poolNow } : undefined,
188
+ }, opts.secretsVault, opts.toolManuals);
84
189
  const stub = {} as never;
85
- // local-token deps (internal tokens / OAuth provider / metadata URL) are only
86
- // dereferenced by that route's handler — stubs suffice here.
87
- app.use('/api', createMcpRoutes(mcpService, stub, fakeAuth, fakeAuth, stub, stub, stub, ''));
88
- app.use('/api', createManualRoutes(registry, fakeAuth));
190
+ app.use('/api', createMcpRoutes(service, stub, fakeAuth, fakeAuth, stub, stub, stub, ''));
191
+ // The loopback catalog is served per caller, from the bearer it carries.
192
+ app.use('/api', (req, res, next) => (bearerOf(req) === KEY_B ? routesB : routesA)(req, res, next));
89
193
 
194
+ let stopped = false;
195
+ const platform: Platform = {
196
+ baseUrl,
197
+ port,
198
+ service,
199
+ askCalls,
200
+ async stop() {
201
+ if (stopped) return;
202
+ stopped = true;
203
+ // A pooled downstream connection belongs to this process; a restart ends it.
204
+ service.onSecretsChanged(null);
205
+ httpServer.closeAllConnections();
206
+ await new Promise<void>((r) => httpServer.close(() => r()));
207
+ },
208
+ };
209
+ platforms.push(platform);
210
+ return platform;
211
+ }
212
+
213
+ /** Stop `platform` and bring a brand-new one up at the same address. */
214
+ async function restart(platform: Platform, opts: PlatformOptions = {}): Promise<Platform> {
215
+ await platform.stop();
216
+ // A real restart has downtime, during which a client's idle keep-alive
217
+ // sockets see the close. Without this pause the test would instead measure a
218
+ // same-tick race — a pooled socket reused before its FIN was processed —
219
+ // that no restart produces and that has nothing to do with sessions.
220
+ await new Promise((r) => setTimeout(r, 50));
221
+ return startPlatform({ ...opts, port: platform.port });
222
+ }
223
+
224
+ async function connectSdkClient(baseUrl: string, bearer = KEY_A): Promise<{ client: Client; transport: StreamableHTTPClientTransport }> {
90
225
  const transport = new StreamableHTTPClientTransport(new URL(`${baseUrl}/api/mcp`), {
91
- requestInit: { headers: { Authorization: `Bearer ${TEST_BEARER}` } },
226
+ requestInit: { headers: { Authorization: `Bearer ${bearer}` } },
92
227
  });
93
228
  const client = new Client({ name: 'e2e-client', version: '0.0.0' }, { capabilities: {} });
94
229
  await client.connect(transport);
95
230
  cleanups.push(async () => {
96
231
  await client.close();
97
232
  });
98
- return client;
233
+ return { client, transport };
234
+ }
235
+
236
+ /** One raw JSON-RPC POST; the SSE or JSON body decoded into its messages. */
237
+ async function rpc(
238
+ baseUrl: string,
239
+ body: unknown,
240
+ headers: Record<string, string> = {},
241
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- raw JSON-RPC bodies, read by shape
242
+ ): Promise<{ res: Response; messages: Array<{ id?: unknown; result?: any; error?: any }> }> {
243
+ const res = await fetch(`${baseUrl}/api/mcp`, {
244
+ method: 'POST',
245
+ headers: {
246
+ 'Content-Type': 'application/json',
247
+ Accept: 'application/json, text/event-stream',
248
+ Authorization: `Bearer ${KEY_A}`,
249
+ ...headers,
250
+ },
251
+ body: JSON.stringify(body),
252
+ });
253
+ const text = await res.text();
254
+ const messages = (res.headers.get('content-type') ?? '').includes('text/event-stream')
255
+ ? text
256
+ .split('\n')
257
+ .filter((line) => line.startsWith('data:'))
258
+ .map((line) => JSON.parse(line.slice('data:'.length)))
259
+ : text
260
+ ? [JSON.parse(text)]
261
+ : [];
262
+ return { res, messages };
99
263
  }
100
264
 
265
+ const INITIALIZE = {
266
+ jsonrpc: '2.0',
267
+ id: 0,
268
+ method: 'initialize',
269
+ params: { protocolVersion: '2025-06-18', capabilities: {}, clientInfo: { name: 'raw', version: '0' } },
270
+ };
271
+
272
+ const toolText = (res: { content?: unknown }) => (res.content as Array<{ text: string }>)[0].text;
273
+ const toolNames = async (client: Client) => (await client.listTools()).tools.map((t) => t.name).sort();
274
+
101
275
  afterEach(async () => {
102
276
  for (const c of cleanups.splice(0)) await c().catch(() => {});
103
- if (httpServer) await new Promise<void>((r) => httpServer!.close(() => r()));
104
- httpServer = undefined;
277
+ for (const p of platforms.splice(0)) await p.stop().catch(() => {});
278
+ vi.restoreAllMocks();
105
279
  });
106
280
 
107
281
  describe('MCP over real Streamable-HTTP transport', () => {
108
282
  it('initializes, discovers, and lists the proxied tools with their schemas verbatim', async () => {
109
- const client = await connectClient();
283
+ const { baseUrl } = await startPlatform();
284
+ const { client } = await connectSdkClient(baseUrl);
110
285
  const { tools } = await client.listTools();
111
286
  const byName = Object.fromEntries(tools.map((t) => [t.name, t]));
112
287
 
113
288
  expect(Object.keys(byName).sort()).toEqual(['ask', 'boom', 'call_tool_chain', 'list_tools', 'tools_info']);
114
289
  // The {body} envelope the manual carries reaches the client untouched.
115
- expect((byName.ask.inputSchema as any).properties.body.properties.prompt).toBeDefined();
290
+ const askSchema = byName.ask.inputSchema as { properties: { body?: { properties?: Record<string, unknown> } } };
291
+ expect(askSchema.properties.body?.properties?.prompt).toBeDefined();
116
292
  });
117
293
 
118
294
  it('calls a tool end-to-end and returns the result text', async () => {
119
- const client = await connectClient();
295
+ const { baseUrl } = await startPlatform();
296
+ const { client } = await connectSdkClient(baseUrl);
120
297
  const res = await client.callTool({ name: 'ask', arguments: { body: { prompt: 'hello' } } });
121
- // The whole structured result is stringified, so the answer text and the
122
- // returned sessionId both survive for a client without proxy auto-inject.
123
- const payload = JSON.parse((res.content as Array<{ text: string }>)[0].text);
298
+ const payload = JSON.parse(toolText(res));
124
299
  expect(payload.text).toBe('echo: hello sid=NONE');
125
300
  expect(payload.sessionId).toBe('sess-1');
126
301
  expect(res.isError).toBeFalsy();
127
302
  });
128
303
 
129
- it('continuity is the caller\'s: an echoed sessionId reaches the tool verbatim', async () => {
130
- const client = await connectClient();
131
- // First call: no sessionId → the tool starts a conversation and returns
132
- // 'sess-1' in the (fully stringified) result for the caller to read.
304
+ it('surfaces a downstream tool failure as an MCP isError result', async () => {
305
+ const { baseUrl } = await startPlatform();
306
+ const { client } = await connectSdkClient(baseUrl);
307
+ const res = await client.callTool({ name: 'boom', arguments: {} });
308
+ expect(res.isError).toBe(true);
309
+ expect(toolText(res)).toMatch(/kaboom/i);
310
+ });
311
+
312
+ it('call_tool_chain runs against the request-built client', async () => {
313
+ const { baseUrl } = await startPlatform();
314
+ const { client } = await connectSdkClient(baseUrl);
315
+ const res = await client.callTool({
316
+ name: 'call_tool_chain',
317
+ arguments: { code: "return KNOWLEDGE_BASE.ask({ body: { prompt: 'chain' } });" },
318
+ });
319
+ expect(JSON.parse(toolText(res)).result).toMatchObject({ text: 'echo: chain sid=NONE' });
320
+ });
321
+ });
322
+
323
+ describe('stateless: no session, every request on its own', () => {
324
+ it('initialize succeeds without ever issuing an Mcp-Session-Id', async () => {
325
+ const { baseUrl } = await startPlatform();
326
+ const { res, messages } = await rpc(baseUrl, INITIALIZE);
327
+ expect(res.status).toBe(200);
328
+ expect(res.headers.get('mcp-session-id')).toBeNull();
329
+ expect(messages[0].result.serverInfo.name).toBe('bevel-mcp');
330
+ // And the official client, following the server's lead, holds no session id.
331
+ const { transport } = await connectSdkClient(baseUrl);
332
+ expect(transport.sessionId).toBeUndefined();
333
+ });
334
+
335
+ it('serves tools/list and tools/call with no prior initialize at all', async () => {
336
+ const { baseUrl } = await startPlatform();
337
+ const list = await rpc(baseUrl, { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} });
338
+ expect(list.res.status).toBe(200);
339
+ expect(list.res.headers.get('mcp-session-id')).toBeNull();
340
+ expect(list.messages[0].result.tools.map((t: { name: string }) => t.name)).toContain('ask');
341
+
342
+ const call = await rpc(baseUrl, {
343
+ jsonrpc: '2.0',
344
+ id: 2,
345
+ method: 'tools/call',
346
+ params: { name: 'ask', arguments: { body: { prompt: 'cold' } } },
347
+ });
348
+ expect(call.res.status).toBe(200);
349
+ expect(JSON.parse(call.messages[0].result.content[0].text).text).toBe('echo: cold sid=NONE');
350
+ });
351
+
352
+ it('never answers "Session not found": a stale session id from before is simply ignored', async () => {
353
+ const { baseUrl } = await startPlatform();
354
+ const { res, messages } = await rpc(
355
+ baseUrl,
356
+ { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} },
357
+ { 'mcp-session-id': 'sess-from-before-the-restart' },
358
+ );
359
+ expect(res.status).toBe(200);
360
+ expect(messages[0].error).toBeUndefined();
361
+ expect(messages[0].result.tools.length).toBeGreaterThan(0);
362
+ });
363
+
364
+ it.each(['GET', 'DELETE'])('%s /mcp is retired with 405', async (method) => {
365
+ const { baseUrl } = await startPlatform();
366
+ const res = await fetch(`${baseUrl}/api/mcp`, { method, headers: { Authorization: `Bearer ${KEY_A}` } });
367
+ expect(res.status).toBe(405);
368
+ expect(res.headers.get('allow')).toBe('POST');
369
+ });
370
+ });
371
+
372
+ describe('a platform restart is invisible to a connected client', () => {
373
+ it('official SDK client: the next tools/list and tools/call after a restart just work', async () => {
374
+ const first = await startPlatform();
375
+ const { client } = await connectSdkClient(first.baseUrl);
376
+ expect(await toolNames(client)).toContain('ask');
377
+
378
+ // A brand-new process at the same address: new app, new McpService, nothing carried over.
379
+ const second = await restart(first);
380
+
381
+ // Same client object, no re-initialize, no reconnect.
382
+ expect(await toolNames(client)).toContain('ask');
383
+ const res = await client.callTool({ name: 'ask', arguments: { body: { prompt: 'after' } } });
384
+ expect(res.isError).toBeFalsy();
385
+ expect(JSON.parse(toolText(res)).text).toBe('echo: after sid=NONE');
386
+ // Served by the new process, which never saw an initialize from this client.
387
+ expect(second.askCalls).toHaveLength(1);
388
+ });
389
+
390
+ it("`@utcp/mcp` (hexis-mcp's client): a registered manual keeps calling across a restart", async () => {
391
+ const first = await startPlatform();
392
+ const client = await CodeModeUtcpClient.create(process.cwd(), new UtcpClientConfigSerializer().validateDict({}));
393
+ cleanups.push(() => client.close());
394
+ const registered = await client.registerManual(
395
+ new CallTemplateSerializer().validateDict({
396
+ name: 'hexis',
397
+ call_template_type: 'mcp',
398
+ config: {
399
+ mcpServers: {
400
+ platform: {
401
+ transport: 'http',
402
+ url: `${first.baseUrl}/api/mcp`,
403
+ headers: { Authorization: `Bearer ${KEY_A}` },
404
+ },
405
+ },
406
+ },
407
+ }),
408
+ );
409
+ expect(registered.success).toBe(true);
410
+ const names = (await client.getTools()).map((t) => t.name);
411
+ expect(names).toContain('hexis.platform.ask');
412
+
413
+ const before = await client.callTool('hexis.platform.ask', { body: { prompt: 'one' } });
414
+ expect(JSON.stringify(before)).toContain('echo: one');
415
+
416
+ const second = await restart(first);
417
+ const after = await client.callTool('hexis.platform.ask', { body: { prompt: 'two' } });
418
+ expect(JSON.stringify(after)).toContain('echo: two');
419
+ expect(second.askCalls.map((c) => c.prompt)).toEqual(['two']);
420
+ });
421
+ });
422
+
423
+ describe('per-request identity: catalog, metering and continuity', () => {
424
+ it("two users' catalogs never bleed into each other, even interleaved on one process", async () => {
425
+ const { baseUrl } = await startPlatform();
426
+ const { client: a } = await connectSdkClient(baseUrl, KEY_A);
427
+ const { client: b } = await connectSdkClient(baseUrl, KEY_B);
428
+ const [listA1, listB1] = await Promise.all([toolNames(a), toolNames(b)]);
429
+ const [listB2, listA2] = await Promise.all([toolNames(b), toolNames(a)]);
430
+ expect(listA1).not.toContain('b_only');
431
+ expect(listA2).not.toContain('b_only');
432
+ expect(listB1).toContain('b_only');
433
+ expect(listB2).toContain('b_only');
434
+ // A cannot call what it was not given, however recently B listed it.
435
+ const denied = await a.callTool({ name: 'b_only', arguments: {} });
436
+ expect(denied.isError).toBe(true);
437
+ expect(toolText(denied)).toMatch(/Unknown tool/);
438
+ });
439
+
440
+ it('answers the retired merge_change_request with who merges now, not "Unknown tool"', async () => {
441
+ const { baseUrl } = await startPlatform();
442
+ const { client } = await connectSdkClient(baseUrl, KEY_A);
443
+ expect(await toolNames(client)).not.toContain('merge_change_request');
444
+ const res = await client.callTool({ name: 'merge_change_request', arguments: { body: { number: 4 } } });
445
+ expect(res.isError).toBe(true);
446
+ expect(toolText(res)).toMatch(/a change request is merged by a person in the app/);
447
+ // And from inside a code-mode chain, where the runner reports the failure
448
+ // in its logs instead of throwing.
449
+ const chained = await client.callTool({
450
+ name: 'call_tool_chain',
451
+ arguments: { code: 'return KNOWLEDGE_BASE.merge_change_request({ body: { number: 4 } })' },
452
+ });
453
+ expect(chained.isError).toBe(true);
454
+ expect(toolText(chained)).toMatch(/a change request is merged by a person in the app/);
455
+ });
456
+
457
+ it('metering: every loopback call carries THAT request\'s connection key', async () => {
458
+ const platform = await startPlatform();
459
+ const { client: a } = await connectSdkClient(platform.baseUrl, KEY_A);
460
+ const { client: b } = await connectSdkClient(platform.baseUrl, KEY_B);
461
+ await a.callTool({ name: 'ask', arguments: { body: { prompt: 'from-a' } } });
462
+ await b.callTool({ name: 'ask', arguments: { body: { prompt: 'from-b' } } });
463
+ await a.callTool({ name: 'ask', arguments: { body: { prompt: 'from-a-again' } } });
464
+ expect(platform.askCalls.map((c) => [c.prompt, c.bearer])).toEqual([
465
+ ['from-a', KEY_A],
466
+ ['from-b', KEY_B],
467
+ ['from-a-again', KEY_A],
468
+ ]);
469
+ });
470
+
471
+ it("`ask` continuity rides the tool's own sessionId — across a restart too", async () => {
472
+ const first = await startPlatform();
473
+ const { client } = await connectSdkClient(first.baseUrl);
133
474
  const res1 = await client.callTool({ name: 'ask', arguments: { body: { prompt: 'one' } } });
134
- const first = JSON.parse((res1.content as Array<{ text: string }>)[0].text);
135
- expect(first.text).toBe('echo: one sid=NONE');
136
- expect(first.sessionId).toBe('sess-1');
137
- // Second call: the CALLER echoes the id per the tool's schema — the proxy
138
- // passes it through untouched, no server-side injection.
139
- const res2 = await client.callTool({
140
- name: 'ask',
141
- arguments: { body: { prompt: 'two', sessionId: first.sessionId } },
475
+ const { sessionId } = JSON.parse(toolText(res1));
476
+ expect(sessionId).toBe('sess-1');
477
+
478
+ const second = await restart(first);
479
+ const res2 = await client.callTool({ name: 'ask', arguments: { body: { prompt: 'two', sessionId } } });
480
+ expect(JSON.parse(toolText(res2)).text).toBe('echo: two sid=sess-1');
481
+ expect(second.askCalls[0].sessionId).toBe('sess-1');
482
+ });
483
+ });
484
+
485
+ describe('agent instructions over the real transport', () => {
486
+ it('the initialize result carries the header and the preamble body inline', async () => {
487
+ const { baseUrl } = await startPlatform({
488
+ readAgentPreamble: async () => 'Acme builds solar farms.\n\n<!-- private -->Look in Projects/ first.',
142
489
  });
143
- expect(JSON.parse((res2.content as Array<{ text: string }>)[0].text).text).toBe('echo: two sid=sess-1');
490
+ const { client } = await connectSdkClient(baseUrl);
491
+ const instructions = client.getInstructions();
492
+ expect(instructions).toBe(`${PLATFORM_HEADER}\n\nAcme builds solar farms.\n\nLook in Projects/ first.`);
493
+ expect(instructions).not.toContain('private');
144
494
  });
145
495
 
146
- it('surfaces a downstream tool failure as an MCP isError result', async () => {
147
- const client = await connectClient();
148
- const res = await client.callTool({ name: 'boom', arguments: {} });
496
+ it('a later initialize on the SAME process after the file changed carries the new text — read per request', async () => {
497
+ let content = 'Version one.';
498
+ const { baseUrl } = await startPlatform({ readAgentPreamble: async () => content });
499
+ const { client: first } = await connectSdkClient(baseUrl);
500
+ expect(first.getInstructions()).toContain('Version one.');
501
+
502
+ content = 'Version two.';
503
+ const { client: second } = await connectSdkClient(baseUrl);
504
+ expect(second.getInstructions()).toContain('Version two.');
505
+ expect(second.getInstructions()).not.toContain('Version one.');
506
+ // The first client keeps the instructions it was initialised with.
507
+ expect(first.getInstructions()).toContain('Version one.');
508
+ });
509
+ });
510
+
511
+ describe('proxied third-party MCP servers: the downstream pool', () => {
512
+ let downstream: FakeDownstreamMcpServer | undefined;
513
+ afterEach(async () => {
514
+ await downstream?.stop();
515
+ downstream = undefined;
516
+ });
517
+
518
+ /** A catalog that gives every caller one `mcp.json`-style manual at the fake server. */
519
+ const withDownstream = (server: FakeDownstreamMcpServer) => () => [
520
+ { name: 'notion', call_template_type: 'mcp', config: { mcpServers: { srv: { transport: 'http', url: server.url } } } },
521
+ ];
522
+
523
+ const echoName = async (client: Client) => {
524
+ const name = (await client.listTools()).tools.map((t) => t.name).find((n) => n.endsWith('echo'));
525
+ expect(name).toBeDefined();
526
+ return name!;
527
+ };
528
+
529
+ it('lists and calls a proxied tool, reusing ONE downstream connection across requests', async () => {
530
+ downstream = await startFakeDownstreamMcpServer();
531
+ const { baseUrl } = await startPlatform({ manualsFor: withDownstream(downstream) });
532
+ const { client } = await connectSdkClient(baseUrl);
533
+ const name = await echoName(client);
534
+ for (const text of ['one', 'two', 'three']) {
535
+ const res = await client.callTool({ name, arguments: { text } });
536
+ expect(res.isError).toBeFalsy();
537
+ expect(toolText(res)).toContain(text);
538
+ }
539
+ // Four requests (list + three calls), one handshake.
540
+ expect(downstream.initializations()).toBe(1);
541
+ expect(downstream.executions()).toBe(3);
542
+ });
543
+
544
+ it('single-flight: concurrent first calls to one server produce one connection', async () => {
545
+ downstream = await startFakeDownstreamMcpServer();
546
+ const { baseUrl } = await startPlatform({ manualsFor: withDownstream(downstream) });
547
+ const results = await Promise.all(
548
+ Array.from({ length: 5 }, (_, i) =>
549
+ rpc(baseUrl, { jsonrpc: '2.0', id: i, method: 'tools/call', params: { name: 'notion_srv_echo', arguments: { text: `c${i}` } } }),
550
+ ),
551
+ );
552
+ for (const { res } of results) expect(res.status).toBe(200);
553
+ expect(downstream.initializations()).toBe(1);
554
+ });
555
+
556
+ it('pools per (user, server): another user gets a connection of their own, even for an identical definition', async () => {
557
+ downstream = await startFakeDownstreamMcpServer();
558
+ // The same server definition for both users, with no per-user credential
559
+ // in it — the case where nothing but the pool boundary keeps them apart.
560
+ const { baseUrl } = await startPlatform({ manualsFor: withDownstream(downstream) });
561
+ const { client: a } = await connectSdkClient(baseUrl, KEY_A);
562
+ const { client: b } = await connectSdkClient(baseUrl, KEY_B);
563
+ await a.callTool({ name: await echoName(a), arguments: { text: 'a' } });
564
+ await b.callTool({ name: await echoName(b), arguments: { text: 'b' } });
565
+ // Each pooled client owns its own `mcp` protocol instance, so each user's
566
+ // entry dials its own session: a second initialization, not a shared one.
567
+ expect(downstream.initializations()).toBe(2);
568
+ });
569
+
570
+ it('recovery pass-through: a downstream restart is healed on the next call', async () => {
571
+ downstream = await startFakeDownstreamMcpServer();
572
+ vi.spyOn(console, 'error').mockImplementation(() => {});
573
+ const { baseUrl } = await startPlatform({ manualsFor: withDownstream(downstream) });
574
+ const { client } = await connectSdkClient(baseUrl);
575
+ const name = await echoName(client);
576
+ await client.callTool({ name, arguments: { text: 'before' } });
577
+
578
+ await downstream.restart(); // the downstream forgets our session
579
+
580
+ const res = await client.callTool({ name, arguments: { text: 'after' } });
581
+ expect(res.isError).toBeFalsy();
582
+ expect(toolText(res)).toContain('after');
583
+ expect(downstream.initializations()).toBe(2);
584
+ });
585
+
586
+ it('an mcp.json edited after a failure is tried on the next request, not after the failure memo expires', async () => {
587
+ downstream = await startFakeDownstreamMcpServer();
588
+ vi.spyOn(console, 'warn').mockImplementation(() => {});
589
+ vi.spyOn(console, 'error').mockImplementation(() => {});
590
+ let url = 'http://127.0.0.1:1/mcp'; // nothing listens: registration fails and is memoized
591
+ const { baseUrl } = await startPlatform({
592
+ manualsFor: () => [
593
+ { name: 'notion', call_template_type: 'mcp', config: { mcpServers: { srv: { transport: 'http', url } } } },
594
+ ],
595
+ });
596
+ const { client } = await connectSdkClient(baseUrl);
597
+ expect((await client.listTools()).tools.some((t) => t.name.endsWith('echo'))).toBe(false);
598
+
599
+ url = downstream.url; // the user corrects the definition
600
+ await echoName(client);
601
+ expect(downstream.initializations()).toBe(1);
602
+ });
603
+
604
+ it('idle eviction: an entry unused past the TTL is closed and re-dialed lazily', async () => {
605
+ downstream = await startFakeDownstreamMcpServer();
606
+ let now = 1_000_000;
607
+ const { baseUrl } = await startPlatform({ manualsFor: withDownstream(downstream), poolNow: () => now });
608
+ const { client } = await connectSdkClient(baseUrl);
609
+ const name = await echoName(client);
610
+ await client.callTool({ name, arguments: { text: 'x' } });
611
+ expect(downstream.initializations()).toBe(1);
612
+
613
+ now += 4 * 60 * 60 * 1000 + 1; // past the default 4h idle TTL
614
+ const res = await client.callTool({ name, arguments: { text: 'y' } });
615
+ expect(res.isError).toBeFalsy();
616
+ expect(downstream.initializations()).toBe(2);
617
+ });
618
+ });
619
+
620
+ describe('proxied third-party MCP servers: a rejected token gets one refresh and a retry', () => {
621
+ let downstream: FakeDownstreamMcpServer | undefined;
622
+ let restoreLogger: ILogger | undefined;
623
+ afterEach(async () => {
624
+ await downstream?.stop();
625
+ downstream = undefined;
626
+ registerBevelSecretsVariableLoader(null as unknown as ISecretsVaultService);
627
+ if (restoreLogger) setLogger(restoreLogger);
628
+ restoreLogger = undefined;
629
+ });
630
+
631
+ const KEY = 'notion_ACCESS_TOKEN';
632
+
633
+ /**
634
+ * One user's stored OAuth sign-in for the `notion` manual, as the proxy sees
635
+ * it through the vault contract: `resolve` hands out the current token, and
636
+ * `forceRefresh` does what the test scripts — rotate it, wipe it (a dead
637
+ * grant), or leave it (a provider outage).
638
+ */
639
+ function fakeSignIn(initialToken: string, onRefresh: (s: { token: string | null }) => ForcedRefreshOutcome) {
640
+ const state = { token: initialToken as string | null, refreshes: 0 };
641
+ const vault = {
642
+ resolve: async (_userId: string, key: string) => (key === KEY ? state.token : null),
643
+ statusFor: async (_userId: string, keys: string[]) =>
644
+ keys.map((key) => ({ key, adminConfigured: false, userConfigured: true, userAuthorized: state.token !== null })),
645
+ forceRefresh: async (_userId: string, key: string) => {
646
+ expect(key).toBe(KEY);
647
+ state.refreshes += 1;
648
+ return onRefresh(state);
649
+ },
650
+ } as unknown as ISecretsVaultService;
651
+ registerBevelSecretsVariableLoader(vault);
652
+ const toolManuals = {
653
+ userScopedKeysForManual: async (manual: string) =>
654
+ manual === 'notion' ? [{ key: KEY, name: 'ACCESS_TOKEN', label: 'Notion sign-in', oauth: true }] : [],
655
+ } as unknown as IToolManualService;
656
+ return { state, vault, toolManuals };
657
+ }
658
+
659
+ const notionManual = (server: FakeDownstreamMcpServer) => () => [
660
+ {
661
+ name: 'notion',
662
+ call_template_type: 'mcp',
663
+ config: {
664
+ mcpServers: { srv: { transport: 'http', url: server.url, headers: { Authorization: 'Bearer ${ACCESS_TOKEN}' } } },
665
+ },
666
+ },
667
+ ];
668
+
669
+ /** Every log line, flattened to text, so a test can assert what was — and was not — written. */
670
+ function captureLogs(): string[] {
671
+ const lines: string[] = [];
672
+ const capture = (bindings: Record<string, unknown>): ILogger => {
673
+ const write = (message: string, fields?: Record<string, unknown>) =>
674
+ lines.push(JSON.stringify({ ...bindings, message, fields }, (_k, v) => (v instanceof Error ? v.message : v)));
675
+ return { debug: write, info: write, warn: write, error: write, child: (more) => capture({ ...bindings, ...more }) };
676
+ };
677
+ restoreLogger = setLogger(capture({}));
678
+ return lines;
679
+ }
680
+ const refreshLines = (lines: string[]) => lines.filter((l) => l.includes('downstream token refresh'));
681
+
682
+ /**
683
+ * Does this text carry anything `printable` escapes? Everything it escapes,
684
+ * not just the C0 controls: DEL and the C1 block (U+009B is a one-byte CSI —
685
+ * an ANSI sequence on its own) and the line separators U+2028/U+2029 all
686
+ * survive `captureLogs`' JSON round trip raw, so a C0-only check would let a
687
+ * regression that dropped their escaping through while the line still breaks
688
+ * or colours an operator's terminal.
689
+ */
690
+ const unprintable = (text: string) =>
691
+ [...text].some((c) => {
692
+ const code = c.codePointAt(0)!;
693
+ return code < 0x20 || (code >= 0x7f && code <= 0x9f) || code === 0x2028 || code === 0x2029;
694
+ });
695
+
696
+ const quiet = () => {
697
+ vi.spyOn(console, 'log').mockImplementation(() => {});
698
+ vi.spyOn(console, 'error').mockImplementation(() => {});
699
+ vi.spyOn(console, 'warn').mockImplementation(() => {});
700
+ };
701
+
702
+ it('reproduction: a deliberately invalid stored token is refreshed at the handshake and the tool works', async () => {
703
+ quiet();
704
+ let valid = 'fresh-token';
705
+ downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === valid });
706
+ const { state, vault, toolManuals } = fakeSignIn('deliberately-invalid-token', (s) => {
707
+ s.token = valid;
708
+ return 'refreshed';
709
+ });
710
+ const logs = captureLogs();
711
+ const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: vault, toolManuals });
712
+ const { client } = await connectSdkClient(baseUrl);
713
+
714
+ const res = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'hello' } });
715
+
716
+ expect(res.isError).toBeFalsy();
717
+ expect(toolText(res)).toContain('hello');
718
+ expect(state.refreshes).toBe(1);
719
+ expect(downstream.rejections()).toBeGreaterThan(0);
720
+ expect(refreshLines(logs)).toHaveLength(1);
721
+ expect(refreshLines(logs)[0]).toContain('manual=\\"notion\\" user=\\"user-A\\" outcome=refreshed');
722
+ valid = 'unused';
723
+ });
724
+
725
+ it('401 → refresh → retry: a token revoked mid-life is refreshed and the call succeeds; the caller sees only that', async () => {
726
+ quiet();
727
+ let valid = 'token-1';
728
+ downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === valid });
729
+ const { state, vault, toolManuals } = fakeSignIn('token-1', (s) => {
730
+ s.token = 'token-2';
731
+ return 'refreshed';
732
+ });
733
+ const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: vault, toolManuals });
734
+ const { client } = await connectSdkClient(baseUrl);
735
+ expect(toolText(await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } }))).toContain('one');
736
+ expect(downstream.initializations()).toBe(1);
737
+
738
+ valid = 'token-2'; // the provider revokes token-1 before its stated expiry
739
+
740
+ const res = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'two' } });
741
+ expect(res.isError).toBeFalsy();
742
+ expect(toolText(res)).toContain('two');
743
+ expect(state.refreshes).toBe(1);
744
+ expect(downstream.executions()).toBe(2); // the refused attempt ran nothing
745
+ expect(downstream.initializations()).toBe(2); // the retry dialed with the fresh token
746
+ });
747
+
748
+ it('refresh rejected → token wiped → status not connected → the answer says to re-authorize on /connect', async () => {
749
+ quiet();
750
+ let valid = 'token-1';
751
+ downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === valid });
752
+ const { state, vault, toolManuals } = fakeSignIn('token-1', (s) => {
753
+ s.token = null; // the vault wipes a dead grant
754
+ return 'rejected';
755
+ });
756
+ const logs = captureLogs();
757
+ const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: vault, toolManuals });
758
+ const { client } = await connectSdkClient(baseUrl);
759
+ await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } });
760
+
761
+ valid = 'nothing-valid-any-more';
762
+
763
+ const res = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'two' } });
764
+ expect(res.isError).toBe(true);
765
+ expect(toolText(res)).toContain('Your sign-in for "notion" was rejected and has been disconnected');
766
+ expect(toolText(res)).toContain('Re-authorize it on http://localhost:5173/connect');
767
+ expect(state.refreshes).toBe(1);
768
+ expect((await vault.statusFor('user-A', [KEY]))[0]?.userAuthorized).toBe(false);
769
+ expect(refreshLines(logs)[0]).toContain('manual=\\"notion\\" user=\\"user-A\\" outcome=rejected');
770
+ });
771
+
772
+ it('transient refresh failure: the original error comes back, the token is kept, and a later call tries again', async () => {
773
+ quiet();
774
+ let now = 1_000_000;
775
+ let valid = 'token-1';
776
+ downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === valid });
777
+ let providerUp = false;
778
+ const { state, vault, toolManuals } = fakeSignIn('token-1', (s) => {
779
+ if (!providerUp) return 'transient';
780
+ s.token = 'token-2';
781
+ return 'refreshed';
782
+ });
783
+ const logs = captureLogs();
784
+ const { baseUrl } = await startPlatform({
785
+ manualsFor: notionManual(downstream),
786
+ secretsVault: vault,
787
+ toolManuals,
788
+ poolNow: () => now,
789
+ });
790
+ const { client } = await connectSdkClient(baseUrl);
791
+ await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } });
792
+
793
+ valid = 'token-2';
794
+
795
+ const failed = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'two' } });
796
+ expect(failed.isError).toBe(true);
797
+ expect(toolText(failed)).toContain('invalid_token'); // the downstream's own refusal, unchanged
798
+ expect(toolText(failed)).not.toContain('Re-authorize');
799
+ expect(state.token).toBe('token-1'); // kept
800
+ expect(refreshLines(logs)[0]).toContain('outcome=transient');
801
+
802
+ providerUp = true;
803
+ now += 60_000;
804
+ const later = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'three' } });
805
+ expect(later.isError).toBeFalsy();
806
+ expect(state.refreshes).toBe(2);
807
+ });
808
+
809
+ it('once per minute: a grant the downstream keeps refusing cannot loop the provider', async () => {
810
+ quiet();
811
+ let now = 1_000_000;
812
+ let valid = 'token-1';
813
+ downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === valid });
814
+ // The provider happily refreshes; the downstream refuses whatever comes back.
815
+ const { state, vault, toolManuals } = fakeSignIn('token-1', (s) => {
816
+ s.token = `token-${s.token}-next`;
817
+ return 'refreshed';
818
+ });
819
+ const { baseUrl } = await startPlatform({
820
+ manualsFor: notionManual(downstream),
821
+ secretsVault: vault,
822
+ toolManuals,
823
+ poolNow: () => now,
824
+ });
825
+ const { client } = await connectSdkClient(baseUrl);
826
+ await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } });
827
+
828
+ valid = 'never';
829
+ for (let i = 0; i < 4; i++) {
830
+ const res = await client.callTool({ name: 'notion_srv_echo', arguments: { text: `x${i}` } });
831
+ expect(res.isError).toBe(true);
832
+ now += 10_000;
833
+ }
834
+ // Four refused calls inside the minute, one refresh. (The window reopening
835
+ // is the guard's own test; here the manual-failure memo additionally keeps
836
+ // a manual that failed to register from being re-dialed for a while.)
837
+ expect(state.refreshes).toBe(1);
838
+ });
839
+
840
+ it('logs one line per refresh attempt — manual, user, outcome — and never the token', async () => {
841
+ quiet();
842
+ let valid = 'secret-token-OLD';
843
+ downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === valid });
844
+ const { vault, toolManuals } = fakeSignIn('secret-token-OLD', (s) => {
845
+ s.token = 'secret-token-NEW';
846
+ return 'refreshed';
847
+ });
848
+ const logs = captureLogs();
849
+ const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: vault, toolManuals });
850
+ const { client } = await connectSdkClient(baseUrl);
851
+ await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } });
852
+ valid = 'secret-token-NEW';
853
+ await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'two' } });
854
+
855
+ expect(refreshLines(logs)).toEqual([
856
+ expect.stringContaining('manual=\\"notion\\" user=\\"user-A\\" outcome=refreshed'),
857
+ ]);
858
+ for (const line of logs) {
859
+ expect(line).not.toContain('secret-token-OLD');
860
+ expect(line).not.toContain('secret-token-NEW');
861
+ }
862
+ });
863
+
864
+ it('the refresh line cannot be forged: caller-controlled text is escaped, so one attempt is one line', async () => {
865
+ quiet();
866
+ let valid = 'token-1';
867
+ downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === valid });
868
+ const { vault, toolManuals } = fakeSignIn('token-1', (s) => {
869
+ s.token = 'token-2';
870
+ return 'refreshed';
871
+ });
872
+ const logs = captureLogs();
873
+ const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: vault, toolManuals });
874
+ // This caller's user id is a newline plus a plausible-looking refresh line.
875
+ const { client } = await connectSdkClient(baseUrl, KEY_FORGER);
876
+ await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } });
877
+ valid = 'token-2';
878
+ await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'two' } });
879
+
880
+ // One attempt, one EVENT. The forged text is carried inside the line it
881
+ // belongs to, escaped, rather than starting a line of its own — here and
882
+ // in every other line that names the caller.
883
+ const messages = logs.map((entry) => JSON.parse(entry).message as string);
884
+ const events = messages.filter((m) => m.startsWith('downstream token refresh:'));
885
+ expect(events).toEqual([
886
+ `downstream token refresh: manual="notion" user=${printable(FORGER_ID)} outcome=refreshed`,
887
+ ]);
888
+ for (const message of messages) expect(unprintable(message)).toBe(false);
889
+ });
890
+
891
+ it('a transient refresh at the HANDSHAKE is not remembered as a dead manual: the next call tries again', async () => {
892
+ quiet();
893
+ let now = 1_000_000;
894
+ // Rejects the stored token outright, so the very first connection — the
895
+ // handshake, before any tool call — is what gets the 401.
896
+ downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === 'token-2' });
897
+ let providerUp = false;
898
+ const { state, vault, toolManuals } = fakeSignIn('token-1', (s) => {
899
+ if (!providerUp) return 'transient';
900
+ s.token = 'token-2';
901
+ return 'refreshed';
902
+ });
903
+ const { baseUrl } = await startPlatform({
904
+ manualsFor: notionManual(downstream),
905
+ secretsVault: vault,
906
+ toolManuals,
907
+ poolNow: () => now,
908
+ });
909
+ const { client } = await connectSdkClient(baseUrl);
910
+
911
+ // The provider is down: the manual cannot register, and the catalog says so.
912
+ expect((await client.listTools()).tools.some((t) => t.name === 'notion_srv_echo')).toBe(false);
913
+ expect(state.refreshes).toBe(1);
914
+
915
+ // The provider comes back inside the manual-failure memo's five minutes.
916
+ // The refresh policy's window is one minute, and it is the one that governs:
917
+ // a transient credential failure is never written into that memo.
918
+ providerUp = true;
919
+ now += 60_000;
920
+ expect((await client.listTools()).tools.some((t) => t.name === 'notion_srv_echo')).toBe(true);
921
+ expect(state.refreshes).toBe(2);
922
+ expect(toolText(await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'hi' } }))).toContain('hi');
923
+ });
924
+
925
+ it('a manual catalog that fails mid-refresh is reported on one line, escaped, and refreshes nothing', async () => {
926
+ quiet();
927
+ // The catalog breaks only BETWEEN the pre-call secrets check and the
928
+ // refresh: the fake downstream flips it the moment it answers 401, which
929
+ // is exactly the window `refreshDownstreamToken` reads it in.
930
+ let broken = false;
931
+ let valid = 'token-1';
932
+ downstream = await startFakeDownstreamMcpServer({
933
+ acceptsToken: (t) => {
934
+ const ok = t === valid;
935
+ if (!ok) broken = true;
936
+ return ok;
937
+ },
938
+ });
939
+ const { state, vault } = fakeSignIn('token-1', () => 'refreshed');
940
+ const boom = new Error('catalog read failed\u009b31m\nerror: forged by the manual name\u2028tail');
941
+ const toolManuals = {
942
+ userScopedKeysForManual: async (manual: string) => {
943
+ if (broken) throw boom;
944
+ return manual === 'notion' ? [{ key: KEY, name: 'ACCESS_TOKEN', label: 'Notion sign-in', oauth: true }] : [];
945
+ },
946
+ } as unknown as IToolManualService;
947
+ const logs = captureLogs();
948
+ const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: vault, toolManuals });
949
+ const { client } = await connectSdkClient(baseUrl);
950
+ await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } });
951
+
952
+ valid = 'token-2'; // the provider revokes the stored token
953
+
954
+ // The token is now refused, so the refresh path runs — and finds the
955
+ // catalog broken.
956
+ const res = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'two' } });
957
+
958
+ expect(res.isError).toBe(true);
959
+ expect(state.refreshes).toBe(0); // nothing to refresh against: the token stands
960
+ const messages = logs.map((entry) => JSON.parse(entry).message as string);
961
+ const warnings = messages.filter((m) => m.startsWith('downstream token refresh: could not read'));
962
+ expect(warnings).toEqual([
963
+ `downstream token refresh: could not read the variables of manual=${printable('notion')}: ${printable(boom.message)}`,
964
+ ]);
965
+ // The error's own text cannot start a line either — it travels escaped,
966
+ // not as a raw Error the logger would render verbatim.
967
+ for (const message of messages) expect(unprintable(message)).toBe(false);
968
+ });
969
+
970
+ /** The connect link every not-signed-in answer carries. */
971
+ const CONNECT = 'http://localhost:5173/connect';
972
+
973
+ it('after the wipe the manual stays on the surface: an interactive caller still lists the tool, and a call answers with the sign-in link', async () => {
974
+ quiet();
975
+ let valid = 'token-1';
976
+ downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === valid });
977
+ const { state, vault, toolManuals } = fakeSignIn('token-1', (s) => {
978
+ s.token = null; // the vault wipes the dead grant
979
+ return 'rejected';
980
+ });
981
+ const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: vault, toolManuals });
982
+ const { client } = await connectSdkClient(baseUrl, KEY_INTERACTIVE);
983
+ expect(toolText(await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } }))).toContain('one');
984
+
985
+ valid = 'nothing-valid-any-more';
986
+ const wiped = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'two' } });
987
+ expect(toolText(wiped)).toContain('has been disconnected');
988
+ expect(state.token).toBeNull();
989
+
990
+ // The sign-in is gone; the manual is not. Its tools stay listed from what
991
+ // the downstream last advertised, and calling one says what to do —
992
+ // rather than the tool vanishing and the call answering "Unknown tool".
993
+ expect((await client.listTools()).tools.some((t) => t.name === 'notion_srv_echo')).toBe(true);
994
+ const res = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'three' } });
995
+ expect(res.isError).toBe(true);
996
+ expect(toolText(res)).toContain('needs credentials you haven\'t set up yet');
997
+ expect(toolText(res)).toContain(CONNECT);
998
+ expect(toolText(res)).not.toContain('Unknown tool');
999
+ // One refresh in total. The wipe's own attempt is the one; whatever the
1000
+ // listing and the third call ran into afterwards (a token that no longer
1001
+ // substitutes, or a rejection inside the same minute) stayed inside the
1002
+ // once-a-minute window and did not reach the provider again.
1003
+ expect(state.refreshes).toBe(1);
1004
+ });
1005
+
1006
+ it('after the wipe a connection-key caller does not list the tool, but a call to it still gets the sign-in link', async () => {
1007
+ quiet();
1008
+ let valid = 'token-1';
1009
+ downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === valid });
1010
+ const { vault, toolManuals } = fakeSignIn('token-1', (s) => {
1011
+ s.token = null;
1012
+ return 'rejected';
1013
+ });
1014
+ const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: vault, toolManuals });
1015
+ const { client } = await connectSdkClient(baseUrl);
1016
+ await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } });
1017
+ valid = 'nothing-valid-any-more';
1018
+ await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'two' } });
1019
+
1020
+ // Autonomous callers are shown only what is ready to run…
1021
+ expect((await client.listTools()).tools.some((t) => t.name === 'notion_srv_echo')).toBe(false);
1022
+ // …but one holding the name from before is told why, and where to go.
1023
+ const res = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'three' } });
1024
+ expect(res.isError).toBe(true);
1025
+ expect(toolText(res)).toContain(CONNECT);
1026
+ expect(toolText(res)).not.toContain('Unknown tool');
1027
+ });
1028
+
1029
+ it('a manual nobody has connected to since the process started still answers a call with the sign-in link', async () => {
1030
+ quiet();
1031
+ // Fresh process (a deploy restarted it), and this caller's sign-in is
1032
+ // already gone: no connection was ever made, so no tool definitions exist
1033
+ // to put on the surface — the call must still not answer "Unknown tool".
1034
+ downstream = await startFakeDownstreamMcpServer({ acceptsToken: () => false });
1035
+ const { state, vault, toolManuals } = fakeSignIn('token-1', () => 'rejected');
1036
+ state.token = null;
1037
+ const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: vault, toolManuals });
1038
+ const { client } = await connectSdkClient(baseUrl);
1039
+
1040
+ expect((await client.listTools()).tools.some((t) => t.name === 'notion_srv_echo')).toBe(false);
1041
+ const res = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } });
1042
+ expect(res.isError).toBe(true);
1043
+ expect(toolText(res)).toContain('needs credentials you haven\'t set up yet');
1044
+ expect(toolText(res)).toContain(CONNECT);
1045
+ expect(toolText(res)).not.toContain('Unknown tool');
1046
+ // A name that belongs to no manual of the caller's is still unknown.
1047
+ expect(toolText(await client.callTool({ name: 'linear_srv_echo', arguments: {} }))).toContain('Unknown tool');
1048
+ });
1049
+
1050
+ it('a leftover sign-in row does not stand in for a plain key the manual now declares: the call asks for setup', async () => {
1051
+ quiet();
1052
+ // The `.tool` was edited from an OAuth sign-in to a static key; the user's
1053
+ // old OAuth row is still there and its token is even accepted downstream.
1054
+ downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === 'old-oauth-token' });
1055
+ const { vault } = fakeSignIn('old-oauth-token', () => 'refreshed');
1056
+ const withKind = {
1057
+ ...vault,
1058
+ statusFor: async (_userId: string, keys: string[]) =>
1059
+ keys.map((key) => ({ key, adminConfigured: false, userConfigured: true, userAuthorized: true, userKind: 'oauth' })),
1060
+ } as unknown as ISecretsVaultService;
1061
+ registerBevelSecretsVariableLoader(withKind);
1062
+ const toolManuals = {
1063
+ userScopedKeysForManual: async (manual: string) =>
1064
+ manual === 'notion' ? [{ key: KEY, name: 'ACCESS_TOKEN', label: 'Notion key', oauth: false }] : [],
1065
+ } as unknown as IToolManualService;
1066
+ const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: withKind, toolManuals });
1067
+ const { client } = await connectSdkClient(baseUrl, KEY_INTERACTIVE);
1068
+
1069
+ const res = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } });
149
1070
  expect(res.isError).toBe(true);
150
- expect((res.content as Array<{ text: string }>)[0].text).toMatch(/kaboom/i);
1071
+ expect(toolText(res)).toContain('needs credentials you haven\'t set up yet');
1072
+ expect(toolText(res)).toContain(CONNECT);
1073
+ expect(downstream.executions()).toBe(0);
1074
+ });
1075
+ });
1076
+
1077
+ // A measurement, not a regression gate: it asserts nothing about latency, so it
1078
+ // runs only on request (`MCP_PERF=1`) instead of spending ~40 loopback calls on
1079
+ // every CI run.
1080
+ describe.runIf(process.env.MCP_PERF === '1')('per-request overhead (reported, not asserted)', () => {
1081
+ it('measures tools/list and tools/call latency against a warm process', async () => {
1082
+ const { baseUrl } = await startPlatform();
1083
+ const log = vi.spyOn(console, 'log').mockImplementation(() => {});
1084
+ const { client } = await connectSdkClient(baseUrl);
1085
+ await client.listTools(); // warm-up
1086
+
1087
+ const time = async (fn: () => Promise<unknown>, runs = 20) => {
1088
+ const samples: number[] = [];
1089
+ for (let i = 0; i < runs; i++) {
1090
+ const t0 = performance.now();
1091
+ await fn();
1092
+ samples.push(performance.now() - t0);
1093
+ }
1094
+ samples.sort((x, y) => x - y);
1095
+ // Nearest-rank percentile: the smallest sample with at least p% of samples at or below it.
1096
+ const pct = (p: number) => samples[Math.min(runs - 1, Math.max(0, Math.ceil(runs * p) - 1))]!;
1097
+ return { p50: pct(0.5), p95: pct(0.95) };
1098
+ };
1099
+ const list = await time(() => client.listTools());
1100
+ const call = await time(() => client.callTool({ name: 'ask', arguments: { body: { prompt: 'perf' } } }));
1101
+ log.mockRestore();
1102
+ console.log(
1103
+ `[mcp perf] per-request (in-process loopback, test catalog): ` +
1104
+ `tools/list p50=${list.p50.toFixed(1)}ms p95=${list.p95.toFixed(1)}ms; ` +
1105
+ `tools/call p50=${call.p50.toFixed(1)}ms p95=${call.p95.toFixed(1)}ms`,
1106
+ );
1107
+ expect(list.p50).toBeGreaterThan(0);
151
1108
  });
152
1109
  });
1110
+
1111
+ /**
1112
+ * The ticket's reproduction, run against the whole platform: real transport,
1113
+ * real `McpService` proxy, real catalog discovery, real tool route.
1114
+ *
1115
+ * The report was one first `start_session` failing on a fresh connection with
1116
+ * a generic error, and an immediate retry working. Fifty fresh connections,
1117
+ * one first call each, all at once — an agent tester opens them in bursts, and
1118
+ * a first-call failure that only shows under contention would never appear one
1119
+ * connection at a time. Every connection pays for its own `initialize` and its
1120
+ * own catalog discovery here, which is the expensive part of a first call and
1121
+ * the part a reused connection never repeats.
1122
+ *
1123
+ * The suite asserts no timing: what it pins is that the burst produces fifty
1124
+ * ids and no failure, so a regression that makes the first call flaky under
1125
+ * load fails here instead of in an agent's transcript.
1126
+ */
1127
+ describe('the first call on a fresh connection, fifty at once', () => {
1128
+ it('serves every one of fifty fresh connections its own session id', async () => {
1129
+ const { baseUrl } = await startPlatform({ extraTools: ['start_session'] });
1130
+
1131
+ // `timeoutMs` well under the vitest budget on purpose. The probe charges it
1132
+ // per phase, so the default 30s would let one hung attempt burn ~60s and
1133
+ // vitest would abort the test before `probeFirstCall` resolved — in exactly
1134
+ // the flaky-first-call scenario this test exists to catch, the failure list
1135
+ // and the report below would never be printed, and the run would show a
1136
+ // bare timeout instead of which attempt died where.
1137
+ const report = await probeFirstCall({ baseUrl, bearer: KEY_A, connections: 50, timeoutMs: 20_000 });
1138
+
1139
+ // Printed as well as asserted: this block is what the ticket's log records,
1140
+ // and a run whose numbers were never shown proves nothing to a reader.
1141
+ console.log(`[first-call probe]\n${formatProbeReport(report)}`);
1142
+ // Not `toHaveLength(0)`: on a failure the assertion should show WHICH
1143
+ // attempt died, at which stage, with the platform's own error text.
1144
+ expect(report.failures).toEqual([]);
1145
+ expect(report.ok).toBe(50);
1146
+ expect(report.distinctSessionIds).toBe(50);
1147
+ }, 120_000);
1148
+ });