@bevel-software/platform-core-backend 0.7.5 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/LICENSE +202 -202
  2. package/THIRD-PARTY-NOTICES.md +428 -454
  3. package/dist/core/core-ports.d.ts +8 -1
  4. package/dist/core/core-ports.d.ts.map +1 -1
  5. package/dist/core/core-ports.js.map +1 -1
  6. package/dist/core/create-core-server.d.ts.map +1 -1
  7. package/dist/core/create-core-server.js +75 -18
  8. package/dist/core/create-core-server.js.map +1 -1
  9. package/dist/core/create-core-services.d.ts +12 -5
  10. package/dist/core/create-core-services.d.ts.map +1 -1
  11. package/dist/core/create-core-services.js +69 -36
  12. package/dist/core/create-core-services.js.map +1 -1
  13. package/dist/modules/access/access-control.interface.d.ts +8 -7
  14. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  15. package/dist/modules/access/access-control.service.d.ts +1 -1
  16. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  17. package/dist/modules/access/access-control.service.js +6 -6
  18. package/dist/modules/access/access-control.service.js.map +1 -1
  19. package/dist/modules/access/access-declarations.d.ts +5 -5
  20. package/dist/modules/access/access-declarations.js +3 -3
  21. package/dist/modules/access/access-mutation.service.d.ts +3 -3
  22. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  23. package/dist/modules/access/access-mutation.service.js +3 -3
  24. package/dist/modules/access/access-mutation.service.js.map +1 -1
  25. package/dist/modules/access/access-splice.js +4 -4
  26. package/dist/modules/access/access-splice.js.map +1 -1
  27. package/dist/modules/access/access.routes.d.ts +3 -1
  28. package/dist/modules/access/access.routes.d.ts.map +1 -1
  29. package/dist/modules/access/access.routes.js +22 -20
  30. package/dist/modules/access/access.routes.js.map +1 -1
  31. package/dist/modules/access/creator-access.d.ts +2 -2
  32. package/dist/modules/access/creator-access.js +5 -5
  33. package/dist/modules/access/creator-access.js.map +1 -1
  34. package/dist/modules/access/render-roles-yaml.d.ts +22 -0
  35. package/dist/modules/access/render-roles-yaml.d.ts.map +1 -0
  36. package/dist/modules/access/render-roles-yaml.js +56 -0
  37. package/dist/modules/access/render-roles-yaml.js.map +1 -0
  38. package/dist/modules/access/roles-admin.service.d.ts +33 -5
  39. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  40. package/dist/modules/access/roles-admin.service.js +44 -19
  41. package/dist/modules/access/roles-admin.service.js.map +1 -1
  42. package/dist/modules/code-mode/code-mode-names.d.ts +5 -13
  43. package/dist/modules/code-mode/code-mode-names.d.ts.map +1 -1
  44. package/dist/modules/code-mode/code-mode-names.js +5 -27
  45. package/dist/modules/code-mode/code-mode-names.js.map +1 -1
  46. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  47. package/dist/modules/code-mode/code-mode.tool.js +29 -7
  48. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  49. package/dist/modules/mcp/mcp.service.d.ts +11 -52
  50. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  51. package/dist/modules/mcp/mcp.service.js +33 -395
  52. package/dist/modules/mcp/mcp.service.js.map +1 -1
  53. package/dist/modules/plugins/index.d.ts +7 -0
  54. package/dist/modules/plugins/index.d.ts.map +1 -0
  55. package/dist/modules/plugins/index.js +6 -0
  56. package/dist/modules/plugins/index.js.map +1 -0
  57. package/dist/modules/plugins/join-proposals.d.ts +53 -0
  58. package/dist/modules/plugins/join-proposals.d.ts.map +1 -0
  59. package/dist/modules/plugins/join-proposals.js +67 -0
  60. package/dist/modules/plugins/join-proposals.js.map +1 -0
  61. package/dist/modules/plugins/join-requests.service.d.ts +81 -0
  62. package/dist/modules/plugins/join-requests.service.d.ts.map +1 -0
  63. package/dist/modules/plugins/join-requests.service.js +135 -0
  64. package/dist/modules/plugins/join-requests.service.js.map +1 -0
  65. package/dist/modules/plugins/plugin-provision.service.d.ts +134 -0
  66. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -0
  67. package/dist/modules/plugins/plugin-provision.service.js +344 -0
  68. package/dist/modules/plugins/plugin-provision.service.js.map +1 -0
  69. package/dist/modules/plugins/plugins.contract.d.ts +106 -0
  70. package/dist/modules/plugins/plugins.contract.d.ts.map +1 -0
  71. package/dist/modules/plugins/plugins.contract.js +36 -0
  72. package/dist/modules/plugins/plugins.contract.js.map +1 -0
  73. package/dist/modules/plugins/plugins.routes.d.ts +42 -0
  74. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -0
  75. package/dist/modules/plugins/plugins.routes.js +379 -0
  76. package/dist/modules/plugins/plugins.routes.js.map +1 -0
  77. package/dist/modules/plugins/plugins.service.d.ts +60 -0
  78. package/dist/modules/plugins/plugins.service.d.ts.map +1 -0
  79. package/dist/modules/plugins/plugins.service.js +172 -0
  80. package/dist/modules/plugins/plugins.service.js.map +1 -0
  81. package/dist/modules/settings/setup.routes.d.ts +10 -1
  82. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  83. package/dist/modules/settings/setup.routes.js +111 -6
  84. package/dist/modules/settings/setup.routes.js.map +1 -1
  85. package/dist/modules/skills/pending-skills.service.d.ts +2 -2
  86. package/dist/modules/skills/pending-skills.service.js +7 -7
  87. package/dist/modules/skills/pending-skills.service.js.map +1 -1
  88. package/dist/modules/skills/skills.contract.d.ts +4 -4
  89. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  90. package/dist/modules/skills/skills.contract.js +1 -1
  91. package/dist/modules/skills/skills.service.js +5 -5
  92. package/dist/modules/skills/skills.service.js.map +1 -1
  93. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +65 -0
  94. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -0
  95. package/dist/modules/tool-manuals/mcp-json-discovery.js +276 -0
  96. package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -0
  97. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +92 -0
  98. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -0
  99. package/dist/modules/tool-manuals/mcp-server-edit.service.js +328 -0
  100. package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -0
  101. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +38 -12
  102. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  103. package/dist/modules/tool-manuals/tool-manuals.contract.js +1 -1
  104. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +13 -2
  105. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  106. package/dist/modules/tool-manuals/tool-manuals.routes.js +233 -2
  107. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  108. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  109. package/dist/modules/tool-manuals/tool-manuals.service.js +74 -37
  110. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  111. package/dist/modules/tool-manuals/tool-manuals.tools.js +6 -3
  112. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  113. package/dist/modules/workflow/git/git.service.js +2 -2
  114. package/dist/modules/workflow/git/git.service.js.map +1 -1
  115. package/dist/modules/workspace/kb-seed.service.d.ts +2 -2
  116. package/dist/modules/workspace/kb-seed.service.d.ts.map +1 -1
  117. package/dist/modules/workspace/kb-seed.service.js +43 -10
  118. package/dist/modules/workspace/kb-seed.service.js.map +1 -1
  119. package/dist/modules/workspace/plugins-migration.d.ts +50 -0
  120. package/dist/modules/workspace/plugins-migration.d.ts.map +1 -0
  121. package/dist/modules/workspace/plugins-migration.js +379 -0
  122. package/dist/modules/workspace/plugins-migration.js.map +1 -0
  123. package/dist/modules/workspace/startup/kb-git.d.ts +23 -0
  124. package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -0
  125. package/dist/modules/workspace/startup/kb-git.js +86 -0
  126. package/dist/modules/workspace/startup/kb-git.js.map +1 -0
  127. package/dist/modules/workspace/startup/kb-startup-runner.d.ts +74 -0
  128. package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -0
  129. package/dist/modules/workspace/startup/kb-startup-runner.js +528 -0
  130. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -0
  131. package/dist/modules/workspace/startup/on-server-start.d.ts +105 -0
  132. package/dist/modules/workspace/startup/on-server-start.d.ts.map +1 -0
  133. package/dist/modules/workspace/startup/on-server-start.js +21 -0
  134. package/dist/modules/workspace/startup/on-server-start.js.map +1 -0
  135. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts +46 -0
  136. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts.map +1 -0
  137. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +492 -0
  138. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -0
  139. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +23 -0
  140. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts.map +1 -0
  141. package/dist/modules/workspace/startup/steps/roles-yaml.step.js +69 -0
  142. package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -0
  143. package/dist/modules/workspace/startup/steps/seed-tree.d.ts +17 -0
  144. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -0
  145. package/dist/modules/workspace/startup/steps/seed-tree.js +109 -0
  146. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -0
  147. package/dist/modules/workspace/startup/steps/template-files.step.d.ts +103 -0
  148. package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -0
  149. package/dist/modules/workspace/startup/steps/template-files.step.js +337 -0
  150. package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -0
  151. package/dist/modules/workspace/workspace.routes.js +3 -3
  152. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  153. package/dist/modules/workspace/workspace.service.d.ts +0 -9
  154. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  155. package/dist/modules/workspace/workspace.service.js +2 -30
  156. package/dist/modules/workspace/workspace.service.js.map +1 -1
  157. package/dist/shared/kb-layout.test.js +3 -3
  158. package/dist/shared/kb-layout.test.js.map +1 -1
  159. package/dist/shared/utcp-namespace.d.ts +6 -27
  160. package/dist/shared/utcp-namespace.d.ts.map +1 -1
  161. package/dist/shared/utcp-namespace.js +6 -63
  162. package/dist/shared/utcp-namespace.js.map +1 -1
  163. package/dist/shared/variable-refs.d.ts +42 -0
  164. package/dist/shared/variable-refs.d.ts.map +1 -0
  165. package/dist/shared/variable-refs.js +60 -0
  166. package/dist/shared/variable-refs.js.map +1 -0
  167. package/kb-template/.bevelignore +1 -1
  168. package/kb-template/AGENTS.md +88 -35
  169. package/kb-template/KnowledgeBase/How to get started.md +10 -10
  170. package/kb-template/access.md +36 -36
  171. package/kb-template/gitignore.template +16 -0
  172. package/migrations/meta/0000_snapshot.json +1479 -1479
  173. package/package.json +5 -4
  174. package/src/assets.ts +25 -25
  175. package/src/core/core-ports.ts +113 -106
  176. package/src/core/create-core-server.ts +85 -20
  177. package/src/core/create-core-services.ts +83 -42
  178. package/src/index.ts +69 -69
  179. package/src/modules/access/__tests__/access-control.atref-batch.test.ts +98 -98
  180. package/src/modules/access/__tests__/access-declarations.test.ts +28 -28
  181. package/src/modules/access/__tests__/access-md-format.test.ts +18 -18
  182. package/src/modules/access/__tests__/access-mutation.service.test.ts +5 -5
  183. package/src/modules/access/__tests__/access-splice.test.ts +2 -2
  184. package/src/modules/access/__tests__/access.routes.overrides.test.ts +16 -16
  185. package/src/modules/access/__tests__/grant-sources.test.ts +12 -12
  186. package/src/modules/access/__tests__/roles-admin.service.test.ts +36 -2
  187. package/src/modules/access/access-control.interface.ts +8 -7
  188. package/src/modules/access/access-control.service.ts +7 -7
  189. package/src/modules/access/access-declarations.ts +5 -5
  190. package/src/modules/access/access-mutation.service.ts +3 -3
  191. package/src/modules/access/access-splice.ts +4 -4
  192. package/src/modules/access/access.routes.ts +23 -20
  193. package/src/modules/access/creator-access.ts +5 -5
  194. package/src/modules/access/render-roles-yaml.ts +65 -0
  195. package/src/modules/access/roles-admin.service.ts +45 -16
  196. package/src/modules/admin/admin-access.routes.ts +29 -29
  197. package/src/modules/auth/__tests__/auth.routes.test.ts +91 -91
  198. package/src/modules/auth/__tests__/rate-limit.test.ts +36 -36
  199. package/src/modules/auth/rate-limit.ts +45 -45
  200. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +67 -0
  201. package/src/modules/code-mode/code-mode-names.ts +10 -36
  202. package/src/modules/code-mode/code-mode.tool.ts +27 -7
  203. package/src/modules/database/connection.ts +15 -15
  204. package/src/modules/database/schema.ts +11 -11
  205. package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +150 -150
  206. package/src/modules/mcp/mcp.service.ts +57 -435
  207. package/src/modules/{groups → plugins}/__tests__/join-proposals.test.ts +1 -1
  208. package/src/modules/{groups → plugins}/__tests__/join-requests.service.test.ts +7 -7
  209. package/src/modules/{groups/__tests__/group-index.service.test.ts → plugins/__tests__/plugin-index.service.test.ts} +41 -41
  210. package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +312 -0
  211. package/src/modules/{groups/__tests__/groups.routes.test.ts → plugins/__tests__/plugins.routes.test.ts} +100 -100
  212. package/src/modules/plugins/index.ts +17 -0
  213. package/src/modules/{groups → plugins}/join-proposals.ts +2 -2
  214. package/src/modules/{groups → plugins}/join-requests.service.ts +8 -8
  215. package/src/modules/{groups/group-provision.service.ts → plugins/plugin-provision.service.ts} +139 -69
  216. package/src/modules/{groups/groups.contract.ts → plugins/plugins.contract.ts} +26 -26
  217. package/src/modules/{groups/groups.routes.ts → plugins/plugins.routes.ts} +102 -102
  218. package/src/modules/{groups/groups.service.ts → plugins/plugins.service.ts} +43 -43
  219. package/src/modules/secrets-vault/__tests__/tool-owner-gate.route.test.ts +143 -143
  220. package/src/modules/settings/__tests__/setup.routes.test.ts +124 -3
  221. package/src/modules/settings/setup.routes.ts +112 -5
  222. package/src/modules/skills/__tests__/pending-skills.service.test.ts +14 -14
  223. package/src/modules/skills/__tests__/skills.service.test.ts +13 -13
  224. package/src/modules/skills/pending-skills.service.ts +7 -7
  225. package/src/modules/skills/skills.contract.ts +4 -4
  226. package/src/modules/skills/skills.service.ts +5 -5
  227. package/src/modules/tool-auth/llm-usage-meter.ts +19 -19
  228. package/src/modules/tool-manuals/__tests__/mcp-json-discovery.test.ts +198 -0
  229. package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +346 -0
  230. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +101 -0
  231. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +3 -3
  232. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +2 -2
  233. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +27 -27
  234. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +3 -3
  235. package/src/modules/tool-manuals/mcp-json-discovery.ts +328 -0
  236. package/src/modules/tool-manuals/mcp-server-edit.service.ts +434 -0
  237. package/src/modules/tool-manuals/tool-manuals.contract.ts +35 -12
  238. package/src/modules/tool-manuals/tool-manuals.routes.ts +222 -1
  239. package/src/modules/tool-manuals/tool-manuals.service.ts +82 -42
  240. package/src/modules/tool-manuals/tool-manuals.tools.ts +6 -3
  241. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
  242. package/src/modules/workflow/git/__tests__/branch-name.test.ts +3 -3
  243. package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +3 -3
  244. package/src/modules/workflow/git/__tests__/git.service.commitFile.test.ts +13 -8
  245. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +1 -1
  246. package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
  247. package/src/modules/workflow/git/git.service.ts +2 -2
  248. package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +1 -1
  249. package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
  250. package/src/modules/workflow/workflow-hooks.ts +101 -101
  251. package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +237 -237
  252. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +236 -236
  253. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +179 -179
  254. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +320 -320
  255. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +337 -337
  256. package/src/modules/workspace/__tests__/workspace.service.test.ts +27 -0
  257. package/src/modules/workspace/bevel-ignore.ts +66 -66
  258. package/src/modules/workspace/session-sink.ts +25 -25
  259. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +495 -0
  260. package/src/modules/workspace/startup/kb-git.ts +94 -0
  261. package/src/modules/workspace/startup/kb-startup-runner.ts +597 -0
  262. package/src/modules/workspace/startup/on-server-start.ts +97 -0
  263. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +636 -0
  264. package/src/modules/workspace/startup/steps/groups-to-plugins.step.ts +561 -0
  265. package/src/modules/workspace/startup/steps/roles-yaml.step.ts +71 -0
  266. package/src/modules/workspace/startup/steps/seed-tree.ts +115 -0
  267. package/src/modules/workspace/startup/steps/template-files.step.ts +360 -0
  268. package/src/modules/workspace/workspace.routes.ts +3 -3
  269. package/src/modules/workspace/workspace.service.ts +2 -35
  270. package/src/modules/workspace/workspace.tools.ts +922 -922
  271. package/src/shared/__tests__/join-request.test.ts +13 -13
  272. package/src/shared/__tests__/kb-layout.plugin.test.ts +45 -0
  273. package/src/shared/kb-layout.test.ts +3 -3
  274. package/src/shared/utcp-namespace.ts +10 -68
  275. package/src/shared/variable-refs.ts +64 -0
  276. package/src/modules/groups/__tests__/group-provision.service.test.ts +0 -247
  277. package/src/modules/groups/index.ts +0 -17
  278. package/src/modules/workspace/__tests__/kb-seed.service.test.ts +0 -444
  279. package/src/modules/workspace/kb-seed.interface.ts +0 -36
  280. package/src/modules/workspace/kb-seed.service.ts +0 -555
  281. package/src/shared/__tests__/kb-layout.group.test.ts +0 -45
  282. /package/kb-template/{Groups → Plugins}/.gitkeep +0 -0
@@ -1,179 +1,179 @@
1
- import type { Server } from 'node:http';
2
- import type { AddressInfo } from 'node:net';
3
- import fs from 'node:fs/promises';
4
- import path from 'node:path';
5
- import os from 'node:os';
6
- import express from 'express';
7
- import { describe, it, expect, afterEach, vi } from 'vitest';
8
- import type { IWorkflowService } from '@bevel-software/platform-shared';
9
- import type { IAccessControl } from '../../access/access-control.interface.js';
10
- import type { WorkflowEventBus } from '../../workflow/event-bus.js';
11
- import type { AuthService } from '../../auth/auth.service.js';
12
- import type { IAdminAccessService } from '../../admin/admin.interface.js';
13
- import { createWorkspaceRoutes } from '../workspace.routes.js';
14
- import type { ICreatorAccess } from '../../access/creator-access.js';
15
- import type { WorkspaceService } from '../workspace.service.js';
16
-
17
- const stubCreatorAccess: ICreatorAccess = {
18
- planForCreate: async () => null,
19
- grantInExtractedFile: async () => null,
20
- noteAccessFileWritten: () => {},
21
- };
22
-
23
- /**
24
- * Contract test for DELETE /workspace/:id/file on a *directory* target —
25
- * the recursive folder-delete branch.
26
- *
27
- * BEVA-132: deleting a folder that contains sub-folders left the (now-empty)
28
- * sub-directory shells on disk, so the explorer (which lists on-disk
29
- * directories, not just tracked files) kept showing the folder and it looked
30
- * undeletable. The route must sweep the whole empty subtree, not just the top
31
- * directory.
32
- */
33
-
34
- const USER_ID = 'user-1';
35
- const USER = { id: USER_ID, email: 'alice@example.com', name: 'Alice' };
36
- const WORKSPACE_ID = 'feature-branch';
37
-
38
- interface Harness {
39
- server: Server;
40
- baseUrl: string;
41
- workspaceDir: string;
42
- }
43
-
44
- async function makeHarness(): Promise<Harness> {
45
- const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'beva132-'));
46
-
47
- const accessControl = {
48
- canWrite: vi.fn(),
49
- canWriteBatch: vi.fn(),
50
- canDownload: vi.fn(),
51
- eligibleWriters: vi.fn(),
52
- eligibleWriterEmails: vi.fn(),
53
- invalidate: vi.fn(),
54
- findEmailByHash: vi.fn(),
55
- canWriteAtRef: vi.fn(),
56
- canWriteBatchAtRef: vi.fn(),
57
- eligibleWritersAtRef: vi.fn(),
58
- eligibleWritersForPathsAtRef: vi.fn(),
59
- } as unknown as IAccessControl;
60
-
61
- // deleteFile just removes the file from disk — the lock/commit machinery is
62
- // mocked out, so the on-disk effect of a per-file delete is all that the
63
- // recursive-cleanup logic under test reacts to.
64
- const workspaceServiceMock: Partial<WorkspaceService> = {
65
- getWorkspacePath: vi.fn(async () => workspaceDir),
66
- deleteFile: vi.fn(async (_id: string, relPath: string) => {
67
- await fs.rm(path.resolve(workspaceDir, relPath));
68
- }),
69
- };
70
- const workspaceService = workspaceServiceMock as WorkspaceService;
71
-
72
- const workflowServiceMock: Partial<IWorkflowService> = {
73
- getLock: vi.fn(async () => null),
74
- acquireLock: vi.fn(async () => ({ acquired: true, lock: {} as never })),
75
- releaseLock: vi.fn(async () => undefined as never),
76
- releaseLockNoCommit: vi.fn(async () => undefined as never),
77
- };
78
- const workflowService = workflowServiceMock as unknown as IWorkflowService;
79
-
80
- const authServiceMock: Partial<AuthService> = {
81
- getUserById: vi.fn(async () => USER),
82
- };
83
- const authService = authServiceMock as AuthService;
84
-
85
- const eventBus = { emit: vi.fn() } as unknown as WorkflowEventBus;
86
-
87
- const app = express();
88
- app.use(express.json());
89
- app.use('/api', (req, _res, next) => {
90
- (req as any).userId = USER_ID;
91
- next();
92
- });
93
- app.use('/api', createWorkspaceRoutes(
94
- workspaceService,
95
- authService,
96
- workflowService,
97
- eventBus,
98
- accessControl,
99
- 'knowledge-base',
100
- stubCreatorAccess,
101
- // Not exercised here — only `.bevelignore`'s tree visibility consults it.
102
- { isAdmin: async () => false } as unknown as IAdminAccessService,
103
- ));
104
-
105
- const server = await new Promise<Server>((resolve) => {
106
- const s = app.listen(0, () => resolve(s));
107
- });
108
- const addr = server.address();
109
- if (addr === null || typeof addr === 'string') {
110
- throw new Error(`Unexpected server.address() shape: ${JSON.stringify(addr)}`);
111
- }
112
- const port = (addr as AddressInfo).port;
113
- return { server, baseUrl: `http://127.0.0.1:${port}`, workspaceDir };
114
- }
115
-
116
- async function closeServer(server: Server): Promise<void> {
117
- await new Promise<void>((resolve, reject) =>
118
- server.close((err) => (err ? reject(err) : resolve())),
119
- );
120
- }
121
-
122
- async function exists(p: string): Promise<boolean> {
123
- try {
124
- await fs.stat(p);
125
- return true;
126
- } catch {
127
- return false;
128
- }
129
- }
130
-
131
- describe('DELETE /workspace/:id/file — recursive folder delete (BEVA-132)', () => {
132
- let h: Harness | null = null;
133
- afterEach(async () => {
134
- if (h) {
135
- await closeServer(h.server);
136
- await fs.rm(h.workspaceDir, { recursive: true, force: true });
137
- }
138
- h = null;
139
- });
140
-
141
- it('removes a folder whose children are sub-folders (nested files) off disk', async () => {
142
- h = await makeHarness();
143
- // parent/
144
- // sub/a.md
145
- // sub/deep/b.md
146
- // other/c.md
147
- await fs.mkdir(path.join(h.workspaceDir, 'parent/sub/deep'), { recursive: true });
148
- await fs.mkdir(path.join(h.workspaceDir, 'parent/other'), { recursive: true });
149
- await fs.writeFile(path.join(h.workspaceDir, 'parent/sub/a.md'), 'a');
150
- await fs.writeFile(path.join(h.workspaceDir, 'parent/sub/deep/b.md'), 'b');
151
- await fs.writeFile(path.join(h.workspaceDir, 'parent/other/c.md'), 'c');
152
-
153
- const res = await fetch(
154
- `${h.baseUrl}/api/workspace/${WORKSPACE_ID}/file?path=${encodeURIComponent('parent')}`,
155
- { method: 'DELETE' },
156
- );
157
- expect(res.status).toBe(200);
158
- const body = (await res.json()) as { status: string; count: number };
159
- expect(body.status).toBe('deleted');
160
- expect(body.count).toBe(3);
161
- // The whole subtree — including the empty sub-folder shells — is gone.
162
- expect(await exists(path.join(h.workspaceDir, 'parent'))).toBe(false);
163
- });
164
-
165
- it('removes a folder that contains only empty sub-folders (no tracked files)', async () => {
166
- h = await makeHarness();
167
- await fs.mkdir(path.join(h.workspaceDir, 'parent/emptyA/nested'), { recursive: true });
168
- await fs.mkdir(path.join(h.workspaceDir, 'parent/emptyB'), { recursive: true });
169
-
170
- const res = await fetch(
171
- `${h.baseUrl}/api/workspace/${WORKSPACE_ID}/file?path=${encodeURIComponent('parent')}`,
172
- { method: 'DELETE' },
173
- );
174
- expect(res.status).toBe(200);
175
- const body = (await res.json()) as { status: string; count: number };
176
- expect(body.count).toBe(0);
177
- expect(await exists(path.join(h.workspaceDir, 'parent'))).toBe(false);
178
- });
179
- });
1
+ import type { Server } from 'node:http';
2
+ import type { AddressInfo } from 'node:net';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+ import express from 'express';
7
+ import { describe, it, expect, afterEach, vi } from 'vitest';
8
+ import type { IWorkflowService } from '@bevel-software/platform-shared';
9
+ import type { IAccessControl } from '../../access/access-control.interface.js';
10
+ import type { WorkflowEventBus } from '../../workflow/event-bus.js';
11
+ import type { AuthService } from '../../auth/auth.service.js';
12
+ import type { IAdminAccessService } from '../../admin/admin.interface.js';
13
+ import { createWorkspaceRoutes } from '../workspace.routes.js';
14
+ import type { ICreatorAccess } from '../../access/creator-access.js';
15
+ import type { WorkspaceService } from '../workspace.service.js';
16
+
17
+ const stubCreatorAccess: ICreatorAccess = {
18
+ planForCreate: async () => null,
19
+ grantInExtractedFile: async () => null,
20
+ noteAccessFileWritten: () => {},
21
+ };
22
+
23
+ /**
24
+ * Contract test for DELETE /workspace/:id/file on a *directory* target —
25
+ * the recursive folder-delete branch.
26
+ *
27
+ * BEVA-132: deleting a folder that contains sub-folders left the (now-empty)
28
+ * sub-directory shells on disk, so the explorer (which lists on-disk
29
+ * directories, not just tracked files) kept showing the folder and it looked
30
+ * undeletable. The route must sweep the whole empty subtree, not just the top
31
+ * directory.
32
+ */
33
+
34
+ const USER_ID = 'user-1';
35
+ const USER = { id: USER_ID, email: 'alice@example.com', name: 'Alice' };
36
+ const WORKSPACE_ID = 'feature-branch';
37
+
38
+ interface Harness {
39
+ server: Server;
40
+ baseUrl: string;
41
+ workspaceDir: string;
42
+ }
43
+
44
+ async function makeHarness(): Promise<Harness> {
45
+ const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'beva132-'));
46
+
47
+ const accessControl = {
48
+ canWrite: vi.fn(),
49
+ canWriteBatch: vi.fn(),
50
+ canDownload: vi.fn(),
51
+ eligibleWriters: vi.fn(),
52
+ eligibleWriterEmails: vi.fn(),
53
+ invalidate: vi.fn(),
54
+ findEmailByHash: vi.fn(),
55
+ canWriteAtRef: vi.fn(),
56
+ canWriteBatchAtRef: vi.fn(),
57
+ eligibleWritersAtRef: vi.fn(),
58
+ eligibleWritersForPathsAtRef: vi.fn(),
59
+ } as unknown as IAccessControl;
60
+
61
+ // deleteFile just removes the file from disk — the lock/commit machinery is
62
+ // mocked out, so the on-disk effect of a per-file delete is all that the
63
+ // recursive-cleanup logic under test reacts to.
64
+ const workspaceServiceMock: Partial<WorkspaceService> = {
65
+ getWorkspacePath: vi.fn(async () => workspaceDir),
66
+ deleteFile: vi.fn(async (_id: string, relPath: string) => {
67
+ await fs.rm(path.resolve(workspaceDir, relPath));
68
+ }),
69
+ };
70
+ const workspaceService = workspaceServiceMock as WorkspaceService;
71
+
72
+ const workflowServiceMock: Partial<IWorkflowService> = {
73
+ getLock: vi.fn(async () => null),
74
+ acquireLock: vi.fn(async () => ({ acquired: true, lock: {} as never })),
75
+ releaseLock: vi.fn(async () => undefined as never),
76
+ releaseLockNoCommit: vi.fn(async () => undefined as never),
77
+ };
78
+ const workflowService = workflowServiceMock as unknown as IWorkflowService;
79
+
80
+ const authServiceMock: Partial<AuthService> = {
81
+ getUserById: vi.fn(async () => USER),
82
+ };
83
+ const authService = authServiceMock as AuthService;
84
+
85
+ const eventBus = { emit: vi.fn() } as unknown as WorkflowEventBus;
86
+
87
+ const app = express();
88
+ app.use(express.json());
89
+ app.use('/api', (req, _res, next) => {
90
+ (req as any).userId = USER_ID;
91
+ next();
92
+ });
93
+ app.use('/api', createWorkspaceRoutes(
94
+ workspaceService,
95
+ authService,
96
+ workflowService,
97
+ eventBus,
98
+ accessControl,
99
+ 'knowledge-base',
100
+ stubCreatorAccess,
101
+ // Not exercised here — only `.bevelignore`'s tree visibility consults it.
102
+ { isAdmin: async () => false } as unknown as IAdminAccessService,
103
+ ));
104
+
105
+ const server = await new Promise<Server>((resolve) => {
106
+ const s = app.listen(0, () => resolve(s));
107
+ });
108
+ const addr = server.address();
109
+ if (addr === null || typeof addr === 'string') {
110
+ throw new Error(`Unexpected server.address() shape: ${JSON.stringify(addr)}`);
111
+ }
112
+ const port = (addr as AddressInfo).port;
113
+ return { server, baseUrl: `http://127.0.0.1:${port}`, workspaceDir };
114
+ }
115
+
116
+ async function closeServer(server: Server): Promise<void> {
117
+ await new Promise<void>((resolve, reject) =>
118
+ server.close((err) => (err ? reject(err) : resolve())),
119
+ );
120
+ }
121
+
122
+ async function exists(p: string): Promise<boolean> {
123
+ try {
124
+ await fs.stat(p);
125
+ return true;
126
+ } catch {
127
+ return false;
128
+ }
129
+ }
130
+
131
+ describe('DELETE /workspace/:id/file — recursive folder delete (BEVA-132)', () => {
132
+ let h: Harness | null = null;
133
+ afterEach(async () => {
134
+ if (h) {
135
+ await closeServer(h.server);
136
+ await fs.rm(h.workspaceDir, { recursive: true, force: true });
137
+ }
138
+ h = null;
139
+ });
140
+
141
+ it('removes a folder whose children are sub-folders (nested files) off disk', async () => {
142
+ h = await makeHarness();
143
+ // parent/
144
+ // sub/a.md
145
+ // sub/deep/b.md
146
+ // other/c.md
147
+ await fs.mkdir(path.join(h.workspaceDir, 'parent/sub/deep'), { recursive: true });
148
+ await fs.mkdir(path.join(h.workspaceDir, 'parent/other'), { recursive: true });
149
+ await fs.writeFile(path.join(h.workspaceDir, 'parent/sub/a.md'), 'a');
150
+ await fs.writeFile(path.join(h.workspaceDir, 'parent/sub/deep/b.md'), 'b');
151
+ await fs.writeFile(path.join(h.workspaceDir, 'parent/other/c.md'), 'c');
152
+
153
+ const res = await fetch(
154
+ `${h.baseUrl}/api/workspace/${WORKSPACE_ID}/file?path=${encodeURIComponent('parent')}`,
155
+ { method: 'DELETE' },
156
+ );
157
+ expect(res.status).toBe(200);
158
+ const body = (await res.json()) as { status: string; count: number };
159
+ expect(body.status).toBe('deleted');
160
+ expect(body.count).toBe(3);
161
+ // The whole subtree — including the empty sub-folder shells — is gone.
162
+ expect(await exists(path.join(h.workspaceDir, 'parent'))).toBe(false);
163
+ });
164
+
165
+ it('removes a folder that contains only empty sub-folders (no tracked files)', async () => {
166
+ h = await makeHarness();
167
+ await fs.mkdir(path.join(h.workspaceDir, 'parent/emptyA/nested'), { recursive: true });
168
+ await fs.mkdir(path.join(h.workspaceDir, 'parent/emptyB'), { recursive: true });
169
+
170
+ const res = await fetch(
171
+ `${h.baseUrl}/api/workspace/${WORKSPACE_ID}/file?path=${encodeURIComponent('parent')}`,
172
+ { method: 'DELETE' },
173
+ );
174
+ expect(res.status).toBe(200);
175
+ const body = (await res.json()) as { status: string; count: number };
176
+ expect(body.count).toBe(0);
177
+ expect(await exists(path.join(h.workspaceDir, 'parent'))).toBe(false);
178
+ });
179
+ });