@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
@@ -0,0 +1,344 @@
1
+ /**
2
+ * Plugin provisioning — the ONE privileged door for bringing a `Plugins/<name>/`
3
+ * folder into existence, and (via {@link PluginProvisionService.deletePlugin})
4
+ * for taking one back out of it.
5
+ *
6
+ * Creating a plugin is the single operation the access model cannot govern
7
+ * from inside: the folder that will carry the rules does not exist yet, and
8
+ * the root's own rule (`write: Admin`) says no. The old answer was a
9
+ * carve-out inside the generic write gate — every write path could claim an
10
+ * unused name under `Plugins/`, and the gate had to re-derive "is this that
11
+ * one blessed case?" on every check. This service replaces that: the generic
12
+ * gate is uniformly strict again, and the privilege lives here, named,
13
+ * behind its own endpoint.
14
+ *
15
+ * What a provision IS: one exclusive write of the new folder's `access.md`
16
+ * (flag `wx`, so two concurrent creators race for the fs, not for the
17
+ * overwrite), then one SYNCHRONOUS commit+push. Synchronous on purpose — the
18
+ * write gate reads rules at HEAD, so a creation whose access.md were still
19
+ * sitting in the async commit queue would 403 the very next thing its
20
+ * creator does (writing the first skill into it).
21
+ *
22
+ * Two shapes, two templates:
23
+ *
24
+ * - A NAMED plugin: discoverable by design. The access.md's own frontmatter
25
+ * reads `everyone` — anyone may open the FILE, see the plugin listed, and
26
+ * ask to join — while the BODY (the folder's actual rules) names only
27
+ * the creator under read, write and owner.
28
+ * - A PERSONAL folder (`personal-<user-id>`): private by design. No
29
+ * frontmatter grant at all, so nobody else can even see it exists; the
30
+ * body names its owner. Created lazily (ensure semantics) on the first
31
+ * personal skill.
32
+ */
33
+ import fs from 'node:fs/promises';
34
+ import path from 'node:path';
35
+ import { DEFAULT_BRANCH, PLUGINS_DIR, PLUGIN_MANIFEST_FILE, pluginManifestName, renderPluginManifest, PERSONAL_PLUGIN_PREFIX, isPersonalPluginFolder, personalPluginFolderName, } from '@bevel-software/platform-shared';
36
+ import { creatorPrincipal } from '../access/creator-access.js';
37
+ import { spliceGrant } from '../access/access-splice.js';
38
+ import { WorkspaceMutex } from '../workflow/git/mutex.js';
39
+ /** A refusal the route can pass through: message + HTTP status. */
40
+ export class PluginProvisionError extends Error {
41
+ status;
42
+ constructor(message, status) {
43
+ super(message);
44
+ this.status = status;
45
+ this.name = 'PluginProvisionError';
46
+ }
47
+ }
48
+ export class PluginProvisionService {
49
+ workspaceService;
50
+ commits;
51
+ accessControl;
52
+ kbDirName;
53
+ events;
54
+ /**
55
+ * Serialises creations and deletions by MANIFEST SLUG
56
+ * (`pluginManifestName(name)`). The wx write arbitrates same-path races,
57
+ * but the identity the collision checks defend is the slug: case-variants
58
+ * (`GTM`/`gtm`) and distinct spellings (`Sales Team`/`Sales-Team`) all
59
+ * derive the same key, so no two requests that would publish one manifest
60
+ * name can hold the lock at once.
61
+ */
62
+ creations = new WorkspaceMutex();
63
+ constructor(workspaceService, commits, accessControl, kbDirName, events) {
64
+ this.workspaceService = workspaceService;
65
+ this.commits = commits;
66
+ this.accessControl = accessControl;
67
+ this.kbDirName = kbDirName;
68
+ this.events = events;
69
+ }
70
+ /**
71
+ * Create `Plugins/<name>/` for `user`. Throws `PluginProvisionError` 422 on a
72
+ * name the filesystem or the model cannot carry, 409 when the name is taken
73
+ * (case-insensitively — the workspaces live on case-insensitive
74
+ * filesystems too, where `GTM` and `gtm` are one folder).
75
+ */
76
+ async createPlugin(user, rawName) {
77
+ const name = rawName.trim();
78
+ if (!name)
79
+ throw new PluginProvisionError('A plugin needs a name.', 422);
80
+ // eslint-disable-next-line no-control-regex -- NUL and control chars are
81
+ // exactly what a filesystem path cannot carry; refusing them here keeps
82
+ // the refusal a 422 instead of the fs layer's 500.
83
+ if (/[/\\\u0000-\u001f\u007f]/.test(name) || name === '.' || name === '..' || name.startsWith('.')) {
84
+ throw new PluginProvisionError('A plugin name can\'t contain / or \\ or control characters, or start with a dot.', 422);
85
+ }
86
+ // Reserved BY SLUG, which subsumes the folder-name spelling: personal
87
+ // folders publish manifests like any plugin, so "Personal Abc" (slug
88
+ // `personal-abc`) squats the namespace exactly as "personal-abc" would —
89
+ // a name-only check let it through.
90
+ if (pluginManifestName(name).startsWith(PERSONAL_PLUGIN_PREFIX)) {
91
+ // The message names the DERIVED slug: for a spelling like "Personal
92
+ // Abc" the reservation is invisible in the name itself, and a refusal
93
+ // the user can't trace to their input is a refusal they can't fix.
94
+ throw new PluginProvisionError(`"${name}" would publish the manifest name "${pluginManifestName(name)}" — ` +
95
+ `the "${PERSONAL_PLUGIN_PREFIX}" namespace is reserved for personal folders. Pick another name.`, 422);
96
+ }
97
+ // Locked on the manifest SLUG, not the lowercased folder: the slug is the
98
+ // identity the twin check below defends, and two spellings that collide
99
+ // on it ("Sales Team" / "Sales-Team") must take the SAME lock or both
100
+ // pass the check concurrently. Case-variants share a slug too, so this
101
+ // key subsumes the old lowercase one — and deletion (below) derives its
102
+ // key the same way, keeping delete/re-create of one name serialized.
103
+ return this.creations.run(`plugin:${pluginManifestName(name)}`, async () => {
104
+ const existing = await this.existingFolder(name);
105
+ if (existing !== null) {
106
+ throw new PluginProvisionError(`A plugin named "${existing}" already exists.`, 409);
107
+ }
108
+ // Folder uniqueness is not manifest uniqueness: the manifest `name` is
109
+ // a LOSSY slug of the folder (`Sales Team` and `Sales-Team` both
110
+ // become `sales-team`), and it is the identity a conformant client
111
+ // keys plugins on — two folders sharing it would be two plugins one
112
+ // key, with no telling which a client resolves.
113
+ const slugTwin = await this.manifestNameTwin(name);
114
+ if (slugTwin !== null) {
115
+ throw new PluginProvisionError(`"${name}" and the existing plugin "${slugTwin}" would share the manifest name ` +
116
+ `"${pluginManifestName(name)}" — pick a more distinct name.`, 409);
117
+ }
118
+ await this.provision(user, name, pluginAccessMd(user));
119
+ return { folder: name, created: true };
120
+ });
121
+ }
122
+ /**
123
+ * Ensure the caller's personal folder exists — idempotent, keyed to the
124
+ * stable user id. Returns `created: false` when it is already there.
125
+ */
126
+ async ensurePersonalPlugin(user) {
127
+ const folder = personalPluginFolderName(user.id);
128
+ return this.creations.run(`plugin:${pluginManifestName(folder)}`, async () => {
129
+ if ((await this.existingFolder(folder)) !== null) {
130
+ return { folder, created: false };
131
+ }
132
+ try {
133
+ await this.provision(user, folder, personalAccessMd(user));
134
+ }
135
+ catch (err) {
136
+ // ENSURE semantics even under a race the lock cannot see (another
137
+ // process, a checkout that appeared between check and write): the
138
+ // folder existing is this method's success case, never its error.
139
+ if (err instanceof PluginProvisionError && err.status === 409) {
140
+ return { folder, created: false };
141
+ }
142
+ throw err;
143
+ }
144
+ return { folder, created: true };
145
+ });
146
+ }
147
+ /**
148
+ * Delete `Plugins/<name>/` — the whole folder, its skills and tools
149
+ * included, in ONE commit. MECHANISM only: the route owns the
150
+ * authorization (the caller must hold the `owner` verb on the folder;
151
+ * this service never re-derives it), exactly as `createPlugin` leaves
152
+ * "any signed-in user" to its endpoint.
153
+ *
154
+ * Shape mirrors a provision run in reverse, with the same failure
155
+ * contract: the folder is PARKED (renamed to a dot-prefixed sibling the
156
+ * scanners ignore) rather than removed, the deletion is committed
157
+ * synchronously, and only a landed commit lets the parked bytes go. A
158
+ * refused commit renames the folder back, so a failed delete leaves the
159
+ * plugin exactly as it was — never half-gone on disk while origin still
160
+ * carries it.
161
+ *
162
+ * Serialised on the same slug-keyed lock creations use, so a delete can
163
+ * never interleave with a re-creation of the same name (the key derives
164
+ * from the name, so both spell it identically).
165
+ */
166
+ async deletePlugin(user, rawName) {
167
+ const name = rawName.trim();
168
+ if (!name)
169
+ throw new PluginProvisionError('A plugin needs a name.', 422);
170
+ if (isPersonalPluginFolder(name)) {
171
+ // Personal folders are not plugins (the catalog never lists them), and
172
+ // nobody deletes somebody's private shelf through the plugin door.
173
+ throw new PluginProvisionError('Unknown plugin', 404);
174
+ }
175
+ return this.creations.run(`plugin:${pluginManifestName(name)}`, async () => {
176
+ const existing = await this.existingFolder(name);
177
+ // Exact match only — the catalog hands the route the on-disk casing,
178
+ // so a mismatch means the plugin is gone (or was never there).
179
+ if (existing !== name)
180
+ throw new PluginProvisionError('Unknown plugin', 404);
181
+ const wsId = await this.readyWorkspaceId();
182
+ const wsDir = await this.workspaceService.getWorkspacePath(wsId);
183
+ const pluginsDir = path.join(wsDir, this.kbDirName, PLUGINS_DIR);
184
+ const folderDir = path.join(pluginsDir, name);
185
+ // Dot-prefixed ⇒ invisible to the plugin scanner and the collision
186
+ // check for the whole window the commit is in flight.
187
+ const parkedDir = path.join(pluginsDir, `.${name}.deleting`);
188
+ await fs.rm(parkedDir, { recursive: true, force: true }); // a stale park from a crashed run
189
+ await fs.rename(folderDir, parkedDir);
190
+ try {
191
+ // Inline and `systemAuthorized`, for `provision`'s reasons in
192
+ // reverse: the gate reads rules at HEAD, and the endpoint has
193
+ // already authorized the delete (owner verdict), so the per-user
194
+ // push gate — which would re-read an access.md this very commit
195
+ // removes — is skipped for exactly this commit. `commitFile` is
196
+ // path-scoped (`git add -- <path>`), and a folder path stages every
197
+ // deletion under it: one commit, one removed plugin.
198
+ await this.commits.runPendingCommit(wsId, DEFAULT_BRANCH, `${this.kbDirName}/${PLUGINS_DIR}/${name}`, user, { systemAuthorized: true });
199
+ }
200
+ catch (err) {
201
+ // The commit did not land: put the folder back, so a failed delete
202
+ // is a no-op rather than a plugin that exists at origin but not here.
203
+ try {
204
+ await fs.rename(parkedDir, folderDir);
205
+ }
206
+ catch {
207
+ /* the park survives for the next attempt — better than masking the real error */
208
+ }
209
+ throw err;
210
+ }
211
+ await fs.rm(parkedDir, { recursive: true, force: true }).catch(() => { });
212
+ // The folder's rules left the access model — drop the resolver cache
213
+ // so the very next check runs against a tree without them.
214
+ this.accessControl.invalidate(wsId);
215
+ this.events?.emit({ kind: 'fs-tree-changed', workspaceId: wsId, branch: DEFAULT_BRANCH });
216
+ });
217
+ }
218
+ /** The taken name (in its on-disk casing) colliding with `name`, or null. */
219
+ async existingFolder(name) {
220
+ const wsId = await this.readyWorkspaceId();
221
+ const wsDir = await this.workspaceService.getWorkspacePath(wsId);
222
+ let children;
223
+ try {
224
+ children = await fs.readdir(path.join(wsDir, this.kbDirName, PLUGINS_DIR));
225
+ }
226
+ catch {
227
+ return null; // no Plugins/ root yet — nothing can collide
228
+ }
229
+ const lower = name.toLowerCase();
230
+ return children.find((c) => c.toLowerCase() === lower) ?? null;
231
+ }
232
+ /** An existing PLUGIN FOLDER whose derived manifest name equals `name`'s, or null. */
233
+ async manifestNameTwin(name) {
234
+ const wsId = await this.readyWorkspaceId();
235
+ const wsDir = await this.workspaceService.getWorkspacePath(wsId);
236
+ let children;
237
+ try {
238
+ children = await fs.readdir(path.join(wsDir, this.kbDirName, PLUGINS_DIR), {
239
+ withFileTypes: true,
240
+ });
241
+ }
242
+ catch {
243
+ return null; // no Plugins/ root yet — nothing can collide
244
+ }
245
+ const slug = pluginManifestName(name);
246
+ // Only what actually publishes a manifest claims a slug: a DIRECTORY
247
+ // that is not dot-prefixed (a parked delete — invisible to every
248
+ // scanner). Personal folders count — they publish a plugin.json like
249
+ // any plugin (though the reservation above means a named plugin can
250
+ // never reach this check with a personal slug). A loose file at the
251
+ // root (`Plugins/slack.tool`) is not a plugin and must not 409 a
252
+ // legitimate "Slack Tool".
253
+ return (children.find((c) => c.isDirectory() && !c.name.startsWith('.') && pluginManifestName(c.name) === slug)?.name ?? null);
254
+ }
255
+ async provision(user, folder, accessMd) {
256
+ const wsId = await this.readyWorkspaceId();
257
+ const folderPath = `${this.kbDirName}/${PLUGINS_DIR}/${folder}`;
258
+ const wsRelPath = `${folderPath}/access.md`;
259
+ try {
260
+ // Exclusive create — the fs is the arbiter of a same-name race, not
261
+ // the (stale-able) existence check above. `access.md` stays the marker
262
+ // that a folder is real (every scanner keys on it), so it is still the
263
+ // file the race is decided on.
264
+ await this.workspaceService.writeFile(wsId, wsRelPath, accessMd, { failIfExists: true });
265
+ }
266
+ catch (err) {
267
+ if (err.status === 409) {
268
+ throw new PluginProvisionError(`A plugin named "${folder}" already exists.`, 409);
269
+ }
270
+ throw err;
271
+ }
272
+ try {
273
+ // The manifest is what makes the folder a PLUGIN to anything outside
274
+ // this app, so it lands in the same commit as the access rules — and
275
+ // INSIDE the rollback scope: a manifest write that fails must clean up
276
+ // the access.md it would otherwise strand as a half-made plugin.
277
+ await this.workspaceService.writeFile(wsId, `${folderPath}/${PLUGIN_MANIFEST_FILE}`, renderPluginManifest(folder));
278
+ // Inline, not enqueued: the gate reads rules at HEAD, so the folder is
279
+ // only real once this commit lands. `runPendingCommit` is the same
280
+ // commit+push (with pull-rebase recovery) the queue worker runs.
281
+ // `systemAuthorized`: the push gate reads access at origin, where this
282
+ // folder does not exist yet and the root says `write: Admin` — the very
283
+ // rule this endpoint exists to carve through. The endpoint has already
284
+ // authorized the write (any signed-in user, unused name, exclusive
285
+ // create), so the per-user gate is skipped for exactly this commit.
286
+ await this.commits.runPendingCommit(wsId, DEFAULT_BRANCH, folderPath, user, {
287
+ systemAuthorized: true,
288
+ });
289
+ }
290
+ catch (err) {
291
+ // The commit did not land: roll the seeded file back off the disk,
292
+ // best-effort, so a retry doesn't find a half-made plugin and report
293
+ // "already exists" for something that never got committed. Only OUR
294
+ // file and — when that leaves it empty — the folder; never recursive,
295
+ // so a concurrent writer's bytes can't be collateral.
296
+ try {
297
+ const wsDir = await this.workspaceService.getWorkspacePath(wsId);
298
+ const folderDir = path.join(wsDir, this.kbDirName, PLUGINS_DIR, folder);
299
+ await fs.rm(path.join(folderDir, 'access.md'), { force: true });
300
+ await fs.rm(path.join(folderDir, PLUGIN_MANIFEST_FILE), { force: true });
301
+ await fs.rmdir(folderDir).catch(() => { });
302
+ }
303
+ catch {
304
+ /* leave it for the next attempt's wx conflict — better than masking the real error */
305
+ }
306
+ throw err;
307
+ }
308
+ // The folder's rules changed the access model — drop the resolver cache
309
+ // so the very next check (the creator's first skill write) sees them.
310
+ this.accessControl.invalidate(wsId);
311
+ this.events?.emit({ kind: 'fs-tree-changed', workspaceId: wsId, branch: DEFAULT_BRANCH });
312
+ }
313
+ async readyWorkspaceId() {
314
+ const ws = await this.workspaceService.getOrCreateForBranch(DEFAULT_BRANCH);
315
+ return ws.id;
316
+ }
317
+ }
318
+ /**
319
+ * A named plugin's access.md: discoverable file (frontmatter `read: everyone`
320
+ * — anyone may see the plugin listed and ask to join), creator-run folder
321
+ * (body read/write/owner name the creator). The `read: []` placeholder makes
322
+ * the body parse as rules from the first byte, so every later splice targets
323
+ * the body rather than the frontmatter.
324
+ */
325
+ export function pluginAccessMd(creator) {
326
+ return withCreatorGrants('---\nread:\n - everyone\n---\nread: []\n', creator);
327
+ }
328
+ /**
329
+ * A personal folder's access.md: PRIVATE — no frontmatter grant, so the
330
+ * folder is invisible to everyone but its owner; the body names the owner
331
+ * under read, write and owner.
332
+ */
333
+ export function personalAccessMd(creator) {
334
+ return withCreatorGrants('read: []\n', creator);
335
+ }
336
+ function withCreatorGrants(base, creator) {
337
+ const principal = creatorPrincipal(creator);
338
+ let out = base;
339
+ for (const verb of ['read', 'write', 'owner']) {
340
+ out = spliceGrant(out, verb, principal, { allowScalar: false, target: 'folder' }).text;
341
+ }
342
+ return out;
343
+ }
344
+ //# sourceMappingURL=plugin-provision.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-provision.service.js","sourceRoot":"","sources":["../../../src/modules/plugins/plugin-provision.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Plugins — the folders under `Plugins/` that carry a team's skills and the
3
+ * tools those skills need. A plugin is a FOLDER, never a role: one access
4
+ * boundary, one place, derived from the path exactly the way `pluginOfPath`
5
+ * derives it.
6
+ *
7
+ * A plugin EXISTS exactly when its folder carries an `access.md` — the file
8
+ * the provisioning endpoint seeds. A bare directory under `Plugins/` is not a
9
+ * plugin (git cannot record an empty folder, so deleted plugins leave ghost
10
+ * directories on live checkouts).
11
+ *
12
+ * Enumeration then follows the KB's read model with NO special cases: an
13
+ * existing plugin appears for a caller exactly when the access resolution says
14
+ * something about it —
15
+ *
16
+ * - MEMBER: the caller can read the folder itself (`canRead`).
17
+ * - MANAGER: the caller can write the folder's `access.md` (`canWrite`,
18
+ * admin-rescued).
19
+ * - DISCOVERABLE: the caller can read the `access.md` FILE — which, in the
20
+ * new body-governed format, is granted by the file's own
21
+ * `read: everyone` frontmatter (see `accessMdSelfEntries`). The plugin
22
+ * shows locked, and the caller may ask to join.
23
+ *
24
+ * A plugin where all three verdicts are false is ABSENT from the response —
25
+ * name, counts and principals never leave the backend. Making a plugin secret
26
+ * is therefore an ordinary access edit: drop the `everyone` grant from its
27
+ * access.md frontmatter.
28
+ *
29
+ * Requesting access is a CHANGE REQUEST, not a bespoke system: the join
30
+ * route commits "add me to the body's `read:` list" on a draft branch and
31
+ * opens a CR that the folder's writers approve by merging (or by granting
32
+ * via Manage access, which makes the CR moot). To agents these are plain
33
+ * change requests; only the UI dresses them up.
34
+ */
35
+ /** Access-rule principals as the resolver hands them back. */
36
+ export interface ResolvedPrincipals {
37
+ roles: string[];
38
+ users: {
39
+ name: string;
40
+ email: string;
41
+ }[];
42
+ }
43
+ export interface ResolvedReaders extends ResolvedPrincipals {
44
+ restricted: boolean;
45
+ }
46
+ /** One plugin as `GET /api/plugins` reports it, resolved for ONE caller. */
47
+ export interface PluginSummary {
48
+ /** Plugin folder name, e.g. `GTM`. */
49
+ name: string;
50
+ /** Repo-relative constituent folders, e.g. `['Plugins/GTM']`. */
51
+ folders: string[];
52
+ /**
53
+ * Per-caller: the caller can read the FOLDER (membership). Every returned
54
+ * plugin has at least one of `canRead` / `canWrite` / discoverability; a
55
+ * plugin with none is not returned at all.
56
+ */
57
+ canRead: boolean;
58
+ /**
59
+ * Per-caller; true ⇒ may manage access. Resolved as write on the folder's
60
+ * `access.md`, which admin-rescues — so a platform Admin locked OUT of
61
+ * reading still sees the plugin and gets the self-service way back in.
62
+ */
63
+ canWrite: boolean;
64
+ /**
65
+ * Per-caller: the caller holds the `owner` verb on the FOLDER — resolved
66
+ * from the `owner:` lists alone, no admin rescue. Deleting the plugin is the
67
+ * owner's verb (the DELETE route enforces the same verdict); managers who
68
+ * merely write the access.md do not get it.
69
+ */
70
+ isOwner: boolean;
71
+ /** Caller-INDEPENDENT total (the plugin's whole content, not the caller's slice). */
72
+ skillCount: number;
73
+ toolCount: number;
74
+ /** For display: "Run by …" (fallback chain: owners → writers → 'the workspace admins'). */
75
+ owners: ResolvedPrincipals;
76
+ writers: ResolvedPrincipals;
77
+ readers: ResolvedReaders;
78
+ /**
79
+ * The caller has an OPEN join change request for this plugin (their
80
+ * deterministic join branch has an open CR). Always false for a member.
81
+ */
82
+ hasRequested: boolean;
83
+ /** The open join CR's number when `hasRequested` (deep-links the UI). */
84
+ requestNumber: number | null;
85
+ }
86
+ /**
87
+ * The caller-INDEPENDENT slice of a plugin — what `catalog()` computes once and
88
+ * caches. The per-caller verdicts (`canRead`/`canWrite`, and whether the plugin
89
+ * appears at all) are resolved per request in the route.
90
+ */
91
+ export interface PluginCatalogEntry {
92
+ name: string;
93
+ folders: string[];
94
+ skillCount: number;
95
+ toolCount: number;
96
+ owners: ResolvedPrincipals;
97
+ writers: ResolvedPrincipals;
98
+ readers: ResolvedReaders;
99
+ }
100
+ export interface IPluginIndexService {
101
+ /** Caller-independent catalog part (folders, counts, principals) — cached 60s. */
102
+ catalog(): Promise<PluginCatalogEntry[]>;
103
+ /** Drop the cached catalog (call after a default-branch change under a plugin root). */
104
+ invalidate(): void;
105
+ }
106
+ //# sourceMappingURL=plugins.contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins.contract.d.ts","sourceRoot":"","sources":["../../../src/modules/plugins/plugins.contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,8DAA8D;AAC9D,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,eAAgB,SAAQ,kBAAkB;IACzD,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,qFAAqF;IACrF,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,2FAA2F;IAC3F,MAAM,EAAE,kBAAkB,CAAC;IAC3B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,OAAO,EAAE,eAAe,CAAC;IACzB;;;OAGG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB,yEAAyE;IACzE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,OAAO,EAAE,eAAe,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,kFAAkF;IAClF,OAAO,IAAI,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;IACzC,wFAAwF;IACxF,UAAU,IAAI,IAAI,CAAC;CACpB"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Plugins — the folders under `Plugins/` that carry a team's skills and the
3
+ * tools those skills need. A plugin is a FOLDER, never a role: one access
4
+ * boundary, one place, derived from the path exactly the way `pluginOfPath`
5
+ * derives it.
6
+ *
7
+ * A plugin EXISTS exactly when its folder carries an `access.md` — the file
8
+ * the provisioning endpoint seeds. A bare directory under `Plugins/` is not a
9
+ * plugin (git cannot record an empty folder, so deleted plugins leave ghost
10
+ * directories on live checkouts).
11
+ *
12
+ * Enumeration then follows the KB's read model with NO special cases: an
13
+ * existing plugin appears for a caller exactly when the access resolution says
14
+ * something about it —
15
+ *
16
+ * - MEMBER: the caller can read the folder itself (`canRead`).
17
+ * - MANAGER: the caller can write the folder's `access.md` (`canWrite`,
18
+ * admin-rescued).
19
+ * - DISCOVERABLE: the caller can read the `access.md` FILE — which, in the
20
+ * new body-governed format, is granted by the file's own
21
+ * `read: everyone` frontmatter (see `accessMdSelfEntries`). The plugin
22
+ * shows locked, and the caller may ask to join.
23
+ *
24
+ * A plugin where all three verdicts are false is ABSENT from the response —
25
+ * name, counts and principals never leave the backend. Making a plugin secret
26
+ * is therefore an ordinary access edit: drop the `everyone` grant from its
27
+ * access.md frontmatter.
28
+ *
29
+ * Requesting access is a CHANGE REQUEST, not a bespoke system: the join
30
+ * route commits "add me to the body's `read:` list" on a draft branch and
31
+ * opens a CR that the folder's writers approve by merging (or by granting
32
+ * via Manage access, which makes the CR moot). To agents these are plain
33
+ * change requests; only the UI dresses them up.
34
+ */
35
+ export {};
36
+ //# sourceMappingURL=plugins.contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins.contract.js","sourceRoot":"","sources":["../../../src/modules/plugins/plugins.contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG"}
@@ -0,0 +1,42 @@
1
+ import express from 'express';
2
+ import '../auth/auth.middleware.js';
3
+ import { type AuthUser, type IWorkflowService } from '@bevel-software/platform-shared';
4
+ import type { IAccessControl } from '../access/access-control.interface.js';
5
+ import type { WorkspaceService } from '../workspace/workspace.service.js';
6
+ import { type PluginProvisionService } from './plugin-provision.service.js';
7
+ import type { JoinRequestsService } from './join-requests.service.js';
8
+ import type { IPluginIndexService } from './plugins.contract.js';
9
+ /**
10
+ * Browser-facing (JWT) plugin routes, mounted behind `authMiddleware`:
11
+ *
12
+ * GET /api/plugins → { plugins: PluginSummary[] }
13
+ * DELETE /api/plugins/:name → { ok } (owners)
14
+ * POST /api/plugins/:name/join-request → { ok, number } (opens a CR)
15
+ * GET /api/plugins/:name/join-requests → { requests } (managers)
16
+ * POST /api/plugins/:name/join-requests/:n/reconcile → { closed }
17
+ *
18
+ * Enumeration is three verdicts per (caller, plugin), all of them ordinary
19
+ * access resolution — no special cases, no side tables:
20
+ *
21
+ * member canRead on the plugin FOLDER
22
+ * manager canWrite on the folder's access.md (admin-rescued)
23
+ * discoverable canRead on the access.md FILE — in the body-governed format
24
+ * its own `read: everyone` frontmatter grants this
25
+ *
26
+ * All three false ⇒ the plugin is absent from the response entirely.
27
+ *
28
+ * A join request is a plain change request whose branch edits the plugin's
29
+ * `access.md`. Managers do NOT merge it: they read its individual proposals
30
+ * (see `join-proposals.ts`), grant the ones they accept through the ordinary
31
+ * access path, and the request retires itself once its rules are a subset of
32
+ * the default branch's — reconciled here, lazily on listing and eagerly right
33
+ * after a grant. Nothing about the lifecycle is stored; it is derived from
34
+ * two copies of one file.
35
+ *
36
+ * Auth gating is explicit and uniform: `authMiddleware` at the mount, PLUS a
37
+ * `req.userEmail` check in every handler (the skills-routes pattern) so a
38
+ * middleware change can never silently un-gate one. Nothing here is reachable
39
+ * with an agent connection key or a manual-auth bearer.
40
+ */
41
+ export declare function createPluginsRoutes(pluginIndex: IPluginIndexService, accessControl: IAccessControl, workflow: IWorkflowService, workspaceService: WorkspaceService, joinRequests: JoinRequestsService, provision: PluginProvisionService, kbDirName: string, resolveUser: (req: express.Request) => Promise<AuthUser | null>): express.Router;
42
+ //# sourceMappingURL=plugins.routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/plugins/plugins.routes.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,4BAA4B,CAAC;AACpC,OAAO,EAGL,KAAK,QAAQ,EAEb,KAAK,gBAAgB,EACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAG5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,OAAO,EAAwB,KAAK,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAClG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,KAAK,EAGV,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,mBAAmB,EAChC,aAAa,EAAE,cAAc,EAC7B,QAAQ,EAAE,gBAAgB,EAC1B,gBAAgB,EAAE,gBAAgB,EAClC,YAAY,EAAE,mBAAmB,EACjC,SAAS,EAAE,sBAAsB,EACjC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,GAC9D,OAAO,CAAC,MAAM,CAiXhB"}