@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,379 @@
1
+ import express from 'express';
2
+ import '../auth/auth.middleware.js'; // Express Request.userId / userEmail augmentation
3
+ import { DEFAULT_BRANCH, joinBranchFor, } from '@bevel-software/platform-shared';
4
+ import { spliceGrant } from '../access/access-splice.js';
5
+ import { WorkflowDomainError } from '../workflow/workflow.errors.js';
6
+ import { pluginsWorkspaceId } from './plugins.service.js';
7
+ import { PluginProvisionError } from './plugin-provision.service.js';
8
+ /**
9
+ * Browser-facing (JWT) plugin routes, mounted behind `authMiddleware`:
10
+ *
11
+ * GET /api/plugins → { plugins: PluginSummary[] }
12
+ * DELETE /api/plugins/:name → { ok } (owners)
13
+ * POST /api/plugins/:name/join-request → { ok, number } (opens a CR)
14
+ * GET /api/plugins/:name/join-requests → { requests } (managers)
15
+ * POST /api/plugins/:name/join-requests/:n/reconcile → { closed }
16
+ *
17
+ * Enumeration is three verdicts per (caller, plugin), all of them ordinary
18
+ * access resolution — no special cases, no side tables:
19
+ *
20
+ * member canRead on the plugin FOLDER
21
+ * manager canWrite on the folder's access.md (admin-rescued)
22
+ * discoverable canRead on the access.md FILE — in the body-governed format
23
+ * its own `read: everyone` frontmatter grants this
24
+ *
25
+ * All three false ⇒ the plugin is absent from the response entirely.
26
+ *
27
+ * A join request is a plain change request whose branch edits the plugin's
28
+ * `access.md`. Managers do NOT merge it: they read its individual proposals
29
+ * (see `join-proposals.ts`), grant the ones they accept through the ordinary
30
+ * access path, and the request retires itself once its rules are a subset of
31
+ * the default branch's — reconciled here, lazily on listing and eagerly right
32
+ * after a grant. Nothing about the lifecycle is stored; it is derived from
33
+ * two copies of one file.
34
+ *
35
+ * Auth gating is explicit and uniform: `authMiddleware` at the mount, PLUS a
36
+ * `req.userEmail` check in every handler (the skills-routes pattern) so a
37
+ * middleware change can never silently un-gate one. Nothing here is reachable
38
+ * with an agent connection key or a manual-auth bearer.
39
+ */
40
+ export function createPluginsRoutes(pluginIndex, accessControl, workflow, workspaceService, joinRequests, provision, kbDirName, resolveUser) {
41
+ const router = express.Router();
42
+ /** The folder-chain probe for MEMBERSHIP — the folder itself. */
43
+ const memberProbe = (folder) => folder;
44
+ /** The FILE probe for discovery/management — the folder's access.md. */
45
+ const accessMdOf = (folder) => `${folder}/access.md`;
46
+ const probesFor = (plugins) => [
47
+ ...new Set(plugins.flatMap((g) => g.folders.flatMap((f) => [memberProbe(f), accessMdOf(f)]))),
48
+ ];
49
+ /** The caller's open join CR for `plugin`, or null. */
50
+ const openJoinCr = (mine, email, plugin) => mine.find((cr) => cr.state === 'open' && cr.branch === joinBranchFor(email, plugin)) ?? null;
51
+ router.get('/plugins', async (req, res) => {
52
+ const email = req.userEmail;
53
+ if (!email) {
54
+ res.status(401).json({ error: 'Unauthenticated' });
55
+ return;
56
+ }
57
+ try {
58
+ const catalog = await pluginIndex.catalog();
59
+ if (catalog.length === 0) {
60
+ res.json({ plugins: [] });
61
+ return;
62
+ }
63
+ const wsId = pluginsWorkspaceId();
64
+ const probes = probesFor(catalog);
65
+ const [readable, writable, owned] = await Promise.all([
66
+ accessControl.canReadBatch(wsId, email, probes),
67
+ accessControl.canWriteBatch(wsId, email, probes),
68
+ accessControl.canOwnerBatch(wsId, email, probes),
69
+ ]);
70
+ // Fail closed on every verdict: a path missing from the map is denied.
71
+ const any = (map, g, probe) => g.folders.some((f) => map.get(probe(f)) === true);
72
+ // The caller's own open join CRs — drives `hasRequested`. A CR-listing
73
+ // hiccup must not take plugin enumeration down: degrade to "nothing
74
+ // requested" and let the next load repair it.
75
+ let mine = [];
76
+ try {
77
+ mine = await workflow.listChangeRequestsAuthoredBy(email);
78
+ }
79
+ catch (err) {
80
+ console.warn(`[plugins] join-request lookup failed: ${err instanceof Error ? err.message : String(err)}`);
81
+ }
82
+ const plugins = [];
83
+ for (const g of catalog) {
84
+ const member = any(readable, g, memberProbe);
85
+ const manager = any(writable, g, accessMdOf);
86
+ // The FOLDER verdict, owner-lists-only — the delete gate, mirrored
87
+ // here so the UI shows the verb to exactly the people the DELETE
88
+ // route will let through.
89
+ const owner = any(owned, g, memberProbe);
90
+ const discoverable = member || any(readable, g, accessMdOf);
91
+ if (!member && !manager && !discoverable)
92
+ continue; // absent — fail closed
93
+ const joinCr = member ? null : openJoinCr(mine, email, g.name);
94
+ plugins.push({
95
+ name: g.name,
96
+ folders: g.folders,
97
+ canRead: member,
98
+ canWrite: manager,
99
+ isOwner: owner,
100
+ skillCount: g.skillCount,
101
+ toolCount: g.toolCount,
102
+ owners: g.owners,
103
+ writers: g.writers,
104
+ readers: g.readers,
105
+ hasRequested: joinCr !== null,
106
+ requestNumber: joinCr?.number ?? null,
107
+ });
108
+ }
109
+ res.json({ plugins });
110
+ }
111
+ catch (err) {
112
+ console.error('[plugins] failed to list plugins:', err);
113
+ res.status(500).json({ error: 'Failed to list plugins' });
114
+ }
115
+ });
116
+ /**
117
+ * Open (or return the existing) join change request for the caller.
118
+ *
119
+ * Idempotent via the deterministic branch name: a second click finds the
120
+ * open CR and returns it. Every step is an existing primitive — branch,
121
+ * splice, commit-and-push, open CR — so the security story is exactly the
122
+ * workflow's: draft branches are ungated, and the merge gate requires an
123
+ * approver who can write the touched access.md.
124
+ */
125
+ /**
126
+ * Create a plugin — the ONE door through which `Plugins/<name>/` folders come
127
+ * to exist. Any authenticated user may create one; that is the product
128
+ * model (making a plugin makes you the one who runs it), and the seeded
129
+ * access.md immediately fences the new folder off from everyone else. See
130
+ * `PluginProvisionService` for why this is an endpoint and not a write path.
131
+ */
132
+ router.post('/plugins', async (req, res) => {
133
+ const user = await resolveUser(req);
134
+ if (!user) {
135
+ res.status(401).json({ error: 'Unauthenticated' });
136
+ return;
137
+ }
138
+ // `req.body` is undefined when no JSON body was sent at all — that is a
139
+ // 400, not a destructuring crash.
140
+ const { name } = (req.body ?? {});
141
+ if (typeof name !== 'string') {
142
+ res.status(400).json({ error: 'name is required in body' });
143
+ return;
144
+ }
145
+ try {
146
+ const result = await provision.createPlugin(user, name);
147
+ res.status(201).json(result);
148
+ }
149
+ catch (err) {
150
+ if (err instanceof PluginProvisionError) {
151
+ res.status(err.status).json({ error: err.message });
152
+ return;
153
+ }
154
+ console.error('[plugins] create failed:', err);
155
+ res.status(500).json({ error: 'Failed to create the plugin' });
156
+ }
157
+ });
158
+ /**
159
+ * Ensure the caller's personal folder (`Plugins/personal-<id>/`) exists —
160
+ * idempotent; the UI calls it lazily right before the first personal-skill
161
+ * write. Private by construction: its access.md names only the caller.
162
+ */
163
+ router.post('/plugins/personal', async (req, res) => {
164
+ const user = await resolveUser(req);
165
+ if (!user) {
166
+ res.status(401).json({ error: 'Unauthenticated' });
167
+ return;
168
+ }
169
+ try {
170
+ res.json(await provision.ensurePersonalPlugin(user));
171
+ }
172
+ catch (err) {
173
+ console.error('[plugins] personal-folder ensure failed:', err);
174
+ res.status(500).json({ error: 'Failed to prepare your personal folder' });
175
+ }
176
+ });
177
+ /**
178
+ * Delete a plugin — the OWNER's verb, and only theirs. Creating a plugin
179
+ * makes you the one who runs it; deleting it is the other end of that same
180
+ * promise, so the gate is the `owner` verdict on the folder (owner-lists
181
+ * only, no admin rescue) — a manager who merely writes the access.md, and
182
+ * an admin rescued into managing it, do not get it.
183
+ *
184
+ * Fail-closed like every other plugin surface: an unknown plugin and a plugin
185
+ * the caller does not own answer IDENTICALLY, so probing the endpoint can
186
+ * confirm nothing about what exists. The mechanism (park, one commit,
187
+ * rollback on refusal) lives in `PluginProvisionService.deletePlugin`.
188
+ */
189
+ router.delete('/plugins/:name', async (req, res) => {
190
+ const email = req.userEmail;
191
+ if (!email) {
192
+ res.status(401).json({ error: 'Unauthenticated' });
193
+ return;
194
+ }
195
+ try {
196
+ const user = await resolveUser(req);
197
+ if (!user) {
198
+ res.status(401).json({ error: 'Unauthenticated' });
199
+ return;
200
+ }
201
+ // Case-sensitive, like `pluginOfPath` — the plugin name IS the folder name.
202
+ const plugin = (await pluginIndex.catalog()).find((g) => g.name === req.params.name);
203
+ const wsId = pluginsWorkspaceId();
204
+ const ownerVerdicts = plugin
205
+ ? await Promise.all(plugin.folders.map((f) => accessControl.canOwner(wsId, email, memberProbe(f))))
206
+ : [];
207
+ if (!plugin || !ownerVerdicts.some((v) => v === true)) {
208
+ res.status(404).json({ error: 'Unknown plugin', kind: 'unknown-plugin' });
209
+ return;
210
+ }
211
+ await provision.deletePlugin(user, plugin.name);
212
+ pluginIndex.invalidate();
213
+ res.json({ ok: true });
214
+ }
215
+ catch (err) {
216
+ if (err instanceof PluginProvisionError) {
217
+ res.status(err.status).json({ error: err.message });
218
+ return;
219
+ }
220
+ console.error('[plugins] delete failed:', err);
221
+ res.status(500).json({ error: 'Failed to delete the plugin' });
222
+ }
223
+ });
224
+ router.post('/plugins/:name/join-request', async (req, res) => {
225
+ const email = req.userEmail;
226
+ if (!email) {
227
+ res.status(401).json({ error: 'Unauthenticated' });
228
+ return;
229
+ }
230
+ try {
231
+ const user = await resolveUser(req);
232
+ if (!user) {
233
+ res.status(401).json({ error: 'Unauthenticated' });
234
+ return;
235
+ }
236
+ const catalog = await pluginIndex.catalog();
237
+ // Case-sensitive, like `pluginOfPath` — the plugin name IS the folder name.
238
+ const plugin = catalog.find((g) => g.name === req.params.name);
239
+ const wsId = pluginsWorkspaceId();
240
+ // Same ANY-folder shape `GET /plugins` resolves with, so a plugin can
241
+ // never be listed as discoverable there and rejected as unknown here.
242
+ const verdicts = plugin
243
+ ? await accessControl.canReadBatch(wsId, email, plugin.folders.flatMap((f) => [memberProbe(f), accessMdOf(f)]))
244
+ : new Map();
245
+ const any = (probe) => plugin?.folders.some((f) => verdicts.get(probe(f)) === true) ?? false;
246
+ // Discovery gate, fail-closed: an unknown plugin and a plugin the caller
247
+ // cannot discover answer IDENTICALLY, so probing can't confirm existence.
248
+ if (!plugin || !any(accessMdOf)) {
249
+ res.status(404).json({ error: 'Unknown plugin', kind: 'unknown-plugin' });
250
+ return;
251
+ }
252
+ if (any(memberProbe)) {
253
+ // Access landed between page load and click — reload, don't ask.
254
+ res.status(409).json({ error: 'You can already read this plugin', kind: 'already-readable' });
255
+ return;
256
+ }
257
+ // The grant is written to the plugin's primary folder — the one the
258
+ // summary's `folders[0]` names and the banner's touched-path check
259
+ // expects.
260
+ const folder = plugin.folders[0];
261
+ const branch = joinBranchFor(email, plugin.name);
262
+ const existing = openJoinCr(await workflow.listChangeRequestsAuthoredBy(email), email, plugin.name);
263
+ if (existing) {
264
+ res.json({ ok: true, number: existing.number });
265
+ return;
266
+ }
267
+ // A leftover branch from a rejected/withdrawn request is reused — the
268
+ // grant commit is already on it and the splice below no-ops.
269
+ try {
270
+ await workflow.createBranch(pluginsWorkspaceId(), branch, DEFAULT_BRANCH);
271
+ }
272
+ catch {
273
+ // exists (or raced) — proceed against it
274
+ }
275
+ const ws = await workspaceService.getOrCreateForBranch(branch);
276
+ const accessPath = `${kbDirName}/${accessMdOf(folder)}`;
277
+ const current = await workspaceService.readFile(ws.id, accessPath).catch(() => '');
278
+ const spliced = spliceGrant(current, 'read', { kind: 'user', email: user.email, displayName: user.name }, { target: 'folder' });
279
+ if (spliced.changed) {
280
+ await workspaceService.writeFile(ws.id, accessPath, spliced.text);
281
+ await workflow.commitChanges(ws.id, user, `Request access to ${plugin.name}`);
282
+ }
283
+ const detail = await workflow.openChangeRequest(ws.id, user, {
284
+ sourceBranch: branch,
285
+ targetBranch: DEFAULT_BRANCH,
286
+ title: `Join request: ${plugin.name}`,
287
+ description: `${user.name} asked to join ${plugin.name}. A manager of the plugin accepts by ` +
288
+ `granting the access this branch proposes; the request closes itself once ` +
289
+ `every proposal has landed.`,
290
+ });
291
+ res.json({ ok: true, number: detail.number });
292
+ }
293
+ catch (err) {
294
+ if (err instanceof WorkflowDomainError) {
295
+ res.status(err.status).json({ error: err.message, ...(err.payload ?? {}) });
296
+ return;
297
+ }
298
+ console.error('[plugins] failed to open a join request:', err);
299
+ res.status(500).json({ error: 'Failed to request access' });
300
+ }
301
+ });
302
+ /**
303
+ * Resolve the caller as a MANAGER of `name` — the only role that may see or
304
+ * settle its join requests. Returns the plugin's primary folder, or null
305
+ * after answering the request.
306
+ *
307
+ * A non-manager gets an empty list rather than a 403 (the frontend asks
308
+ * unconditionally, exactly as it does for every other plugin surface), and
309
+ * an unknown plugin is indistinguishable from an unmanaged one.
310
+ */
311
+ async function requireManager(req, res, onDenied) {
312
+ const email = req.userEmail;
313
+ if (!email) {
314
+ res.status(401).json({ error: 'Unauthenticated' });
315
+ return null;
316
+ }
317
+ const plugin = (await pluginIndex.catalog()).find((g) => g.name === req.params.name);
318
+ if (!plugin) {
319
+ onDenied();
320
+ return null;
321
+ }
322
+ const writable = await accessControl.canWriteBatch(pluginsWorkspaceId(), email, plugin.folders.map(accessMdOf));
323
+ if (!plugin.folders.some((f) => writable.get(accessMdOf(f)) === true)) {
324
+ onDenied();
325
+ return null;
326
+ }
327
+ const user = await resolveUser(req);
328
+ if (!user) {
329
+ res.status(401).json({ error: 'Unauthenticated' });
330
+ return null;
331
+ }
332
+ return { plugin, folder: plugin.folders[0], user };
333
+ }
334
+ router.get('/plugins/:name/join-requests', async (req, res) => {
335
+ try {
336
+ const ctx = await requireManager(req, res, () => res.json({ requests: [] }));
337
+ if (!ctx)
338
+ return;
339
+ const crs = await workflow.listChangeRequests();
340
+ res.json({
341
+ requests: await joinRequests.list(ctx.plugin.name, ctx.folder, crs, ctx.user),
342
+ });
343
+ }
344
+ catch (err) {
345
+ console.error('[plugins] failed to list join requests:', err);
346
+ res.status(500).json({ error: 'Failed to list join requests' });
347
+ }
348
+ });
349
+ /**
350
+ * Settle one request if its proposals have all landed on the default
351
+ * branch. Called right after a grant so the banner updates in the same
352
+ * round-trip; the listing does the same thing lazily, so skipping this (a
353
+ * dropped response, a closed tab) only delays it.
354
+ */
355
+ router.post('/plugins/:name/join-requests/:number/reconcile', async (req, res) => {
356
+ try {
357
+ const ctx = await requireManager(req, res, () => res.status(404).json({ error: 'Not found' }));
358
+ if (!ctx)
359
+ return;
360
+ const crNumber = Number(req.params.number);
361
+ if (!Number.isSafeInteger(crNumber) || crNumber <= 0) {
362
+ res.status(400).json({ error: 'Invalid change request number' });
363
+ return;
364
+ }
365
+ const cr = await workflow.getChangeRequest(crNumber);
366
+ if (!cr) {
367
+ res.status(404).json({ error: 'Not found' });
368
+ return;
369
+ }
370
+ res.json({ closed: await joinRequests.reconcile(ctx.plugin.name, ctx.folder, cr, ctx.user) });
371
+ }
372
+ catch (err) {
373
+ console.error('[plugins] failed to reconcile a join request:', err);
374
+ res.status(500).json({ error: 'Failed to update the request' });
375
+ }
376
+ });
377
+ return router;
378
+ }
379
+ //# sourceMappingURL=plugins.routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins.routes.js","sourceRoot":"","sources":["../../../src/modules/plugins/plugins.routes.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,4BAA4B,CAAC,CAAC,kDAAkD;AACvF,OAAO,EACL,cAAc,EACd,aAAa,GAId,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAErE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAA+B,MAAM,+BAA+B,CAAC;AAQlG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,UAAU,mBAAmB,CACjC,WAAgC,EAChC,aAA6B,EAC7B,QAA0B,EAC1B,gBAAkC,EAClC,YAAiC,EACjC,SAAiC,EACjC,SAAiB,EACjB,WAA+D;IAE/D,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAEhC,iEAAiE;IACjE,MAAM,WAAW,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,MAAM,CAAC;IAC/C,wEAAwE;IACxE,MAAM,UAAU,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,GAAG,MAAM,YAAY,CAAC;IAE7D,MAAM,SAAS,GAAG,CAAC,OAA6B,EAAY,EAAE,CAAC;QAC7D,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KAC9F,CAAC;IAEF,uDAAuD;IACvD,MAAM,UAAU,GAAG,CAAC,IAAqB,EAAE,KAAa,EAAE,MAAc,EAAwB,EAAE,CAChG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,KAAK,MAAM,IAAI,EAAE,CAAC,MAAM,KAAK,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,IAAI,IAAI,CAAC;IAE/F,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACxC,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,CAAC;QAC5B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;YACnD,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC;YAC5C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACzB,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;gBAC1B,OAAO;YACT,CAAC;YACD,MAAM,IAAI,GAAG,kBAAkB,EAAE,CAAC;YAClC,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;YAClC,MAAM,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBACpD,aAAa,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC;gBAC/C,aAAa,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC;gBAChD,aAAa,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC;aACjD,CAAC,CAAC;YACH,uEAAuE;YACvE,MAAM,GAAG,GAAG,CAAC,GAAyB,EAAE,CAAqB,EAAE,KAA4B,EAAE,EAAE,CAC7F,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;YAEpD,uEAAuE;YACvE,oEAAoE;YACpE,8CAA8C;YAC9C,IAAI,IAAI,GAAoB,EAAE,CAAC;YAC/B,IAAI,CAAC;gBACH,IAAI,GAAG,MAAM,QAAQ,CAAC,4BAA4B,CAAC,KAAK,CAAC,CAAC;YAC5D,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,IAAI,CACV,yCAAyC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC5F,CAAC;YACJ,CAAC;YAED,MAAM,OAAO,GAAoB,EAAE,CAAC;YACpC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;gBACxB,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC,EAAE,WAAW,CAAC,CAAC;gBAC7C,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC;gBAC7C,mEAAmE;gBACnE,iEAAiE;gBACjE,0BAA0B;gBAC1B,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,WAAW,CAAC,CAAC;gBACzC,MAAM,YAAY,GAAG,MAAM,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC;gBAC5D,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,IAAI,CAAC,YAAY;oBAAE,SAAS,CAAC,uBAAuB;gBAC3E,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;gBAC/D,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,OAAO,EAAE,CAAC,CAAC,OAAO;oBAClB,OAAO,EAAE,MAAM;oBACf,QAAQ,EAAE,OAAO;oBACjB,OAAO,EAAE,KAAK;oBACd,UAAU,EAAE,CAAC,CAAC,UAAU;oBACxB,SAAS,EAAE,CAAC,CAAC,SAAS;oBACtB,MAAM,EAAE,CAAC,CAAC,MAAM;oBAChB,OAAO,EAAE,CAAC,CAAC,OAAO;oBAClB,OAAO,EAAE,CAAC,CAAC,OAAO;oBAClB,YAAY,EAAE,MAAM,KAAK,IAAI;oBAC7B,aAAa,EAAE,MAAM,EAAE,MAAM,IAAI,IAAI;iBACtC,CAAC,CAAC;YACL,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QACxB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,GAAG,CAAC,CAAC;YACxD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;;;;;;;OAQG;IACH;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACzC,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;YACnD,OAAO;QACT,CAAC;QACD,wEAAwE;QACxE,kCAAkC;QAClC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAsB,CAAC;QACvD,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC;YAC5D,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACxD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,oBAAoB,EAAE,CAAC;gBACxC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;gBACpD,OAAO;YACT,CAAC;YACD,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,GAAG,CAAC,CAAC;YAC/C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,6BAA6B,EAAE,CAAC,CAAC;QACjE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;;;OAIG;IACH,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAClD,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;YACnD,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,GAAG,CAAC,IAAI,CAAC,MAAM,SAAS,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC;QACvD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,0CAA0C,EAAE,GAAG,CAAC,CAAC;YAC/D,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,wCAAwC,EAAE,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,MAAM,CAAC,gBAAgB,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACjD,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,CAAC;QAC5B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;YACnD,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,CAAC;YACpC,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;gBACnD,OAAO;YACT,CAAC;YACD,4EAA4E;YAC5E,MAAM,MAAM,GAAG,CAAC,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACrF,MAAM,IAAI,GAAG,kBAAkB,EAAE,CAAC;YAClC,MAAM,aAAa,GAAG,MAAM;gBAC1B,CAAC,CAAC,MAAM,OAAO,CAAC,GAAG,CACf,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAC/E;gBACH,CAAC,CAAC,EAAE,CAAC;YACP,IAAI,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;gBACtD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC;gBAC1E,OAAO;YACT,CAAC;YACD,MAAM,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YAChD,WAAW,CAAC,UAAU,EAAE,CAAC;YACzB,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QACzB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,oBAAoB,EAAE,CAAC;gBACxC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;gBACpD,OAAO;YACT,CAAC;YACD,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,GAAG,CAAC,CAAC;YAC/C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,6BAA6B,EAAE,CAAC,CAAC;QACjE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,IAAI,CAAC,6BAA6B,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAC5D,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,CAAC;QAC5B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;YACnD,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,CAAC;YACpC,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;gBACnD,OAAO;YACT,CAAC;YACD,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC;YAC5C,4EAA4E;YAC5E,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC/D,MAAM,IAAI,GAAG,kBAAkB,EAAE,CAAC;YAClC,sEAAsE;YACtE,sEAAsE;YACtE,MAAM,QAAQ,GAAG,MAAM;gBACrB,CAAC,CAAC,MAAM,aAAa,CAAC,YAAY,CAC9B,IAAI,EACJ,KAAK,EACL,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAC/D;gBACH,CAAC,CAAC,IAAI,GAAG,EAAmB,CAAC;YAC/B,MAAM,GAAG,GAAG,CAAC,KAA4B,EAAE,EAAE,CAC3C,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,IAAI,KAAK,CAAC;YACxE,yEAAyE;YACzE,0EAA0E;YAC1E,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;gBAChC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC;gBAC1E,OAAO;YACT,CAAC;YACD,IAAI,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;gBACrB,iEAAiE;gBACjE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,kCAAkC,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBAC9F,OAAO;YACT,CAAC;YACD,oEAAoE;YACpE,mEAAmE;YACnE,WAAW;YACX,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAEjC,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YACjD,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,QAAQ,CAAC,4BAA4B,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YACpG,IAAI,QAAQ,EAAE,CAAC;gBACb,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;gBAChD,OAAO;YACT,CAAC;YAED,sEAAsE;YACtE,6DAA6D;YAC7D,IAAI,CAAC;gBACH,MAAM,QAAQ,CAAC,YAAY,CAAC,kBAAkB,EAAE,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;YAC5E,CAAC;YAAC,MAAM,CAAC;gBACP,yCAAyC;YAC3C,CAAC;YACD,MAAM,EAAE,GAAG,MAAM,gBAAgB,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;YAC/D,MAAM,UAAU,GAAG,GAAG,SAAS,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACxD,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YACnF,MAAM,OAAO,GAAG,WAAW,CACzB,OAAO,EACP,MAAM,EACN,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,IAAI,EAAE,EAC3D,EAAE,MAAM,EAAE,QAAQ,EAAE,CACrB,CAAC;YACF,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;gBACpB,MAAM,gBAAgB,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;gBAClE,MAAM,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,qBAAqB,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YAChF,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE;gBAC3D,YAAY,EAAE,MAAM;gBACpB,YAAY,EAAE,cAAc;gBAC5B,KAAK,EAAE,iBAAiB,MAAM,CAAC,IAAI,EAAE;gBACrC,WAAW,EACT,GAAG,IAAI,CAAC,IAAI,kBAAkB,MAAM,CAAC,IAAI,uCAAuC;oBAChF,2EAA2E;oBAC3E,4BAA4B;aAC/B,CAAC,CAAC;YACH,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QAChD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,mBAAmB,EAAE,CAAC;gBACvC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;gBAC5E,OAAO;YACT,CAAC;YACD,OAAO,CAAC,KAAK,CAAC,0CAA0C,EAAE,GAAG,CAAC,CAAC;YAC/D,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;;;;;;;OAQG;IACH,KAAK,UAAU,cAAc,CAC3B,GAAoB,EACpB,GAAqB,EACrB,QAAoB;QAEpB,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,CAAC;QAC5B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;YACnD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,MAAM,GAAG,CAAC,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACrF,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,QAAQ,EAAE,CAAC;YACX,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,aAAa,CAChD,kBAAkB,EAAE,EACpB,KAAK,EACL,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAC/B,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;YACtE,QAAQ,EAAE,CAAC;YACX,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;YACnD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;IACrD,CAAC;IAED,MAAM,CAAC,GAAG,CAAC,8BAA8B,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAC5D,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;YAC7E,IAAI,CAAC,GAAG;gBAAE,OAAO;YACjB,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,kBAAkB,EAAE,CAAC;YAChD,GAAG,CAAC,IAAI,CAAC;gBACP,QAAQ,EAAE,MAAM,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC;aAC9E,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,yCAAyC,EAAE,GAAG,CAAC,CAAC;YAC9D,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC,CAAC;QAClE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;;;;OAKG;IACH,MAAM,CAAC,IAAI,CAAC,gDAAgD,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAC/E,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAC9C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAC7C,CAAC;YACF,IAAI,CAAC,GAAG;gBAAE,OAAO;YACjB,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC3C,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;gBACrD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAC,CAAC;gBACjE,OAAO;YACT,CAAC;YACD,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;YACrD,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;gBAC7C,OAAO;YACT,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChG,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,+CAA+C,EAAE,GAAG,CAAC,CAAC;YACpE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC,CAAC;QAClE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,60 @@
1
+ import type { WorkspaceService } from '../workspace/workspace.service.js';
2
+ import type { IAccessControl } from '../access/access-control.interface.js';
3
+ import type { ISkillService } from '../skills/skills.contract.js';
4
+ import type { IToolManualService } from '../tool-manuals/tool-manuals.contract.js';
5
+ import type { PluginCatalogEntry, IPluginIndexService } from './plugins.contract.js';
6
+ /**
7
+ * The plugin index: every plugin folder in the default-branch KB, with its
8
+ * caller-independent totals and access principals.
9
+ *
10
+ * Two decisions worth keeping straight:
11
+ *
12
+ * - **Existence is an `access.md`, counting is not.** A plugin EXISTS because
13
+ * its folder carries an `access.md` — the file the provisioning endpoint
14
+ * seeds and the one every per-caller verdict (member / manager /
15
+ * discoverable) resolves against. A bare directory under `Plugins/` is NOT
16
+ * a plugin: git cannot record an empty folder, so deleting a plugin's files
17
+ * leaves its directory behind on live checkouts, and enumerating by
18
+ * directory would resurrect every such ghost. Counts come from the
19
+ * already-cached global catalogs — `skillService.listSkills(undefined)` and
20
+ * `toolManualService.listAllSummaries()` — bucketed by `pluginOfPath`. A
21
+ * second `walkFiles` pass would re-read the same tree and could disagree
22
+ * with the catalogs about what counts as a skill.
23
+ * - **Loose files are not plugins.** `Plugins/slack.tool` sits directly under
24
+ * the root, so it is a file, not a directory — the same ≥3-segment rule
25
+ * `pluginOfPath` applies, arrived at from the other side.
26
+ *
27
+ * Cached for {@link CACHE_TTL_MS} and dropped by `invalidate()` from the
28
+ * file-change subscriber, so a grant committed on the default branch is
29
+ * reflected within one round-trip rather than one TTL.
30
+ */
31
+ export declare class PluginIndexService implements IPluginIndexService {
32
+ private readonly workspaceService;
33
+ private readonly accessControl;
34
+ private readonly skillService;
35
+ private readonly toolManualService;
36
+ private readonly kbDirName;
37
+ private readonly cache;
38
+ constructor(workspaceService: WorkspaceService, accessControl: IAccessControl, skillService: ISkillService, toolManualService: IToolManualService, kbDirName: string, now?: () => number);
39
+ invalidate(): void;
40
+ catalog(): Promise<PluginCatalogEntry[]>;
41
+ /**
42
+ * Degrades on ANY failure rather than throwing. The Library must never break
43
+ * because a plugin folder can't be read — same philosophy as the skill and
44
+ * tool-manual scanners, and the reason the route's 500 is reserved for the
45
+ * per-caller half.
46
+ *
47
+ * `null` is that degraded case and is deliberately NOT the same value as an
48
+ * empty array: a KB with no plugin folders returns `[]`, which is true and
49
+ * cacheable, while a failure returns `null`, which `catalog()` serves as `[]`
50
+ * without storing it.
51
+ */
52
+ private build;
53
+ /** name → repo-relative plugin folder (always a single-element list). */
54
+ private scanFolders;
55
+ private countSkills;
56
+ private countTools;
57
+ }
58
+ /** The default-branch workspace id every plugin resolution runs against. */
59
+ export declare function pluginsWorkspaceId(): string;
60
+ //# sourceMappingURL=plugins.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins.service.d.ts","sourceRoot":"","sources":["../../../src/modules/plugins/plugins.service.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAEnF,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAIrF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,kBAAmB,YAAW,mBAAmB;IAI1D,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAP5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;gBAGpC,gBAAgB,EAAE,gBAAgB,EAClC,aAAa,EAAE,cAAc,EAC7B,YAAY,EAAE,aAAa,EAC3B,iBAAiB,EAAE,kBAAkB,EACrC,SAAS,EAAE,MAAM,EAClC,GAAG,GAAE,MAAM,MAAiB;IAK9B,UAAU,IAAI,IAAI;IAIZ,OAAO,IAAI,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAkB7C;;;;;;;;;;MAUE;YACW,KAAK;IAyCnB,yEAAyE;YAC3D,WAAW;YAkCX,WAAW;YAMX,UAAU;CAGzB;AAaD,4EAA4E;AAC5E,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C"}
@@ -0,0 +1,172 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ import { DEFAULT_BRANCH, PLUGINS_DIR, pluginOfPath, isPersonalPluginFolder, } from '@bevel-software/platform-shared';
4
+ import { workspaceIdForBranch } from '../workspace/workspace.service.js';
5
+ import { TtlCache } from '../../shared/ttl-cache.js';
6
+ const CACHE_TTL_MS = 60_000;
7
+ /**
8
+ * The plugin index: every plugin folder in the default-branch KB, with its
9
+ * caller-independent totals and access principals.
10
+ *
11
+ * Two decisions worth keeping straight:
12
+ *
13
+ * - **Existence is an `access.md`, counting is not.** A plugin EXISTS because
14
+ * its folder carries an `access.md` — the file the provisioning endpoint
15
+ * seeds and the one every per-caller verdict (member / manager /
16
+ * discoverable) resolves against. A bare directory under `Plugins/` is NOT
17
+ * a plugin: git cannot record an empty folder, so deleting a plugin's files
18
+ * leaves its directory behind on live checkouts, and enumerating by
19
+ * directory would resurrect every such ghost. Counts come from the
20
+ * already-cached global catalogs — `skillService.listSkills(undefined)` and
21
+ * `toolManualService.listAllSummaries()` — bucketed by `pluginOfPath`. A
22
+ * second `walkFiles` pass would re-read the same tree and could disagree
23
+ * with the catalogs about what counts as a skill.
24
+ * - **Loose files are not plugins.** `Plugins/slack.tool` sits directly under
25
+ * the root, so it is a file, not a directory — the same ≥3-segment rule
26
+ * `pluginOfPath` applies, arrived at from the other side.
27
+ *
28
+ * Cached for {@link CACHE_TTL_MS} and dropped by `invalidate()` from the
29
+ * file-change subscriber, so a grant committed on the default branch is
30
+ * reflected within one round-trip rather than one TTL.
31
+ */
32
+ export class PluginIndexService {
33
+ workspaceService;
34
+ accessControl;
35
+ skillService;
36
+ toolManualService;
37
+ kbDirName;
38
+ cache;
39
+ constructor(workspaceService, accessControl, skillService, toolManualService, kbDirName, now = Date.now) {
40
+ this.workspaceService = workspaceService;
41
+ this.accessControl = accessControl;
42
+ this.skillService = skillService;
43
+ this.toolManualService = toolManualService;
44
+ this.kbDirName = kbDirName;
45
+ this.cache = new TtlCache(CACHE_TTL_MS, now);
46
+ }
47
+ invalidate() {
48
+ this.cache.invalidate();
49
+ }
50
+ async catalog() {
51
+ const cached = this.cache.get();
52
+ if (cached)
53
+ return cached;
54
+ const entries = await this.build();
55
+ // A failed scan and a KB with genuinely no plugin folders both serve `[]`,
56
+ // but only the second is a fact worth holding for the TTL. Caching the
57
+ // failure would hide every plugin from every user for the full 60s AFTER
58
+ // the cause is gone — a clone mid-creation, a readdir that raced a
59
+ // checkout — and `invalidate()` only fires on a file-change event that may
60
+ // never arrive in that window. So a degraded read is served, not stored,
61
+ // and the next caller retries.
62
+ if (entries === null)
63
+ return [];
64
+ this.cache.set(entries);
65
+ return entries;
66
+ }
67
+ // --- internal --------------------------------------------------------------
68
+ /**
69
+ * Degrades on ANY failure rather than throwing. The Library must never break
70
+ * because a plugin folder can't be read — same philosophy as the skill and
71
+ * tool-manual scanners, and the reason the route's 500 is reserved for the
72
+ * per-caller half.
73
+ *
74
+ * `null` is that degraded case and is deliberately NOT the same value as an
75
+ * empty array: a KB with no plugin folders returns `[]`, which is true and
76
+ * cacheable, while a failure returns `null`, which `catalog()` serves as `[]`
77
+ * without storing it.
78
+ */
79
+ async build() {
80
+ try {
81
+ const wsId = (await this.workspaceService.getOrCreateForBranch(DEFAULT_BRANCH)).id;
82
+ const kbRoot = path.join(await this.workspaceService.getWorkspacePath(wsId), this.kbDirName);
83
+ const folders = await this.scanFolders(kbRoot);
84
+ if (folders.size === 0)
85
+ return [];
86
+ const [skillCounts, toolCounts] = await Promise.all([
87
+ this.countSkills(),
88
+ this.countTools(),
89
+ ]);
90
+ const entries = [];
91
+ for (const [name, pluginFolders] of folders) {
92
+ // One folder, one access boundary — the folder IS the plugin.
93
+ const [primary] = pluginFolders;
94
+ const [owners, writers, readers] = await Promise.all([
95
+ this.accessControl.eligibleOwners(wsId, primary),
96
+ this.accessControl.eligibleWriters(wsId, primary),
97
+ this.accessControl.eligibleReaders(wsId, primary),
98
+ ]);
99
+ entries.push({
100
+ name,
101
+ folders: pluginFolders,
102
+ skillCount: skillCounts.get(name) ?? 0,
103
+ toolCount: toolCounts.get(name) ?? 0,
104
+ owners,
105
+ writers,
106
+ readers,
107
+ });
108
+ }
109
+ return entries.sort((a, b) => a.name.localeCompare(b.name));
110
+ }
111
+ catch (err) {
112
+ console.warn(`[plugins] plugin index unavailable: ${err instanceof Error ? err.message : String(err)}`);
113
+ return null;
114
+ }
115
+ }
116
+ /** name → repo-relative plugin folder (always a single-element list). */
117
+ async scanFolders(kbRoot) {
118
+ const byName = new Map();
119
+ let children;
120
+ try {
121
+ children = await fs.readdir(path.join(kbRoot, PLUGINS_DIR), { withFileTypes: true });
122
+ }
123
+ catch {
124
+ return byName; // a KB without a `Plugins/` root simply has no plugins
125
+ }
126
+ const candidates = children.filter((child) => child.isDirectory() &&
127
+ !child.name.startsWith('.') &&
128
+ // Personal folders live under Plugins/ but are not plugins: one exists
129
+ // per person, private by construction, and listing them would put a
130
+ // locked row per employee in everyone's index.
131
+ !isPersonalPluginFolder(child.name));
132
+ // A plugin exists exactly when its folder carries an `access.md` (see the
133
+ // class doc) — stat that file, don't trust the directory.
134
+ const verdicts = await Promise.all(candidates.map(async (child) => {
135
+ try {
136
+ return (await fs.stat(path.join(kbRoot, PLUGINS_DIR, child.name, 'access.md'))).isFile();
137
+ }
138
+ catch {
139
+ return false;
140
+ }
141
+ }));
142
+ candidates.forEach((child, i) => {
143
+ if (verdicts[i])
144
+ byName.set(child.name, [`${PLUGINS_DIR}/${child.name}`]);
145
+ });
146
+ return byName;
147
+ }
148
+ async countSkills() {
149
+ // `undefined` is the documented GLOBAL, unfiltered mode — counts are a
150
+ // property of the plugin, not of who is asking.
151
+ return bucketByPlugin(await this.skillService.listSkills(undefined));
152
+ }
153
+ async countTools() {
154
+ return bucketByPlugin(await this.toolManualService.listAllSummaries());
155
+ }
156
+ }
157
+ /** Count items per plugin folder name; ungrouped items (`null`) count nowhere. */
158
+ function bucketByPlugin(items) {
159
+ const counts = new Map();
160
+ for (const item of items) {
161
+ const plugin = pluginOfPath(item.path);
162
+ if (plugin === null)
163
+ continue;
164
+ counts.set(plugin, (counts.get(plugin) ?? 0) + 1);
165
+ }
166
+ return counts;
167
+ }
168
+ /** The default-branch workspace id every plugin resolution runs against. */
169
+ export function pluginsWorkspaceId() {
170
+ return workspaceIdForBranch(DEFAULT_BRANCH);
171
+ }
172
+ //# sourceMappingURL=plugins.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins.service.js","sourceRoot":"","sources":["../../../src/modules/plugins/plugins.service.ts"],"names":[],"mappings":"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"}