@bevel-software/platform-core-backend 0.7.5 → 0.8.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 (230) hide show
  1. package/LICENSE +202 -202
  2. package/THIRD-PARTY-NOTICES.md +428 -454
  3. package/dist/core/core-ports.d.ts +1 -1
  4. package/dist/core/create-core-server.d.ts.map +1 -1
  5. package/dist/core/create-core-server.js +51 -7
  6. package/dist/core/create-core-server.js.map +1 -1
  7. package/dist/core/create-core-services.d.ts +5 -3
  8. package/dist/core/create-core-services.d.ts.map +1 -1
  9. package/dist/core/create-core-services.js +30 -18
  10. package/dist/core/create-core-services.js.map +1 -1
  11. package/dist/modules/access/access-control.interface.d.ts +8 -7
  12. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  13. package/dist/modules/access/access-control.service.d.ts +1 -1
  14. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  15. package/dist/modules/access/access-control.service.js +6 -6
  16. package/dist/modules/access/access-control.service.js.map +1 -1
  17. package/dist/modules/access/access-declarations.d.ts +5 -5
  18. package/dist/modules/access/access-declarations.js +3 -3
  19. package/dist/modules/access/access-mutation.service.d.ts +3 -3
  20. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  21. package/dist/modules/access/access-mutation.service.js +3 -3
  22. package/dist/modules/access/access-mutation.service.js.map +1 -1
  23. package/dist/modules/access/access-splice.js +4 -4
  24. package/dist/modules/access/access-splice.js.map +1 -1
  25. package/dist/modules/access/access.routes.js +19 -19
  26. package/dist/modules/access/access.routes.js.map +1 -1
  27. package/dist/modules/access/creator-access.d.ts +2 -2
  28. package/dist/modules/access/creator-access.js +5 -5
  29. package/dist/modules/access/creator-access.js.map +1 -1
  30. package/dist/modules/access/roles-admin.service.d.ts +18 -4
  31. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  32. package/dist/modules/access/roles-admin.service.js +14 -4
  33. package/dist/modules/access/roles-admin.service.js.map +1 -1
  34. package/dist/modules/code-mode/code-mode-names.d.ts +5 -13
  35. package/dist/modules/code-mode/code-mode-names.d.ts.map +1 -1
  36. package/dist/modules/code-mode/code-mode-names.js +5 -27
  37. package/dist/modules/code-mode/code-mode-names.js.map +1 -1
  38. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  39. package/dist/modules/code-mode/code-mode.tool.js +29 -7
  40. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  41. package/dist/modules/mcp/mcp.service.d.ts +11 -52
  42. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  43. package/dist/modules/mcp/mcp.service.js +33 -395
  44. package/dist/modules/mcp/mcp.service.js.map +1 -1
  45. package/dist/modules/plugins/index.d.ts +7 -0
  46. package/dist/modules/plugins/index.d.ts.map +1 -0
  47. package/dist/modules/plugins/index.js +6 -0
  48. package/dist/modules/plugins/index.js.map +1 -0
  49. package/dist/modules/plugins/join-proposals.d.ts +53 -0
  50. package/dist/modules/plugins/join-proposals.d.ts.map +1 -0
  51. package/dist/modules/plugins/join-proposals.js +67 -0
  52. package/dist/modules/plugins/join-proposals.js.map +1 -0
  53. package/dist/modules/plugins/join-requests.service.d.ts +81 -0
  54. package/dist/modules/plugins/join-requests.service.d.ts.map +1 -0
  55. package/dist/modules/plugins/join-requests.service.js +135 -0
  56. package/dist/modules/plugins/join-requests.service.js.map +1 -0
  57. package/dist/modules/plugins/plugin-provision.service.d.ts +134 -0
  58. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -0
  59. package/dist/modules/plugins/plugin-provision.service.js +344 -0
  60. package/dist/modules/plugins/plugin-provision.service.js.map +1 -0
  61. package/dist/modules/plugins/plugins.contract.d.ts +106 -0
  62. package/dist/modules/plugins/plugins.contract.d.ts.map +1 -0
  63. package/dist/modules/plugins/plugins.contract.js +36 -0
  64. package/dist/modules/plugins/plugins.contract.js.map +1 -0
  65. package/dist/modules/plugins/plugins.routes.d.ts +42 -0
  66. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -0
  67. package/dist/modules/plugins/plugins.routes.js +379 -0
  68. package/dist/modules/plugins/plugins.routes.js.map +1 -0
  69. package/dist/modules/plugins/plugins.service.d.ts +60 -0
  70. package/dist/modules/plugins/plugins.service.d.ts.map +1 -0
  71. package/dist/modules/plugins/plugins.service.js +172 -0
  72. package/dist/modules/plugins/plugins.service.js.map +1 -0
  73. package/dist/modules/skills/pending-skills.service.d.ts +2 -2
  74. package/dist/modules/skills/pending-skills.service.js +7 -7
  75. package/dist/modules/skills/pending-skills.service.js.map +1 -1
  76. package/dist/modules/skills/skills.contract.d.ts +4 -4
  77. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  78. package/dist/modules/skills/skills.contract.js +1 -1
  79. package/dist/modules/skills/skills.service.js +5 -5
  80. package/dist/modules/skills/skills.service.js.map +1 -1
  81. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +65 -0
  82. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -0
  83. package/dist/modules/tool-manuals/mcp-json-discovery.js +276 -0
  84. package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -0
  85. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +92 -0
  86. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -0
  87. package/dist/modules/tool-manuals/mcp-server-edit.service.js +328 -0
  88. package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -0
  89. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +38 -12
  90. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  91. package/dist/modules/tool-manuals/tool-manuals.contract.js +1 -1
  92. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +13 -2
  93. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  94. package/dist/modules/tool-manuals/tool-manuals.routes.js +233 -2
  95. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  96. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  97. package/dist/modules/tool-manuals/tool-manuals.service.js +74 -37
  98. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  99. package/dist/modules/tool-manuals/tool-manuals.tools.js +6 -3
  100. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  101. package/dist/modules/workflow/git/git.service.js +2 -2
  102. package/dist/modules/workflow/git/git.service.js.map +1 -1
  103. package/dist/modules/workspace/kb-seed.service.d.ts +2 -2
  104. package/dist/modules/workspace/kb-seed.service.d.ts.map +1 -1
  105. package/dist/modules/workspace/kb-seed.service.js +43 -10
  106. package/dist/modules/workspace/kb-seed.service.js.map +1 -1
  107. package/dist/modules/workspace/plugins-migration.d.ts +50 -0
  108. package/dist/modules/workspace/plugins-migration.d.ts.map +1 -0
  109. package/dist/modules/workspace/plugins-migration.js +379 -0
  110. package/dist/modules/workspace/plugins-migration.js.map +1 -0
  111. package/dist/modules/workspace/workspace.routes.js +3 -3
  112. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  113. package/dist/modules/workspace/workspace.service.d.ts +26 -0
  114. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  115. package/dist/modules/workspace/workspace.service.js +83 -12
  116. package/dist/modules/workspace/workspace.service.js.map +1 -1
  117. package/dist/shared/kb-layout.test.js +3 -3
  118. package/dist/shared/kb-layout.test.js.map +1 -1
  119. package/dist/shared/utcp-namespace.d.ts +6 -27
  120. package/dist/shared/utcp-namespace.d.ts.map +1 -1
  121. package/dist/shared/utcp-namespace.js +6 -63
  122. package/dist/shared/utcp-namespace.js.map +1 -1
  123. package/dist/shared/variable-refs.d.ts +42 -0
  124. package/dist/shared/variable-refs.d.ts.map +1 -0
  125. package/dist/shared/variable-refs.js +60 -0
  126. package/dist/shared/variable-refs.js.map +1 -0
  127. package/kb-template/.bevelignore +1 -1
  128. package/kb-template/AGENTS.md +88 -35
  129. package/kb-template/KnowledgeBase/How to get started.md +10 -10
  130. package/kb-template/access.md +36 -36
  131. package/migrations/meta/0000_snapshot.json +1479 -1479
  132. package/package.json +5 -4
  133. package/src/assets.ts +25 -25
  134. package/src/core/core-ports.ts +106 -106
  135. package/src/core/create-core-server.ts +55 -9
  136. package/src/core/create-core-services.ts +40 -20
  137. package/src/index.ts +69 -69
  138. package/src/modules/access/__tests__/access-control.atref-batch.test.ts +98 -98
  139. package/src/modules/access/__tests__/access-declarations.test.ts +28 -28
  140. package/src/modules/access/__tests__/access-md-format.test.ts +18 -18
  141. package/src/modules/access/__tests__/access-mutation.service.test.ts +5 -5
  142. package/src/modules/access/__tests__/access-splice.test.ts +2 -2
  143. package/src/modules/access/__tests__/access.routes.overrides.test.ts +16 -16
  144. package/src/modules/access/__tests__/grant-sources.test.ts +12 -12
  145. package/src/modules/access/__tests__/roles-admin.service.test.ts +13 -1
  146. package/src/modules/access/access-control.interface.ts +8 -7
  147. package/src/modules/access/access-control.service.ts +7 -7
  148. package/src/modules/access/access-declarations.ts +5 -5
  149. package/src/modules/access/access-mutation.service.ts +3 -3
  150. package/src/modules/access/access-splice.ts +4 -4
  151. package/src/modules/access/access.routes.ts +20 -20
  152. package/src/modules/access/creator-access.ts +5 -5
  153. package/src/modules/access/roles-admin.service.ts +13 -2
  154. package/src/modules/admin/admin-access.routes.ts +29 -29
  155. package/src/modules/auth/__tests__/auth.routes.test.ts +91 -91
  156. package/src/modules/auth/__tests__/rate-limit.test.ts +36 -36
  157. package/src/modules/auth/rate-limit.ts +45 -45
  158. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +67 -0
  159. package/src/modules/code-mode/code-mode-names.ts +10 -36
  160. package/src/modules/code-mode/code-mode.tool.ts +27 -7
  161. package/src/modules/database/connection.ts +15 -15
  162. package/src/modules/database/schema.ts +11 -11
  163. package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +150 -150
  164. package/src/modules/mcp/mcp.service.ts +57 -435
  165. package/src/modules/{groups → plugins}/__tests__/join-proposals.test.ts +1 -1
  166. package/src/modules/{groups → plugins}/__tests__/join-requests.service.test.ts +7 -7
  167. package/src/modules/{groups/__tests__/group-index.service.test.ts → plugins/__tests__/plugin-index.service.test.ts} +41 -41
  168. package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +312 -0
  169. package/src/modules/{groups/__tests__/groups.routes.test.ts → plugins/__tests__/plugins.routes.test.ts} +100 -100
  170. package/src/modules/plugins/index.ts +17 -0
  171. package/src/modules/{groups → plugins}/join-proposals.ts +2 -2
  172. package/src/modules/{groups → plugins}/join-requests.service.ts +8 -8
  173. package/src/modules/{groups/group-provision.service.ts → plugins/plugin-provision.service.ts} +139 -69
  174. package/src/modules/{groups/groups.contract.ts → plugins/plugins.contract.ts} +26 -26
  175. package/src/modules/{groups/groups.routes.ts → plugins/plugins.routes.ts} +102 -102
  176. package/src/modules/{groups/groups.service.ts → plugins/plugins.service.ts} +43 -43
  177. package/src/modules/secrets-vault/__tests__/tool-owner-gate.route.test.ts +143 -143
  178. package/src/modules/skills/__tests__/pending-skills.service.test.ts +14 -14
  179. package/src/modules/skills/__tests__/skills.service.test.ts +13 -13
  180. package/src/modules/skills/pending-skills.service.ts +7 -7
  181. package/src/modules/skills/skills.contract.ts +4 -4
  182. package/src/modules/skills/skills.service.ts +5 -5
  183. package/src/modules/tool-auth/llm-usage-meter.ts +19 -19
  184. package/src/modules/tool-manuals/__tests__/mcp-json-discovery.test.ts +198 -0
  185. package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +346 -0
  186. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +101 -0
  187. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +3 -3
  188. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +2 -2
  189. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +27 -27
  190. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +3 -3
  191. package/src/modules/tool-manuals/mcp-json-discovery.ts +328 -0
  192. package/src/modules/tool-manuals/mcp-server-edit.service.ts +434 -0
  193. package/src/modules/tool-manuals/tool-manuals.contract.ts +35 -12
  194. package/src/modules/tool-manuals/tool-manuals.routes.ts +222 -1
  195. package/src/modules/tool-manuals/tool-manuals.service.ts +82 -42
  196. package/src/modules/tool-manuals/tool-manuals.tools.ts +6 -3
  197. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
  198. package/src/modules/workflow/git/__tests__/branch-name.test.ts +3 -3
  199. package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +3 -3
  200. package/src/modules/workflow/git/__tests__/git.service.commitFile.test.ts +13 -8
  201. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +1 -1
  202. package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
  203. package/src/modules/workflow/git/git.service.ts +2 -2
  204. package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +1 -1
  205. package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
  206. package/src/modules/workflow/workflow-hooks.ts +101 -101
  207. package/src/modules/workspace/__tests__/kb-seed.service.test.ts +81 -13
  208. package/src/modules/workspace/__tests__/plugins-migration.test.ts +427 -0
  209. package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +237 -237
  210. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +236 -236
  211. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +179 -179
  212. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +320 -320
  213. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +337 -337
  214. package/src/modules/workspace/__tests__/workspace.service.test.ts +116 -0
  215. package/src/modules/workspace/bevel-ignore.ts +66 -66
  216. package/src/modules/workspace/kb-seed.service.ts +38 -9
  217. package/src/modules/workspace/plugins-migration.ts +479 -0
  218. package/src/modules/workspace/session-sink.ts +25 -25
  219. package/src/modules/workspace/workspace.routes.ts +3 -3
  220. package/src/modules/workspace/workspace.service.ts +85 -14
  221. package/src/modules/workspace/workspace.tools.ts +922 -922
  222. package/src/shared/__tests__/join-request.test.ts +13 -13
  223. package/src/shared/__tests__/kb-layout.plugin.test.ts +45 -0
  224. package/src/shared/kb-layout.test.ts +3 -3
  225. package/src/shared/utcp-namespace.ts +10 -68
  226. package/src/shared/variable-refs.ts +64 -0
  227. package/src/modules/groups/__tests__/group-provision.service.test.ts +0 -247
  228. package/src/modules/groups/index.ts +0 -17
  229. package/src/shared/__tests__/kb-layout.group.test.ts +0 -45
  230. /package/kb-template/{Groups → Plugins}/.gitkeep +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bevel-software/platform-core-backend",
3
- "version": "0.7.5",
3
+ "version": "0.8.0",
4
4
  "description": "Open-source core backend of the Bevel platform: git-backed knowledge workspace, workflow (branches/change requests/locks/SSE), skills, tools, secrets vault, access control and the remote MCP surface.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -36,7 +36,7 @@
36
36
  "@utcp/http": "^1.1.7",
37
37
  "@utcp/mcp": "^1.1.3",
38
38
  "@utcp/sdk": "^1.1.1",
39
- "adm-zip": "^0.5.17",
39
+ "adm-zip": "^0.6.0",
40
40
  "cors": "^2.8.5",
41
41
  "dotenv": "^16.4.0",
42
42
  "drizzle-orm": "^0.45.2",
@@ -46,7 +46,8 @@
46
46
  "pg": "^8.20.0",
47
47
  "yaml": "^2.9.0",
48
48
  "zod": "^3.24.0",
49
- "@bevel-software/platform-shared": "0.7.5"
49
+ "@bevel-software/platform-mcp-core": "0.8.0",
50
+ "@bevel-software/platform-shared": "0.8.0"
50
51
  },
51
52
  "devDependencies": {
52
53
  "@types/adm-zip": "^0.5.8",
@@ -60,7 +61,7 @@
60
61
  "drizzle-kit": "^0.31.10",
61
62
  "tsx": "^4.19.0",
62
63
  "typescript": "^5.8.0",
63
- "vitest": "^2.1.9"
64
+ "vitest": "^3.2.6"
64
65
  },
65
66
  "license": "Apache-2.0",
66
67
  "repository": {
package/src/assets.ts CHANGED
@@ -1,25 +1,25 @@
1
- import path from 'node:path';
2
- import { fileURLToPath } from 'node:url';
3
-
4
- /**
5
- * Locations of the assets shipped INSIDE this package (`files` in
6
- * package.json): the squashed core migration history (`migrations/`) and the
7
- * KB seed template (`kb-template/`). Both live at the PACKAGE ROOT, and this
8
- * module is a direct child of either `src/` (in-repo / tsx) or `dist/`
9
- * (compiled) — so one `..` hop from the module URL reaches the package root
10
- * in BOTH layouts. Resolved lazily so bundlers that rewrite `import.meta.url`
11
- * still get a sensible answer at call time.
12
- */
13
- function packageRoot(): string {
14
- return fileURLToPath(new URL('..', import.meta.url));
15
- }
16
-
17
- /** Absolute path of the packaged core Drizzle migrations folder. */
18
- export function coreMigrationsDir(): string {
19
- return path.join(packageRoot(), 'migrations');
20
- }
21
-
22
- /** Absolute path of the packaged KB seed template (`kb-template/`). */
23
- export function defaultKbTemplateDir(): string {
24
- return path.join(packageRoot(), 'kb-template');
25
- }
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+
4
+ /**
5
+ * Locations of the assets shipped INSIDE this package (`files` in
6
+ * package.json): the squashed core migration history (`migrations/`) and the
7
+ * KB seed template (`kb-template/`). Both live at the PACKAGE ROOT, and this
8
+ * module is a direct child of either `src/` (in-repo / tsx) or `dist/`
9
+ * (compiled) — so one `..` hop from the module URL reaches the package root
10
+ * in BOTH layouts. Resolved lazily so bundlers that rewrite `import.meta.url`
11
+ * still get a sensible answer at call time.
12
+ */
13
+ function packageRoot(): string {
14
+ return fileURLToPath(new URL('..', import.meta.url));
15
+ }
16
+
17
+ /** Absolute path of the packaged core Drizzle migrations folder. */
18
+ export function coreMigrationsDir(): string {
19
+ return path.join(packageRoot(), 'migrations');
20
+ }
21
+
22
+ /** Absolute path of the packaged KB seed template (`kb-template/`). */
23
+ export function defaultKbTemplateDir(): string {
24
+ return path.join(packageRoot(), 'kb-template');
25
+ }
@@ -1,106 +1,106 @@
1
- import type { ISessionSink } from '../modules/workspace/session-sink.js';
2
- import type {
3
- ISystemNoticeSink,
4
- RecoveryAgentRunner,
5
- } from '../modules/workflow/pending-commits.worker.js';
6
- import type { ILlmUsageMeter } from '../modules/tool-auth/llm-usage-meter.js';
7
- import type { AuthProviderPlugin } from '../modules/auth/auth.routes.js';
8
- import type { IErasureParticipant } from '../modules/auth/account-erasure.service.js';
9
-
10
- /**
11
- * Every seam the enterprise overlay can fill in the CORE composition
12
- * (`createCoreServices`). All fields are optional; each has a safe core-only
13
- * default, so `createCoreServices(config)` with no ports yields a fully
14
- * working core deployment.
15
- *
16
- * Two consumption patterns exist, and they matter for wiring order:
17
- *
18
- * - CONSTRUCTION-TIME ports (`recoveryAgent`, `systemNotice`,
19
- * `erasureParticipants`): dereferenced while core services are being
20
- * constructed. Enterprise implementations of these need core services that
21
- * don't exist yet at that point (the background-agent factory, the thread
22
- * service, …), so the enterprise root passes FORWARDING DELEGATORS / a
23
- * mutable array here and assigns the real targets right after
24
- * `createCoreServices` returns. That is safe because core only *calls* these
25
- * ports at request/commit/erasure time, long after boot — the same
26
- * late-binding pattern the composition root already uses for e.g. the MCP
27
- * OAuth revoke closure.
28
- *
29
- * - SERVER-TIME seams (`sessionSink`, `usageMeter`, `authProviders`): only
30
- * read when `createCoreServer` mounts routes / registers tools. Core seeds
31
- * them onto `CoreServices` from these ports (with the defaults below); the
32
- * enterprise root overwrites the `CoreServices` fields (or pushes into the
33
- * `authProviders` array) after construction, before the server is built.
34
- *
35
- * (Commit-time KB validation and the ontology write block are NOT ports:
36
- * they are workflow lifecycle HOOKS the enterprise root registers on
37
- * `workflowService.hooks` after construction — see
38
- * `modules/workflow/workflow-hooks.ts`.)
39
- */
40
- export interface CorePorts {
41
- /**
42
- * Backs the `start_session` tool. Core default: {@link UuidSessionSink}
43
- * (a bare random id). Enterprise substitutes a sink that mints a real chat
44
- * thread so the same id works end to end with `ask`.
45
- */
46
- sessionSink?: ISessionSink;
47
- /**
48
- * Recovery-agent dispatcher for the pending-commits worker. The worker
49
- * requires one, so the core default is {@link noopRecoveryAgent} — a run
50
- * that just returns, meaning the row stays pending until the recovery
51
- * budget is exhausted and the worker escalates via `systemNotice`.
52
- */
53
- recoveryAgent?: RecoveryAgentRunner;
54
- /**
55
- * Where the pending-commits worker escalates terminal failures. Core
56
- * default: {@link consoleSystemNoticeSink} (stderr, no dashboard).
57
- * Enterprise passes its feedback service.
58
- */
59
- systemNotice?: ISystemNoticeSink;
60
- /**
61
- * Per-connection-key LLM usage metering shown on the connection-key
62
- * management routes. Core default: {@link unmeteredLlmUsage} (no proxy,
63
- * nothing to meter). Enterprise passes the LLM proxy's usage service.
64
- */
65
- usageMeter?: ILlmUsageMeter;
66
- /**
67
- * SSO login plugins the auth router mounts. Core default: `[]` (password
68
- * login only). The array instance is kept on `CoreServices.authProviders`,
69
- * so an enterprise root may pass an array it fills after construction.
70
- */
71
- authProviders?: AuthProviderPlugin[];
72
- /**
73
- * Module-owned GDPR-erasure slices run by `AccountErasureService`. Core
74
- * default: `[]` (core erases only the rows it owns). The array reference is
75
- * held — not copied — so an enterprise root may pass an array it fills after
76
- * construction (participants are only iterated at erasure time).
77
- */
78
- erasureParticipants?: IErasureParticipant[];
79
- /**
80
- * Root folders THIS distribution reserves in the knowledge base, on top of
81
- * core's `KnowledgeBase/` + `Groups/`. Core default: `[]`.
82
- *
83
- * A name here does two things: the seeder guarantees the folder exists on
84
- * every branch it tops up, and — because the names are already reserved in
85
- * `kb-layout.ts` — the file tree keeps rendering it as its own root instead
86
- * of folding it into Knowledge as stray content.
87
- *
88
- * The folder is created as an empty `<dir>/.gitkeep`, written rather than
89
- * copied, so reserving a root does not oblige a distribution to fork the
90
- * packaged template. Ship a `KB_TEMPLATE_DIR` of your own when those folders
91
- * should arrive carrying READMEs.
92
- */
93
- kbExtraRootDirs?: readonly string[];
94
- }
95
-
96
- /**
97
- * Core default recovery agent: does nothing. The worker's retry ladder still
98
- * works — a stuck row burns its recovery budget on these no-op runs and then
99
- * escalates to the `systemNotice` sink, which is exactly the "no recovery
100
- * agent available" behavior a core-only deployment wants.
101
- */
102
- export const noopRecoveryAgent: RecoveryAgentRunner = {
103
- async run(): Promise<void> {
104
- // Intentionally empty — see doc comment.
105
- },
106
- };
1
+ import type { ISessionSink } from '../modules/workspace/session-sink.js';
2
+ import type {
3
+ ISystemNoticeSink,
4
+ RecoveryAgentRunner,
5
+ } from '../modules/workflow/pending-commits.worker.js';
6
+ import type { ILlmUsageMeter } from '../modules/tool-auth/llm-usage-meter.js';
7
+ import type { AuthProviderPlugin } from '../modules/auth/auth.routes.js';
8
+ import type { IErasureParticipant } from '../modules/auth/account-erasure.service.js';
9
+
10
+ /**
11
+ * Every seam the enterprise overlay can fill in the CORE composition
12
+ * (`createCoreServices`). All fields are optional; each has a safe core-only
13
+ * default, so `createCoreServices(config)` with no ports yields a fully
14
+ * working core deployment.
15
+ *
16
+ * Two consumption patterns exist, and they matter for wiring order:
17
+ *
18
+ * - CONSTRUCTION-TIME ports (`recoveryAgent`, `systemNotice`,
19
+ * `erasureParticipants`): dereferenced while core services are being
20
+ * constructed. Enterprise implementations of these need core services that
21
+ * don't exist yet at that point (the background-agent factory, the thread
22
+ * service, …), so the enterprise root passes FORWARDING DELEGATORS / a
23
+ * mutable array here and assigns the real targets right after
24
+ * `createCoreServices` returns. That is safe because core only *calls* these
25
+ * ports at request/commit/erasure time, long after boot — the same
26
+ * late-binding pattern the composition root already uses for e.g. the MCP
27
+ * OAuth revoke closure.
28
+ *
29
+ * - SERVER-TIME seams (`sessionSink`, `usageMeter`, `authProviders`): only
30
+ * read when `createCoreServer` mounts routes / registers tools. Core seeds
31
+ * them onto `CoreServices` from these ports (with the defaults below); the
32
+ * enterprise root overwrites the `CoreServices` fields (or pushes into the
33
+ * `authProviders` array) after construction, before the server is built.
34
+ *
35
+ * (Commit-time KB validation and the ontology write block are NOT ports:
36
+ * they are workflow lifecycle HOOKS the enterprise root registers on
37
+ * `workflowService.hooks` after construction — see
38
+ * `modules/workflow/workflow-hooks.ts`.)
39
+ */
40
+ export interface CorePorts {
41
+ /**
42
+ * Backs the `start_session` tool. Core default: {@link UuidSessionSink}
43
+ * (a bare random id). Enterprise substitutes a sink that mints a real chat
44
+ * thread so the same id works end to end with `ask`.
45
+ */
46
+ sessionSink?: ISessionSink;
47
+ /**
48
+ * Recovery-agent dispatcher for the pending-commits worker. The worker
49
+ * requires one, so the core default is {@link noopRecoveryAgent} — a run
50
+ * that just returns, meaning the row stays pending until the recovery
51
+ * budget is exhausted and the worker escalates via `systemNotice`.
52
+ */
53
+ recoveryAgent?: RecoveryAgentRunner;
54
+ /**
55
+ * Where the pending-commits worker escalates terminal failures. Core
56
+ * default: {@link consoleSystemNoticeSink} (stderr, no dashboard).
57
+ * Enterprise passes its feedback service.
58
+ */
59
+ systemNotice?: ISystemNoticeSink;
60
+ /**
61
+ * Per-connection-key LLM usage metering shown on the connection-key
62
+ * management routes. Core default: {@link unmeteredLlmUsage} (no proxy,
63
+ * nothing to meter). Enterprise passes the LLM proxy's usage service.
64
+ */
65
+ usageMeter?: ILlmUsageMeter;
66
+ /**
67
+ * SSO login plugins the auth router mounts. Core default: `[]` (password
68
+ * login only). The array instance is kept on `CoreServices.authProviders`,
69
+ * so an enterprise root may pass an array it fills after construction.
70
+ */
71
+ authProviders?: AuthProviderPlugin[];
72
+ /**
73
+ * Module-owned GDPR-erasure slices run by `AccountErasureService`. Core
74
+ * default: `[]` (core erases only the rows it owns). The array reference is
75
+ * held — not copied — so an enterprise root may pass an array it fills after
76
+ * construction (participants are only iterated at erasure time).
77
+ */
78
+ erasureParticipants?: IErasureParticipant[];
79
+ /**
80
+ * Root folders THIS distribution reserves in the knowledge base, on top of
81
+ * core's `KnowledgeBase/` + `Plugins/`. Core default: `[]`.
82
+ *
83
+ * A name here does two things: the seeder guarantees the folder exists on
84
+ * every branch it tops up, and — because the names are already reserved in
85
+ * `kb-layout.ts` — the file tree keeps rendering it as its own root instead
86
+ * of folding it into Knowledge as stray content.
87
+ *
88
+ * The folder is created as an empty `<dir>/.gitkeep`, written rather than
89
+ * copied, so reserving a root does not oblige a distribution to fork the
90
+ * packaged template. Ship a `KB_TEMPLATE_DIR` of your own when those folders
91
+ * should arrive carrying READMEs.
92
+ */
93
+ kbExtraRootDirs?: readonly string[];
94
+ }
95
+
96
+ /**
97
+ * Core default recovery agent: does nothing. The worker's retry ladder still
98
+ * works — a stuck row burns its recovery budget on these no-op runs and then
99
+ * escalates to the `systemNotice` sink, which is exactly the "no recovery
100
+ * agent available" behavior a core-only deployment wants.
101
+ */
102
+ export const noopRecoveryAgent: RecoveryAgentRunner = {
103
+ async run(): Promise<void> {
104
+ // Intentionally empty — see doc comment.
105
+ },
106
+ };
@@ -21,7 +21,7 @@ import { registerWorkflowTools } from '../modules/workflow/agent-tools/workflow.
21
21
  import { registerWorkspaceTools } from '../modules/workspace/workspace.tools.js';
22
22
  import { RECOVERY_BOT_EMAIL } from '../modules/workflow/recovery-bot.js';
23
23
  import { registerSkillsTools, createSkillsRoutes } from '../modules/skills/index.js';
24
- import { createGroupsRoutes } from '../modules/groups/index.js';
24
+ import { createPluginsRoutes } from '../modules/plugins/index.js';
25
25
  import type { SessionOntologyGate } from '../modules/workspace/session-ontology.gate.js';
26
26
  import {
27
27
  createSecretsVaultRoutes,
@@ -30,7 +30,7 @@ import {
30
30
  import { createAdminAccessRoutes } from '../modules/admin/admin-access.routes.js';
31
31
  import { createAccountRoutes } from '../modules/auth/account.routes.js';
32
32
  import { createSetupRoutes } from '../modules/settings/setup.routes.js';
33
- import { DEFAULT_BRANCH, PROTECTED_BRANCHES } from '@bevel-software/platform-shared';
33
+ import { DEFAULT_BRANCH, PROTECTED_BRANCHES, type AuthUser } from '@bevel-software/platform-shared';
34
34
  import { GIT_SHA } from '../version.js';
35
35
  import type { CoreServices } from './create-core-services.js';
36
36
 
@@ -150,6 +150,30 @@ export async function createCoreServer(
150
150
  res.json({ status: 'ok', sha: GIT_SHA, timestamp: Date.now() });
151
151
  });
152
152
 
153
+ /**
154
+ * This deployment's MCP endpoint, derived ONCE.
155
+ *
156
+ * Two consumers must agree on it: `/api/config` below, which is where the
157
+ * frontend learns what address to show people, and the OAuth
158
+ * `resourceServerUrl` further down, which is the resource identifier the
159
+ * protected-resource metadata publishes and therefore the one that decides
160
+ * whether a connection actually works.
161
+ *
162
+ * A constant rather than the same expression written twice. The frontend
163
+ * carried the two-places version of this bug for a long time — six inline
164
+ * sites each rebuilding the address, held together by a docstring asserting
165
+ * they could not diverge — and a comment is not a mechanism.
166
+ *
167
+ * Userinfo is STRIPPED: a `PUBLIC_BACKEND_URL` spelled with `user:pass@`
168
+ * (a basic-auth proxy in front of the deployment, say) would otherwise be
169
+ * republished verbatim by the unauthenticated `/api/config` below — a
170
+ * credential handed to any caller. The address is ours to publish; the
171
+ * credential never was.
172
+ */
173
+ const mcpResourceUrl = new URL('/api/mcp', core.config.publicBackendUrl);
174
+ mcpResourceUrl.username = '';
175
+ mcpResourceUrl.password = '';
176
+
153
177
  /**
154
178
  * The handful of facts the browser needs BEFORE it can render anything, and
155
179
  * therefore before it can authenticate: the branch model.
@@ -170,6 +194,21 @@ export async function createCoreServer(
170
194
  defaultBranch: DEFAULT_BRANCH,
171
195
  protectedBranches: [...PROTECTED_BRANCHES],
172
196
  },
197
+ /**
198
+ * The same value the OAuth metadata publishes (see `mcpResourceUrl`).
199
+ *
200
+ * The frontend used to build this from `window.location.origin`, which
201
+ * is the browser's idea of our address rather than ours. The two agree
202
+ * on a simple deployment and disagree behind a proxy, on a second
203
+ * domain, or on an internal hostname — and the one that decides whether
204
+ * a connection works is this one.
205
+ *
206
+ * That was survivable while every surface was copy-paste: a human sees
207
+ * the host before pasting it. It stops being survivable the moment we
208
+ * hand the URL to a third party (a connector install link), where
209
+ * nobody reads it and the failure surfaces inside someone else's UI.
210
+ */
211
+ mcpUrl: mcpResourceUrl.toString(),
173
212
  });
174
213
  });
175
214
 
@@ -225,7 +264,7 @@ export async function createCoreServer(
225
264
  app.use(mcpAuthRouter({
226
265
  provider: core.mcpOAuthProvider,
227
266
  issuerUrl: new URL(core.config.publicBackendUrl),
228
- resourceServerUrl: new URL('/api/mcp', core.config.publicBackendUrl),
267
+ resourceServerUrl: mcpResourceUrl,
229
268
  scopesSupported: ['mcp'],
230
269
  resourceName: 'Bevel MCP',
231
270
  }));
@@ -300,6 +339,7 @@ export async function createCoreServer(
300
339
  core.toolManualService,
301
340
  core.manualAuthMiddleware,
302
341
  async (userId) => (await core.authService.getUserById(userId))?.email,
342
+ { workspaceService: core.workspaceService, accessControl: core.accessControl, kbDirName: core.kbDirName },
303
343
  ));
304
344
  app.use('/api', toolsRouter);
305
345
 
@@ -355,17 +395,17 @@ export async function createCoreServer(
355
395
  core.authMiddleware,
356
396
  createSkillsRoutes(core.skillService, core.pendingSkillsService),
357
397
  );
358
- // Group enumeration + join requests. Browser-only (JWT), and fail-closed
359
- // like every other read surface: groups the caller cannot access (member,
398
+ // Plugin enumeration + join requests. Browser-only (JWT), and fail-closed
399
+ // like every other read surface: plugins the caller cannot access (member,
360
400
  // manager, or discoverable via the access.md file's own read grant) are
361
401
  // absent from the list. A join request is a plain change request.
362
- app.use('/api', core.authMiddleware, createGroupsRoutes(
363
- core.groupIndexService,
402
+ app.use('/api', core.authMiddleware, createPluginsRoutes(
403
+ core.pluginIndexService,
364
404
  core.accessControl,
365
405
  core.workflowService,
366
406
  core.workspaceService,
367
407
  core.joinRequestsService,
368
- core.groupProvisionService,
408
+ core.pluginProvisionService,
369
409
  core.kbDirName,
370
410
  async (req) => (req.userId ? ((await core.authService.getUserById(req.userId)) ?? null) : null),
371
411
  ));
@@ -383,7 +423,13 @@ export async function createCoreServer(
383
423
  // workspace — it has to work on a deployment that has no knowledge base yet,
384
424
  // which is the whole reason it exists.
385
425
  app.use('/api', core.authMiddleware, createSetupRoutes(core.settings, core.adminAccess));
386
- app.use('/api', core.authMiddleware, createToolManualsBrowserRoutes(core.toolManualService));
426
+ app.use('/api', core.authMiddleware, createToolManualsBrowserRoutes(core.toolManualService, {
427
+ service: core.mcpServerEditService,
428
+ getUser: async (userId) => {
429
+ const u = await core.authService.getUserById(userId);
430
+ return u ? ({ id: u.id, email: u.email, name: u.name } as AuthUser) : undefined;
431
+ },
432
+ }));
387
433
  app.use('/api', core.authMiddleware, createSecretsVaultRoutes(secretsVaultRoutesDeps));
388
434
  // The authed tail of the MCP OAuth flow: /connect calls these to describe
389
435
  // the pending authorization and, on Finish, to mint the one-time code. The
@@ -5,7 +5,7 @@ import {
5
5
  configureBranchModel,
6
6
  validateBranchModel,
7
7
  PROTECTED_BRANCHES,
8
- GROUPS_DIR,
8
+ PLUGINS_DIR,
9
9
  } from '@bevel-software/platform-shared';
10
10
  import { CoreConfig } from '../core-config.js';
11
11
  import { getDb, type Database } from '../modules/database/connection.js';
@@ -33,7 +33,8 @@ import { AccessControlService } from '../modules/access/access-control.service.j
33
33
  import { CreatorAccessService } from '../modules/access/creator-access.js';
34
34
  import { PendingSkillsService, SkillService } from '../modules/skills/index.js';
35
35
  import { ToolManualService } from '../modules/tool-manuals/index.js';
36
- import { GroupIndexService, GroupProvisionService, JoinRequestsService } from '../modules/groups/index.js';
36
+ import { McpServerEditService } from '../modules/tool-manuals/mcp-server-edit.service.js';
37
+ import { PluginIndexService, PluginProvisionService, JoinRequestsService } from '../modules/plugins/index.js';
37
38
  import {
38
39
  DbSecretsVaultService,
39
40
  McpOAuthDiscoveryService,
@@ -102,8 +103,9 @@ export interface CoreServices {
102
103
  skillService: SkillService;
103
104
  pendingSkillsService: PendingSkillsService;
104
105
  toolManualService: ToolManualService;
105
- groupIndexService: GroupIndexService;
106
- groupProvisionService: GroupProvisionService;
106
+ mcpServerEditService: McpServerEditService;
107
+ pluginIndexService: PluginIndexService;
108
+ pluginProvisionService: PluginProvisionService;
107
109
  joinRequestsService: JoinRequestsService;
108
110
  authService: AuthService;
109
111
  authMiddleware: ReturnType<typeof createAuthMiddleware>;
@@ -260,15 +262,15 @@ export async function createCoreServices(
260
262
  const routineWritePolicy = new RoutineWritePolicyService();
261
263
  // Skills: discovered from the default-branch workspace only (global catalog).
262
264
  const skillService = new SkillService(workspaceService, accessControl, kbDirName);
263
- // Tool manuals: user-authored `*.tool` files under `Groups/` in the default
265
+ // Tool manuals: user-authored `*.tool` files under `Plugins/` in the default
264
266
  // branch — access-controlled like Skills, served to external agents via
265
267
  // `GET /api/agent/all-tools` and registered on the MCP proxy's UTCP client.
266
268
  const toolManualService = new ToolManualService(workspaceService, accessControl, kbDirName);
267
- // Groups: the folders under `Groups/` that carry a
269
+ // Plugins: the folders under `Plugins/` that carry a
268
270
  // team's skills AND the tools they need. Enumerated for EVERY authenticated
269
- // caller — a group they cannot read still exists for them, as a locked one —
271
+ // caller — a plugin they cannot read still exists for them, as a locked one —
270
272
  // with the counts read off the two catalogs above rather than a second scan.
271
- const groupIndexService = new GroupIndexService(
273
+ const pluginIndexService = new PluginIndexService(
272
274
  workspaceService,
273
275
  accessControl,
274
276
  skillService,
@@ -384,16 +386,28 @@ export async function createCoreServices(
384
386
  workflowHooks,
385
387
  );
386
388
 
387
- // Join requests: derived entirely from two copies of a group's `access.md`
389
+ // Join requests: derived entirely from two copies of a plugin's `access.md`
388
390
  // (the request's branch vs the default branch), so it holds no state — it
389
391
  // only needs to read files at refs and to close a request whose proposals
390
392
  // have all landed.
391
393
  const joinRequestsService = new JoinRequestsService(workspaceService, workflowService);
392
- // Group provisioning — the one privileged door that brings `Groups/<name>/`
393
- // folders into existence (named groups and personal folders alike). Commits
394
+ // Server-scoped MCP editing — the tool page's edit form. One server's truth
395
+ // spans a plugin's mcp.json AND plugin.json extensions block, and this is
396
+ // the ONE writer that rewrites both entries and commits them together (see
397
+ // McpServerEditService).
398
+ const mcpServerEditService = new McpServerEditService(
399
+ workspaceService,
400
+ workflowService,
401
+ accessControl,
402
+ toolManualService,
403
+ kbDirName,
404
+ );
405
+
406
+ // Plugin provisioning — the one privileged door that brings `Plugins/<name>/`
407
+ // folders into existence (named plugins and personal folders alike). Commits
394
408
  // INLINE through the same pipeline the pending-commits worker uses, so the
395
409
  // folder's rules are at HEAD before the endpoint answers.
396
- const groupProvisionService = new GroupProvisionService(
410
+ const pluginProvisionService = new PluginProvisionService(
397
411
  workspaceService,
398
412
  workflowService,
399
413
  accessControl,
@@ -422,16 +436,16 @@ export async function createCoreServices(
422
436
  // caches are independent, so the split preserves behavior.)
423
437
  fileChangeNotifier.onFilesChanged(({ branch, paths }) => {
424
438
  if (branch !== DEFAULT_BRANCH) return;
425
- // Skills, tools and the group index all live under `Groups/`, so one
439
+ // Skills, tools and the plugin index all live under `Plugins/`, so one
426
440
  // touch check drives all three caches. An access grant lands as a
427
- // default-branch change to `Groups/<group>/access.md`, so this is also
428
- // what makes a newly-granted group unlock within one round-trip instead
441
+ // default-branch change to `Plugins/<plugin>/access.md`, so this is also
442
+ // what makes a newly-granted plugin unlock within one round-trip instead
429
443
  // of one TTL.
430
- const touched = paths.some((p) => p.startsWith(`${kbDirName}/${GROUPS_DIR}/`));
444
+ const touched = paths.some((p) => p.startsWith(`${kbDirName}/${PLUGINS_DIR}/`));
431
445
  if (touched) {
432
446
  toolManualService.invalidate();
433
447
  skillService.invalidate();
434
- groupIndexService.invalidate();
448
+ pluginIndexService.invalidate();
435
449
  }
436
450
  });
437
451
 
@@ -448,7 +462,7 @@ export async function createCoreServices(
448
462
  if (!('branch' in event) || event.branch !== DEFAULT_BRANCH) return;
449
463
  toolManualService.invalidate();
450
464
  skillService.invalidate();
451
- groupIndexService.invalidate();
465
+ pluginIndexService.invalidate();
452
466
  });
453
467
 
454
468
  // Admin = `Admin` role in roles.yaml, resolved through the access model on the
@@ -567,7 +581,12 @@ export async function createCoreServices(
567
581
  });
568
582
  // RFC 9728 pointer carried on every MCP 401 challenge so OAuth-capable
569
583
  // clients discover the AS. Single source of truth for the resource id.
584
+ // Userinfo is stripped for the same reason the server's own copy strips it
585
+ // (see create-core-server.ts): the pointer rides an unauthenticated 401
586
+ // challenge, and a `user:pass@` from PUBLIC_BACKEND_URL must not ride along.
570
587
  const mcpResourceUrl = new URL('/api/mcp', config.publicBackendUrl);
588
+ mcpResourceUrl.username = '';
589
+ mcpResourceUrl.password = '';
571
590
  const mcpResourceMetadataUrl = getOAuthProtectedResourceMetadataUrl(mcpResourceUrl);
572
591
  const mcpAuthMiddleware = createMcpAuthMiddleware(
573
592
  authService,
@@ -675,6 +694,7 @@ export async function createCoreServices(
675
694
  return {
676
695
  config,
677
696
  db,
697
+ mcpServerEditService,
678
698
  workspaceService,
679
699
  kbSeedService,
680
700
  settings,
@@ -687,8 +707,8 @@ export async function createCoreServices(
687
707
  skillService,
688
708
  pendingSkillsService,
689
709
  toolManualService,
690
- groupIndexService,
691
- groupProvisionService,
710
+ pluginIndexService,
711
+ pluginProvisionService,
692
712
  joinRequestsService,
693
713
  authService,
694
714
  authMiddleware,