@bevel-software/platform-core-backend 0.33.0 → 0.34.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 (497) hide show
  1. package/agent-guide/access-control.md +19 -8
  2. package/agent-guide/html-views.md +231 -5
  3. package/agent-guide/tool-manuals.md +1 -1
  4. package/dist/core/catalog-revision.d.ts +12 -1
  5. package/dist/core/catalog-revision.d.ts.map +1 -1
  6. package/dist/core/catalog-revision.js +23 -3
  7. package/dist/core/catalog-revision.js.map +1 -1
  8. package/dist/core/core-ports.d.ts +41 -0
  9. package/dist/core/core-ports.d.ts.map +1 -1
  10. package/dist/core/core-ports.js.map +1 -1
  11. package/dist/core/create-core-server.d.ts.map +1 -1
  12. package/dist/core/create-core-server.js +14 -7
  13. package/dist/core/create-core-server.js.map +1 -1
  14. package/dist/core/create-core-services.d.ts +16 -5
  15. package/dist/core/create-core-services.d.ts.map +1 -1
  16. package/dist/core/create-core-services.js +37 -12
  17. package/dist/core/create-core-services.js.map +1 -1
  18. package/dist/core/lifecycle.d.ts +16 -0
  19. package/dist/core/lifecycle.d.ts.map +1 -1
  20. package/dist/core/lifecycle.js +12 -2
  21. package/dist/core/lifecycle.js.map +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/modules/access/access-control.interface.d.ts +17 -9
  26. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  27. package/dist/modules/access/access-control.service.d.ts +12 -2
  28. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  29. package/dist/modules/access/access-control.service.js +164 -75
  30. package/dist/modules/access/access-control.service.js.map +1 -1
  31. package/dist/modules/access/access-mutation.service.d.ts +22 -24
  32. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  33. package/dist/modules/access/access-mutation.service.js +48 -51
  34. package/dist/modules/access/access-mutation.service.js.map +1 -1
  35. package/dist/modules/access/access-view.d.ts.map +1 -1
  36. package/dist/modules/access/access-view.js +12 -11
  37. package/dist/modules/access/access-view.js.map +1 -1
  38. package/dist/modules/access/access.routes.d.ts +3 -0
  39. package/dist/modules/access/access.routes.d.ts.map +1 -1
  40. package/dist/modules/access/access.routes.js +35 -29
  41. package/dist/modules/access/access.routes.js.map +1 -1
  42. package/dist/modules/access/groups-admin.routes.d.ts.map +1 -1
  43. package/dist/modules/access/groups-admin.routes.js +4 -2
  44. package/dist/modules/access/groups-admin.routes.js.map +1 -1
  45. package/dist/modules/access/groups-admin.service.d.ts +44 -13
  46. package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
  47. package/dist/modules/access/groups-admin.service.js +80 -38
  48. package/dist/modules/access/groups-admin.service.js.map +1 -1
  49. package/dist/modules/access/groups-edit.d.ts +1 -1
  50. package/dist/modules/access/groups-edit.d.ts.map +1 -1
  51. package/dist/modules/access/groups-edit.js +1 -1
  52. package/dist/modules/access/groups-edit.js.map +1 -1
  53. package/dist/modules/access/reference-scan.d.ts +116 -24
  54. package/dist/modules/access/reference-scan.d.ts.map +1 -1
  55. package/dist/modules/access/reference-scan.js +305 -44
  56. package/dist/modules/access/reference-scan.js.map +1 -1
  57. package/dist/modules/access/roles-admin.service.d.ts +17 -8
  58. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  59. package/dist/modules/access/roles-admin.service.js +32 -23
  60. package/dist/modules/access/roles-admin.service.js.map +1 -1
  61. package/dist/modules/access-model/access-grammar.d.ts +35 -10
  62. package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
  63. package/dist/modules/access-model/access-grammar.js +60 -20
  64. package/dist/modules/access-model/access-grammar.js.map +1 -1
  65. package/dist/modules/access-model/access-splice.d.ts +22 -18
  66. package/dist/modules/access-model/access-splice.d.ts.map +1 -1
  67. package/dist/modules/access-model/access-splice.js +20 -14
  68. package/dist/modules/access-model/access-splice.js.map +1 -1
  69. package/dist/modules/access-model/group-files.d.ts +1 -0
  70. package/dist/modules/access-model/group-files.d.ts.map +1 -1
  71. package/dist/modules/access-model/group-files.js +5 -1
  72. package/dist/modules/access-model/group-files.js.map +1 -1
  73. package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -1
  74. package/dist/modules/agent-guide/agent-guide.tools.js +13 -3
  75. package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -1
  76. package/dist/modules/agent-instructions/compose.d.ts +3 -1
  77. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  78. package/dist/modules/agent-instructions/compose.js +3 -1
  79. package/dist/modules/agent-instructions/compose.js.map +1 -1
  80. package/dist/modules/agent-instructions/shared-file-rules.d.ts +5 -1
  81. package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
  82. package/dist/modules/agent-instructions/shared-file-rules.js +16 -1
  83. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  84. package/dist/modules/agent-scope/agent-scope.contract.d.ts +67 -0
  85. package/dist/modules/agent-scope/agent-scope.contract.d.ts.map +1 -0
  86. package/dist/modules/agent-scope/agent-scope.contract.js +27 -0
  87. package/dist/modules/agent-scope/agent-scope.contract.js.map +1 -0
  88. package/dist/modules/agent-scope/connection-scope.d.ts +111 -0
  89. package/dist/modules/agent-scope/connection-scope.d.ts.map +1 -0
  90. package/dist/modules/agent-scope/connection-scope.js +165 -0
  91. package/dist/modules/agent-scope/connection-scope.js.map +1 -0
  92. package/dist/modules/agent-scope/index.d.ts +3 -0
  93. package/dist/modules/agent-scope/index.d.ts.map +1 -0
  94. package/dist/modules/agent-scope/index.js +3 -0
  95. package/dist/modules/agent-scope/index.js.map +1 -0
  96. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  97. package/dist/modules/audit/agent-audit.service.js +2 -0
  98. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  99. package/dist/modules/audit/audit.contract.d.ts +6 -0
  100. package/dist/modules/audit/audit.contract.d.ts.map +1 -1
  101. package/dist/modules/audit/audit.contract.js.map +1 -1
  102. package/dist/modules/auth/account-admission.d.ts +12 -0
  103. package/dist/modules/auth/account-admission.d.ts.map +1 -1
  104. package/dist/modules/auth/account-admission.js +15 -0
  105. package/dist/modules/auth/account-admission.js.map +1 -1
  106. package/dist/modules/auth/auth.middleware.d.ts +27 -1
  107. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  108. package/dist/modules/auth/auth.middleware.js +42 -4
  109. package/dist/modules/auth/auth.middleware.js.map +1 -1
  110. package/dist/modules/auth/auth.routes.d.ts +13 -9
  111. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  112. package/dist/modules/auth/auth.routes.js +35 -29
  113. package/dist/modules/auth/auth.routes.js.map +1 -1
  114. package/dist/modules/auth/auth.service.d.ts +55 -6
  115. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  116. package/dist/modules/auth/auth.service.js +128 -34
  117. package/dist/modules/auth/auth.service.js.map +1 -1
  118. package/dist/modules/auth/index.d.ts +1 -1
  119. package/dist/modules/auth/index.d.ts.map +1 -1
  120. package/dist/modules/auth/index.js +1 -1
  121. package/dist/modules/auth/index.js.map +1 -1
  122. package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
  123. package/dist/modules/auth/oidc-auth-provider.js +2 -9
  124. package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
  125. package/dist/modules/database/core-schema.d.ts +68 -0
  126. package/dist/modules/database/core-schema.d.ts.map +1 -1
  127. package/dist/modules/database/core-schema.js +33 -0
  128. package/dist/modules/database/core-schema.js.map +1 -1
  129. package/dist/modules/declared-variables/declared-variables.routes.d.ts +8 -1
  130. package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
  131. package/dist/modules/declared-variables/declared-variables.routes.js +13 -2
  132. package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
  133. package/dist/modules/embed/embed.tools.d.ts.map +1 -1
  134. package/dist/modules/embed/embed.tools.js +5 -0
  135. package/dist/modules/embed/embed.tools.js.map +1 -1
  136. package/dist/modules/kb-fs/index.d.ts +2 -0
  137. package/dist/modules/kb-fs/index.d.ts.map +1 -1
  138. package/dist/modules/kb-fs/index.js +1 -0
  139. package/dist/modules/kb-fs/index.js.map +1 -1
  140. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  141. package/dist/modules/kb-fs/locking-filesystem.js +7 -1
  142. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  143. package/dist/modules/kb-fs/repo-path.d.ts +26 -6
  144. package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
  145. package/dist/modules/kb-fs/repo-path.js +22 -7
  146. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  147. package/dist/modules/kb-fs/tmp-ref.d.ts +36 -0
  148. package/dist/modules/kb-fs/tmp-ref.d.ts.map +1 -0
  149. package/dist/modules/kb-fs/tmp-ref.js +76 -0
  150. package/dist/modules/kb-fs/tmp-ref.js.map +1 -0
  151. package/dist/modules/mcp/mcp-auth.middleware.d.ts +7 -0
  152. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  153. package/dist/modules/mcp/mcp-auth.middleware.js +14 -1
  154. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  155. package/dist/modules/mcp/mcp.routes.d.ts +7 -1
  156. package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
  157. package/dist/modules/mcp/mcp.routes.js +39 -4
  158. package/dist/modules/mcp/mcp.routes.js.map +1 -1
  159. package/dist/modules/mcp/mcp.service.d.ts +11 -0
  160. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  161. package/dist/modules/mcp/mcp.service.js +68 -13
  162. package/dist/modules/mcp/mcp.service.js.map +1 -1
  163. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -1
  164. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  165. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +45 -10
  166. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  167. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +9 -0
  168. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
  169. package/dist/modules/mcp/oauth/oauth-consent.routes.js +34 -1
  170. package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
  171. package/dist/modules/plugins/index.d.ts +1 -1
  172. package/dist/modules/plugins/index.d.ts.map +1 -1
  173. package/dist/modules/plugins/index.js.map +1 -1
  174. package/dist/modules/plugins/plugin-links.d.ts +18 -2
  175. package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
  176. package/dist/modules/plugins/plugin-links.js.map +1 -1
  177. package/dist/modules/plugins/plugins.routes.d.ts +5 -1
  178. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  179. package/dist/modules/plugins/plugins.routes.js +32 -5
  180. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  181. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +9 -0
  182. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
  183. package/dist/modules/secrets-vault/secrets-vault.routes.js +15 -3
  184. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  185. package/dist/modules/skills/index.d.ts +3 -3
  186. package/dist/modules/skills/index.d.ts.map +1 -1
  187. package/dist/modules/skills/index.js +0 -2
  188. package/dist/modules/skills/index.js.map +1 -1
  189. package/dist/modules/skills/skills.contract.d.ts +16 -0
  190. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  191. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  192. package/dist/modules/skills/skills.service.js +6 -1
  193. package/dist/modules/skills/skills.service.js.map +1 -1
  194. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  195. package/dist/modules/skills/skills.tools.js +24 -12
  196. package/dist/modules/skills/skills.tools.js.map +1 -1
  197. package/dist/modules/tool-auth/external-api-key.interface.d.ts +11 -0
  198. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  199. package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
  200. package/dist/modules/tool-auth/external-api-key.service.d.ts +1 -0
  201. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  202. package/dist/modules/tool-auth/external-api-key.service.js +4 -1
  203. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  204. package/dist/modules/tool-auth/index.d.ts +1 -1
  205. package/dist/modules/tool-auth/index.d.ts.map +1 -1
  206. package/dist/modules/tool-auth/internal-token.service.d.ts +9 -0
  207. package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
  208. package/dist/modules/tool-auth/internal-token.service.js +1 -0
  209. package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
  210. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +27 -3
  211. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  212. package/dist/modules/tool-auth/tool-auth.middleware.js +32 -6
  213. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  214. package/dist/modules/tool-helpers/tool-context.d.ts +7 -0
  215. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
  216. package/dist/modules/tool-helpers/tool-context.js +3 -0
  217. package/dist/modules/tool-helpers/tool-context.js.map +1 -1
  218. package/dist/modules/tool-helpers/tool-handler.d.ts +15 -0
  219. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  220. package/dist/modules/tool-helpers/tool-handler.js +19 -0
  221. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  222. package/dist/modules/tool-helpers/tool.contract.d.ts +10 -0
  223. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  224. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  225. package/dist/modules/tool-helpers/validate-token.d.ts +3 -1
  226. package/dist/modules/tool-helpers/validate-token.d.ts.map +1 -1
  227. package/dist/modules/tool-helpers/validate-token.js +1 -1
  228. package/dist/modules/tool-helpers/validate-token.js.map +1 -1
  229. package/dist/modules/tool-manuals/index.d.ts +3 -3
  230. package/dist/modules/tool-manuals/index.d.ts.map +1 -1
  231. package/dist/modules/tool-manuals/index.js +0 -2
  232. package/dist/modules/tool-manuals/index.js.map +1 -1
  233. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +19 -6
  234. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  235. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +7 -1
  236. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  237. package/dist/modules/tool-manuals/tool-manuals.routes.js +41 -4
  238. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  239. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +13 -6
  240. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  241. package/dist/modules/tool-manuals/tool-manuals.service.js +23 -15
  242. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  243. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  244. package/dist/modules/tool-manuals/tool-manuals.tools.js +25 -10
  245. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  246. package/dist/modules/tool-registry/manual.routes.d.ts +8 -1
  247. package/dist/modules/tool-registry/manual.routes.d.ts.map +1 -1
  248. package/dist/modules/tool-registry/manual.routes.js +27 -7
  249. package/dist/modules/tool-registry/manual.routes.js.map +1 -1
  250. package/dist/modules/tool-registry/tool.contract.d.ts +7 -0
  251. package/dist/modules/tool-registry/tool.contract.d.ts.map +1 -1
  252. package/dist/modules/tool-registry/tool.contract.js.map +1 -1
  253. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  254. package/dist/modules/workflow/agent-tools/workflow.tools.js +14 -9
  255. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  256. package/dist/modules/workflow/git/git.service.d.ts +2 -2
  257. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  258. package/dist/modules/workflow/git/git.service.js +20 -35
  259. package/dist/modules/workflow/git/git.service.js.map +1 -1
  260. package/dist/modules/workflow/workflow.service.d.ts +2 -1
  261. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  262. package/dist/modules/workflow/workflow.service.js +3 -0
  263. package/dist/modules/workflow/workflow.service.js.map +1 -1
  264. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +28 -4
  265. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
  266. package/dist/modules/workspace/file-readers/doc-extract.service.js +61 -40
  267. package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
  268. package/dist/modules/workspace/index.d.ts +8 -0
  269. package/dist/modules/workspace/index.d.ts.map +1 -1
  270. package/dist/modules/workspace/index.js +8 -0
  271. package/dist/modules/workspace/index.js.map +1 -1
  272. package/dist/modules/workspace/nul-scan.d.ts +19 -0
  273. package/dist/modules/workspace/nul-scan.d.ts.map +1 -0
  274. package/dist/modules/workspace/nul-scan.js +55 -0
  275. package/dist/modules/workspace/nul-scan.js.map +1 -0
  276. package/dist/modules/workspace/pattern-matcher.contract.d.ts +110 -27
  277. package/dist/modules/workspace/pattern-matcher.contract.d.ts.map +1 -1
  278. package/dist/modules/workspace/pattern-matcher.contract.js +30 -13
  279. package/dist/modules/workspace/pattern-matcher.contract.js.map +1 -1
  280. package/dist/modules/workspace/ripgrep-search.d.ts +54 -0
  281. package/dist/modules/workspace/ripgrep-search.d.ts.map +1 -0
  282. package/dist/modules/workspace/ripgrep-search.js +657 -0
  283. package/dist/modules/workspace/ripgrep-search.js.map +1 -0
  284. package/dist/modules/workspace/ripgrep-version.d.ts +15 -0
  285. package/dist/modules/workspace/ripgrep-version.d.ts.map +1 -0
  286. package/dist/modules/workspace/ripgrep-version.js +31 -0
  287. package/dist/modules/workspace/ripgrep-version.js.map +1 -0
  288. package/dist/modules/workspace/startup/beside-checkout.d.ts +8 -5
  289. package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -1
  290. package/dist/modules/workspace/startup/beside-checkout.js +14 -5
  291. package/dist/modules/workspace/startup/beside-checkout.js.map +1 -1
  292. package/dist/modules/workspace/worker-pool.d.ts +3 -4
  293. package/dist/modules/workspace/worker-pool.d.ts.map +1 -1
  294. package/dist/modules/workspace/worker-pool.js.map +1 -1
  295. package/dist/modules/workspace/workspace-tmp.d.ts +146 -0
  296. package/dist/modules/workspace/workspace-tmp.d.ts.map +1 -0
  297. package/dist/modules/workspace/workspace-tmp.js +435 -0
  298. package/dist/modules/workspace/workspace-tmp.js.map +1 -0
  299. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  300. package/dist/modules/workspace/workspace.routes.js +5 -26
  301. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  302. package/dist/modules/workspace/workspace.service.d.ts +47 -6
  303. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  304. package/dist/modules/workspace/workspace.service.js +86 -6
  305. package/dist/modules/workspace/workspace.service.js.map +1 -1
  306. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  307. package/dist/modules/workspace/workspace.tools.js +460 -116
  308. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  309. package/dist/shared/path-order.d.ts +28 -8
  310. package/dist/shared/path-order.d.ts.map +1 -1
  311. package/dist/shared/path-order.js +37 -8
  312. package/dist/shared/path-order.js.map +1 -1
  313. package/migrations/0022_session_version.sql +6 -0
  314. package/migrations/0023_agent_path.sql +3 -0
  315. package/migrations/meta/0022_snapshot.json +2360 -0
  316. package/migrations/meta/0023_snapshot.json +2378 -0
  317. package/migrations/meta/_journal.json +14 -0
  318. package/package.json +3 -3
  319. package/src/__tests__/pg-scratch.test.ts +69 -0
  320. package/src/__tests__/pg-scratch.ts +31 -0
  321. package/src/__tests__/slow-ripgrep.ts +65 -0
  322. package/src/core/__tests__/boot-without-ripgrep.test.ts +34 -0
  323. package/src/core/__tests__/catalog-revision.test.ts +89 -0
  324. package/src/core/__tests__/damaged-document.tools.test.ts +2 -2
  325. package/src/core/__tests__/gated-boot.test.ts +8 -1
  326. package/src/core/__tests__/lifecycle.test.ts +46 -13
  327. package/src/core/catalog-revision.ts +24 -2
  328. package/src/core/core-ports.ts +42 -0
  329. package/src/core/create-core-server.ts +13 -0
  330. package/src/core/create-core-services.ts +49 -12
  331. package/src/core/lifecycle.ts +26 -2
  332. package/src/index.ts +1 -1
  333. package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +12 -0
  334. package/src/modules/access/__tests__/access-control.colliding-name-cases.test.ts +113 -0
  335. package/src/modules/access/__tests__/access-control.frontmatter-head.test.ts +117 -0
  336. package/src/modules/access/__tests__/access-control.group-named-admin.test.ts +122 -0
  337. package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +50 -0
  338. package/src/modules/access/__tests__/access-declarations.test.ts +19 -0
  339. package/src/modules/access/__tests__/access-groups.test.ts +55 -22
  340. package/src/modules/access/__tests__/access-mutation.service.test.ts +85 -59
  341. package/src/modules/access/__tests__/access-restrict-here.test.ts +7 -5
  342. package/src/modules/access/__tests__/access.routes.admins.test.ts +1 -0
  343. package/src/modules/access/__tests__/access.routes.batch.test.ts +2 -0
  344. package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +2 -0
  345. package/src/modules/access/__tests__/access.routes.group-grant.test.ts +50 -14
  346. package/src/modules/access/__tests__/access.routes.overrides.test.ts +2 -1
  347. package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +1 -0
  348. package/src/modules/access/__tests__/access.routes.prospective.test.ts +2 -0
  349. package/src/modules/access/__tests__/access.routes.revoke.test.ts +18 -13
  350. package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +2 -0
  351. package/src/modules/access/__tests__/grant-sources.test.ts +20 -15
  352. package/src/modules/access/__tests__/groups-admin.routes.test.ts +61 -0
  353. package/src/modules/access/__tests__/groups-admin.service.test.ts +211 -18
  354. package/src/modules/access/__tests__/reference-scan.test.ts +366 -14
  355. package/src/modules/access/__tests__/roles-admin.security.test.ts +1 -1
  356. package/src/modules/access/__tests__/roles-admin.service.test.ts +43 -22
  357. package/src/modules/access/__tests__/roles-capabilities.test.ts +6 -2
  358. package/src/modules/access/__tests__/roles.routes.test.ts +1 -1
  359. package/src/modules/access/access-control.interface.ts +17 -10
  360. package/src/modules/access/access-control.service.ts +158 -72
  361. package/src/modules/access/access-mutation.service.ts +48 -53
  362. package/src/modules/access/access-view.ts +12 -10
  363. package/src/modules/access/access.routes.ts +43 -33
  364. package/src/modules/access/groups-admin.routes.ts +4 -2
  365. package/src/modules/access/groups-admin.service.ts +114 -44
  366. package/src/modules/access/groups-edit.ts +1 -1
  367. package/src/modules/access/reference-scan.ts +319 -46
  368. package/src/modules/access/roles-admin.service.ts +43 -26
  369. package/src/modules/access/synced-groups-writer.ts +1 -1
  370. package/src/modules/access-model/__tests__/access-grammar.test.ts +38 -1
  371. package/src/modules/access-model/__tests__/access-splice.test.ts +50 -8
  372. package/src/modules/access-model/access-grammar.ts +65 -20
  373. package/src/modules/access-model/access-splice.ts +31 -23
  374. package/src/modules/access-model/group-files.ts +5 -0
  375. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +31 -0
  376. package/src/modules/agent-guide/agent-guide.tools.ts +13 -2
  377. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +1 -0
  378. package/src/modules/agent-instructions/compose.ts +3 -1
  379. package/src/modules/agent-instructions/shared-file-rules.ts +17 -1
  380. package/src/modules/agent-scope/__tests__/agent-surfaces.connect-as.test.ts +421 -0
  381. package/src/modules/agent-scope/__tests__/connection-scope.test.ts +46 -0
  382. package/src/modules/agent-scope/__tests__/in-memory-kb.ts +148 -0
  383. package/src/modules/agent-scope/agent-scope.contract.ts +83 -0
  384. package/src/modules/agent-scope/connection-scope.ts +199 -0
  385. package/src/modules/agent-scope/index.ts +15 -0
  386. package/src/modules/audit/agent-audit.service.ts +2 -0
  387. package/src/modules/audit/audit.contract.ts +6 -0
  388. package/src/modules/auth/__tests__/auth.middleware.test.ts +30 -2
  389. package/src/modules/auth/__tests__/auth.routes.test.ts +71 -5
  390. package/src/modules/auth/__tests__/auth.service.test.ts +90 -9
  391. package/src/modules/auth/__tests__/password-change-sessions.pg.test.ts +269 -0
  392. package/src/modules/auth/account-admission.ts +19 -0
  393. package/src/modules/auth/auth.middleware.ts +45 -4
  394. package/src/modules/auth/auth.routes.ts +46 -29
  395. package/src/modules/auth/auth.service.ts +135 -34
  396. package/src/modules/auth/index.ts +2 -0
  397. package/src/modules/auth/oidc-auth-provider.ts +2 -9
  398. package/src/modules/database/__tests__/session-version-migration.pg.test.ts +133 -0
  399. package/src/modules/database/core-schema.ts +33 -0
  400. package/src/modules/declared-variables/__tests__/declared-variables.route.test.ts +44 -1
  401. package/src/modules/declared-variables/declared-variables.routes.ts +12 -1
  402. package/src/modules/embed/__tests__/embed.tools.test.ts +12 -0
  403. package/src/modules/embed/embed.tools.ts +5 -0
  404. package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +25 -0
  405. package/src/modules/kb-fs/__tests__/repo-path.test.ts +53 -8
  406. package/src/modules/kb-fs/index.ts +2 -0
  407. package/src/modules/kb-fs/locking-filesystem.ts +7 -1
  408. package/src/modules/kb-fs/repo-path.ts +33 -7
  409. package/src/modules/kb-fs/tmp-ref.ts +81 -0
  410. package/src/modules/mcp/__tests__/connect-as.pg.test.ts +137 -0
  411. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +29 -5
  412. package/src/modules/mcp/__tests__/mcp-routes-harness.ts +8 -0
  413. package/src/modules/mcp/__tests__/mcp.connect-as.test.ts +195 -0
  414. package/src/modules/mcp/__tests__/mcp.routes.connect-as.test.ts +192 -0
  415. package/src/modules/mcp/__tests__/oauth-consent.connect-as.test.ts +146 -0
  416. package/src/modules/mcp/mcp-auth.middleware.ts +19 -2
  417. package/src/modules/mcp/mcp.routes.ts +39 -3
  418. package/src/modules/mcp/mcp.service.ts +107 -17
  419. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +46 -8
  420. package/src/modules/mcp/oauth/oauth-consent.routes.ts +41 -1
  421. package/src/modules/plugins/__tests__/plugins.tools.test.ts +38 -2
  422. package/src/modules/plugins/index.ts +1 -1
  423. package/src/modules/plugins/plugin-links.ts +19 -2
  424. package/src/modules/plugins/plugins.routes.ts +35 -4
  425. package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +113 -0
  426. package/src/modules/secrets-vault/secrets-vault.routes.ts +23 -3
  427. package/src/modules/skills/__tests__/skills.service.test.ts +34 -0
  428. package/src/modules/skills/index.ts +7 -2
  429. package/src/modules/skills/skills.contract.ts +17 -0
  430. package/src/modules/skills/skills.service.ts +5 -1
  431. package/src/modules/skills/skills.tools.ts +44 -13
  432. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +4 -0
  433. package/src/modules/tool-auth/external-api-key.interface.ts +11 -1
  434. package/src/modules/tool-auth/external-api-key.service.ts +5 -2
  435. package/src/modules/tool-auth/index.ts +1 -1
  436. package/src/modules/tool-auth/internal-token.service.ts +10 -0
  437. package/src/modules/tool-auth/tool-auth.middleware.ts +45 -3
  438. package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +3 -1
  439. package/src/modules/tool-helpers/tool-context.ts +10 -0
  440. package/src/modules/tool-helpers/tool-handler.ts +34 -0
  441. package/src/modules/tool-helpers/tool.contract.ts +10 -0
  442. package/src/modules/tool-helpers/validate-token.ts +4 -2
  443. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +80 -4
  444. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +42 -0
  445. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +3 -3
  446. package/src/modules/tool-manuals/index.ts +6 -2
  447. package/src/modules/tool-manuals/tool-manuals.contract.ts +18 -6
  448. package/src/modules/tool-manuals/tool-manuals.routes.ts +40 -3
  449. package/src/modules/tool-manuals/tool-manuals.service.ts +31 -15
  450. package/src/modules/tool-manuals/tool-manuals.tools.ts +28 -11
  451. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +2 -2
  452. package/src/modules/tool-registry/manual.routes.ts +26 -5
  453. package/src/modules/tool-registry/tool.contract.ts +7 -0
  454. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
  455. package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +1 -1
  456. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +54 -3
  457. package/src/modules/workflow/agent-tools/workflow.tools.ts +14 -9
  458. package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +130 -3
  459. package/src/modules/workflow/git/git.service.ts +29 -48
  460. package/src/modules/workflow/workflow.service.ts +5 -0
  461. package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -2
  462. package/src/modules/workspace/__tests__/holds-nul.test.ts +66 -0
  463. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +3 -3
  464. package/src/modules/workspace/__tests__/ripgrep-search.test.ts +570 -0
  465. package/src/modules/workspace/__tests__/ripgrep-version.test.ts +29 -0
  466. package/src/modules/workspace/__tests__/workspace-tmp.tools.test.ts +928 -0
  467. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +17 -37
  468. package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +20 -8
  469. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +2 -2
  470. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +22 -10
  471. package/src/modules/workspace/__tests__/workspace.tools.test.ts +393 -113
  472. package/src/modules/workspace/file-readers/__tests__/doc-extract.service.test.ts +10 -0
  473. package/src/modules/workspace/file-readers/doc-extract.service.ts +71 -38
  474. package/src/modules/workspace/index.ts +20 -0
  475. package/src/modules/workspace/nul-scan.ts +50 -0
  476. package/src/modules/workspace/pattern-matcher.contract.ts +123 -32
  477. package/src/modules/workspace/ripgrep-search.ts +715 -0
  478. package/src/modules/workspace/ripgrep-version.ts +35 -0
  479. package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +11 -0
  480. package/src/modules/workspace/startup/beside-checkout.ts +13 -5
  481. package/src/modules/workspace/worker-pool.ts +3 -4
  482. package/src/modules/workspace/workspace-tmp.ts +457 -0
  483. package/src/modules/workspace/workspace.routes.ts +6 -27
  484. package/src/modules/workspace/workspace.service.ts +92 -15
  485. package/src/modules/workspace/workspace.tools.ts +500 -155
  486. package/src/shared/path-order.ts +37 -8
  487. package/starter-packs/engineering/Plugins/engineering-starter/access.md +1 -1
  488. package/starter-packs/general/Plugins/general-starter/access.md +1 -1
  489. package/starter-packs/operations/Plugins/operations-starter/access.md +1 -1
  490. package/starter-packs/sales/Plugins/sales-starter/access.md +1 -1
  491. package/starter-packs/support/Plugins/support-starter/access.md +1 -1
  492. package/dist/modules/workspace/bounded-matcher.d.ts +0 -73
  493. package/dist/modules/workspace/bounded-matcher.d.ts.map +0 -1
  494. package/dist/modules/workspace/bounded-matcher.js +0 -200
  495. package/dist/modules/workspace/bounded-matcher.js.map +0 -1
  496. package/src/modules/workspace/__tests__/bounded-matcher.test.ts +0 -213
  497. package/src/modules/workspace/bounded-matcher.ts +0 -271
@@ -47,6 +47,15 @@ export interface InternalTokenClaim {
47
47
  * internal token.
48
48
  */
49
49
  connectionId?: string;
50
+ /**
51
+ * The agent configuration the external caller's connection was made as (a
52
+ * repo-relative knowledge-base path), copied at mint from the verified
53
+ * grant or connection — the hosted proxy's loopback token and the local
54
+ * server's exchanged grant both carry it, so every agent surface narrows
55
+ * their calls to that agent's scope (`modules/agent-scope`). Absent is
56
+ * Everything. Carried, never decided, like `connectionId`.
57
+ */
58
+ agentPath?: string;
50
59
  }
51
60
 
52
61
  interface SignedPayload extends InternalTokenClaim {
@@ -148,6 +157,7 @@ export class InternalTokenService {
148
157
  ...(typeof payload.focusedBranch === 'string' ? { focusedBranch: payload.focusedBranch } : {}),
149
158
  ...(payload.externalProxy === true ? { externalProxy: true } : {}),
150
159
  ...(typeof payload.connectionId === 'string' ? { connectionId: payload.connectionId } : {}),
160
+ ...(typeof payload.agentPath === 'string' && payload.agentPath ? { agentPath: payload.agentPath } : {}),
151
161
  };
152
162
  }
153
163
 
@@ -47,6 +47,13 @@ export interface ToolAuth {
47
47
  * recorder attributes a direct tool call to.
48
48
  */
49
49
  connectionId?: string;
50
+ /**
51
+ * The agent configuration an EXTERNAL caller's connection was made as — a
52
+ * connection key's `api_tokens.agent_path`, or the one an `externalProxy`
53
+ * token carries for an agent connection. Absent is Everything. Every agent
54
+ * surface resolves it to the connection's scope (`modules/agent-scope`).
55
+ */
56
+ agentPath?: string;
50
57
  /** Always `'write'` now (the middleware sets it for both internal + external — neither credential carries scope). The read path is dormant until consumer agents are removed. */
51
58
  scope: 'read' | 'write';
52
59
  }
@@ -114,6 +121,14 @@ export type VerifyResult =
114
121
  | { ok: true; auth: ToolAuth }
115
122
  | { ok: false; status: number; message: string; challenge?: string };
116
123
 
124
+ /**
125
+ * A live agent connection's current "Connect as" choice, or null when it is
126
+ * gone or revoked — what `BevelOAuthProvider.liveConnectionChoice` answers.
127
+ */
128
+ export interface ConnectionChoices {
129
+ liveConnectionChoice(connectionId: string): Promise<{ agentPath: string | null } | null>;
130
+ }
131
+
117
132
  /**
118
133
  * The framework-agnostic verify core: bearer token (the value AFTER `Bearer `) →
119
134
  * normalized `ToolAuth`, accepting EITHER a `bevel_…` connection key (external —
@@ -135,6 +150,14 @@ export function createTokenVerifier(
135
150
  * it; absent, internal tokens are judged on their signature alone.
136
151
  */
137
152
  accounts?: Pick<AuthService, 'isActive'>,
153
+ /**
154
+ * Where an `externalProxy` token that names an agent connection reads the
155
+ * connection's CURRENT choice: the token outlives a re-consent, so the
156
+ * agent it was minted with may no longer be the one chosen. A
157
+ * connection no longer live (revoked, deleted) refuses the token. Absent,
158
+ * the token's own choice stands.
159
+ */
160
+ connections?: ConnectionChoices,
138
161
  ): (token: string | undefined) => Promise<VerifyResult> {
139
162
  const missing = { ok: false as const, status: 401, message: 'A connection key or internal token is required' };
140
163
  return async (token) => {
@@ -152,6 +175,13 @@ export function createTokenVerifier(
152
175
  if (accounts && !(await accounts.isActive(claim.userId))) {
153
176
  return { ok: false, status: 401, message: ACCOUNT_DEACTIVATED_MESSAGE };
154
177
  }
178
+ let agentPath = claim.agentPath;
179
+ if (claim.externalProxy && claim.connectionId && connections) {
180
+ const live = await connections.liveConnectionChoice(claim.connectionId);
181
+ // A revoked connection's token is dead here too, as it is at the MCP endpoint.
182
+ if (!live) return { ok: false, status: 401, message: 'Access for this agent was revoked' };
183
+ agentPath = live.agentPath ?? undefined;
184
+ }
155
185
  return claim.externalProxy
156
186
  ? {
157
187
  ok: true,
@@ -159,6 +189,7 @@ export function createTokenVerifier(
159
189
  source: 'external',
160
190
  userId: claim.userId,
161
191
  ...(claim.connectionId ? { connectionId: claim.connectionId } : {}),
192
+ ...(agentPath ? { agentPath } : {}),
162
193
  scope: 'write',
163
194
  },
164
195
  }
@@ -169,7 +200,16 @@ export function createTokenVerifier(
169
200
  if (externalApiKeyService.looksLikeExternalApiKey(token)) {
170
201
  const resolved = await externalApiKeyService.verifyAndLoadToken(token);
171
202
  if (resolved) {
172
- return { ok: true, auth: { source: 'external', userId: resolved.user.id, tokenId: resolved.tokenId, scope: 'write' } };
203
+ return {
204
+ ok: true,
205
+ auth: {
206
+ source: 'external',
207
+ userId: resolved.user.id,
208
+ tokenId: resolved.tokenId,
209
+ ...(resolved.agentPath ? { agentPath: resolved.agentPath } : {}),
210
+ scope: 'write',
211
+ },
212
+ };
173
213
  }
174
214
  // A live key of a switched-off account is told so, not sent to mint another.
175
215
  if (await externalApiKeyService.isKeyOfSwitchedOffAccount?.(token)) {
@@ -192,8 +232,9 @@ export function createToolAuthMiddleware(
192
232
  externalApiKeyService: IExternalApiKeyService,
193
233
  internalTokenService: InternalTokenService,
194
234
  accounts?: Pick<AuthService, 'isActive'>,
235
+ connections?: ConnectionChoices,
195
236
  ): RequestHandler {
196
- const verify = createTokenVerifier(externalApiKeyService, internalTokenService, accounts);
237
+ const verify = createTokenVerifier(externalApiKeyService, internalTokenService, accounts, connections);
197
238
  return async (req: Request, res: Response, next: NextFunction) => {
198
239
  const header = req.headers.authorization;
199
240
  if (!header || !header.toLowerCase().startsWith('bearer ')) {
@@ -233,8 +274,9 @@ export function createManualAuthMiddleware(
233
274
  externalApiKeyService: IExternalApiKeyService,
234
275
  internalTokenService: InternalTokenService,
235
276
  authService: Pick<AuthService, 'isActive' | 'resolveSession'>,
277
+ connections?: ConnectionChoices,
236
278
  ): RequestHandler {
237
- const verify = createTokenVerifier(externalApiKeyService, internalTokenService, authService);
279
+ const verify = createTokenVerifier(externalApiKeyService, internalTokenService, authService, connections);
238
280
  return async (req: Request, res: Response, next: NextFunction) => {
239
281
  const header = req.headers.authorization;
240
282
  if (!header || !header.toLowerCase().startsWith('bearer ')) {
@@ -24,7 +24,9 @@ import { NodeFs } from '../../kb-fs/node-fs.js';
24
24
  import { workspaceIdForBranch } from '../../../shared/workspace-id.js';
25
25
  import { BranchNotFoundError } from '../../../shared/domain-errors.js';
26
26
  import type { WorkspaceService } from '../../workspace/workspace.service.js';
27
- import { SkillService, registerSkillsTools } from '../../skills/index.js';
27
+ import { registerSkillsTools } from '../../skills/index.js';
28
+ // The service itself, from its file: the module entry offers it as a type only.
29
+ import { SkillService } from '../../skills/skills.service.js';
28
30
  import { defaultBranchWriteVerdicts } from '../index.js';
29
31
 
30
32
  /**
@@ -17,6 +17,7 @@ import type { WorkflowEventBus } from '../workflow/index.js';
17
17
  import type { AuthService } from '../auth/index.js';
18
18
  import { ToolError, type ToolContext } from './tool.contract.js';
19
19
  import type { ToolAuth } from '../tool-auth/index.js';
20
+ import { resolveConnectionScope, type IAgentScopeResolver } from '../agent-scope/index.js';
20
21
 
21
22
  export interface ToolContextDeps {
22
23
  authService: AuthService;
@@ -40,6 +41,12 @@ export interface ToolContextDeps {
40
41
  loadActiveGroups?: (
41
42
  read: (filename: string) => Promise<string | null>,
42
43
  ) => Promise<{ groups: GroupsIndex; sourceFile: string; health: { ok: boolean } }>;
44
+ /**
45
+ * Resolves the scope of a connection made as an agent configuration. Every
46
+ * deployment passes it. Absent, a credential that names an agent loads
47
+ * nothing — a narrowing that cannot be read is never read as Everything.
48
+ */
49
+ agentScope?: Pick<IAgentScopeResolver, 'resolve'>;
43
50
  }
44
51
 
45
52
  /** A file's text, or null when it does not exist. Any other failure throws. */
@@ -149,9 +156,12 @@ export function createToolContextResolver(deps: ToolContextDeps): ResolveToolCon
149
156
  return fs;
150
157
  };
151
158
 
159
+ const connectionScope = await resolveConnectionScope(deps.agentScope, user.email, auth.agentPath);
160
+
152
161
  return {
153
162
  user,
154
163
  scope: auth.scope,
164
+ connectionScope,
155
165
  source: auth.source,
156
166
  tokenId: auth.tokenId,
157
167
  // For `internal` callers the signed token claim is the ONLY trustworthy
@@ -13,6 +13,8 @@ import { branchHandlingFor, type BranchHandling } from './tool-def.js';
13
13
  import { domainErrorBody } from '../../shared/http-errors.js';
14
14
  import type { ResolveToolContext } from './tool-context.js';
15
15
  import { argumentsRefusal } from './route-argument-check.js';
16
+ import { routeToolName, routeToolSchemasForRequest } from './route-tool-schemas.js';
17
+ import { ConnectionScope, NOT_AVAILABLE_TO_CONNECTION } from '../agent-scope/index.js';
16
18
  import { alwaysWritable, READ_ONLY_CODE, refuseWriteTool, type IWriteAccess } from '../write-access/index.js';
17
19
  import '../tool-auth/index.js'; // Express Request.toolAuth augmentation
18
20
 
@@ -40,6 +42,19 @@ export interface ToolHandlerOptions {
40
42
  * being refused).
41
43
  */
42
44
  branch?: BranchHandling;
45
+ /**
46
+ * The route is ALSO a discovery read of the connection's surface, which
47
+ * both MCP servers make whatever tools the connection is offered:
48
+ * `list_skills` and `get_skill` are how `prompts/list` and `prompts/get`
49
+ * are built, and `list_local_tools` is how the local server learns which
50
+ * connectors it must materialize. A connection's `platformTools` decides
51
+ * which tools its agent is OFFERED — the MCP listing and `tools/call`
52
+ * refuse the rest by name — not what its surface holds, so such a route is
53
+ * not refused for want of a `platformTools` entry. Its answer is still
54
+ * narrowed to the connection's skills or connectors by the handler, and a
55
+ * connection that loads nothing is refused it like any other route.
56
+ */
57
+ discovery?: boolean;
43
58
  }
44
59
 
45
60
  /**
@@ -255,6 +270,23 @@ export function createToolHandlerFactory(
255
270
  ? withoutBranch(args)
256
271
  : args;
257
272
  const ctx = await resolve(auth, abort.signal, sessionId);
273
+ // The connection's scope, before the tool runs, for every caller of a
274
+ // hosted platform tool — MCP, a chain, the local server, a script with
275
+ // a key. A connection made as an agent configuration that does not
276
+ // list this tool is refused it by name: the ONE policy,
277
+ // `ConnectionScope.platformToolRefusal`, which the platform-tool routes
278
+ // this handler does not host (the plugin-creation routes) ask as well.
279
+ const toolName = routeToolSchemasForRequest(req.originalUrl)?.name ?? routeToolName(req.originalUrl);
280
+ const scope =
281
+ ctx.connectionScope ?? (auth.agentPath ? ConnectionScope.gone(auth.agentPath) : ConnectionScope.everything());
282
+ // The handler narrows by the scope decided here, so a context built
283
+ // without one cannot read as Everything inside the tool either.
284
+ ctx.connectionScope = scope;
285
+ const refusal = scope.platformToolRefusal(toolName, { discovery: opts.discovery === true });
286
+ if (refusal) {
287
+ res.status(403).json(refusal);
288
+ return;
289
+ }
258
290
  if (resolved) ctx.branch = resolved.branch;
259
291
  let out = await handler(toolArgs, ctx);
260
292
  // A defaulted call's answer says which branch it ran on, so a caller
@@ -315,3 +347,5 @@ function withoutBranch(args: Record<string, unknown>): Record<string, unknown> {
315
347
  }
316
348
 
317
349
  export type ToolHandlerFactory = ReturnType<typeof createToolHandlerFactory>;
350
+
351
+ export { NOT_AVAILABLE_TO_CONNECTION };
@@ -1,4 +1,5 @@
1
1
  import type { AuthUser, IWorkflowService } from '@bevel-software/platform-shared';
2
+ import type { ConnectionScope } from '../agent-scope/index.js';
2
3
  import type { LocalFilesystem } from '@mastra/core/workspace';
3
4
  import type { WorkspaceService } from '../workspace/index.js';
4
5
  import type { WorkflowEventBus } from '../workflow/index.js';
@@ -58,6 +59,15 @@ export interface ToolContext {
58
59
  * tool that takes no branch.
59
60
  */
60
61
  branch?: string;
62
+ /**
63
+ * What the caller's connection may load (`modules/agent-scope`): resolved
64
+ * once per call from the agent configuration its credential connects as. The
65
+ * tool handler refuses a platform tool outside it before the tool runs; the
66
+ * tools that list skills and connectors narrow by it. Absent is Everything
67
+ * — and a context built without it for a credential that names an agent is
68
+ * refused every tool by the handler, never served Everything.
69
+ */
70
+ connectionScope?: ConnectionScope;
61
71
  abortSignal: AbortSignal;
62
72
  workspaceService: WorkspaceService;
63
73
  workflowService: IWorkflowService;
@@ -1,4 +1,4 @@
1
- import type { IExternalApiKeyService, InternalTokenService } from '../tool-auth/index.js';
1
+ import type { IExternalApiKeyService, InternalTokenService, ConnectionChoices } from '../tool-auth/index.js';
2
2
  import { createTokenVerifier } from '../tool-auth/index.js';
3
3
  import { createToolContextResolver, type ToolContextDeps } from './tool-context.js';
4
4
  import { ToolError, type ToolContext } from './tool.contract.js';
@@ -6,6 +6,8 @@ import { ToolError, type ToolContext } from './tool.contract.js';
6
6
  export interface ToolValidatorDeps extends ToolContextDeps {
7
7
  externalApiKeyService: IExternalApiKeyService;
8
8
  internalTokenService: InternalTokenService;
9
+ /** Where an agent connection's current "Connect as" choice is read (see `createTokenVerifier`). */
10
+ connections?: ConnectionChoices;
9
11
  }
10
12
 
11
13
  /**
@@ -34,7 +36,7 @@ export type ValidateToken = (
34
36
  ) => Promise<ToolContext>;
35
37
 
36
38
  export function createToolValidator(deps: ToolValidatorDeps): ValidateToken {
37
- const verify = createTokenVerifier(deps.externalApiKeyService, deps.internalTokenService, deps.authService);
39
+ const verify = createTokenVerifier(deps.externalApiKeyService, deps.internalTokenService, deps.authService, deps.connections);
38
40
  const resolve = createToolContextResolver(deps);
39
41
  return async (token, signal) => {
40
42
  let result;
@@ -12,6 +12,7 @@ import type { WorkspaceService } from '../../workspace/workspace.service.js';
12
12
  import type { IAccessControl } from '../../access/access-control.interface.js';
13
13
  import type { IToolManualService } from '../tool-manuals.contract.js';
14
14
  import { testKbContext } from '../../../__tests__/kb-context.js';
15
+ import { ConnectionScope, type IAgentScopeResolver } from '../../agent-scope/index.js';
15
16
 
16
17
  /**
17
18
  * `GET /agent/plugins/:folder/archive` — error taxonomy of the plugin-folder
@@ -27,10 +28,20 @@ let httpServer: HttpServer | undefined;
27
28
  let root: string;
28
29
  let wsDir: string;
29
30
 
30
- async function baseUrl(pluginIndex?: { catalog(): Promise<{ name: string; folders: string[] }[]> }): Promise<string> {
31
+ async function baseUrl(
32
+ pluginIndex?: { catalog(): Promise<{ name: string; folders: string[] }[]> },
33
+ /** The connection's `.agent` choice; absent is Everything. */
34
+ connection?: {
35
+ agentPath: string;
36
+ agentScope?: Pick<IAgentScopeResolver, 'resolve'>;
37
+ /** Whether the person may read the plugin's files; default yes. */
38
+ readable?: boolean;
39
+ },
40
+ toolManualService: IToolManualService = {} as unknown as IToolManualService,
41
+ ): Promise<string> {
31
42
  const app = express();
32
43
  const manualAuth: express.RequestHandler = (req, _res, next) => {
33
- req.toolAuth = { userId: 'u-1' } as never;
44
+ req.toolAuth = { userId: 'u-1', ...(connection ? { agentPath: connection.agentPath } : {}) } as never;
34
45
  next();
35
46
  };
36
47
  const workspaceService = {
@@ -39,16 +50,17 @@ async function baseUrl(pluginIndex?: { catalog(): Promise<{ name: string; folder
39
50
  } as unknown as WorkspaceService;
40
51
  const accessControl = {
41
52
  canReadBatch: vi.fn(async (_ws: string, _email: string, rels: string[]) => {
42
- return new Map(rels.map((r) => [r, true]));
53
+ return new Map(rels.map((r) => [r, connection?.readable ?? true]));
43
54
  }),
44
55
  } as unknown as IAccessControl;
45
56
  app.use(
46
57
  '/api',
47
58
  createToolManualsAgentRoutes(
48
- {} as unknown as IToolManualService,
59
+ toolManualService,
49
60
  manualAuth,
50
61
  async () => 'ali@example.com',
51
62
  { workspaceService, accessControl, kb: testKbContext({ kbDirName: KB }), disk: new NodeFs(), pluginIndex: pluginIndex as never },
63
+ connection?.agentScope,
52
64
  ),
53
65
  );
54
66
  httpServer = await new Promise<HttpServer>((resolve) => {
@@ -71,7 +83,53 @@ afterEach(async () => {
71
83
  await fs.rm(root, { recursive: true, force: true });
72
84
  });
73
85
 
86
+ const OPS = { path: 'Agents/ops.agent', name: 'Ops' };
87
+ /** A resolver that narrows every connection to `connectors`. */
88
+ function loading(connectors: string[]): Pick<IAgentScopeResolver, 'resolve'> {
89
+ return { resolve: async () => ConnectionScope.narrowed(OPS, { skills: [], connectors, platformTools: null }, 'fp') };
90
+ }
91
+
74
92
  describe('GET /agent/plugins/:folder/archive', () => {
93
+ it("a connection made as an .agent file archives only a plugin defining a connector the file loads", async () => {
94
+ const pluginDir = path.join(wsDir, KB, 'Plugins', 'GTM');
95
+ await fs.mkdir(pluginDir, { recursive: true });
96
+ await fs.writeFile(path.join(pluginDir, 'mcp.json'), '{"mcpServers":{}}', 'utf-8');
97
+ const archive = async (connectors: string[] | null) => {
98
+ const base = await baseUrl(undefined, {
99
+ agentPath: OPS.path,
100
+ ...(connectors ? { agentScope: loading(connectors) } : {}),
101
+ });
102
+ const res = await fetch(`${base}/api/agent/plugins/GTM/archive`);
103
+ await new Promise<void>((r) => httpServer!.close(() => r()));
104
+ httpServer = undefined;
105
+ return res;
106
+ };
107
+ // The plugin's own definition file, the plugin folder, a folder above it.
108
+ expect((await archive(['Plugins/GTM/mcp.json'])).status).toBe(200);
109
+ expect((await archive(['Plugins/GTM'])).status).toBe(200);
110
+ expect((await archive(['Plugins'])).status).toBe(200);
111
+ // A connector elsewhere: refused by name, before anything is read.
112
+ const refused = await archive(['Plugins/Other']);
113
+ expect(refused.status).toBe(403);
114
+ const body = (await refused.json()) as { error: string; code: string };
115
+ expect(body.code).toBe('not_available_to_connection');
116
+ expect(body.error).toContain('The plugin "GTM" is not available to this connection.');
117
+ // A narrowed connection with no resolver wired loads nothing.
118
+ expect((await archive(null)).status).toBe(403);
119
+ });
120
+
121
+ it('an out-of-scope plugin folder the person cannot read answers the 404 an absent one does', async () => {
122
+ const pluginDir = path.join(wsDir, KB, 'Plugins', 'Private');
123
+ await fs.mkdir(pluginDir, { recursive: true });
124
+ await fs.writeFile(path.join(pluginDir, 'mcp.json'), '{"mcpServers":{}}', 'utf-8');
125
+ const base = await baseUrl(undefined, { agentPath: OPS.path, agentScope: loading(['Plugins/Other']), readable: false });
126
+ const existing = await fetch(`${base}/api/agent/plugins/Private/archive`);
127
+ const absent = await fetch(`${base}/api/agent/plugins/Nowhere/archive`);
128
+ expect(existing.status).toBe(404);
129
+ expect(await existing.json()).toEqual(await absent.json());
130
+ expect(absent.status).toBe(404);
131
+ });
132
+
75
133
  it('zips a readable plugin folder', async () => {
76
134
  const pluginDir = path.join(wsDir, KB, 'Plugins', 'GTM');
77
135
  await fs.mkdir(pluginDir, { recursive: true });
@@ -119,3 +177,21 @@ describe('GET /agent/plugins/:folder/archive', () => {
119
177
  expect(await res.json()).toEqual({ error: 'Failed to archive plugin' });
120
178
  });
121
179
  });
180
+
181
+ describe('GET /tools/:slug/manual', () => {
182
+ it("narrows an inline manual read to the connectors the connection's .agent file loads", async () => {
183
+ const resolveInlineManual = vi.fn(async () => ({ utcp_version: '1.1.0', manual_version: '1.0.0', tools: [] }));
184
+ const service = { resolveInlineManual } as unknown as IToolManualService;
185
+ // Everything asks exactly what it asked before: no narrowing at all.
186
+ let base = await baseUrl(undefined, undefined, service);
187
+ expect((await fetch(`${base}/api/tools/weather/manual`)).status).toBe(200);
188
+ expect(resolveInlineManual).toHaveBeenLastCalledWith('ali@example.com', 'weather', undefined);
189
+ await new Promise<void>((r) => httpServer!.close(() => r()));
190
+
191
+ base = await baseUrl(undefined, { agentPath: OPS.path, agentScope: loading(['Plugins/GTM']) }, service);
192
+ expect((await fetch(`${base}/api/tools/weather/manual`)).status).toBe(200);
193
+ const within = (resolveInlineManual.mock.calls.at(-1) as unknown[])[2] as { within: (p: string) => boolean };
194
+ expect(within.within('Plugins/GTM/weather.tool')).toBe(true);
195
+ expect(within.within('Plugins/Other/weather.tool')).toBe(false);
196
+ });
197
+ });
@@ -183,6 +183,15 @@ describe('ToolManualService', () => {
183
183
  expect(manual).toBeNull();
184
184
  });
185
185
 
186
+ test("does not resolve a readable inline manual outside a connection's connectors", async () => {
187
+ // The narrowing applies after the read gate, on the manual's own path.
188
+ const paths: string[] = [];
189
+ const within = (p: string) => (paths.push(p), false);
190
+ expect(await svc().resolveInlineManual('user@x.eu', 'weather', { within })).toBeNull();
191
+ expect(paths).toEqual([expect.stringMatching(/weather\.tool$/)]);
192
+ expect(await svc().resolveInlineManual('user@x.eu', 'weather', { within: () => true })).not.toBeNull();
193
+ });
194
+
186
195
  test('resolves an inline tool that calls Google as a service account, and surfaces the key variable', async () => {
187
196
  // The auth block is what makes the call; the key it names has to reach the
188
197
  // secrets UI as a shared variable or nobody can store it.
@@ -486,6 +495,30 @@ describe('ToolManualService', () => {
486
495
  });
487
496
  });
488
497
 
498
+ /**
499
+ * A connection made as an `.agent` file narrows the catalog (`within`) —
500
+ * AFTER the read gate, on every read an agent surface makes, so it can take
501
+ * a manual away and never give one.
502
+ */
503
+ describe('narrowed to a connection’s connectors', () => {
504
+ const onlyBilling = (p: string) => p === 'Plugins/billing.tool';
505
+ const everything = () => true;
506
+
507
+ test('every agent-facing read answers only what is both readable and within', async () => {
508
+ const s = svc();
509
+ expect((await s.toManualCallTemplates('user@x.eu', { within: onlyBilling })).map((t) => t.name)).toEqual(['billing']);
510
+ expect((await s.listAccessibleCatalog('user@x.eu', onlyBilling)).tools.map((t) => t.name)).toEqual(['billing']);
511
+ expect(await s.catalogFingerprints('user@x.eu', onlyBilling)).toHaveLength(1);
512
+ expect(await s.listLocalOnly('user@x.eu', () => false)).toEqual([]);
513
+ });
514
+
515
+ test('an entry naming a manual the person may not read loads nothing for it', async () => {
516
+ const s = svc(denyBilling);
517
+ expect(await s.toManualCallTemplates('user@x.eu', { within: onlyBilling })).toEqual([]);
518
+ expect((await s.toManualCallTemplates('user@x.eu', { within: everything })).map((t) => t.name)).toEqual(['weather']);
519
+ });
520
+ });
521
+
489
522
  test('skips a `.tool` with a malformed `variables` entry (never silently mis-scoped)', async () => {
490
523
  root = await mkdtemp(join(tmpdir(), 'toolsvbad-'));
491
524
  const tools = join(root, wsId, KB_DIR, 'Plugins');
@@ -920,6 +953,15 @@ describe('ToolManualService.listDeclaredOnlyOnBranch', () => {
920
953
  ]);
921
954
  });
922
955
 
956
+ test("narrowed to a connection's connectors, after the read gate", async () => {
957
+ await writeMcpJson(draftWs, 'Ops', ['pager']);
958
+ expect(await svc().listDeclaredOnlyOnBranch('user@example.com', DRAFT, (p) => p.startsWith('Plugins/Ops/'))).toEqual([
959
+ { name: 'pager', path: 'Plugins/Ops/mcp.json', type: 'mcp' },
960
+ ]);
961
+ // Naming an unreadable declaration does not let it through.
962
+ expect(await svc().listDeclaredOnlyOnBranch('user@example.com', DRAFT, (p) => p.startsWith('Plugins/Secret Plugin/'))).toEqual([]);
963
+ });
964
+
923
965
  test('is empty for the default branch itself and for a branch without a workspace', async () => {
924
966
  expect(await svc().listDeclaredOnlyOnBranch('user@example.com', DEFAULT_BRANCH)).toEqual([]);
925
967
  expect(await svc().listDeclaredOnlyOnBranch('user@example.com', 'user/gone')).toEqual([]);
@@ -273,7 +273,7 @@ describe('list_tool_setup — a declaration that lives on a draft says so', () =
273
273
  note?: string;
274
274
  };
275
275
  // Resolved for the caller, on the branch they named.
276
- expect(toolManualService.listDeclaredOnlyOnBranch).toHaveBeenCalledWith(ALICE.email, 'alice/add-crm');
276
+ expect(toolManualService.listDeclaredOnlyOnBranch).toHaveBeenCalledWith(ALICE.email, 'alice/add-crm', undefined);
277
277
  // The released catalog is unchanged — the draft's server is not in it...
278
278
  expect(body.tools.map((t) => t.slug)).not.toContain('crm');
279
279
  // ...and the agent is told where it is and what makes it live.
@@ -294,7 +294,7 @@ describe('list_tool_setup — a declaration that lives on a draft says so', () =
294
294
  const res = await callSetup(base, agentToken);
295
295
  expect(res.status).toBe(200);
296
296
  const body = (await res.json()) as { onBranchOnly: { name: string; branch: string }[]; note?: string };
297
- expect(toolManualService.listDeclaredOnlyOnBranch).toHaveBeenCalledWith(ALICE.email, 'alice/add-crm');
297
+ expect(toolManualService.listDeclaredOnlyOnBranch).toHaveBeenCalledWith(ALICE.email, 'alice/add-crm', undefined);
298
298
  expect(body.onBranchOnly).toEqual([
299
299
  { name: 'crm', path: 'Plugins/Sales/mcp.json', type: 'mcp', branch: 'alice/add-crm' },
300
300
  ]);
@@ -311,7 +311,7 @@ describe('list_tool_setup — a declaration that lives on a draft says so', () =
311
311
  onBranchOnly: { name: string }[];
312
312
  };
313
313
  expect(toolManualService.listDeclaredOnlyOnBranch).toHaveBeenCalledTimes(1);
314
- expect(toolManualService.listDeclaredOnlyOnBranch).toHaveBeenCalledWith(ALICE.email, 'alice/add-crm');
314
+ expect(toolManualService.listDeclaredOnlyOnBranch).toHaveBeenCalledWith(ALICE.email, 'alice/add-crm', undefined);
315
315
  expect(body.onBranchOnly.map((p) => p.name)).toEqual(['crm']);
316
316
  });
317
317
 
@@ -1,5 +1,8 @@
1
- export { ToolManualService } from './tool-manuals.service.js';
2
- export { PendingToolsService } from './pending-tools.service.js';
1
+ // The services are types here: the composition root constructs them from
2
+ // their files, so a module that imports this entry for a helper or a type
3
+ // does not load the implementations.
4
+ export type { ToolManualService } from './tool-manuals.service.js';
5
+ export type { PendingToolsService } from './pending-tools.service.js';
3
6
  export { registerToolManualsTools } from './tool-manuals.tools.js';
4
7
  export {
5
8
  createToolManualsAgentRoutes,
@@ -7,6 +10,7 @@ export {
7
10
  } from './tool-manuals.routes.js';
8
11
  export {
9
12
  EXTERNAL_KB_MANUAL_NAME,
13
+ type ConnectorScope,
10
14
  type IPendingToolService,
11
15
  type IToolManualService,
12
16
  type PendingTool,
@@ -356,6 +356,16 @@ export interface ToolManualDetail extends Omit<ToolManualSummary, 'description'>
356
356
  hiddenTools: HiddenTool[];
357
357
  }
358
358
 
359
+ /**
360
+ * A connection's narrowing of the catalog: whether the connector defined at
361
+ * `definitionPath` (a `.tool` file, or the MCP server definition a server
362
+ * entry came from) is one the connection loads. Applied AFTER the caller's
363
+ * read gate, so it can only take manuals away. The agent surfaces pass the
364
+ * connection scope's `allowsConnector` (`modules/agent-scope`); absent is the
365
+ * whole readable catalog.
366
+ */
367
+ export type ConnectorScope = (definitionPath: string) => boolean;
368
+
359
369
  export interface IToolManualService {
360
370
  /** The `.tool` manuals the user can read, as summaries. */
361
371
  listAccessible(userEmail: string): Promise<ToolManualSummary[]>;
@@ -377,7 +387,7 @@ export interface IToolManualService {
377
387
  * unparseable by definition, so the verdict comes from its folder's
378
388
  * `access.md` chain — default-deny, as everywhere else.
379
389
  */
380
- listAccessibleCatalog(userEmail: string): Promise<AccessibleCatalog>;
390
+ listAccessibleCatalog(userEmail: string, within?: ConnectorScope): Promise<AccessibleCatalog>;
381
391
  /**
382
392
  * One readable `.tool` by slug, with its description + capabilities, for the
383
393
  * browser tool page. `null` when no such slug exists OR the caller can't read
@@ -406,7 +416,7 @@ export interface IToolManualService {
406
416
  * manual's embedded tools breaks without touching anything a summary shows.
407
417
  * Opaque to callers: only equality is ever asked of these lines.
408
418
  */
409
- catalogFingerprints(userEmail: string): Promise<string[]>;
419
+ catalogFingerprints(userEmail: string, within?: ConnectorScope): Promise<string[]>;
410
420
  /**
411
421
  * Every manual in the catalog, UNFILTERED by access — the mirror of
412
422
  * `skillService.listSkills(undefined)`. For caller-INDEPENDENT counting only
@@ -422,7 +432,7 @@ export interface IToolManualService {
422
432
  * NOT include the KB manual (the route prepends that). With `{ remoteOnly: true }`
423
433
  * (the remote MCP proxy), LOCAL-ONLY manuals (`remote: false`) are excluded.
424
434
  */
425
- toManualCallTemplates(userEmail: string, opts?: { remoteOnly?: boolean }): Promise<CallTemplate[]>;
435
+ toManualCallTemplates(userEmail: string, opts?: { remoteOnly?: boolean; within?: ConnectorScope }): Promise<CallTemplate[]>;
426
436
  /**
427
437
  * The caller's accessible LOCAL-ONLY (`remote: false`) manuals.
428
438
  *
@@ -430,21 +440,23 @@ export interface IToolManualService {
430
440
  * both: `name` is the UTCP namespace its `${VAR}` refs are keyed by, and
431
441
  * `slug` is what addresses the manual on the variable-resolution route.
432
442
  */
433
- listLocalOnly(userEmail: string): Promise<{ slug: string; name: string; path: string }[]>;
443
+ listLocalOnly(userEmail: string, within?: ConnectorScope): Promise<{ slug: string; name: string; path: string }[]>;
434
444
  /**
435
445
  * Tools declared on `branch` whose namespace the default-branch catalog does
436
446
  * not serve yet, filtered to what the caller can read there. The catalog is
437
447
  * built from the default branch only, so a server an agent declares on its
438
448
  * draft is invisible to every tool surface until the draft is merged — this
439
449
  * is how the agent is told where its declaration lives. `[]` for the default
440
- * branch itself, or a branch with no workspace.
450
+ * branch itself, or a branch with no workspace. `within` narrows the answer
451
+ * to a connection's connectors, after the read gate, as the catalog's does.
441
452
  */
442
453
  listDeclaredOnlyOnBranch(
443
454
  userEmail: string,
444
455
  branch: string,
456
+ within?: ConnectorScope,
445
457
  ): Promise<{ name: string; path: string; type: ToolManualType }[]>;
446
458
  /** The embedded UTCP manual for an inline `.tool` (served at `/api/tools/:slug/manual`). */
447
- resolveInlineManual(userEmail: string, slug: string): Promise<UtcpManualDict | null>;
459
+ resolveInlineManual(userEmail: string, slug: string, opts?: { within?: ConnectorScope }): Promise<UtcpManualDict | null>;
448
460
  /** Validate a draft `.tool` file's content for the renderer preview. */
449
461
  preview(content: string): Promise<ToolManualPreview>;
450
462
  /**