@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
@@ -155,6 +155,20 @@
155
155
  "when": 1792491290917,
156
156
  "tag": "0021_data_migrations_ledger",
157
157
  "breakpoints": true
158
+ },
159
+ {
160
+ "idx": 22,
161
+ "version": "7",
162
+ "when": 1792657514328,
163
+ "tag": "0022_session_version",
164
+ "breakpoints": true
165
+ },
166
+ {
167
+ "idx": 23,
168
+ "version": "7",
169
+ "when": 1793089896833,
170
+ "tag": "0023_agent_path",
171
+ "breakpoints": true
158
172
  }
159
173
  ]
160
174
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bevel-software/platform-core-backend",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "description": "Open-source core backend of the Bevel platform: git-backed knowledge workspace, workflow (branches/change requests/locks/SSE), skills, tools, secrets vault, access control and the remote MCP surface.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -55,8 +55,8 @@
55
55
  "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
56
56
  "yaml": "^2.9.0",
57
57
  "zod": "^3.24.0",
58
- "@bevel-software/platform-mcp-core": "0.33.0",
59
- "@bevel-software/platform-shared": "0.33.0"
58
+ "@bevel-software/platform-mcp-core": "0.34.0",
59
+ "@bevel-software/platform-shared": "0.34.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/adm-zip": "^0.5.8",
@@ -0,0 +1,69 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ /**
4
+ * `openMigratedScratchDb` owns the pool it opens until it hands the handle
5
+ * over: when the migrations fail, the caller never receives `close`, so the
6
+ * helper must close the pool itself and let the error through. No Postgres
7
+ * here — the connection, the migrations and the `pg` admin client are stubs,
8
+ * since the subject is the helper's ownership of what it opened, not the
9
+ * database.
10
+ */
11
+
12
+ const stubs = vi.hoisted(() => ({
13
+ db: { kind: 'fake-db' },
14
+ createDb: vi.fn(),
15
+ closeDb: vi.fn(async () => {}),
16
+ runCoreMigrations: vi.fn(async () => {}),
17
+ adminQuery: vi.fn(async () => ({ rows: [] })),
18
+ }));
19
+
20
+ vi.mock('../modules/database/connection.js', () => ({
21
+ createDb: stubs.createDb,
22
+ closeDb: stubs.closeDb,
23
+ }));
24
+ vi.mock('../modules/database/migrate.js', () => ({
25
+ runCoreMigrations: stubs.runCoreMigrations,
26
+ }));
27
+ vi.mock('../assets.js', () => ({ coreMigrationsDir: () => '/migrations' }));
28
+ vi.mock('pg', () => ({
29
+ default: {
30
+ Client: class {
31
+ connect = async () => {};
32
+ query = stubs.adminQuery;
33
+ end = async () => {};
34
+ },
35
+ },
36
+ }));
37
+
38
+ // Read once, at import: a URL the stubbed admin client will never dial.
39
+ process.env.TEST_DATABASE_URL = 'postgres://scratch:scratch@127.0.0.1:1/admin';
40
+ const { openMigratedScratchDb } = await import('./pg-scratch.js');
41
+
42
+ describe('openMigratedScratchDb', () => {
43
+ beforeEach(() => {
44
+ stubs.createDb.mockReturnValue(stubs.db);
45
+ });
46
+
47
+ afterEach(() => {
48
+ vi.clearAllMocks();
49
+ stubs.runCoreMigrations.mockResolvedValue(undefined);
50
+ });
51
+
52
+ it('hands the migrated database over with a close the caller owns', async () => {
53
+ const opened = await openMigratedScratchDb('hexis_owned');
54
+ expect(opened.db).toBe(stubs.db);
55
+ expect(stubs.runCoreMigrations).toHaveBeenCalledWith(stubs.db, '/migrations');
56
+ // The pool stays open until the caller closes it.
57
+ expect(stubs.closeDb).not.toHaveBeenCalled();
58
+ await opened.close();
59
+ expect(stubs.closeDb).toHaveBeenCalledWith(stubs.db);
60
+ });
61
+
62
+ it('closes the pool it opened when the migrations fail, and lets the failure through', async () => {
63
+ stubs.runCoreMigrations.mockRejectedValueOnce(new Error('migration 0022 failed'));
64
+ await expect(openMigratedScratchDb('hexis_failed')).rejects.toThrow('migration 0022 failed');
65
+ // Closed by the helper: the caller never received a handle to close.
66
+ expect(stubs.closeDb).toHaveBeenCalledTimes(1);
67
+ expect(stubs.closeDb).toHaveBeenCalledWith(stubs.db);
68
+ });
69
+ });
@@ -1,5 +1,8 @@
1
1
  import { randomBytes } from 'node:crypto';
2
2
  import pg from 'pg';
3
+ import { closeDb, createDb, type Database } from '../modules/database/connection.js';
4
+ import { runCoreMigrations } from '../modules/database/migrate.js';
5
+ import { coreMigrationsDir } from '../assets.js';
3
6
 
4
7
  /**
5
8
  * The one fixture for tests that cross a real Postgres. They run where
@@ -9,6 +12,11 @@ import pg from 'pg';
9
12
  * of its own, made by {@link scratchDatabaseUrl} and dropped by
10
13
  * {@link dropScratchDatabases}; the database the URL names is only connected
11
14
  * to, never written.
15
+ *
16
+ * A test that wants the schema as a deployment has it opens the database
17
+ * through {@link openMigratedScratchDb}: this fixture is the one place
18
+ * outside the database module that knows how a core database is connected
19
+ * to and migrated, so a module's test need not import those files itself.
12
20
  */
13
21
  export const PG_ADMIN_URL = process.env.TEST_DATABASE_URL;
14
22
 
@@ -38,6 +46,29 @@ export async function scratchDatabaseUrl(prefix: string): Promise<string> {
38
46
  return url.toString();
39
47
  }
40
48
 
49
+ /**
50
+ * A scratch database at the current core schema — created, connected to with
51
+ * the PII key given (a fresh random one when none is), and every core
52
+ * migration run — as a module's test wants it. `close` ends the connection;
53
+ * {@link dropScratchDatabases} drops the database afterwards as for any other.
54
+ */
55
+ export async function openMigratedScratchDb(
56
+ prefix: string,
57
+ opts: { piiKey?: string } = {},
58
+ ): Promise<{ db: Database; close: () => Promise<void> }> {
59
+ const piiKey = opts.piiKey ?? randomBytes(32).toString('base64');
60
+ const db = createDb(await scratchDatabaseUrl(prefix), { piiKey });
61
+ try {
62
+ await runCoreMigrations(db, coreMigrationsDir());
63
+ } catch (err) {
64
+ // The caller never receives the handle on this path, so the pool is
65
+ // closed here; the database itself is dropped with the others.
66
+ await closeDb(db);
67
+ throw err;
68
+ }
69
+ return { db, close: () => closeDb(db) };
70
+ }
71
+
41
72
  /** Drop every scratch database this file made; for `afterAll`, with {@link PG_TEST_TIMEOUT}. */
42
73
  export async function dropScratchDatabases(): Promise<void> {
43
74
  const names = created.splice(0);
@@ -0,0 +1,65 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { chmod, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { delimiter, join } from 'node:path';
5
+ import { promisify } from 'node:util';
6
+
7
+ /**
8
+ * A ripgrep that never finishes a TREE search, for the suites about the
9
+ * search budget and about stopping: an `rg` put first on this process's PATH,
10
+ * where the server finds its ripgrep. A run reading stdin (the pattern check,
11
+ * a document's text) is handed to the real `rg`, so a pattern is judged as it
12
+ * always is; a walk records its process id and sleeps for a minute, which no
13
+ * budget waits out — or, given `walkMs`, sleeps that long and then finishes
14
+ * having found nothing, which is how a suite spends a budget in parts.
15
+ *
16
+ * `alive()` answers which of the walks it started are still running, so a
17
+ * suite can assert that a stopped search left no process behind. `remove()`
18
+ * puts the PATH back.
19
+ */
20
+ export interface SlowRipgrep {
21
+ alive(): Promise<number[]>;
22
+ remove(): Promise<void>;
23
+ }
24
+
25
+ export async function slowRipgrep(opts: { walkMs?: number } = {}): Promise<SlowRipgrep> {
26
+ const seconds = opts.walkMs === undefined ? '60' : (opts.walkMs / 1000).toFixed(3);
27
+ const real = (await promisify(execFile)('sh', ['-c', 'command -v rg'])).stdout.trim();
28
+ const dir = await mkdtemp(join(tmpdir(), 'slow-rg-'));
29
+ const pids = join(dir, 'pids');
30
+ const binary = join(dir, 'rg');
31
+ await writeFile(
32
+ binary,
33
+ [
34
+ '#!/bin/sh',
35
+ 'for a in "$@"; do last="$a"; done',
36
+ `if [ "$last" = "-" ]; then exec '${real}' "$@"; fi`,
37
+ `echo $$ >> '${pids}'`,
38
+ `exec sleep ${seconds}`,
39
+ '',
40
+ ].join('\n'),
41
+ );
42
+ await chmod(binary, 0o755);
43
+ const path = process.env.PATH;
44
+ process.env.PATH = `${dir}${delimiter}${path ?? ''}`;
45
+ return {
46
+ async alive() {
47
+ const started = (await readFile(pids, 'utf8').catch(() => ''))
48
+ .split('\n')
49
+ .filter(Boolean)
50
+ .map(Number);
51
+ return started.filter((pid) => {
52
+ try {
53
+ process.kill(pid, 0);
54
+ return true;
55
+ } catch {
56
+ return false;
57
+ }
58
+ });
59
+ },
60
+ async remove() {
61
+ process.env.PATH = path;
62
+ await rm(dir, { recursive: true, force: true });
63
+ },
64
+ };
65
+ }
@@ -0,0 +1,34 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { mkdtemp, rm, symlink } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { promisify } from 'node:util';
6
+ import { afterEach, describe, expect, it } from 'vitest';
7
+ import { createCoreServices } from '../../index.js';
8
+ import type { TenantConfig } from '../../core-config.js';
9
+
10
+ /**
11
+ * The server's boot, through the package's own entry, on a PATH that carries
12
+ * git and no ripgrep: it refuses to build any service, naming the binary.
13
+ */
14
+ describe('booting without ripgrep', () => {
15
+ const path = process.env.PATH;
16
+ let bin = '';
17
+ afterEach(async () => {
18
+ process.env.PATH = path;
19
+ if (bin) await rm(bin, { recursive: true, force: true });
20
+ bin = '';
21
+ });
22
+
23
+ it('refuses to start, naming rg and the Dockerfile that installs it', async () => {
24
+ const git = (await promisify(execFile)('sh', ['-c', 'command -v git'])).stdout.trim();
25
+ bin = await mkdtemp(join(tmpdir(), 'no-rg-'));
26
+ await symlink(git, join(bin, 'git'));
27
+ process.env.PATH = bin;
28
+ // The binaries are checked before the configuration is read, so none is needed.
29
+ const err = (await createCoreServices({} as TenantConfig).catch((e: unknown) => e)) as Error;
30
+ expect(err).toBeInstanceOf(Error);
31
+ expect(err.message).toMatch(/^ripgrep \(`rg`\) is required on the server's PATH but could not be run: .*ENOENT/);
32
+ expect(err.message).toContain('The Hexis Dockerfile installs it');
33
+ });
34
+ });
@@ -4,6 +4,7 @@ import type { Server } from 'node:http';
4
4
  import { afterEach, describe, expect, it, vi } from 'vitest';
5
5
  import type { SkillSummary } from '../../modules/skills/skills.contract.js';
6
6
  import { catalogRevision, createCatalogRevisionRoutes } from '../catalog-revision.js';
7
+ import { agentFile, agentScopeOver, inMemoryKb, type InMemoryKb } from '../../modules/agent-scope/__tests__/in-memory-kb.js';
7
8
 
8
9
  /**
9
10
  * The fingerprint a long-lived client polls to learn that the released catalog
@@ -228,3 +229,91 @@ describe('GET /agent/catalog-revision', () => {
228
229
  expect(await res.json()).toEqual({ revision: catalogRevision([], []), tools: 0, skills: 0 });
229
230
  });
230
231
  });
232
+
233
+ describe('GET /agent/catalog-revision, for a connection made as an .agent file', () => {
234
+ let http: Server | null = null;
235
+
236
+ afterEach(async () => {
237
+ if (http) await new Promise<void>((resolve) => http!.close(() => resolve()));
238
+ http = null;
239
+ });
240
+
241
+ const lines = [
242
+ ['ops', 'ops', 'Plugins/Ops/ops.tool', 'http', 'remote', '', 's'].join('\u0000'),
243
+ ['crm', 'crm', 'Plugins/Sales/crm.tool', 'http', 'remote', '', 's'].join('\u0000'),
244
+ ];
245
+ const paths = ['Plugins/Ops/ops.tool', 'Plugins/Sales/crm.tool'];
246
+ const skills = [skill({ name: 'notes', path: 'Skills/Release/notes' }), skill({ name: 'pitch', path: 'Skills/Sales/pitch' })];
247
+
248
+ async function mount(kb: InMemoryKb, agentPath: string | undefined) {
249
+ // The two services narrow by the predicate they are handed, AFTER their
250
+ // own read gate — stood in for here by the lists above, all readable.
251
+ const catalogFingerprints = vi.fn(async (_email: string, within?: (p: string) => boolean) =>
252
+ lines.filter((_, i) => !within || within(paths[i]!)),
253
+ );
254
+ const listSkills = vi.fn(async (_email: string, opts?: { within?: (p: string) => boolean }) =>
255
+ skills.filter((s) => !opts?.within || opts.within(s.path)),
256
+ );
257
+ const agentScope = agentScopeOver(kb);
258
+ const resolve = vi.spyOn(agentScope, 'resolve');
259
+ const app = express();
260
+ app.use(
261
+ createCatalogRevisionRoutes({
262
+ toolManuals: { catalogFingerprints } as never,
263
+ skills: { listSkills } as never,
264
+ manualAuth: (req, _res, next) => {
265
+ req.toolAuth = { userId: 'u1', ...(agentPath ? { agentPath } : {}) } as never;
266
+ next();
267
+ },
268
+ resolveUserEmail: async () => 'ada@example.com',
269
+ agentScope,
270
+ }),
271
+ );
272
+ http = app.listen(0);
273
+ await new Promise<void>((r) => http!.once('listening', r));
274
+ const url = `http://127.0.0.1:${(http.address() as AddressInfo).port}/agent/catalog-revision`;
275
+ const read = async () =>
276
+ (await (await fetch(url)).json()) as { revision: string; tools: number; skills: number; connection?: unknown };
277
+ return { read, catalogFingerprints, listSkills, resolve };
278
+ }
279
+
280
+ it('Everything is the revision it always was: nothing resolved, nothing narrowed, no `connection`', async () => {
281
+ const { read, catalogFingerprints, listSkills, resolve } = await mount(inMemoryKb({}), undefined);
282
+ expect(await read()).toEqual({ revision: catalogRevision(lines, skills), tools: 2, skills: 2 });
283
+ expect(catalogFingerprints).toHaveBeenCalledWith('ada@example.com');
284
+ expect(listSkills).toHaveBeenCalledWith('ada@example.com');
285
+ expect(resolve).not.toHaveBeenCalled();
286
+ });
287
+
288
+ it("is the fingerprint of what the agent loads, and names the agent", async () => {
289
+ const kb = inMemoryKb({ 'Agents/ops.agent': agentFile('name: ops\nconnectors: [Plugins/Ops]\nskills: [Skills/Release]') });
290
+ const { read } = await mount(kb, 'Agents/ops.agent');
291
+ const body = await read();
292
+ expect(body.tools).toBe(1);
293
+ expect(body.skills).toBe(1);
294
+ expect(body.connection).toEqual({ agent: { path: 'Agents/ops.agent', name: 'ops' } });
295
+ expect(body.revision).not.toBe(catalogRevision(lines, skills));
296
+ });
297
+
298
+ it('moves at the next read after the .agent file is edited — no reconnect', async () => {
299
+ const kb = inMemoryKb({ 'Agents/ops.agent': agentFile('connectors: [Plugins/Ops]') });
300
+ const { read } = await mount(kb, 'Agents/ops.agent');
301
+ const before = await read();
302
+ expect(await read()).toEqual(before);
303
+ kb.files.set('Agents/ops.agent', agentFile('connectors: [Plugins/Ops]\nskills: [Skills/Sales]'));
304
+ const after = await read();
305
+ expect(after.revision).not.toBe(before.revision);
306
+ expect(after.skills).toBe(1);
307
+ // Even an edit that changes nothing listed moves it: the file is the configuration.
308
+ kb.files.set('Agents/ops.agent', agentFile('connectors: [Plugins/Ops]\nskills: [Skills/Sales]\ndescription: now described'));
309
+ expect((await read()).revision).not.toBe(after.revision);
310
+ });
311
+
312
+ it('a deleted file is an empty catalog with the reason, never Everything', async () => {
313
+ const { read } = await mount(inMemoryKb({}), 'Agents/gone.agent');
314
+ const body = await read();
315
+ expect(body.tools).toBe(0);
316
+ expect(body.skills).toBe(0);
317
+ expect((body.connection as { unavailable?: string }).unavailable).toMatch(/no longer exists or which you can no longer read/);
318
+ });
319
+ });
@@ -16,7 +16,7 @@ import { RoutineWritePolicyService } from '../../modules/workspace/routine-write
16
16
  import { WorkflowHooks } from '../../modules/workflow/workflow-hooks.js';
17
17
  import { ToolDescriptionNotes } from '../../modules/workspace/agent-access.gate.js';
18
18
  import { SpillStore } from '../../modules/workspace/spill-store.js';
19
- import { WorkerPatternMatcher } from '../../modules/workspace/bounded-matcher.js';
19
+ import { RipgrepPatternMatcher } from '../../modules/workspace/index.js';
20
20
  import { DocExtractService, type UnreadableDocument } from '../../modules/workspace/file-readers/doc-extract.service.js';
21
21
  import type { IDocumentExtractor } from '../../modules/workspace/file-readers/document-extractor.contract.js';
22
22
  import { WorkerDocumentExtractor, type WorkerDocumentExtractorOptions } from '../../modules/workspace/file-readers/extract-in-worker.js';
@@ -82,7 +82,7 @@ async function startPlatform(extractorOpts: WorkerDocumentExtractorOptions = {})
82
82
  };
83
83
  const told: UnreadableDocument[] = [];
84
84
  const docExtract = new DocExtractService(join(tempDir, '.doc-extract'), counting, { onUnreadable: (d) => told.push(d) });
85
- const matcher = new WorkerPatternMatcher();
85
+ const matcher = new RipgrepPatternMatcher();
86
86
 
87
87
  const registry = new ToolRegistry();
88
88
  const resolve = async (auth: ToolAuth, signal: AbortSignal, sessionId?: string): Promise<ToolContext> => ({
@@ -37,7 +37,7 @@ afterEach(async () => {
37
37
 
38
38
  /** A core whose startup phase fails in a way the test picks. */
39
39
  function bootable(runAll: () => Promise<void>) {
40
- const retries = { started: 0 };
40
+ const retries = { started: 0, tmpSweeps: 0 };
41
41
  const core: BootableCore = {
42
42
  config: { workspacesRoot },
43
43
  kbDirName: 'knowledge-base',
@@ -53,6 +53,11 @@ function bootable(runAll: () => Promise<void>) {
53
53
  tidyAfterSweep: async () => ({ closedEmpty: 0, removedLeftovers: 0 }),
54
54
  } as BootableCore['workflowService'],
55
55
  pluginJoinRequestJobs: { startSweeping() {} } as BootableCore['pluginJoinRequestJobs'],
56
+ tmpSweeper: {
57
+ startSweeping() {
58
+ retries.tmpSweeps += 1;
59
+ },
60
+ },
56
61
  startupRetry: null,
57
62
  };
58
63
  return { core, retries };
@@ -79,6 +84,8 @@ describe('startCore over a repository the host will not serve', () => {
79
84
  // that is granted access to it, needs no settings change on this side.
80
85
  expect(retries.started).toBe(1);
81
86
  expect(core.startupRetry).not.toBeNull();
87
+ // The tmp sweep reads the workspaces root alone, so a gated boot still runs it.
88
+ expect(retries.tmpSweeps).toBe(1);
82
89
  });
83
90
 
84
91
  it('comes up gated when the host rejected the credentials', async () => {
@@ -5,7 +5,10 @@ import { createShutdown, stopCore, withStartupTask, type LeasedWorker } from '..
5
5
  import { holdCommitWorkerLease, holdable } from '../lifecycle.js';
6
6
  import { BevelSecretsVariableLoader, registerBevelSecretsVariableLoader } from '../../modules/secrets-vault/secrets-variable-loader.js';
7
7
  import type { AdvisoryLease } from '../../modules/database/advisory-lock.js';
8
- import { WorkerPatternMatcher } from '../../modules/workspace/bounded-matcher.js';
8
+ import { realpath } from 'node:fs/promises';
9
+ import { tmpdir } from 'node:os';
10
+ import { RipgrepPatternMatcher } from '../../modules/workspace/index.js';
11
+ import { slowRipgrep } from '../../__tests__/slow-ripgrep.js';
9
12
 
10
13
  /** A lease the suite scripts: what each `tryAcquire` answers, and a way to lose it. */
11
14
  function fakeLease(answers: boolean[]) {
@@ -510,6 +513,24 @@ describe('stopCore', () => {
510
513
  expect(drained).toBeLessThan(g.order.indexOf('db.end'));
511
514
  });
512
515
 
516
+ it('stops the workspaces\' tmp sweep with the graph: the timer cleared, the sweep in flight awaited before the pool goes', async () => {
517
+ const g = graph();
518
+ const tmpSweeper = {
519
+ stopSweeping() {
520
+ g.order.push('tmpSweeper.stopSweeping');
521
+ },
522
+ async drainSweep() {
523
+ g.order.push('tmpSweeper.drainSweep');
524
+ },
525
+ };
526
+ await stopCore({ ...g.core, tmpSweeper } as never);
527
+ const stopped = g.order.indexOf('tmpSweeper.stopSweeping');
528
+ const drained = g.order.indexOf('tmpSweeper.drainSweep');
529
+ expect(stopped).toBeGreaterThanOrEqual(0);
530
+ expect(stopped).toBeLessThan(drained);
531
+ expect(drained).toBeLessThan(g.order.indexOf('db.end'));
532
+ });
533
+
513
534
  it('forgets the graph\'s secrets scope, so a descriptor that outlives it resolves nothing', async () => {
514
535
  const g = graph();
515
536
  registerBevelSecretsVariableLoader(
@@ -538,19 +559,31 @@ describe('stopCore', () => {
538
559
  ]);
539
560
  });
540
561
 
541
- it('ends the grep matcher\'s workers, so an evicted graph leaves no search running behind it', async () => {
562
+ it('ends the grep matcher\'s running searches, so an evicted graph leaves no ripgrep running behind it', async () => {
542
563
  const g = graph();
543
- const patternMatcher = new WorkerPatternMatcher();
544
- // A search holding a worker for its whole 5 s budget when the graph is stopped.
545
- const running = patternMatcher.search('^(a+)+$', '').matchingLines([`${'a'.repeat(39)}!`], 1);
546
- await new Promise((r) => setTimeout(r, 100));
547
- const started = Date.now();
548
- await stopCore({ ...g.core, patternMatcher } as never);
549
- await expect(running).rejects.toMatchObject({ status: 503, details: { code: 'search_busy' } });
550
- expect(Date.now() - started).toBeLessThan(1_000);
551
- // Closed for good: nothing after the stop starts a worker.
552
- await expect(patternMatcher.search('b', '').matchingLines(['b'], 1)).rejects.toMatchObject({ status: 503 });
553
- expect(g.order.at(-1)).toBe('db.end');
564
+ const slow = await slowRipgrep();
565
+ const patternMatcher = new RipgrepPatternMatcher();
566
+ try {
567
+ // A tree search that would hold its ripgrep for the whole 5 s budget when the graph is stopped.
568
+ const search = await patternMatcher.search('needle', { ignoreCase: false });
569
+ // A scope's cwd is canonical: where the temp folder is a link (macOS),
570
+ // the raw spelling would read as a root that has moved, and walk nothing.
571
+ const cwd = await realpath(tmpdir());
572
+ const running = (async () => {
573
+ for await (const file of search.files({ cwd, root: '.' }, { only: ['hit.md'], maxLinesPerFile: 1 })) void file;
574
+ })();
575
+ while ((await slow.alive()).length === 0) await new Promise((r) => setTimeout(r, 20));
576
+ const started = Date.now();
577
+ await stopCore({ ...g.core, patternMatcher } as never);
578
+ await expect(running).rejects.toMatchObject({ status: 503, details: { code: 'search_busy' } });
579
+ expect(Date.now() - started).toBeLessThan(1_000);
580
+ expect(await slow.alive()).toEqual([]);
581
+ // Closed for good: nothing after the stop starts a search.
582
+ await expect(patternMatcher.search('b', { ignoreCase: false })).rejects.toMatchObject({ status: 503 });
583
+ expect(g.order.at(-1)).toBe('db.end');
584
+ } finally {
585
+ await slow.remove();
586
+ }
554
587
  });
555
588
 
556
589
  it('does not wait past the budget for a worker that never stops, and still ends the pool', async () => {
@@ -4,6 +4,7 @@ import { logger } from '../shared/logging.js';
4
4
  import type { ISkillService, SkillSummary } from '../modules/skills/skills.contract.js';
5
5
  import type { IToolManualService } from '../modules/tool-manuals/tool-manuals.contract.js';
6
6
  import '../modules/tool-auth/tool-auth.middleware.js'; // Express Request augmentation (req.toolAuth)
7
+ import { resolveConnectionScope, type IAgentScopeResolver } from '../modules/agent-scope/index.js';
7
8
 
8
9
  const log = logger('catalog-revision');
9
10
 
@@ -65,6 +66,14 @@ export function catalogRevision(
65
66
  * server already running keeps serving the view it read at startup.
66
67
  */
67
68
  views: readonly { uri: string; text: string }[] = [],
69
+ /**
70
+ * The connection's agent configuration — its path and the digest of its bytes, or
71
+ * why it cannot be used (`ConnectionScope.fingerprint`) — so an edit of
72
+ * the file is a new revision even when it changes nothing listed (a new
73
+ * skill entry that names nothing yet still moves it). Absent for
74
+ * Everything, whose revision is exactly what it was before agents existed.
75
+ */
76
+ agent: string | null = null,
68
77
  ): string {
69
78
  const lines = [
70
79
  ...[...manuals].sort(),
@@ -72,6 +81,7 @@ export function catalogRevision(
72
81
  ...skills.map(skillFingerprint).sort(),
73
82
  '\u0001',
74
83
  ...views.map((v) => `${v.uri}\u0000${createHash('sha256').update(v.text).digest('hex')}`).sort(),
84
+ ...(agent !== null ? ['\u0001', agent] : []),
75
85
  ];
76
86
  return createHash('sha256').update(lines.join('\n')).digest('hex').slice(0, 32);
77
87
  }
@@ -90,6 +100,8 @@ export function createCatalogRevisionRoutes(deps: {
90
100
  toolManuals: Pick<IToolManualService, 'catalogFingerprints'>;
91
101
  skills: Pick<ISkillService, 'listSkills'>;
92
102
  manualAuth: RequestHandler;
103
+ /** Resolves the scope of a connection made as an agent configuration (`modules/agent-scope`). */
104
+ agentScope?: Pick<IAgentScopeResolver, 'resolve'>;
93
105
  /** Connection-key/internal-token user id → email, for the per-caller ACL. */
94
106
  resolveUserEmail: (userId: string) => Promise<string | undefined>;
95
107
  /** The MCP App views served; absent on a deployment that serves none. */
@@ -104,16 +116,26 @@ export function createCatalogRevisionRoutes(deps: {
104
116
  // No resolvable caller is an EMPTY catalog, not a failure: that is what
105
117
  // both list surfaces answer such a caller, so the fingerprint agrees
106
118
  // with them rather than inventing a third verdict.
119
+ // A connection made as an agent configuration is fingerprinted over what IT
120
+ // loads, plus the file itself; Everything asks exactly what it always did.
121
+ const scope = await resolveConnectionScope(deps.agentScope, email, req.toolAuth?.agentPath);
122
+ const connectors = scope.connectorFilter();
123
+ const within = scope.skillFilter();
107
124
  const [manuals, skills] = email
108
- ? await Promise.all([deps.toolManuals.catalogFingerprints(email), deps.skills.listSkills(email)])
125
+ ? await Promise.all([
126
+ connectors ? deps.toolManuals.catalogFingerprints(email, connectors) : deps.toolManuals.catalogFingerprints(email),
127
+ within ? deps.skills.listSkills(email, { within }) : deps.skills.listSkills(email),
128
+ ])
109
129
  : [[], []];
110
130
  // A manifest that cannot be read counts as no views, which is also what
111
131
  // the MCP endpoint serves in that state.
112
132
  const views = deps.mcpApps ? ((await deps.mcpApps.manifest().catch(() => null))?.resources ?? []) : [];
133
+ const connection = scope.toWire();
113
134
  res.json({
114
- revision: catalogRevision(manuals, skills, views),
135
+ revision: catalogRevision(manuals, skills, views, scope.fingerprint()),
115
136
  tools: manuals.length,
116
137
  skills: skills.length,
138
+ ...(connection ? { connection } : {}),
117
139
  });
118
140
  } catch (err) {
119
141
  log.error('catalog-revision failed:', { err });
@@ -10,7 +10,34 @@ import type { IAccountAdmission } from '../modules/auth/account-admission.js';
10
10
  import type { IWriteAccess } from '../modules/write-access/write-access.js';
11
11
  import type { OnServerStart } from '../modules/workspace/startup/on-server-start.js';
12
12
  import type { AgentGuideHook } from '../modules/agent-guide/agent-guide.js';
13
+ import type { IAgentScopeResolver } from '../modules/agent-scope/index.js';
13
14
  import type { EmbedNodeIdResolver } from '../modules/embed/embed.interface.js';
15
+ import type { IWorkspaceService } from '@bevel-software/platform-shared';
16
+ import type { IAccessControl } from '../modules/access/index.js';
17
+ import type { KbContext } from '../shared/kb-context.js';
18
+ import type { ITreeWalker } from '../shared/fs.contract.js';
19
+ import type { IPluginLinkIndex } from '../modules/plugins/index.js';
20
+
21
+ /**
22
+ * The core services a distribution's agent-scope resolver reads through,
23
+ * handed to `CorePorts.agentScope` when core makes it: the clones of the
24
+ * branches, the person's read gate, the branch model and knowledge-base
25
+ * layout, the disk walk that finds its configurations, and the plugin link
26
+ * index that says which plugins a `plugins:`-style entry stands for.
27
+ *
28
+ * Each is the service's CONTRACT, not its class: a resolver written against
29
+ * these runs on any implementation of them — core's own services in a
30
+ * deployment, a stand-in in a distribution's tests — and a resolver that
31
+ * needs more than a contract offers asks for the contract to grow, not for
32
+ * the class.
33
+ */
34
+ export interface AgentScopeCollaborators {
35
+ workspaceService: IWorkspaceService;
36
+ accessControl: IAccessControl;
37
+ kb: KbContext;
38
+ disk: ITreeWalker;
39
+ pluginLinkIndex: IPluginLinkIndex;
40
+ }
14
41
 
15
42
  /**
16
43
  * Every seam the enterprise overlay can fill in the CORE composition
@@ -112,6 +139,21 @@ export interface CorePorts {
112
139
  * `modules/agent-guide`.
113
140
  */
114
141
  agentGuide?: AgentGuideHook;
142
+ /**
143
+ * THIS distribution's agent configurations: what "Connect as" offers beside
144
+ * Everything, and what a connection made as one of them loads (see
145
+ * `modules/agent-scope`). Core owns the mechanism and no configuration —
146
+ * the file kind, where it lives and what its definition says are the
147
+ * distribution's, as the file kinds registered for access verbs are. A
148
+ * factory, since a resolver reads through core services that exist only
149
+ * once core is built: called ONCE, during construction, with those
150
+ * services (`AgentScopeCollaborators`), and its answer is the one resolver
151
+ * every surface consults from then on — the tool contexts, which capture it
152
+ * at construction, as much as the routes the server mounts later. Core
153
+ * default: `noAgentConfigurations()` — nothing is offered, and a connection
154
+ * recorded as some path loads nothing, never Everything.
155
+ */
156
+ agentScope?: (core: AgentScopeCollaborators) => IAgentScopeResolver;
115
157
  /**
116
158
  * Mirror this composition's branch model and knowledge-base layout onto the
117
159
  * shared package's PROCESS-WIDE live bindings (`DEFAULT_BRANCH`,