@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
@@ -16,6 +16,8 @@ import type { BevelOAuthProvider } from './oauth/bevel-oauth-provider.js';
16
16
  import '../tool-auth/index.js'; // req.externalApiKeyId augmentation
17
17
  import './mcp-auth.middleware.js'; // req.mcpCallerKind augmentation
18
18
  import { isPagePath } from '../audit/index.js';
19
+ import type { IAgentScopeResolver } from '../agent-scope/index.js';
20
+ import { NOT_AN_AGENT_CHOICE } from './oauth/oauth-consent.routes.js';
19
21
 
20
22
  /** Pull the raw bearer token off an already-authenticated request. */
21
23
  function extractBearer(req: Request): string {
@@ -104,6 +106,11 @@ export function createMcpRoutes(
104
106
  // RFC 9728 pointer carried on this router's own 401 challenges (the
105
107
  // local-token exchange), same value McpAuthMiddleware advertises.
106
108
  resourceMetadataUrl: string,
109
+ /**
110
+ * The "Connect as" choices a key may be minted as (`modules/agent-scope`).
111
+ * Absent: keys are minted as Everything only, and no choices are listed.
112
+ */
113
+ agents?: Pick<IAgentScopeResolver, 'listChoices' | 'choosable'>,
107
114
  ): express.Router {
108
115
  const router = express.Router();
109
116
 
@@ -153,6 +160,10 @@ export function createMcpRoutes(
153
160
  // exchanged grant; undefined for keys/JWTs) — the Audit log's other
154
161
  // principal, resolved per request like the key id.
155
162
  connectionId: req.agentConnectionId ?? null,
163
+ // The agent configuration the credential connects as (a key's, an agent
164
+ // connection's, or the one an exchanged grant carries); null is
165
+ // Everything. Resolved per request like the ids above.
166
+ ...(req.mcpAgentPath ? { agentPath: req.mcpAgentPath } : {}),
156
167
  // The proxy authenticates its loopback calls with the SAME bearer the
157
168
  // client used here, so it acts on the request exactly as the caller would.
158
169
  bearer: extractBearer(req),
@@ -233,6 +244,17 @@ export function createMcpRoutes(
233
244
 
234
245
  // ── Connection-key management (JWT-only) ───────────────────────────────
235
246
 
247
+ // The agents a key may be minted as — the same choices the OAuth consent
248
+ // step offers below Everything, for the key-mint dialog.
249
+ router.get('/mcp/agent-choices', jwtAuthMiddleware, async (req, res) => {
250
+ try {
251
+ res.json({ agents: agents && req.userEmail ? await agents.listChoices(req.userEmail) : [] });
252
+ } catch (err) {
253
+ log.error('listing the agents to connect as failed:', { err });
254
+ res.status(500).json({ error: 'Failed to list the agents' });
255
+ }
256
+ });
257
+
236
258
  router.get('/mcp/external-api-keys', jwtAuthMiddleware, async (req, res) => {
237
259
  try {
238
260
  const tokens = await externalApiKeyService.listForUser(req.userId!);
@@ -264,12 +286,23 @@ export function createMcpRoutes(
264
286
  res.status(400).json({ error: 'JSON body required' });
265
287
  return;
266
288
  }
267
- const { label } = req.body as { label?: string };
289
+ const { label, agentPath: asked } = req.body as { label?: string; agentPath?: unknown };
268
290
  if (!label) {
269
291
  res.status(400).json({ error: 'label is required' });
270
292
  return;
271
293
  }
272
- const minted = await externalApiKeyService.mint(req.userId!, label);
294
+ // "Connect as", exactly as on the consent step: absent, null or empty is
295
+ // Everything; anything else must be an agent the owner may choose now.
296
+ let agentPath: string | null = null;
297
+ if (asked !== undefined && asked !== null && asked !== '') {
298
+ agentPath =
299
+ typeof asked === 'string' && agents && req.userEmail ? await agents.choosable(req.userEmail, asked) : null;
300
+ if (agentPath === null) {
301
+ res.status(400).json({ error: NOT_AN_AGENT_CHOICE });
302
+ return;
303
+ }
304
+ }
305
+ const minted = await externalApiKeyService.mint(req.userId!, label, agentPath ? { agentPath } : {});
273
306
  // The plaintext field is the *only* read path for the raw key. The
274
307
  // frontend must store it nowhere — it shows the dialog once and then
275
308
  // discards. Subsequent fetches return only `summary`-shaped rows.
@@ -428,8 +461,11 @@ export function createMcpRoutes(
428
461
  // The grant's agent connection rides along, so the local server's
429
462
  // calls with this token stay attributed to the agent in the Audit log.
430
463
  const connectionId = typeof info.extra?.connectionId === 'string' ? info.extra.connectionId : undefined;
464
+ // So does the agent the connection was made as: the local server's
465
+ // calls see the same narrowed surface as the hosted endpoint's.
466
+ const agentPath = typeof info.extra?.agentPath === 'string' && info.extra.agentPath ? info.extra.agentPath : undefined;
431
467
  const minted = internalTokens.mint(
432
- { userId, externalProxy: true, ...(connectionId ? { connectionId } : {}) },
468
+ { userId, externalProxy: true, ...(connectionId ? { connectionId } : {}), ...(agentPath ? { agentPath } : {}) },
433
469
  ttlMs,
434
470
  );
435
471
  // The ACTUAL lifetime, not the constant — the caller schedules its
@@ -75,6 +75,16 @@ import {
75
75
  type ComposedAgentInstructions,
76
76
  } from '../agent-instructions/index.js';
77
77
  import { guideFirstDescription } from '../tool-registry/index.js';
78
+ import {
79
+ CONNECTION_META_KEY,
80
+ connectionRefusal,
81
+ connectionWireOf,
82
+ describeConnection,
83
+ type ConnectionScopeWire,
84
+ } from '@bevel-software/platform-mcp-core';
85
+
86
+ // Still offered from here, as this module's tests and callers learnt it.
87
+ export { CONNECTION_META_KEY };
78
88
 
79
89
  /**
80
90
  * Configuration for the loopback proxy. `loopbackBaseUrl` is the backend's own
@@ -142,6 +152,14 @@ export interface McpCaller {
142
152
  * attributed in the Audit log; a request with neither records nothing.
143
153
  */
144
154
  connectionId?: string | null;
155
+ /**
156
+ * The agent configuration the caller's credential connects as (a connection
157
+ * key's, or an agent connection's), or null for Everything. It rides the
158
+ * request's loopback identity, so every agent surface behind the loopback
159
+ * narrows to that agent's scope (`modules/agent-scope`); here it only keys
160
+ * the surface and words the handshake.
161
+ */
162
+ agentPath?: string | null;
145
163
  /** The raw bearer the request carried. */
146
164
  bearer: string;
147
165
  /**
@@ -222,6 +240,24 @@ interface RequestSurface {
222
240
  * build, at the latest after an hour unused.
223
241
  */
224
242
  degraded: boolean;
243
+ /**
244
+ * The agent the catalog was narrowed to, as `all-tools` reported it —
245
+ * absent for Everything. A call to a tool not on a narrowed surface is
246
+ * refused by name and agent rather than as unknown, and a connection whose
247
+ * agent configuration cannot be used is served nothing, with the sentence that says
248
+ * why.
249
+ */
250
+ connection?: ConnectionScopeWire;
251
+ }
252
+
253
+ /**
254
+ * The `connection` a loopback answer (`all-tools`, `catalog-revision`)
255
+ * carries, as a field to spread: validated at the boundary by the MCP
256
+ * core's reader (HTTP loses the type), and absent for Everything.
257
+ */
258
+ function connectionOf(body: unknown): { connection?: ConnectionScopeWire } {
259
+ const connection = connectionWireOf(body);
260
+ return connection ? { connection } : {};
225
261
  }
226
262
 
227
263
  /**
@@ -490,13 +526,26 @@ export class McpService {
490
526
  // verifier resolves it to `source: 'external'`: the caller IS an external
491
527
  // agent and must be treated like one (admitted to `start_session`/`ask`,
492
528
  // refused from internal-only tools).
529
+ // The agent the connection was made as rides that token too, so the
530
+ // surfaces behind the loopback narrow to its scope; a connection key
531
+ // carries its own.
532
+ const agentPath = caller.agentPath || null;
493
533
  const loopbackBearer =
494
534
  tokenId == null && this.internalTokens
495
- ? this.internalTokens.mint({ userId, externalProxy: true }, MCP_LOOPBACK_TOKEN_TTL_MS)
535
+ ? this.internalTokens.mint(
536
+ { userId, externalProxy: true, ...(agentPath ? { agentPath } : {}) },
537
+ MCP_LOOPBACK_TOKEN_TTL_MS,
538
+ )
496
539
  : bearer;
497
540
 
498
541
  let instructions: Promise<ComposedAgentInstructions> | undefined;
499
542
  const agentInstructions = () => (instructions ??= this.composeAgentInstructions());
543
+ // What a narrowed connection is told about itself at the handshake: the
544
+ // agent it is connected as, or why it loads nothing. Read off the catalog
545
+ // revision (cheap), not the surface (a full discovery). Everything is
546
+ // told nothing new, so its handshake is exactly what it was.
547
+ const connectionLine = async (): Promise<string | null> =>
548
+ agentPath ? describeConnection((await this.fetchCatalogRevision(loopbackBearer)).connection) : null;
500
549
  // The Audit log's view of this request — only when the request can be
501
550
  // attributed to a key, an agent connection or a page. The catalog names
502
551
  // it classifies pooled tools by are the surface's, which is looked up
@@ -534,6 +583,10 @@ export class McpService {
534
583
  }));
535
584
 
536
585
  const initializing = (Array.isArray(messages) ? messages : [messages]).some((m) => isInitializeRequest(m));
586
+ const handshakeInstructions = async (): Promise<string> => {
587
+ const [{ instructions: text }, line] = await Promise.all([agentInstructions(), connectionLine()]);
588
+ return line ? `${text}\n\n${line}` : text;
589
+ };
537
590
  // The deployment's MCP Apps, read once per request. Declaring the
538
591
  // `resources` capability is what permits the two handlers below, and a
539
592
  // deployment with no view must not declare it — a client would then list
@@ -554,12 +607,12 @@ export class McpService {
554
607
  },
555
608
  // `instructions` rides the initialize result; clients that honour it
556
609
  // place the text in the model's system prompt without the model acting.
557
- ...(initializing ? { instructions: (await agentInstructions()).instructions } : {}),
610
+ ...(initializing ? { instructions: await handshakeInstructions() } : {}),
558
611
  },
559
612
  );
560
613
 
561
614
  server.setRequestHandler(ListToolsRequestSchema, async () => inRequest(async () => {
562
- const [{ tools, catalogNames }, { toolPrefix }, readOnly] = await Promise.all([
615
+ const [{ tools, catalogNames, connection }, { toolPrefix }, readOnly] = await Promise.all([
563
616
  requestSurface(),
564
617
  agentInstructions(),
565
618
  // A failed read lists the built-in hints rather than no listing at all.
@@ -568,6 +621,12 @@ export class McpService {
568
621
  return undefined;
569
622
  }),
570
623
  ]);
624
+ // A connection whose agent configuration cannot be used is served NOTHING — not
625
+ // Everything — and the one sentence saying why rides the listing's
626
+ // `_meta` (the handshake carries it too).
627
+ if (connection?.unavailable) {
628
+ return { tools: [], _meta: { [CONNECTION_META_KEY]: connection.unavailable } };
629
+ }
571
630
  // A discovered tool whose name collides with a meta-tool would be
572
631
  // listed but never callable (the dispatcher routes the name to the
573
632
  // meta-tool first), so drop it from the listing entirely.
@@ -694,7 +753,10 @@ export class McpService {
694
753
  }
695
754
 
696
755
  server.setRequestHandler(CallToolRequestSchema, async (request, extra) => inRequest(async () => {
697
- const { client: shared, tools, unavailable, catalogNames, hidden: hiddenTools } = await requestSurface();
756
+ const { client: shared, tools, unavailable, catalogNames, hidden: hiddenTools, connection } = await requestSurface();
757
+ // Nothing is callable on a connection that loads nothing, meta-tools
758
+ // included: every call is answered with the reason.
759
+ if (connection?.unavailable) return toolError(connection.unavailable);
698
760
  // The person's client, as this request uses it: every call it makes —
699
761
  // a chain's too, which comes back from the isolate outside any async
700
762
  // context — carries this request's bearer and audit.
@@ -769,7 +831,16 @@ export class McpService {
769
831
  if (answer && owner && audit) {
770
832
  await audit.denied(`${owner.utcpName}.${toolName.slice(owner.utcpName.length + 1)}`, args);
771
833
  }
772
- return answer ?? toolError(retiredToolMessage(toolName) ?? `Unknown tool "${toolName}".`);
834
+ // On a narrowed connection, a tool off the surface is one its agent
835
+ // does not load: refused by name and agent, never "unknown" — the tool
836
+ // may well exist, for this person, on another connection.
837
+ return (
838
+ answer ??
839
+ toolError(
840
+ retiredToolMessage(toolName) ??
841
+ (connection ? connectionRefusal(toolName, connection) : `Unknown tool "${toolName}".`),
842
+ )
843
+ );
773
844
  }
774
845
  const needsAuth = await needsAuthorization(proxied.manualName);
775
846
  if (needsAuth) {
@@ -879,9 +950,20 @@ export class McpService {
879
950
  */
880
951
  private async surfaceFor(caller: McpCaller, loopbackBearer: string): Promise<RequestSurface> {
881
952
  const { userId, tokenId } = caller;
882
- const revision = await this.fetchCatalogRevision(loopbackBearer);
953
+ const { revision } = await this.fetchCatalogRevision(loopbackBearer);
883
954
  if (revision === null) return this.buildSurface(userId, tokenId, loopbackBearer, 'uncached');
884
- const key = [userId, tokenId != null ? 'key' : 'person', caller.scope ?? 'write', revision].join(POOL_KEY_SEPARATOR);
955
+ // The agent the connection was made as is part of the key: two
956
+ // connections of one person made as different agents are two surfaces.
957
+ // (The revision already moves when the agent's FILE changes — it carries
958
+ // the file's digest — so an edit is a new build at the next request.)
959
+ // Everything adds nothing, and keeps the key it always had.
960
+ const key = [
961
+ userId,
962
+ tokenId != null ? 'key' : 'person',
963
+ caller.scope ?? 'write',
964
+ revision,
965
+ ...(caller.agentPath ? [caller.agentPath] : []),
966
+ ].join(POOL_KEY_SEPARATOR);
885
967
  const { value } = await this.surfaces.get(
886
968
  key,
887
969
  () => this.buildSurface(userId, tokenId, loopbackBearer, 'miss'),
@@ -893,10 +975,13 @@ export class McpService {
893
975
  /**
894
976
  * The caller's catalog revision (`GET /api/agent/catalog-revision`, the one
895
977
  * `hexis-mcp` polls), read with their bearer; null when it cannot be read.
978
+ * With it, the agent the caller's connection is narrowed to, when it is.
896
979
  * A deployment or a test that does not serve the route answers 404, which
897
980
  * is not worth a line in the log; anything else is.
898
981
  */
899
- private async fetchCatalogRevision(bearer: string): Promise<string | null> {
982
+ private async fetchCatalogRevision(
983
+ bearer: string,
984
+ ): Promise<{ revision: string | null; connection?: ConnectionScopeWire }> {
900
985
  try {
901
986
  const res = await fetch(`${this.opts.loopbackBaseUrl}/api/agent/catalog-revision`, {
902
987
  headers: { Authorization: `Bearer ${bearer}` },
@@ -904,13 +989,16 @@ export class McpService {
904
989
  });
905
990
  if (!res.ok) {
906
991
  if (res.status !== 404) log.warn(`catalog-revision loopback failed: HTTP ${res.status}; building this request's surface uncached`);
907
- return null;
992
+ return { revision: null };
908
993
  }
909
- const body = (await res.json()) as { revision?: unknown };
910
- return typeof body.revision === 'string' && body.revision ? body.revision : null;
994
+ const body = (await res.json()) as { revision?: unknown; connection?: unknown };
995
+ return {
996
+ revision: typeof body.revision === 'string' && body.revision ? body.revision : null,
997
+ ...connectionOf(body),
998
+ };
911
999
  } catch (err) {
912
1000
  log.warn("catalog-revision loopback threw; building this request's surface uncached:", { err });
913
- return null;
1001
+ return { revision: null };
914
1002
  }
915
1003
  }
916
1004
 
@@ -935,7 +1023,7 @@ export class McpService {
935
1023
  // The list of manuals this caller gets: the KB manual + each `.tool` they
936
1024
  // can read. Fetched over loopback with the caller's key, so ACL filtering
937
1025
  // lives ONCE behind the REST surface.
938
- const manuals = await this.fetchManualTemplates(loopbackBearer);
1026
+ const { manuals, connection } = await this.fetchManualTemplates(loopbackBearer);
939
1027
  const catalogMs = performance.now() - started;
940
1028
  const client = await this.buildClient(userId, manuals);
941
1029
  const discovered = await this.discoverTools(client, manuals, userId);
@@ -960,7 +1048,7 @@ export class McpService {
960
1048
  (decision.suppressed > 0 ? ` [+${decision.suppressed} identical rebuild(s) since last line]` : ''),
961
1049
  );
962
1050
  }
963
- return { ...discovered, client, tools };
1051
+ return { ...discovered, client, tools, ...(connection ? { connection } : {}) };
964
1052
  }
965
1053
 
966
1054
  /**
@@ -1039,11 +1127,13 @@ export class McpService {
1039
1127
  * request. HTTP transport loses the `CallTemplate` type, so we re-validate the
1040
1128
  * received JSON even though the producing endpoint already validated it.
1041
1129
  */
1042
- private async fetchManualTemplates(bearer: string): Promise<CallTemplate[]> {
1130
+ private async fetchManualTemplates(
1131
+ bearer: string,
1132
+ ): Promise<{ manuals: CallTemplate[]; connection?: ConnectionScopeWire }> {
1043
1133
  // This is the REMOTE proxy, so ask for remote-capable manuals only — local-only
1044
1134
  // `.tool`s are surfaced instead via the `list_local_tools` tool in the KB manual.
1045
1135
  const body = (await this.loopbackJson(bearer, '/api/agent/all-tools?remote=true', 'all-tools')) as
1046
- | { manuals?: unknown }
1136
+ | { manuals?: unknown; connection?: unknown }
1047
1137
  | null;
1048
1138
  const rawManuals: unknown[] = Array.isArray(body?.manuals) ? body.manuals : [];
1049
1139
 
@@ -1062,7 +1152,7 @@ export class McpService {
1062
1152
  }
1063
1153
  // Always include the KB manual, even if `all-tools` was unavailable/regressed.
1064
1154
  if (!hasKb) out.unshift(this.kbManualTemplate());
1065
- return out;
1155
+ return { manuals: out, ...connectionOf(body) };
1066
1156
  }
1067
1157
 
1068
1158
  /** The KB manual's discovery template — the fallback if `all-tools` is unavailable. */
@@ -199,10 +199,16 @@ export class BevelOAuthProvider implements OAuthServerProvider {
199
199
  * Called by the Finish endpoint once an authenticated user completes the
200
200
  * /connect step: bind a one-time code to (user, client, redirect_uri, PKCE
201
201
  * challenge) and hand back the client redirect the browser should follow.
202
+ *
203
+ * @param agentPath The "Connect as" choice — an agent configuration the consent
204
+ * route has already checked the person may choose — or null for
205
+ * Everything. Carried on the code to the exchange, which records it on
206
+ * the agent connection.
202
207
  */
203
208
  async issueAuthCode(
204
209
  userId: string,
205
210
  st: McpAuthRequestState,
211
+ agentPath: string | null = null,
206
212
  ): Promise<{ redirectTo: string }> {
207
213
  // A state without a PKCE challenge is a Claude-link request, which the
208
214
  // marketplace bridge finishes; it must never reach the SDK's code store.
@@ -217,6 +223,7 @@ export class BevelOAuthProvider implements OAuthServerProvider {
217
223
  scope: st.sc ?? null,
218
224
  resource: st.rs ?? null,
219
225
  expiresAt: new Date(Date.now() + CODE_TTL_MS),
226
+ ...(agentPath ? { agentPath } : {}),
220
227
  });
221
228
  const redirect = new URL(st.r);
222
229
  redirect.searchParams.set('code', code);
@@ -269,7 +276,7 @@ export class BevelOAuthProvider implements OAuthServerProvider {
269
276
  if (redirectUri && redirectUri !== row.redirectUri) {
270
277
  throw new InvalidGrantError('redirect_uri does not match the authorization request');
271
278
  }
272
- return this.mintTokens(row.userId, row.clientId, row.scope, row.resource);
279
+ return this.mintTokens(row.userId, row.clientId, row.scope, row.resource, undefined, undefined, row.agentPath ?? null);
273
280
  }
274
281
 
275
282
  async exchangeRefreshToken(
@@ -463,6 +470,7 @@ export class BevelOAuthProvider implements OAuthServerProvider {
463
470
  id: oauthTokens.id,
464
471
  clientId: oauthTokens.clientId,
465
472
  connectionId: oauthTokens.connectionId,
473
+ agentPath: agentConnections.agentPath,
466
474
  scope: oauthTokens.scope,
467
475
  resource: oauthTokens.resource,
468
476
  expiresAt: oauthTokens.expiresAt,
@@ -507,6 +515,10 @@ export class BevelOAuthProvider implements OAuthServerProvider {
507
515
  // Null on a token minted before connections existed; the auth
508
516
  // middleware then binds no agent and the call goes unrecorded.
509
517
  connectionId: row.connectionId,
518
+ // The connection's "Connect as" choice, read off the row on every
519
+ // verification, so a re-consent with another choice reaches the
520
+ // agent's very next request. Null is Everything.
521
+ agentPath: row.agentPath ?? null,
510
522
  },
511
523
  };
512
524
  }
@@ -575,6 +587,9 @@ export class BevelOAuthProvider implements OAuthServerProvider {
575
587
  * first mint, which finds or makes the live connection for (user, client).
576
588
  * @param tx The transaction the insert belongs to, when the mint must land
577
589
  * with the statement that consumed the token it replaces (a refresh).
590
+ * @param agentPath The consent's "Connect as" choice, recorded on the
591
+ * connection the pair is bound to (null: Everything). Only a first mint —
592
+ * a consent — passes it; a refresh leaves the connection's choice alone.
578
593
  * The prune is then the caller's, before the transaction opens: a
579
594
  * best-effort statement that fails inside a transaction would abort the
580
595
  * mint it is meant to spare, and one on the pool from inside it would
@@ -587,6 +602,7 @@ export class BevelOAuthProvider implements OAuthServerProvider {
587
602
  resource: string | null,
588
603
  connectionId?: string,
589
604
  tx?: Tx,
605
+ agentPath?: string | null,
590
606
  ): Promise<OAuthTokens> {
591
607
  const accessToken = this.deps.tokenPrefix + randomBytes(TOKEN_BYTES).toString('base64url');
592
608
  const refreshToken = this.refreshPrefix + randomBytes(TOKEN_BYTES).toString('base64url');
@@ -594,7 +610,7 @@ export class BevelOAuthProvider implements OAuthServerProvider {
594
610
  // A mint of its own (a sign-in) prunes here; a refresh pruned before its
595
611
  // transaction opened.
596
612
  if (!tx) await this.pruneTokenTables(now);
597
- const boundTo = connectionId ?? (await this.ensureConnection(userId, clientId));
613
+ const boundTo = connectionId ?? (await this.ensureConnection(userId, clientId, agentPath));
598
614
  const sink: Tx | Database = tx ?? this.deps.db;
599
615
  await sink.insert(oauthTokens).values({
600
616
  accessTokenHash: hashToken(accessToken),
@@ -665,12 +681,23 @@ export class BevelOAuthProvider implements OAuthServerProvider {
665
681
  * revoke. A read, not a write: the use is stamped separately, throttled.
666
682
  */
667
683
  async isConnectionLive(connectionId: string): Promise<boolean> {
684
+ return (await this.liveConnectionChoice(connectionId)) !== null;
685
+ }
686
+
687
+ /**
688
+ * A live agent connection's current "Connect as" choice (`agentPath` null
689
+ * = Everything), or null when the connection is gone or revoked. Read on
690
+ * every request through the local server's exchanged grant: that token is
691
+ * stateless and outlives a re-consent, so the choice it was minted with is
692
+ * not the one it is served as — the row's is.
693
+ */
694
+ async liveConnectionChoice(connectionId: string): Promise<{ agentPath: string | null } | null> {
668
695
  const [row] = await this.deps.db
669
- .select({ id: agentConnections.id })
696
+ .select({ agentPath: agentConnections.agentPath })
670
697
  .from(agentConnections)
671
698
  .where(and(eq(agentConnections.id, connectionId), isNull(agentConnections.revokedAt)))
672
699
  .limit(1);
673
- return row !== undefined;
700
+ return row ? { agentPath: row.agentPath ?? null } : null;
674
701
  }
675
702
 
676
703
  /**
@@ -737,8 +764,13 @@ export class BevelOAuthProvider implements OAuthServerProvider {
737
764
  * the other's `ON CONFLICT DO NOTHING` returns no row, and it re-reads the
738
765
  * winner's. The client's display name is snapshotted from its registration
739
766
  * at that moment.
767
+ *
768
+ * `agentPath` is a consent's "Connect as" choice: written on the row made,
769
+ * and on the live row found — the person's latest consent is what the agent
770
+ * connects as. `undefined` (a refresh of a token from before connections
771
+ * existed) leaves a found row's choice as it is.
740
772
  */
741
- private async ensureConnection(userId: string, clientId: string): Promise<string> {
773
+ private async ensureConnection(userId: string, clientId: string, agentPath?: string | null): Promise<string> {
742
774
  const [client] = await this.deps.db
743
775
  .select({ clientName: oauthClients.clientName })
744
776
  .from(oauthClients)
@@ -758,11 +790,17 @@ export class BevelOAuthProvider implements OAuthServerProvider {
758
790
  ),
759
791
  )
760
792
  .limit(1);
793
+ const choose = async (id: string): Promise<string> => {
794
+ if (agentPath !== undefined) {
795
+ await this.deps.db.update(agentConnections).set({ agentPath }).where(eq(agentConnections.id, id));
796
+ }
797
+ return id;
798
+ };
761
799
  const [existing] = await live();
762
- if (existing) return existing.id;
800
+ if (existing) return choose(existing.id);
763
801
  const [inserted] = await this.deps.db
764
802
  .insert(agentConnections)
765
- .values({ userId, clientId, clientName, agentKey })
803
+ .values({ userId, clientId, clientName, agentKey, ...(agentPath ? { agentPath } : {}) })
766
804
  .onConflictDoNothing({
767
805
  target: [agentConnections.userId, agentConnections.agentKey],
768
806
  where: sql`${agentConnections.revokedAt} is null`,
@@ -771,7 +809,7 @@ export class BevelOAuthProvider implements OAuthServerProvider {
771
809
  if (inserted) return inserted.id;
772
810
  const [winner] = await live();
773
811
  if (!winner) throw new Error('agent connection vanished between insert and re-read');
774
- return winner.id;
812
+ return choose(winner.id);
775
813
  }
776
814
  }
777
815
 
@@ -4,6 +4,7 @@ import { logger } from '../../../shared/logging.js';
4
4
  const log = logger('mcp-oauth');
5
5
  import type { BevelOAuthProvider } from './bevel-oauth-provider.js';
6
6
  import { verifyAuthRequest, type McpAuthRequestState } from './oauth-state.js';
7
+ import type { IAgentScopeResolver } from '../../agent-scope/index.js';
7
8
  import '../../auth/index.js'; // Express Request augmentation (req.userId / req.userEmail)
8
9
 
9
10
  export interface OAuthConsentRoutesDeps {
@@ -21,8 +22,17 @@ export interface OAuthConsentRoutesDeps {
21
22
  clientNameFor(st: McpAuthRequestState): string;
22
23
  completeConsent(userId: string, st: McpAuthRequestState): Promise<{ redirectTo: string }>;
23
24
  };
25
+ /**
26
+ * The "Connect as" choices: the agent configurations the person may connect an
27
+ * MCP client as, offered below Everything. Absent, the consent offers
28
+ * Everything alone and Finish refuses any other choice.
29
+ */
30
+ agents?: Pick<IAgentScopeResolver, 'listChoices' | 'choosable'>;
24
31
  }
25
32
 
33
+ /** The refusal of a choice that is not one of the person's — one answer for missing and unreadable. */
34
+ export const NOT_AN_AGENT_CHOICE = 'That agent is not one you can connect as. Choose Everything or one of the agents listed.';
35
+
26
36
  /**
27
37
  * The authenticated tail of the MCP OAuth flow. `/authorize` (SDK-owned)
28
38
  * validated the client's request, packed it into a signed state, and sent the
@@ -49,10 +59,25 @@ export function createOAuthConsentRoutes(deps: OAuthConsentRoutesDeps): express.
49
59
  return;
50
60
  }
51
61
  const client = await deps.provider.clientsStore.getClient(st.c);
62
+ // Everything is not in the list: it is the choice every consent has, and
63
+ // the one the page selects. A failure to read the agents costs the choice
64
+ // of an agent, never the consent itself — but it is SAID
65
+ // (`agentsUnavailable`), so the page does not pass an empty list off as
66
+ // "you have no agents" and approve Everything unasked.
67
+ let agentsUnavailable = false;
68
+ const agents = deps.agents && req.userEmail
69
+ ? await deps.agents.listChoices(req.userEmail).catch((err: unknown) => {
70
+ log.warn('listing the agents to connect as failed:', { detail: err instanceof Error ? err.message : String(err) });
71
+ agentsUnavailable = true;
72
+ return [];
73
+ })
74
+ : [];
52
75
  res.json({
53
76
  clientName: client?.client_name ?? null,
54
77
  scope: st.sc ?? null,
55
78
  resource: st.rs ?? null,
79
+ agents,
80
+ ...(agentsUnavailable ? { agentsUnavailable: true } : {}),
56
81
  });
57
82
  });
58
83
 
@@ -65,10 +90,25 @@ export function createOAuthConsentRoutes(deps: OAuthConsentRoutesDeps): express.
65
90
  const st = raw ? verifyAuthRequest(deps.stateSecret, raw) : null;
66
91
  if (!st) return void res.status(400).json({ error: 'Invalid or expired authorization request. Restart the connection from your agent.' });
67
92
  if (st.gh && !deps.facade) return void res.status(400).json({ error: NO_FACADE });
93
+ // "Connect as": absent, null or empty is Everything. Anything else must be
94
+ // an agent the person may choose right now — checked here, at the only
95
+ // place a choice is made, so the token exchange records it as given.
96
+ const asked: unknown = (req.body ?? {}).agentPath;
97
+ if (asked !== undefined && asked !== null && typeof asked !== 'string') {
98
+ return void res.status(400).json({ error: NOT_AN_AGENT_CHOICE });
99
+ }
100
+ const choosing = typeof asked === 'string' && asked.trim() !== '';
101
+ // An account linked through the GitHub facade has no agent connection to
102
+ // record a choice on.
103
+ if (choosing && (st.gh || !deps.agents || !req.userEmail)) {
104
+ return void res.status(400).json({ error: NOT_AN_AGENT_CHOICE });
105
+ }
68
106
  try {
107
+ const agentPath = choosing && deps.agents && req.userEmail ? await deps.agents.choosable(req.userEmail, asked as string) : null;
108
+ if (choosing && agentPath === null) return void res.status(400).json({ error: NOT_AN_AGENT_CHOICE });
69
109
  const { redirectTo } = st.gh && deps.facade
70
110
  ? await deps.facade.completeConsent(userId, st)
71
- : await deps.provider.issueAuthCode(userId, st);
111
+ : await deps.provider.issueAuthCode(userId, st, agentPath);
72
112
  res.json({ redirectTo });
73
113
  } catch (err) {
74
114
  // Message only — the raw error object can carry sensitive context
@@ -13,6 +13,8 @@ import { GUIDE_FIRST_SENTENCE } from '../../tool-registry/guide-first.js';
13
13
  import { registerPluginsTools, CREATE_PLUGIN, myPluginDef, myPluginDescription } from '../plugins.tools.js';
14
14
  import { createPluginCreationRoutes } from '../plugins.routes.js';
15
15
  import { PluginProvisionError } from '../plugin-provision.service.js';
16
+ import type { IAgentScopeResolver } from '../../agent-scope/index.js';
17
+ import { agentFile, agentScopeOver, inMemoryKb } from '../../agent-scope/__tests__/in-memory-kb.js';
16
18
 
17
19
  /**
18
20
  * The plugin tools are DESCRIPTIONS of the app's own creation endpoints.
@@ -200,7 +202,12 @@ describe('the creation endpoints behind the key-or-session gate', () => {
200
202
 
201
203
  const externalApiKeyService = {
202
204
  looksLikeExternalApiKey: (t: string) => typeof t === 'string' && t.startsWith('bevel_'),
203
- verifyAndLoadToken: async (t: string) => (t === 'bevel_alice' ? { user: ALICE, tokenId: 'tok-a' } : null),
205
+ verifyAndLoadToken: async (t: string) =>
206
+ t === 'bevel_alice'
207
+ ? { user: ALICE, tokenId: 'tok-a' }
208
+ : t === 'bevel_alice_agent'
209
+ ? { user: ALICE, tokenId: 'tok-b', agentPath: 'Agents/plugin-maker.agent' }
210
+ : null,
204
211
  } as never;
205
212
  const authService = {
206
213
  getUserById: async (id: string) => (id === ALICE.id ? ALICE : null),
@@ -211,7 +218,7 @@ describe('the creation endpoints behind the key-or-session gate', () => {
211
218
  },
212
219
  } as never;
213
220
 
214
- async function start(): Promise<string> {
221
+ async function start(agentScope?: Pick<IAgentScopeResolver, 'resolve'>): Promise<string> {
215
222
  const internalToken = new InternalTokenService({ secret: 'test-secret' });
216
223
  const app = express();
217
224
  app.use(express.json());
@@ -224,6 +231,7 @@ describe('the creation endpoints behind the key-or-session gate', () => {
224
231
  }),
225
232
  createPluginCreationRoutes({ ensurePersonalPlugin, createPlugin } as never, async (req) =>
226
233
  req.userId ? ((await (authService as { getUserById(id: string): Promise<unknown> }).getUserById(req.userId)) as never) : null,
234
+ agentScope,
227
235
  ),
228
236
  );
229
237
  httpServer = await new Promise<HttpServer>((r) => {
@@ -302,4 +310,32 @@ describe('the creation endpoints behind the key-or-session gate', () => {
302
310
  );
303
311
  expect(createPlugin).not.toHaveBeenCalled();
304
312
  });
313
+
314
+ it("refuses a connection made as an .agent that does not list the tool, by name, before anything is provisioned", async () => {
315
+ // The agent lists `my_plugin` only. These routes are not hosted by
316
+ // `toolHandler`, so they make its connection-scope check themselves.
317
+ const kb = inMemoryKb(
318
+ { 'Agents/plugin-maker.agent': agentFile('name: Plugin maker\nplatformTools: [my_plugin]') },
319
+ { reader: ALICE.email },
320
+ );
321
+ const base = await start(agentScopeOver(kb));
322
+ const refused = await post(base, '/api/plugins', { name: 'GTM' }, 'bevel_alice_agent');
323
+ expect(refused.status).toBe(403);
324
+ expect(await refused.json()).toEqual({
325
+ error: expect.stringContaining('The tool "create_plugin" is not available to this connection'),
326
+ code: 'not_available_to_connection',
327
+ });
328
+ expect(createPlugin).not.toHaveBeenCalled();
329
+
330
+ // The tool it lists is served; a key made as Everything and a session are untouched.
331
+ expect((await post(base, '/api/plugins/personal', {}, 'bevel_alice_agent')).status).toBe(200);
332
+ expect((await post(base, '/api/plugins', { name: 'Ops' }, 'bevel_alice')).status).toBe(201);
333
+ expect((await post(base, '/api/plugins', { name: 'Fin' }, 'session-alice')).status).toBe(201);
334
+ });
335
+
336
+ it('a narrowed connection with no scope resolver wired loads nothing, never Everything', async () => {
337
+ const base = await start();
338
+ expect((await post(base, '/api/plugins/personal', {}, 'bevel_alice_agent')).status).toBe(403);
339
+ expect(ensurePersonalPlugin).not.toHaveBeenCalled();
340
+ });
305
341
  });
@@ -19,7 +19,7 @@ export {
19
19
  type JoinRequestStatus,
20
20
  type JoinRequestStore,
21
21
  } from './join-request-records.store.js';
22
- export { PluginLinkIndex, type LinkMembership, type PluginLinks } from './plugin-links.js';
22
+ export { PluginLinkIndex, type IPluginLinkIndex, type LinkMembership, type PluginLinks } from './plugin-links.js';
23
23
  export { PluginLinksService, PluginLinkError } from './plugin-links.service.js';
24
24
  export { PluginRenameService, PluginRenameError, renamePluginPrincipalInText } from './plugin-rename.service.js';
25
25
  export { compileMarketplace, type VirtualTree, type CompileInput, type MarketplaceOptions } from './compile/compile-marketplace.js';