@bevel-software/platform-core-backend 0.7.5 → 0.9.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 (282) hide show
  1. package/LICENSE +202 -202
  2. package/THIRD-PARTY-NOTICES.md +428 -454
  3. package/dist/core/core-ports.d.ts +8 -1
  4. package/dist/core/core-ports.d.ts.map +1 -1
  5. package/dist/core/core-ports.js.map +1 -1
  6. package/dist/core/create-core-server.d.ts.map +1 -1
  7. package/dist/core/create-core-server.js +75 -18
  8. package/dist/core/create-core-server.js.map +1 -1
  9. package/dist/core/create-core-services.d.ts +12 -5
  10. package/dist/core/create-core-services.d.ts.map +1 -1
  11. package/dist/core/create-core-services.js +69 -36
  12. package/dist/core/create-core-services.js.map +1 -1
  13. package/dist/modules/access/access-control.interface.d.ts +8 -7
  14. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  15. package/dist/modules/access/access-control.service.d.ts +1 -1
  16. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  17. package/dist/modules/access/access-control.service.js +6 -6
  18. package/dist/modules/access/access-control.service.js.map +1 -1
  19. package/dist/modules/access/access-declarations.d.ts +5 -5
  20. package/dist/modules/access/access-declarations.js +3 -3
  21. package/dist/modules/access/access-mutation.service.d.ts +3 -3
  22. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  23. package/dist/modules/access/access-mutation.service.js +3 -3
  24. package/dist/modules/access/access-mutation.service.js.map +1 -1
  25. package/dist/modules/access/access-splice.js +4 -4
  26. package/dist/modules/access/access-splice.js.map +1 -1
  27. package/dist/modules/access/access.routes.d.ts +3 -1
  28. package/dist/modules/access/access.routes.d.ts.map +1 -1
  29. package/dist/modules/access/access.routes.js +22 -20
  30. package/dist/modules/access/access.routes.js.map +1 -1
  31. package/dist/modules/access/creator-access.d.ts +2 -2
  32. package/dist/modules/access/creator-access.js +5 -5
  33. package/dist/modules/access/creator-access.js.map +1 -1
  34. package/dist/modules/access/render-roles-yaml.d.ts +22 -0
  35. package/dist/modules/access/render-roles-yaml.d.ts.map +1 -0
  36. package/dist/modules/access/render-roles-yaml.js +56 -0
  37. package/dist/modules/access/render-roles-yaml.js.map +1 -0
  38. package/dist/modules/access/roles-admin.service.d.ts +33 -5
  39. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  40. package/dist/modules/access/roles-admin.service.js +44 -19
  41. package/dist/modules/access/roles-admin.service.js.map +1 -1
  42. package/dist/modules/code-mode/code-mode-names.d.ts +5 -13
  43. package/dist/modules/code-mode/code-mode-names.d.ts.map +1 -1
  44. package/dist/modules/code-mode/code-mode-names.js +5 -27
  45. package/dist/modules/code-mode/code-mode-names.js.map +1 -1
  46. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  47. package/dist/modules/code-mode/code-mode.tool.js +29 -7
  48. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  49. package/dist/modules/mcp/mcp.service.d.ts +11 -52
  50. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  51. package/dist/modules/mcp/mcp.service.js +33 -395
  52. package/dist/modules/mcp/mcp.service.js.map +1 -1
  53. package/dist/modules/plugins/index.d.ts +7 -0
  54. package/dist/modules/plugins/index.d.ts.map +1 -0
  55. package/dist/modules/plugins/index.js +6 -0
  56. package/dist/modules/plugins/index.js.map +1 -0
  57. package/dist/modules/plugins/join-proposals.d.ts +53 -0
  58. package/dist/modules/plugins/join-proposals.d.ts.map +1 -0
  59. package/dist/modules/plugins/join-proposals.js +67 -0
  60. package/dist/modules/plugins/join-proposals.js.map +1 -0
  61. package/dist/modules/plugins/join-requests.service.d.ts +81 -0
  62. package/dist/modules/plugins/join-requests.service.d.ts.map +1 -0
  63. package/dist/modules/plugins/join-requests.service.js +135 -0
  64. package/dist/modules/plugins/join-requests.service.js.map +1 -0
  65. package/dist/modules/plugins/plugin-provision.service.d.ts +134 -0
  66. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -0
  67. package/dist/modules/plugins/plugin-provision.service.js +344 -0
  68. package/dist/modules/plugins/plugin-provision.service.js.map +1 -0
  69. package/dist/modules/plugins/plugins.contract.d.ts +106 -0
  70. package/dist/modules/plugins/plugins.contract.d.ts.map +1 -0
  71. package/dist/modules/plugins/plugins.contract.js +36 -0
  72. package/dist/modules/plugins/plugins.contract.js.map +1 -0
  73. package/dist/modules/plugins/plugins.routes.d.ts +42 -0
  74. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -0
  75. package/dist/modules/plugins/plugins.routes.js +379 -0
  76. package/dist/modules/plugins/plugins.routes.js.map +1 -0
  77. package/dist/modules/plugins/plugins.service.d.ts +60 -0
  78. package/dist/modules/plugins/plugins.service.d.ts.map +1 -0
  79. package/dist/modules/plugins/plugins.service.js +172 -0
  80. package/dist/modules/plugins/plugins.service.js.map +1 -0
  81. package/dist/modules/settings/setup.routes.d.ts +10 -1
  82. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  83. package/dist/modules/settings/setup.routes.js +111 -6
  84. package/dist/modules/settings/setup.routes.js.map +1 -1
  85. package/dist/modules/skills/pending-skills.service.d.ts +2 -2
  86. package/dist/modules/skills/pending-skills.service.js +7 -7
  87. package/dist/modules/skills/pending-skills.service.js.map +1 -1
  88. package/dist/modules/skills/skills.contract.d.ts +4 -4
  89. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  90. package/dist/modules/skills/skills.contract.js +1 -1
  91. package/dist/modules/skills/skills.service.js +5 -5
  92. package/dist/modules/skills/skills.service.js.map +1 -1
  93. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +65 -0
  94. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -0
  95. package/dist/modules/tool-manuals/mcp-json-discovery.js +276 -0
  96. package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -0
  97. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +92 -0
  98. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -0
  99. package/dist/modules/tool-manuals/mcp-server-edit.service.js +328 -0
  100. package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -0
  101. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +38 -12
  102. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  103. package/dist/modules/tool-manuals/tool-manuals.contract.js +1 -1
  104. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +13 -2
  105. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  106. package/dist/modules/tool-manuals/tool-manuals.routes.js +233 -2
  107. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  108. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  109. package/dist/modules/tool-manuals/tool-manuals.service.js +74 -37
  110. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  111. package/dist/modules/tool-manuals/tool-manuals.tools.js +6 -3
  112. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  113. package/dist/modules/workflow/git/git.service.js +2 -2
  114. package/dist/modules/workflow/git/git.service.js.map +1 -1
  115. package/dist/modules/workspace/kb-seed.service.d.ts +2 -2
  116. package/dist/modules/workspace/kb-seed.service.d.ts.map +1 -1
  117. package/dist/modules/workspace/kb-seed.service.js +43 -10
  118. package/dist/modules/workspace/kb-seed.service.js.map +1 -1
  119. package/dist/modules/workspace/plugins-migration.d.ts +50 -0
  120. package/dist/modules/workspace/plugins-migration.d.ts.map +1 -0
  121. package/dist/modules/workspace/plugins-migration.js +379 -0
  122. package/dist/modules/workspace/plugins-migration.js.map +1 -0
  123. package/dist/modules/workspace/startup/kb-git.d.ts +23 -0
  124. package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -0
  125. package/dist/modules/workspace/startup/kb-git.js +86 -0
  126. package/dist/modules/workspace/startup/kb-git.js.map +1 -0
  127. package/dist/modules/workspace/startup/kb-startup-runner.d.ts +74 -0
  128. package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -0
  129. package/dist/modules/workspace/startup/kb-startup-runner.js +528 -0
  130. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -0
  131. package/dist/modules/workspace/startup/on-server-start.d.ts +105 -0
  132. package/dist/modules/workspace/startup/on-server-start.d.ts.map +1 -0
  133. package/dist/modules/workspace/startup/on-server-start.js +21 -0
  134. package/dist/modules/workspace/startup/on-server-start.js.map +1 -0
  135. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts +46 -0
  136. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts.map +1 -0
  137. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +492 -0
  138. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -0
  139. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +23 -0
  140. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts.map +1 -0
  141. package/dist/modules/workspace/startup/steps/roles-yaml.step.js +69 -0
  142. package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -0
  143. package/dist/modules/workspace/startup/steps/seed-tree.d.ts +17 -0
  144. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -0
  145. package/dist/modules/workspace/startup/steps/seed-tree.js +109 -0
  146. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -0
  147. package/dist/modules/workspace/startup/steps/template-files.step.d.ts +103 -0
  148. package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -0
  149. package/dist/modules/workspace/startup/steps/template-files.step.js +337 -0
  150. package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -0
  151. package/dist/modules/workspace/workspace.routes.js +3 -3
  152. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  153. package/dist/modules/workspace/workspace.service.d.ts +0 -9
  154. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  155. package/dist/modules/workspace/workspace.service.js +2 -30
  156. package/dist/modules/workspace/workspace.service.js.map +1 -1
  157. package/dist/shared/kb-layout.test.js +3 -3
  158. package/dist/shared/kb-layout.test.js.map +1 -1
  159. package/dist/shared/utcp-namespace.d.ts +6 -27
  160. package/dist/shared/utcp-namespace.d.ts.map +1 -1
  161. package/dist/shared/utcp-namespace.js +6 -63
  162. package/dist/shared/utcp-namespace.js.map +1 -1
  163. package/dist/shared/variable-refs.d.ts +42 -0
  164. package/dist/shared/variable-refs.d.ts.map +1 -0
  165. package/dist/shared/variable-refs.js +60 -0
  166. package/dist/shared/variable-refs.js.map +1 -0
  167. package/kb-template/.bevelignore +1 -1
  168. package/kb-template/AGENTS.md +88 -35
  169. package/kb-template/KnowledgeBase/How to get started.md +10 -10
  170. package/kb-template/access.md +36 -36
  171. package/kb-template/gitignore.template +16 -0
  172. package/migrations/meta/0000_snapshot.json +1479 -1479
  173. package/package.json +5 -4
  174. package/src/assets.ts +25 -25
  175. package/src/core/core-ports.ts +113 -106
  176. package/src/core/create-core-server.ts +85 -20
  177. package/src/core/create-core-services.ts +83 -42
  178. package/src/index.ts +69 -69
  179. package/src/modules/access/__tests__/access-control.atref-batch.test.ts +98 -98
  180. package/src/modules/access/__tests__/access-declarations.test.ts +28 -28
  181. package/src/modules/access/__tests__/access-md-format.test.ts +18 -18
  182. package/src/modules/access/__tests__/access-mutation.service.test.ts +5 -5
  183. package/src/modules/access/__tests__/access-splice.test.ts +2 -2
  184. package/src/modules/access/__tests__/access.routes.overrides.test.ts +16 -16
  185. package/src/modules/access/__tests__/grant-sources.test.ts +12 -12
  186. package/src/modules/access/__tests__/roles-admin.service.test.ts +36 -2
  187. package/src/modules/access/access-control.interface.ts +8 -7
  188. package/src/modules/access/access-control.service.ts +7 -7
  189. package/src/modules/access/access-declarations.ts +5 -5
  190. package/src/modules/access/access-mutation.service.ts +3 -3
  191. package/src/modules/access/access-splice.ts +4 -4
  192. package/src/modules/access/access.routes.ts +23 -20
  193. package/src/modules/access/creator-access.ts +5 -5
  194. package/src/modules/access/render-roles-yaml.ts +65 -0
  195. package/src/modules/access/roles-admin.service.ts +45 -16
  196. package/src/modules/admin/admin-access.routes.ts +29 -29
  197. package/src/modules/auth/__tests__/auth.routes.test.ts +91 -91
  198. package/src/modules/auth/__tests__/rate-limit.test.ts +36 -36
  199. package/src/modules/auth/rate-limit.ts +45 -45
  200. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +67 -0
  201. package/src/modules/code-mode/code-mode-names.ts +10 -36
  202. package/src/modules/code-mode/code-mode.tool.ts +27 -7
  203. package/src/modules/database/connection.ts +15 -15
  204. package/src/modules/database/schema.ts +11 -11
  205. package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +150 -150
  206. package/src/modules/mcp/mcp.service.ts +57 -435
  207. package/src/modules/{groups → plugins}/__tests__/join-proposals.test.ts +1 -1
  208. package/src/modules/{groups → plugins}/__tests__/join-requests.service.test.ts +7 -7
  209. package/src/modules/{groups/__tests__/group-index.service.test.ts → plugins/__tests__/plugin-index.service.test.ts} +41 -41
  210. package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +312 -0
  211. package/src/modules/{groups/__tests__/groups.routes.test.ts → plugins/__tests__/plugins.routes.test.ts} +100 -100
  212. package/src/modules/plugins/index.ts +17 -0
  213. package/src/modules/{groups → plugins}/join-proposals.ts +2 -2
  214. package/src/modules/{groups → plugins}/join-requests.service.ts +8 -8
  215. package/src/modules/{groups/group-provision.service.ts → plugins/plugin-provision.service.ts} +139 -69
  216. package/src/modules/{groups/groups.contract.ts → plugins/plugins.contract.ts} +26 -26
  217. package/src/modules/{groups/groups.routes.ts → plugins/plugins.routes.ts} +102 -102
  218. package/src/modules/{groups/groups.service.ts → plugins/plugins.service.ts} +43 -43
  219. package/src/modules/secrets-vault/__tests__/tool-owner-gate.route.test.ts +143 -143
  220. package/src/modules/settings/__tests__/setup.routes.test.ts +124 -3
  221. package/src/modules/settings/setup.routes.ts +112 -5
  222. package/src/modules/skills/__tests__/pending-skills.service.test.ts +14 -14
  223. package/src/modules/skills/__tests__/skills.service.test.ts +13 -13
  224. package/src/modules/skills/pending-skills.service.ts +7 -7
  225. package/src/modules/skills/skills.contract.ts +4 -4
  226. package/src/modules/skills/skills.service.ts +5 -5
  227. package/src/modules/tool-auth/llm-usage-meter.ts +19 -19
  228. package/src/modules/tool-manuals/__tests__/mcp-json-discovery.test.ts +198 -0
  229. package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +346 -0
  230. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +101 -0
  231. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +3 -3
  232. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +2 -2
  233. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +27 -27
  234. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +3 -3
  235. package/src/modules/tool-manuals/mcp-json-discovery.ts +328 -0
  236. package/src/modules/tool-manuals/mcp-server-edit.service.ts +434 -0
  237. package/src/modules/tool-manuals/tool-manuals.contract.ts +35 -12
  238. package/src/modules/tool-manuals/tool-manuals.routes.ts +222 -1
  239. package/src/modules/tool-manuals/tool-manuals.service.ts +82 -42
  240. package/src/modules/tool-manuals/tool-manuals.tools.ts +6 -3
  241. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
  242. package/src/modules/workflow/git/__tests__/branch-name.test.ts +3 -3
  243. package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +3 -3
  244. package/src/modules/workflow/git/__tests__/git.service.commitFile.test.ts +13 -8
  245. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +1 -1
  246. package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
  247. package/src/modules/workflow/git/git.service.ts +2 -2
  248. package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +1 -1
  249. package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
  250. package/src/modules/workflow/workflow-hooks.ts +101 -101
  251. package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +237 -237
  252. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +236 -236
  253. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +179 -179
  254. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +320 -320
  255. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +337 -337
  256. package/src/modules/workspace/__tests__/workspace.service.test.ts +27 -0
  257. package/src/modules/workspace/bevel-ignore.ts +66 -66
  258. package/src/modules/workspace/session-sink.ts +25 -25
  259. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +495 -0
  260. package/src/modules/workspace/startup/kb-git.ts +94 -0
  261. package/src/modules/workspace/startup/kb-startup-runner.ts +597 -0
  262. package/src/modules/workspace/startup/on-server-start.ts +97 -0
  263. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +636 -0
  264. package/src/modules/workspace/startup/steps/groups-to-plugins.step.ts +561 -0
  265. package/src/modules/workspace/startup/steps/roles-yaml.step.ts +71 -0
  266. package/src/modules/workspace/startup/steps/seed-tree.ts +115 -0
  267. package/src/modules/workspace/startup/steps/template-files.step.ts +360 -0
  268. package/src/modules/workspace/workspace.routes.ts +3 -3
  269. package/src/modules/workspace/workspace.service.ts +2 -35
  270. package/src/modules/workspace/workspace.tools.ts +922 -922
  271. package/src/shared/__tests__/join-request.test.ts +13 -13
  272. package/src/shared/__tests__/kb-layout.plugin.test.ts +45 -0
  273. package/src/shared/kb-layout.test.ts +3 -3
  274. package/src/shared/utcp-namespace.ts +10 -68
  275. package/src/shared/variable-refs.ts +64 -0
  276. package/src/modules/groups/__tests__/group-provision.service.test.ts +0 -247
  277. package/src/modules/groups/index.ts +0 -17
  278. package/src/modules/workspace/__tests__/kb-seed.service.test.ts +0 -444
  279. package/src/modules/workspace/kb-seed.interface.ts +0 -36
  280. package/src/modules/workspace/kb-seed.service.ts +0 -555
  281. package/src/shared/__tests__/kb-layout.group.test.ts +0 -45
  282. /package/kb-template/{Groups → Plugins}/.gitkeep +0 -0
@@ -1,36 +1,36 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { FixedWindowRateLimiter } from '../rate-limit.js';
3
-
4
- describe('FixedWindowRateLimiter', () => {
5
- it('allows up to max attempts per window, then refuses', () => {
6
- let now = 0;
7
- const limiter = new FixedWindowRateLimiter(3, 1000, () => now);
8
- expect(limiter.consume('k')).toBe(true);
9
- expect(limiter.consume('k')).toBe(true);
10
- expect(limiter.consume('k')).toBe(true);
11
- expect(limiter.consume('k')).toBe(false);
12
- // A different key has its own budget.
13
- expect(limiter.consume('other')).toBe(true);
14
- // Window rollover restores the budget.
15
- now = 1001;
16
- expect(limiter.consume('k')).toBe(true);
17
- });
18
-
19
- it('reset() clears a key immediately', () => {
20
- const limiter = new FixedWindowRateLimiter(1, 60_000, () => 0);
21
- expect(limiter.consume('k')).toBe(true);
22
- expect(limiter.consume('k')).toBe(false);
23
- limiter.reset('k');
24
- expect(limiter.consume('k')).toBe(true);
25
- });
26
-
27
- it('prunes expired windows so the map stays bounded', () => {
28
- let now = 0;
29
- const limiter = new FixedWindowRateLimiter(1, 1000, () => now);
30
- for (let i = 0; i < 100; i++) limiter.consume(`k${i}`);
31
- now = 2000;
32
- limiter.consume('fresh');
33
- // Internal map pruned to just the fresh key.
34
- expect((limiter as unknown as { hits: Map<string, unknown> }).hits.size).toBe(1);
35
- });
36
- });
1
+ import { describe, expect, it } from 'vitest';
2
+ import { FixedWindowRateLimiter } from '../rate-limit.js';
3
+
4
+ describe('FixedWindowRateLimiter', () => {
5
+ it('allows up to max attempts per window, then refuses', () => {
6
+ let now = 0;
7
+ const limiter = new FixedWindowRateLimiter(3, 1000, () => now);
8
+ expect(limiter.consume('k')).toBe(true);
9
+ expect(limiter.consume('k')).toBe(true);
10
+ expect(limiter.consume('k')).toBe(true);
11
+ expect(limiter.consume('k')).toBe(false);
12
+ // A different key has its own budget.
13
+ expect(limiter.consume('other')).toBe(true);
14
+ // Window rollover restores the budget.
15
+ now = 1001;
16
+ expect(limiter.consume('k')).toBe(true);
17
+ });
18
+
19
+ it('reset() clears a key immediately', () => {
20
+ const limiter = new FixedWindowRateLimiter(1, 60_000, () => 0);
21
+ expect(limiter.consume('k')).toBe(true);
22
+ expect(limiter.consume('k')).toBe(false);
23
+ limiter.reset('k');
24
+ expect(limiter.consume('k')).toBe(true);
25
+ });
26
+
27
+ it('prunes expired windows so the map stays bounded', () => {
28
+ let now = 0;
29
+ const limiter = new FixedWindowRateLimiter(1, 1000, () => now);
30
+ for (let i = 0; i < 100; i++) limiter.consume(`k${i}`);
31
+ now = 2000;
32
+ limiter.consume('fresh');
33
+ // Internal map pruned to just the fresh key.
34
+ expect((limiter as unknown as { hits: Map<string, unknown> }).hits.size).toBe(1);
35
+ });
36
+ });
@@ -1,45 +1,45 @@
1
- /**
2
- * Tiny fixed-window in-memory rate limiter for the password endpoints —
3
- * brute-force / scrypt-DoS protection, not traffic shaping. In-memory is the
4
- * right scope for the single-instance deployment model (same assumption as
5
- * the MCP session store); a multi-replica future moves this behind a shared
6
- * store.
7
- *
8
- * Expired windows are pruned opportunistically on each hit so the map stays
9
- * bounded by the number of DISTINCT active keys per window.
10
- */
11
- export class FixedWindowRateLimiter {
12
- private readonly hits = new Map<string, { count: number; resetAt: number }>();
13
-
14
- constructor(
15
- /** Attempts allowed per window per key. */
16
- private readonly max: number,
17
- private readonly windowMs: number,
18
- /** Injectable clock for tests. */
19
- private readonly now: () => number = Date.now,
20
- ) {}
21
-
22
- /**
23
- * Record one attempt for `key`; returns true when the attempt is ALLOWED
24
- * (i.e. the key is within its budget for the current window).
25
- */
26
- consume(key: string): boolean {
27
- const now = this.now();
28
- for (const [k, entry] of this.hits) {
29
- if (entry.resetAt <= now) this.hits.delete(k);
30
- }
31
- const entry = this.hits.get(key);
32
- if (!entry || entry.resetAt <= now) {
33
- this.hits.set(key, { count: 1, resetAt: now + this.windowMs });
34
- return true;
35
- }
36
- entry.count += 1;
37
- return entry.count <= this.max;
38
- }
39
-
40
- /** Clear a key's window — called after a SUCCESSFUL login so legitimate
41
- * users who fumbled a few attempts aren't locked out of their next visit. */
42
- reset(key: string): void {
43
- this.hits.delete(key);
44
- }
45
- }
1
+ /**
2
+ * Tiny fixed-window in-memory rate limiter for the password endpoints —
3
+ * brute-force / scrypt-DoS protection, not traffic shaping. In-memory is the
4
+ * right scope for the single-instance deployment model (same assumption as
5
+ * the MCP session store); a multi-replica future moves this behind a shared
6
+ * store.
7
+ *
8
+ * Expired windows are pruned opportunistically on each hit so the map stays
9
+ * bounded by the number of DISTINCT active keys per window.
10
+ */
11
+ export class FixedWindowRateLimiter {
12
+ private readonly hits = new Map<string, { count: number; resetAt: number }>();
13
+
14
+ constructor(
15
+ /** Attempts allowed per window per key. */
16
+ private readonly max: number,
17
+ private readonly windowMs: number,
18
+ /** Injectable clock for tests. */
19
+ private readonly now: () => number = Date.now,
20
+ ) {}
21
+
22
+ /**
23
+ * Record one attempt for `key`; returns true when the attempt is ALLOWED
24
+ * (i.e. the key is within its budget for the current window).
25
+ */
26
+ consume(key: string): boolean {
27
+ const now = this.now();
28
+ for (const [k, entry] of this.hits) {
29
+ if (entry.resetAt <= now) this.hits.delete(k);
30
+ }
31
+ const entry = this.hits.get(key);
32
+ if (!entry || entry.resetAt <= now) {
33
+ this.hits.set(key, { count: 1, resetAt: now + this.windowMs });
34
+ return true;
35
+ }
36
+ entry.count += 1;
37
+ return entry.count <= this.max;
38
+ }
39
+
40
+ /** Clear a key's window — called after a SUCCESSFUL login so legitimate
41
+ * users who fumbled a few attempts aren't locked out of their next visit. */
42
+ reset(key: string): void {
43
+ this.hits.delete(key);
44
+ }
45
+ }
@@ -0,0 +1,67 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import type { CodeModeUtcpClient } from '@utcp/code-mode';
3
+
4
+ /**
5
+ * `tools_info` containment of a THROWING name lookup. `findToolByName`
6
+ * (mcp-core) throws on an ambiguous sanitized name — sanitization is lossy
7
+ * (`a-b` and `a+b` both become `a_b`) — and one ambiguous entry in a batch
8
+ * must cost that entry an error message, never the whole response. The
9
+ * lookup is mocked at the module seam so the test pins THIS module's
10
+ * per-name containment, not mcp-core's collision detection.
11
+ */
12
+
13
+ vi.mock('../code-mode-names.js', async (importOriginal) => {
14
+ const actual = await importOriginal<typeof import('../code-mode-names.js')>();
15
+ return {
16
+ ...actual,
17
+ findToolByName: vi.fn(async (_client: unknown, name: string) => {
18
+ if (name === 'a_b.fetch') {
19
+ // The REAL sentinel class — the containment branches on instanceof,
20
+ // and a lookalike Error would test the wrong contract.
21
+ throw new actual.AmbiguousToolNameError(
22
+ 'Tool name "a_b.fetch" is ambiguous: "a-b.fetch", "a+b.fetch" all sanitize to it. ' +
23
+ 'Call the tool by its exact UTCP name instead.',
24
+ );
25
+ }
26
+ if (name === 'down.run') throw new Error('repository unavailable: ECONNREFUSED');
27
+ if (name === 'solo.run') return { tool: { name: 'solo.run' }, utcpName: 'solo.run' };
28
+ return null;
29
+ }),
30
+ };
31
+ });
32
+
33
+ const { createToolsInfoTool } = await import('../code-mode.tool.js');
34
+
35
+ const client = {
36
+ toolToTypeScriptInterface: (tool: { name: string }) => `interface ${tool.name}`,
37
+ } as unknown as CodeModeUtcpClient;
38
+
39
+ type ToolsInfoResult = { interfaces: string; not_found: string[]; errors?: string[] };
40
+
41
+ async function run(toolNames: string[]): Promise<ToolsInfoResult> {
42
+ const tool = createToolsInfoTool(client) as unknown as {
43
+ execute: (input: { tool_names: string[] }) => Promise<ToolsInfoResult>;
44
+ };
45
+ return tool.execute({ tool_names: toolNames });
46
+ }
47
+
48
+ describe('tools_info', () => {
49
+ it('answers every resolvable name even when one in the batch throws as ambiguous', async () => {
50
+ const result = await run(['a_b.fetch', 'solo.run', 'nope']);
51
+ expect(result.interfaces).toContain('interface solo.run');
52
+ expect(result.not_found).toEqual(['nope']);
53
+ expect(result.errors).toHaveLength(1);
54
+ expect(result.errors?.[0]).toMatch(/^a_b\.fetch: .*ambiguous/);
55
+ });
56
+
57
+ it('omits the errors field entirely when every name resolves or misses cleanly', async () => {
58
+ const result = await run(['solo.run', 'nope']);
59
+ expect(result.interfaces).toBe('interface solo.run');
60
+ expect(result.not_found).toEqual(['nope']);
61
+ expect(result.errors).toBeUndefined();
62
+ });
63
+
64
+ it('contains ONLY ambiguity — an outage rethrows instead of posing as partial success', async () => {
65
+ await expect(run(['solo.run', 'down.run'])).rejects.toThrow(/repository unavailable/);
66
+ });
67
+ });
@@ -1,38 +1,12 @@
1
- import type { Tool } from '@utcp/sdk';
2
- import type { CodeModeUtcpClient } from '@utcp/code-mode';
3
-
4
1
  /**
5
- * Pure helpers for mapping UTCP tool names to their code-mode (TypeScript)
6
- * accessible form. Kept Mastra-free so both the agent's Mastra tools
7
- * (`code-mode.tool.ts`) and the MCP proxy can share them.
2
+ * Re-export of the code-mode name helpers, which now live in
3
+ * `@bevel-software/platform-mcp-core` so the local MCP server can share them
4
+ * without depending on this package. Kept as a module here because the agent's
5
+ * Mastra tools and the MCP proxy both import it by this path.
8
6
  */
9
-
10
- export function sanitizeIdentifier(name: string): string {
11
- return name.replace(/[^a-zA-Z0-9_]/g, '_').replace(/^[0-9]/, '_$&');
12
- }
13
-
14
- /** `MANUAL.tool` → the `manual.tool` form the code-mode runtime exposes. */
15
- export function utcpNameToTsInterfaceName(utcpName: string): string {
16
- if (utcpName.includes('.')) {
17
- const [manualName, ...toolParts] = utcpName.split('.');
18
- return `${sanitizeIdentifier(manualName!)}.${toolParts.map(sanitizeIdentifier).join('_')}`;
19
- }
20
- return sanitizeIdentifier(utcpName);
21
- }
22
-
23
- /** Resolve a tool by its raw UTCP name or its sanitized TS-accessible name. */
24
- export async function findToolByName(
25
- client: CodeModeUtcpClient,
26
- name: string,
27
- ): Promise<{ tool: Tool; utcpName: string } | null> {
28
- const direct = await client.config.tool_repository.getTool(name);
29
- if (direct) return { tool: direct, utcpName: name };
30
-
31
- const all = await client.config.tool_repository.getTools();
32
- for (const tool of all) {
33
- if (utcpNameToTsInterfaceName(tool.name) === name) {
34
- return { tool, utcpName: tool.name };
35
- }
36
- }
37
- return null;
38
- }
7
+ export {
8
+ sanitizeIdentifier,
9
+ utcpNameToTsInterfaceName,
10
+ findToolByName,
11
+ AmbiguousToolNameError,
12
+ } from '@bevel-software/platform-mcp-core';
@@ -3,7 +3,7 @@ import { createTool } from '@mastra/core/tools';
3
3
  import { z } from 'zod';
4
4
  import { CodeModeUtcpClient } from '@utcp/code-mode';
5
5
  import type { SpillStore } from '../workspace/spill-store.js';
6
- import { utcpNameToTsInterfaceName, findToolByName } from './code-mode-names.js';
6
+ import { utcpNameToTsInterfaceName, findToolByName, AmbiguousToolNameError } from './code-mode-names.js';
7
7
 
8
8
  export function createCallToolChainTool(
9
9
  client: CodeModeUtcpClient,
@@ -103,15 +103,35 @@ export function createToolsInfoTool(client: CodeModeUtcpClient) {
103
103
  execute: async (input) => {
104
104
  const interfaces: string[] = [];
105
105
  const notFound: string[] = [];
106
+ const errors: string[] = [];
106
107
  for (const name of input.tool_names) {
107
- const found = await findToolByName(client, name);
108
- if (found) {
109
- interfaces.push(client.toolToTypeScriptInterface(found.tool));
110
- } else {
111
- notFound.push(name);
108
+ // Per-name, not per-call: `findToolByName` THROWS on an ambiguous
109
+ // sanitized name (two UTCP tools collapsing to one TS name), and one
110
+ // ambiguous entry aborting the whole batch would cost the agent every
111
+ // other answer in it. The error text says how to disambiguate, so it
112
+ // is the per-name answer, not a failure of the tool.
113
+ try {
114
+ const found = await findToolByName(client, name);
115
+ if (found) {
116
+ interfaces.push(client.toolToTypeScriptInterface(found.tool));
117
+ } else {
118
+ notFound.push(name);
119
+ }
120
+ } catch (err) {
121
+ // Only AMBIGUITY is a per-name answer (the message says how to
122
+ // disambiguate). Anything else — a repository outage, a catalog
123
+ // failure — must fail the call: containing it would dress an
124
+ // outage up as a successful lookup with partial data. Typed, not
125
+ // message-matched: the wording belongs to another package.
126
+ if (!(err instanceof AmbiguousToolNameError)) throw err;
127
+ errors.push(`${name}: ${err.message}`);
112
128
  }
113
129
  }
114
- return { interfaces: interfaces.join('\n\n'), not_found: notFound };
130
+ return {
131
+ interfaces: interfaces.join('\n\n'),
132
+ not_found: notFound,
133
+ ...(errors.length > 0 ? { errors } : {}),
134
+ };
115
135
  },
116
136
  });
117
137
  }
@@ -1,15 +1,15 @@
1
- import { drizzle } from 'drizzle-orm/node-postgres';
2
- import pg from 'pg';
3
- import * as schema from './schema.js';
4
-
5
- let db: ReturnType<typeof drizzle<typeof schema>> | null = null;
6
-
7
- export function getDb(databaseUrl: string) {
8
- if (!db) {
9
- const pool = new pg.Pool({ connectionString: databaseUrl });
10
- db = drizzle(pool, { schema });
11
- }
12
- return db;
13
- }
14
-
15
- export type Database = ReturnType<typeof getDb>;
1
+ import { drizzle } from 'drizzle-orm/node-postgres';
2
+ import pg from 'pg';
3
+ import * as schema from './schema.js';
4
+
5
+ let db: ReturnType<typeof drizzle<typeof schema>> | null = null;
6
+
7
+ export function getDb(databaseUrl: string) {
8
+ if (!db) {
9
+ const pool = new pg.Pool({ connectionString: databaseUrl });
10
+ db = drizzle(pool, { schema });
11
+ }
12
+ return db;
13
+ }
14
+
15
+ export type Database = ReturnType<typeof getDb>;
@@ -1,11 +1,11 @@
1
- /**
2
- * The database schema of the CORE platform.
3
- *
4
- * In this package `schema.ts` is a re-export of `core-schema.ts` ONLY — the
5
- * enterprise tables live with the enterprise product, whose own schema file
6
- * imports the FK targets (`users`, `apiTokens`) from
7
- * `@bevel-software/platform-core-backend/schema`. Kept as a separate module (rather
8
- * than folding the tables in here) so module code keeps importing
9
- * `./schema.js` unchanged.
10
- */
11
- export * from './core-schema.js';
1
+ /**
2
+ * The database schema of the CORE platform.
3
+ *
4
+ * In this package `schema.ts` is a re-export of `core-schema.ts` ONLY — the
5
+ * enterprise tables live with the enterprise product, whose own schema file
6
+ * imports the FK targets (`users`, `apiTokens`) from
7
+ * `@bevel-software/platform-core-backend/schema`. Kept as a separate module (rather
8
+ * than folding the tables in here) so module code keeps importing
9
+ * `./schema.js` unchanged.
10
+ */
11
+ export * from './core-schema.js';