@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
@@ -0,0 +1,83 @@
1
+ import { ConnectionScope } from './connection-scope.js';
2
+
3
+ /** One agent configuration a person may connect as — a row of the "Connect as" choice. */
4
+ export interface AgentChoice {
5
+ /** Repo-relative path of the configuration — what the connection records. */
6
+ path: string;
7
+ /** Its name, as the choice shows it. */
8
+ name: string;
9
+ /** Its description, one line; empty when it has none. */
10
+ description: string;
11
+ }
12
+
13
+ /**
14
+ * The connection-scope mechanism: which agent configurations a person may
15
+ * connect as, and what a connection made as one may load right now.
16
+ *
17
+ * Core owns the MECHANISM and none of the configurations: what an agent
18
+ * configuration is — the file kind, where it lives, what its definition
19
+ * says — is the distribution's (`CorePorts.agentScope`), exactly as the
20
+ * file kinds whose frontmatter carries access verbs are
21
+ * (`registerAccessFrontmatterExtensions`). Core itself offers no agent to
22
+ * connect as ({@link noAgentConfigurations}): every connection it makes is
23
+ * Everything, and a connection recorded as some path by a distribution that
24
+ * is no longer there loads nothing, never Everything.
25
+ *
26
+ * Every agent surface resolves its caller's scope here, with the agent path
27
+ * its credential carries — a connection key's `api_tokens.agent_path`, an
28
+ * agent connection's `agent_connections.agent_path` — and asks the scope;
29
+ * none reads a configuration itself.
30
+ */
31
+ export interface IAgentScopeResolver {
32
+ /**
33
+ * The scope of a connection made as `agentPath` for the person `userEmail`.
34
+ * No path is Everything. The configuration is read, through the person's
35
+ * read gate, on EVERY call — the connection follows the configuration, not
36
+ * a copy of it — and one that is missing, unreadable to the person or not
37
+ * a valid definition yields a scope that loads nothing, never Everything.
38
+ */
39
+ resolve(userEmail: string | undefined, agentPath: string | null | undefined): Promise<ConnectionScope>;
40
+ /**
41
+ * The agent configurations `userEmail` may connect as, by name — what the
42
+ * consent step and the key-mint dialog offer below Everything. Rejects
43
+ * when they cannot be listed at all (the default branch cannot be opened):
44
+ * that is a failed listing, which the caller says, never an empty one.
45
+ */
46
+ listChoices(userEmail: string): Promise<AgentChoice[]>;
47
+ /**
48
+ * `agentPath` normalized when it is one of {@link listChoices}' paths, else
49
+ * null — one answer for a configuration that does not exist and one the
50
+ * person may not read, so a refused choice says nothing about either.
51
+ */
52
+ choosable(userEmail: string, agentPath: string): Promise<string | null>;
53
+ }
54
+
55
+ /**
56
+ * The resolver of a distribution that has no agent configurations — core's
57
+ * own default. Nothing is offered, nothing is choosable, and a recorded path
58
+ * resolves to a scope that loads nothing: a narrowing that cannot be read is
59
+ * never read as Everything.
60
+ */
61
+ export function noAgentConfigurations(): IAgentScopeResolver {
62
+ return {
63
+ resolve: async (_userEmail, agentPath) => (agentPath ? ConnectionScope.gone(agentPath) : ConnectionScope.everything()),
64
+ listChoices: async () => [],
65
+ choosable: async () => null,
66
+ };
67
+ }
68
+
69
+ /**
70
+ * The scope of a caller whose credential connects as `agentPath` — THE way
71
+ * every agent surface turns a credential into a scope. No path is
72
+ * Everything, without asking anything. A path with no resolver to read it
73
+ * (a construction that wired none) loads nothing: a narrowing that cannot be
74
+ * read is never read as Everything.
75
+ */
76
+ export async function resolveConnectionScope(
77
+ resolver: Pick<IAgentScopeResolver, 'resolve'> | undefined,
78
+ userEmail: string | undefined,
79
+ agentPath: string | null | undefined,
80
+ ): Promise<ConnectionScope> {
81
+ if (!agentPath) return ConnectionScope.everything();
82
+ return resolver ? resolver.resolve(userEmail, agentPath) : ConnectionScope.gone(agentPath);
83
+ }
@@ -0,0 +1,199 @@
1
+ import { pathUnderRoot } from '@bevel-software/platform-shared';
2
+ import {
3
+ NOT_AVAILABLE_TO_CONNECTION,
4
+ connectionRefusal,
5
+ describeConnection,
6
+ type ConnectionScopeWire,
7
+ type ScopeAgent,
8
+ } from '@bevel-software/platform-mcp-core';
9
+
10
+ // The wire and its words are the MCP core's (`connection-scope-wire.ts`),
11
+ // so the hosted proxy and the local server read and word a connection the
12
+ // same way; offered here too, as this module's callers learnt them.
13
+ export { NOT_AVAILABLE_TO_CONNECTION, connectionRefusal, describeConnection, type ConnectionScopeWire, type ScopeAgent };
14
+
15
+ /**
16
+ * The resolved roots a usable agent configuration narrows a connection to — what
17
+ * {@link ConnectionScope.narrowed} is built from.
18
+ */
19
+ export interface ScopeRoots {
20
+ /** Skill folders and subtrees: the `skills:` entries, each plugin folder, and the skills its manifest links. */
21
+ skills: readonly string[];
22
+ /** Connector files and subtrees: the `connectors:` entries and each plugin folder. */
23
+ connectors: readonly string[];
24
+ /** Platform tool names; null for all of them. */
25
+ platformTools: readonly string[] | null;
26
+ }
27
+
28
+ type State =
29
+ | { kind: 'everything' }
30
+ | { kind: 'narrowed'; agent: ScopeAgent; roots: ScopeRoots; fingerprint: string }
31
+ | { kind: 'unavailable'; agent: ScopeAgent; sentence: string; fingerprint: string };
32
+
33
+ /**
34
+ * What one connection may load — the ONE answer every agent surface asks
35
+ * (`all-tools`, the `/agent/utcp` manual, the tool route, the skill tools,
36
+ * the setup tools, the catalog revision). Everything is the scope of every
37
+ * connection made without a choice, and asks nothing of anyone: each of its
38
+ * predicates is true, so a surface that consults it serves exactly what it
39
+ * served before connections could be narrowed.
40
+ *
41
+ * A scope only ever NARROWS. It is applied to what the person may already
42
+ * read — each surface asks it about items that passed the person's own read
43
+ * gate — so no list in an agent configuration can add an item to a connection.
44
+ */
45
+ export class ConnectionScope {
46
+ private constructor(private readonly state: State) {}
47
+
48
+ static everything(): ConnectionScope {
49
+ return new ConnectionScope({ kind: 'everything' });
50
+ }
51
+
52
+ static narrowed(agent: ScopeAgent, roots: ScopeRoots, fingerprint: string): ConnectionScope {
53
+ return new ConnectionScope({ kind: 'narrowed', agent, roots, fingerprint });
54
+ }
55
+
56
+ /** A chosen file that cannot be used: nothing at all is loaded, and `sentence` says why. */
57
+ static unavailable(agent: ScopeAgent, sentence: string, fingerprint: string): ConnectionScope {
58
+ return new ConnectionScope({ kind: 'unavailable', agent, sentence, fingerprint });
59
+ }
60
+
61
+ /**
62
+ * A chosen file that is not there for the person — missing, or one they may
63
+ * no longer read: ONE answer for both, so the sentence discloses neither.
64
+ */
65
+ static gone(agentPath: string): ConnectionScope {
66
+ return ConnectionScope.unavailable(
67
+ { path: agentPath, name: agentPath },
68
+ `This connection was made as the agent \`${agentPath}\`, which no longer exists or which you can no ` +
69
+ 'longer read, so it loads no tools and no skills. Reconnect and choose another agent, or Everything.',
70
+ `agent\u0000${agentPath}\u0000gone`,
71
+ );
72
+ }
73
+
74
+ /** True for Everything: the connection was made without choosing an agent. */
75
+ isEverything(): boolean {
76
+ return this.state.kind === 'everything';
77
+ }
78
+
79
+ /** Whether the skill in `skillFolder` (repo-relative) is loaded. */
80
+ allowsSkill(skillFolder: string): boolean {
81
+ const s = this.state;
82
+ if (s.kind === 'everything') return true;
83
+ if (s.kind === 'unavailable') return false;
84
+ return s.roots.skills.some((root) => pathUnderRoot(skillFolder, root));
85
+ }
86
+
87
+ /** Whether the connector defined at `definitionPath` (a `.tool` file or an MCP server definition) is loaded. */
88
+ allowsConnector(definitionPath: string): boolean {
89
+ const s = this.state;
90
+ if (s.kind === 'everything') return true;
91
+ if (s.kind === 'unavailable') return false;
92
+ return s.roots.connectors.some((root) => pathUnderRoot(definitionPath, root));
93
+ }
94
+
95
+ /**
96
+ * Whether any connector this connection loads is defined in `folder`
97
+ * (repo-relative) or in a folder that contains it — what a plugin's
98
+ * archive asks, since the local MCP server materializes a plugin only to
99
+ * run a connector it defines.
100
+ */
101
+ loadsConnectorIn(folder: string): boolean {
102
+ const s = this.state;
103
+ if (s.kind === 'everything') return true;
104
+ if (s.kind === 'unavailable') return false;
105
+ return s.roots.connectors.some((root) => pathUnderRoot(folder, root) || pathUnderRoot(root, folder));
106
+ }
107
+
108
+ /**
109
+ * {@link allowsSkill} as the narrowing a skill listing takes, or undefined
110
+ * for Everything — so an Everything caller's listing runs exactly the code
111
+ * it ran before connections could be narrowed.
112
+ */
113
+ skillFilter(): ((skillFolder: string) => boolean) | undefined {
114
+ return this.state.kind === 'everything' ? undefined : (p) => this.allowsSkill(p);
115
+ }
116
+
117
+ /** {@link allowsConnector} as the narrowing a catalog read takes, or undefined for Everything. */
118
+ connectorFilter(): ((definitionPath: string) => boolean) | undefined {
119
+ return this.state.kind === 'everything' ? undefined : (p) => this.allowsConnector(p);
120
+ }
121
+
122
+ /**
123
+ * Whether the connection's surface may be discovered: its skills read as
124
+ * MCP prompts, and its local-only connectors read by the local server so
125
+ * it can materialize them. Neither is a platform tool, so `platformTools`
126
+ * has no say — an agent that names skills gets them as prompts, and one
127
+ * that names connectors gets them served, whatever tools it is offered.
128
+ * False only for a file that cannot be used, which loads nothing at all.
129
+ */
130
+ allowsDiscovery(): boolean {
131
+ return this.state.kind !== 'unavailable';
132
+ }
133
+
134
+ /** Whether the platform's own tool `name` is loaded. */
135
+ allowsPlatformTool(name: string): boolean {
136
+ const s = this.state;
137
+ if (s.kind === 'everything') return true;
138
+ if (s.kind === 'unavailable') return false;
139
+ return s.roots.platformTools === null || s.roots.platformTools.includes(name);
140
+ }
141
+
142
+ /**
143
+ * The answer to a call of `toolName`, a tool this connection does not load:
144
+ * names the tool and the agent, so the caller knows the tool was refused by
145
+ * the connection's choice and not missing from the deployment. For a file
146
+ * that cannot be used, the sentence that says why.
147
+ */
148
+ refusal(toolName: string): string {
149
+ return connectionRefusal(toolName, this.toWire());
150
+ }
151
+
152
+ /**
153
+ * THE policy for a call of the platform tool `toolName` by this connection:
154
+ * null when the call may proceed, else the body the route answers with a
155
+ * 403 — the refusal naming the tool and the agent, and the code that says
156
+ * it was the connection's choice. Every route that IS a platform tool asks
157
+ * this, whether `toolHandler` hosts it or it is mounted on its own (the
158
+ * plugin-creation routes), so the rule is written once and the answers
159
+ * cannot drift. `discovery` marks a route that is a discovery read of the
160
+ * surface (the skill routes, the local-only tool list), which a narrowed
161
+ * connection is answered even when its `platformTools` omits the tool;
162
+ * a connection that loads nothing is refused it like everything else.
163
+ */
164
+ platformToolRefusal(
165
+ toolName: string,
166
+ opts: { discovery?: boolean } = {},
167
+ ): { error: string; code: typeof NOT_AVAILABLE_TO_CONNECTION } | null {
168
+ if (this.allowsPlatformTool(toolName)) return null;
169
+ if (opts.discovery === true && this.allowsDiscovery()) return null;
170
+ return { error: this.refusal(toolName), code: NOT_AVAILABLE_TO_CONNECTION };
171
+ }
172
+
173
+ /**
174
+ * One line for the text an agent is told about its connection (the MCP
175
+ * handshake, the agent guide); null for Everything, which is told nothing
176
+ * new.
177
+ */
178
+ describe(): string | null {
179
+ return describeConnection(this.toWire());
180
+ }
181
+
182
+ /**
183
+ * What moves the catalog revision when the agent's FILE changes — its path
184
+ * and a digest of its bytes, or the reason it cannot be used. Null for
185
+ * Everything, whose revision is exactly what it was before agents existed.
186
+ */
187
+ fingerprint(): string | null {
188
+ const s = this.state;
189
+ return s.kind === 'everything' ? null : s.fingerprint;
190
+ }
191
+
192
+ /** See {@link ConnectionScopeWire}; undefined for Everything. */
193
+ toWire(): ConnectionScopeWire | undefined {
194
+ const s = this.state;
195
+ if (s.kind === 'everything') return undefined;
196
+ if (s.kind === 'unavailable') return { agent: s.agent, unavailable: s.sentence };
197
+ return { agent: s.agent };
198
+ }
199
+ }
@@ -0,0 +1,15 @@
1
+ export {
2
+ noAgentConfigurations,
3
+ resolveConnectionScope,
4
+ type AgentChoice,
5
+ type IAgentScopeResolver,
6
+ } from './agent-scope.contract.js';
7
+ export {
8
+ ConnectionScope,
9
+ NOT_AVAILABLE_TO_CONNECTION,
10
+ connectionRefusal,
11
+ describeConnection,
12
+ type ConnectionScopeWire,
13
+ type ScopeAgent,
14
+ type ScopeRoots,
15
+ } from './connection-scope.js';
@@ -151,6 +151,7 @@ export class AgentAuditService implements IAgentAuditService, IAgentEventRecorde
151
151
  deletedAt: k.deletedAt,
152
152
  eventCount: counts.get(k.id) ?? 0,
153
153
  user: k.user,
154
+ agentPath: k.agentPath ?? null,
154
155
  })),
155
156
  ...connectionRows.map(({ connection: c, user }) => ({
156
157
  kind: 'agent' as const,
@@ -162,6 +163,7 @@ export class AgentAuditService implements IAgentAuditService, IAgentEventRecorde
162
163
  revokedBy: (c.revokedBy as 'owner' | 'admin' | null) ?? null,
163
164
  eventCount: counts.get(c.id) ?? 0,
164
165
  user,
166
+ agentPath: c.agentPath ?? null,
165
167
  })),
166
168
  ...pageRows.flatMap((r) =>
167
169
  r.pagePath
@@ -128,6 +128,12 @@ export interface AuditPrincipal {
128
128
  /** Events currently on record for it (within the retention window). */
129
129
  eventCount: number;
130
130
  user: { id: string; email: string; name: string };
131
+ /**
132
+ * For a key or an agent connection: the agent configuration it connects as (a
133
+ * repo-relative knowledge-base path), or null for Everything. Absent for a
134
+ * page, which acts as its viewer.
135
+ */
136
+ agentPath?: string | null;
131
137
  }
132
138
 
133
139
  /** One event as the page reads it. */
@@ -1,7 +1,7 @@
1
1
  import { describe, it, expect, vi } from 'vitest';
2
2
  import type { Request, Response, NextFunction } from 'express';
3
- import { createAuthMiddleware, AUTH_COOKIE_NAME } from '../auth.middleware.js';
4
- import { AccountDeactivatedError, AuthBackendError } from '../account-admission.js';
3
+ import { authCookieSecure, createAuthMiddleware, AUTH_COOKIE_NAME } from '../auth.middleware.js';
4
+ import { AccountDeactivatedError, AuthBackendError, SessionEndedError } from '../account-admission.js';
5
5
  import type { AuthService } from '../auth.service.js';
6
6
 
7
7
  /**
@@ -14,11 +14,13 @@ import type { AuthService } from '../auth.service.js';
14
14
  const GOOD_TOKEN = 'good-token';
15
15
  const OFF_TOKEN = 'switched-off-token';
16
16
  const DOWN_TOKEN = 'database-down-token';
17
+ const ENDED_TOKEN = 'password-changed-since-token';
17
18
  const IDENTITY = { userId: 'user-1', email: 'alice@example.com' };
18
19
 
19
20
  async function run(headers: Record<string, string>) {
20
21
  const resolveSession = vi.fn(async (token: string) => {
21
22
  if (token === OFF_TOKEN) throw new AccountDeactivatedError();
23
+ if (token === ENDED_TOKEN) throw new SessionEndedError();
22
24
  if (token === DOWN_TOKEN) throw new AuthBackendError(new Error('db down'));
23
25
  if (token !== GOOD_TOKEN) throw new Error('bad token');
24
26
  return IDENTITY;
@@ -91,6 +93,32 @@ describe('createAuthMiddleware — a switched-off account', () => {
91
93
  });
92
94
  });
93
95
 
96
+ describe('authCookieSecure', () => {
97
+ it('marks the cookie Secure for an https deployment, whatever the case of the scheme', () => {
98
+ expect(authCookieSecure('https://kb.example.com')).toBe(true);
99
+ expect(authCookieSecure('HTTPS://kb.example.com/')).toBe(true);
100
+ expect(authCookieSecure('Https://kb.example.com:8443/base')).toBe(true);
101
+ });
102
+
103
+ it('leaves it off for a deployment reached over plain http', () => {
104
+ expect(authCookieSecure('http://localhost:3001')).toBe(false);
105
+ expect(authCookieSecure('HTTP://10.0.0.5')).toBe(false);
106
+ });
107
+
108
+ it('refuses a public URL that is not one, as the rest of the boot does', () => {
109
+ expect(() => authCookieSecure('kb.example.com')).toThrow();
110
+ });
111
+ });
112
+
113
+ describe('createAuthMiddleware — a session a password change ended', () => {
114
+ it('refuses it with a 401 that says the password was changed, so the sign-in form can', async () => {
115
+ const { next, status, json } = await run({ cookie: `${AUTH_COOKIE_NAME}=${ENDED_TOKEN}` });
116
+ expect(next).not.toHaveBeenCalled();
117
+ expect(status).toHaveBeenCalledWith(401);
118
+ expect(json.mock.calls[0][0].error).toMatch(/password was changed after this session was opened/);
119
+ });
120
+ });
121
+
94
122
  describe('createAuthMiddleware — the account cannot be looked up', () => {
95
123
  it('500s rather than signing a valid session out during an outage', async () => {
96
124
  const { next, status, json } = await run({ authorization: `Bearer ${DOWN_TOKEN}` });
@@ -1,15 +1,16 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
2
  import express from 'express';
3
3
  import type { Server } from 'node:http';
4
- import { createAuthRoutes } from '../auth.routes.js';
5
- import type { AuthService } from '../auth.service.js';
4
+ import { createAuthRoutes, type AuthRoutesOptions } from '../auth.routes.js';
5
+ import { PasswordChangedMeanwhileError, PasswordRefusedError, type AuthService } from '../auth.service.js';
6
+ import { AUTH_COOKIE_NAME } from '../auth.middleware.js';
6
7
 
7
8
  const authService = {
8
9
  loginWithPassword: vi.fn(async () => {
9
10
  throw new Error('Invalid credentials');
10
11
  }),
11
12
  changePassword: vi.fn(async () => {
12
- throw new Error('Current password is incorrect');
13
+ throw new PasswordRefusedError('Current password is incorrect');
13
14
  }),
14
15
  getUserById: vi.fn(async () => null),
15
16
  } as unknown as AuthService;
@@ -28,10 +29,10 @@ afterEach(() => {
28
29
  server?.close();
29
30
  });
30
31
 
31
- async function listen(): Promise<string> {
32
+ async function listen(options?: AuthRoutesOptions): Promise<string> {
32
33
  const app = express();
33
34
  app.use(express.json());
34
- app.use('/api', createAuthRoutes(authService, fakeAuthMiddleware));
35
+ app.use('/api', createAuthRoutes(authService, fakeAuthMiddleware, [], true, options));
35
36
  await new Promise<void>((resolve) => {
36
37
  server = app.listen(0, resolve);
37
38
  });
@@ -89,3 +90,68 @@ describe('auth routes — rate limiting', () => {
89
90
  expect((await post()).status).toBe(429);
90
91
  });
91
92
  });
93
+
94
+ describe('auth routes — change-password', () => {
95
+ const post = (base: string) =>
96
+ fetch(`${base}/api/auth/change-password`, {
97
+ method: 'POST',
98
+ headers: { 'Content-Type': 'application/json' },
99
+ body: JSON.stringify({ currentPassword: 'old-password', newPassword: 'new-password-1' }),
100
+ });
101
+
102
+ it('hands the changer a fresh token, in the body and the auth cookie', async () => {
103
+ vi.mocked(authService.changePassword).mockResolvedValueOnce({ token: 'fresh-jwt' });
104
+ const res = await post(await listen());
105
+ expect(res.status).toBe(200);
106
+ expect(await res.json()).toEqual({ ok: true, token: 'fresh-jwt' });
107
+ expect(res.headers.getSetCookie().some((c) => c.startsWith(`${AUTH_COOKIE_NAME}=fresh-jwt;`))).toBe(true);
108
+ });
109
+
110
+ it('answers a change another session got in first with a 409 and its words, and no cookie', async () => {
111
+ vi.mocked(authService.changePassword).mockRejectedValueOnce(new PasswordChangedMeanwhileError());
112
+ const res = await post(await listen());
113
+ expect(res.status).toBe(409);
114
+ expect(((await res.json()) as { error: string }).error).toMatch(/changed from another session/);
115
+ expect(res.headers.getSetCookie()).toEqual([]);
116
+ });
117
+
118
+ it('answers a refusal the service made on purpose with a 400 and its words', async () => {
119
+ vi.mocked(authService.changePassword).mockRejectedValueOnce(new PasswordRefusedError('Password must be at least 8 characters'));
120
+ const res = await post(await listen());
121
+ expect(res.status).toBe(400);
122
+ expect(await res.json()).toEqual({ error: 'Password must be at least 8 characters' });
123
+ });
124
+
125
+ it('answers a fault with a generic 500 that carries nothing of the cause', async () => {
126
+ vi.mocked(authService.changePassword).mockRejectedValueOnce(
127
+ new Error('connect ECONNREFUSED db.internal:5432 (role bevel, database bevel)'),
128
+ );
129
+ const res = await post(await listen());
130
+ expect(res.status).toBe(500);
131
+ const body = (await res.json()) as { error: string };
132
+ expect(body).toEqual({ error: 'Could not change the password' });
133
+ expect(JSON.stringify(body)).not.toMatch(/ECONNREFUSED|5432|bevel/);
134
+ });
135
+
136
+ it('marks the cookie Secure when the deployment is reached over https, and not otherwise', async () => {
137
+ vi.mocked(authService.changePassword).mockResolvedValueOnce({ token: 'fresh-jwt' });
138
+ const secure = await post(await listen({ cookieSecure: true }));
139
+ expect(secure.headers.getSetCookie()[0]).toMatch(/; Secure/);
140
+ vi.mocked(authService.changePassword).mockResolvedValueOnce({ token: 'fresh-jwt' });
141
+ const plain = await post(await listen({ cookieSecure: false }));
142
+ expect(plain.headers.getSetCookie()[0]).not.toMatch(/; Secure/);
143
+ });
144
+ });
145
+
146
+ describe('auth routes — me', () => {
147
+ it('answers a fault with a generic 500 that carries nothing of the cause', async () => {
148
+ vi.mocked(authService.getUserById).mockRejectedValueOnce(
149
+ new Error('connect ECONNREFUSED db.internal:5432 (role bevel, database bevel)'),
150
+ );
151
+ const res = await fetch(`${await listen()}/api/auth/me`);
152
+ expect(res.status).toBe(500);
153
+ const body = (await res.json()) as { error: string };
154
+ expect(body).toEqual({ error: 'Could not load your account' });
155
+ expect(JSON.stringify(body)).not.toMatch(/ECONNREFUSED|5432|bevel/);
156
+ });
157
+ });
@@ -3,11 +3,12 @@ import { PgDialect } from 'drizzle-orm/pg-core';
3
3
  import type { SQL } from 'drizzle-orm';
4
4
  import type { Database } from '../../database/connection.js';
5
5
  import type { CoreConfig } from '../../../core-config.js';
6
- import { AuthService } from '../auth.service.js';
7
- import { AccountAdmissionRefusedError, type IAccountAdmission } from '../account-admission.js';
6
+ import jwt from 'jsonwebtoken';
7
+ import { AuthService, PasswordChangedMeanwhileError, PasswordRefusedError } from '../auth.service.js';
8
+ import { AccountAdmissionRefusedError, SessionEndedError, type IAccountAdmission } from '../account-admission.js';
8
9
  import { hashPassword, verifyPassword } from '../password-hash.js';
9
- import { RECOVERY_BOT_EMAIL } from '../../workflow/recovery-bot.js';
10
- import { DIRECTORY_SYNC_BOT_EMAIL } from '../../access/directory-sync-bot.js';
10
+ import { RECOVERY_BOT_EMAIL } from '../../workflow/index.js';
11
+ import { DIRECTORY_SYNC_BOT_EMAIL } from '../../access/index.js';
11
12
 
12
13
  /**
13
14
  * Minimal drizzle chain stub (same idiom as the secrets-vault tests): each
@@ -78,8 +79,12 @@ const ROW = {
78
79
  // always has a boolean here, so the fixture carries one rather than letting
79
80
  // every asserted payload read `undefined`.
80
81
  onboardingDone: false,
82
+ sessionVersion: 0,
81
83
  };
82
84
 
85
+ /** What a password change's conditional write hands back: the row, one session generation on. */
86
+ const CHANGED = [{ ...ROW, sessionVersion: 1 }];
87
+
83
88
  describe('AuthService.loginWithPassword — env bootstrap admin', () => {
84
89
  const config = makeConfig({ adminEmail: 'root@example.com', adminPassword: 'sup3r-secret' });
85
90
 
@@ -283,14 +288,14 @@ describe('AuthService.createAccount / changePassword', () => {
283
288
  new AuthService(wrong.db, makeConfig()).changePassword('user-1', 'not-it', 'new-password-1'),
284
289
  ).rejects.toThrow('Current password is incorrect');
285
290
 
286
- const right = makeFakeDb([[{ ...ROW, passwordHash }], undefined]);
291
+ const right = makeFakeDb([[{ ...ROW, passwordHash }], CHANGED]);
287
292
  await expect(
288
293
  new AuthService(right.db, makeConfig()).changePassword('user-1', 'old-password', 'new-password-1'),
289
- ).resolves.toBeUndefined();
294
+ ).resolves.toMatchObject({ token: expect.any(String) });
290
295
  });
291
296
 
292
297
  it('changePassword lets an SSO-only account set its first password without one', async () => {
293
- const { db, captured } = makeFakeDb([[{ ...ROW, passwordHash: null }], undefined]);
298
+ const { db, captured } = makeFakeDb([[{ ...ROW, passwordHash: null }], CHANGED]);
294
299
  await new AuthService(db, makeConfig()).changePassword('user-1', undefined, 'first-password');
295
300
  const set = captured.set[0] as { passwordHash: string };
296
301
  expect(set.passwordHash.startsWith('scrypt:')).toBe(true);
@@ -452,7 +457,7 @@ describe('AuthService.changePassword — the three account kinds', () => {
452
457
  });
453
458
 
454
459
  it('stores a new hash for the right current password', async () => {
455
- const { db, captured } = makeFakeDb([[await row()], undefined]);
460
+ const { db, captured } = makeFakeDb([[await row()], CHANGED]);
456
461
  await new AuthService(db, makeConfig(ENV_ADMIN)).changePassword(
457
462
  kind.id,
458
463
  'old-password',
@@ -585,7 +590,7 @@ describe('AuthService.changePassword — the three account kinds', () => {
585
590
  ).getUserById('user-root');
586
591
  expect(flagged?.isEnvAdmin).toBe(false);
587
592
 
588
- const { db, captured } = makeFakeDb([[{ ...rootRow, passwordHash }], undefined]);
593
+ const { db, captured } = makeFakeDb([[{ ...rootRow, passwordHash }], CHANGED]);
589
594
  await new AuthService(db, config).changePassword('user-root', 'old-password', 'new-password-1');
590
595
  expect((captured.set[0] as { passwordHash: string }).passwordHash.startsWith('scrypt:')).toBe(
591
596
  true,
@@ -594,6 +599,82 @@ describe('AuthService.changePassword — the three account kinds', () => {
594
599
  });
595
600
  });
596
601
 
602
+ /**
603
+ * A password change ends the sessions opened before it. The real-Postgres
604
+ * half (`password-change-sessions.pg.test.ts`) proves it end to end; these
605
+ * pin the service's side without a database.
606
+ */
607
+ describe('AuthService.changePassword — the sessions opened before it', () => {
608
+ const ON = { deactivatedAt: null };
609
+
610
+ it('refuses a session issued before the change, and accepts the token the change hands back', async () => {
611
+ const passwordHash = await hashPassword('old-password');
612
+ const { db } = makeFakeDb([
613
+ [{ ...ROW, passwordHash }], // sign-in
614
+ [{ ...ROW, passwordHash }], // the change reads the row...
615
+ CHANGED, // ...and writes it one generation on
616
+ [{ ...ON, ...CHANGED[0] }], // the old token's lookup...
617
+ [{ ...ON, ...CHANGED[0] }], // ...and the new one's: a session is never answered from the cache
618
+ ]);
619
+ const svc = new AuthService(db, makeConfig());
620
+ const before = await svc.loginWithPassword('alice@example.com', 'old-password');
621
+ const { token } = await svc.changePassword('user-1', 'old-password', 'new-password-1');
622
+ // Named, and worded for the person: the middleware shows these words
623
+ // where a bare "invalid token" would leave them guessing.
624
+ const ended = svc.resolveSession(before.token);
625
+ await expect(ended).rejects.toBeInstanceOf(SessionEndedError);
626
+ await expect(ended).rejects.toThrow(/password was changed after this session was opened/);
627
+ await expect(svc.resolveSession(token)).resolves.toEqual({ userId: ROW.id, email: ROW.email });
628
+ });
629
+
630
+ it('refuses a wrong current password as a refusal the caller is told, not a fault', async () => {
631
+ const passwordHash = await hashPassword('old-password');
632
+ const { db } = makeFakeDb([[{ ...ROW, passwordHash }]]);
633
+ const refused = new AuthService(db, makeConfig()).changePassword('user-1', 'not-it', 'new-password-1');
634
+ await expect(refused).rejects.toBeInstanceOf(PasswordRefusedError);
635
+ await expect(refused).rejects.toThrow('Current password is incorrect');
636
+ });
637
+
638
+ it('reads the account row for every session it resolves, whatever the cache holds', async () => {
639
+ // Another replica's password change is only visible in the row, so a
640
+ // session answered from the cache would outlive it by up to 30 seconds.
641
+ const live = [{ ...ON, ...ROW }];
642
+ const { db } = makeFakeDb([live, live, live]);
643
+ const svc = new AuthService(db, makeConfig());
644
+ const token = jwt.sign({ userId: ROW.id, email: ROW.email, sv: 0 }, 'test-jwt-secret', { expiresIn: '7d' });
645
+ const select = vi.mocked(db.select);
646
+
647
+ await svc.resolveSession(token);
648
+ expect(select).toHaveBeenCalledTimes(1);
649
+ // The cache now holds the account; isActive may use it...
650
+ await expect(svc.isActive(ROW.id)).resolves.toBe(true);
651
+ expect(select).toHaveBeenCalledTimes(1);
652
+ // ...but a session never does.
653
+ await svc.resolveSession(token);
654
+ expect(select).toHaveBeenCalledTimes(2);
655
+ await svc.resolveSession(token);
656
+ expect(select).toHaveBeenCalledTimes(3);
657
+ });
658
+
659
+ it('keeps a token issued before sessions had generations while the password never changed', async () => {
660
+ const { db } = makeFakeDb([[{ ...ON, ...ROW }]]);
661
+ const legacy = jwt.sign({ userId: ROW.id, email: ROW.email }, 'test-jwt-secret', { expiresIn: '7d' });
662
+ await expect(new AuthService(db, makeConfig()).resolveSession(legacy)).resolves.toEqual({
663
+ userId: ROW.id,
664
+ email: ROW.email,
665
+ });
666
+ });
667
+
668
+ it('is refused, in words, when another session changed the password between the read and the write', async () => {
669
+ const passwordHash = await hashPassword('old-password');
670
+ // The conditional write finds the row no longer at the generation read.
671
+ const { db } = makeFakeDb([[{ ...ROW, passwordHash }], []]);
672
+ const refused = new AuthService(db, makeConfig()).changePassword('user-1', 'old-password', 'new-password-1');
673
+ await expect(refused).rejects.toBeInstanceOf(PasswordChangedMeanwhileError);
674
+ await expect(refused).rejects.toThrow(/changed from another session.*nothing was changed/);
675
+ });
676
+ });
677
+
597
678
  describe('AuthService.listAccounts', () => {
598
679
  it('reports hasPassword without ever exposing the hash', async () => {
599
680
  const passwordHash = await hashPassword('pw-longer-than-8');