@bevel-software/platform-core-backend 0.4.1 → 0.5.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 (304) hide show
  1. package/dist/core/core-ports.d.ts +15 -0
  2. package/dist/core/core-ports.d.ts.map +1 -1
  3. package/dist/core/core-ports.js.map +1 -1
  4. package/dist/core/create-core-server.d.ts.map +1 -1
  5. package/dist/core/create-core-server.js +40 -6
  6. package/dist/core/create-core-server.js.map +1 -1
  7. package/dist/core/create-core-services.d.ts +17 -1
  8. package/dist/core/create-core-services.d.ts.map +1 -1
  9. package/dist/core/create-core-services.js +153 -29
  10. package/dist/core/create-core-services.js.map +1 -1
  11. package/dist/core-config.d.ts +70 -21
  12. package/dist/core-config.d.ts.map +1 -1
  13. package/dist/core-config.js +134 -58
  14. package/dist/core-config.js.map +1 -1
  15. package/dist/modules/access/access-control.interface.d.ts +6 -0
  16. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  17. package/dist/modules/access/access-control.service.d.ts +74 -5
  18. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  19. package/dist/modules/access/access-control.service.js +116 -15
  20. package/dist/modules/access/access-control.service.js.map +1 -1
  21. package/dist/modules/access/access-declarations.d.ts +93 -0
  22. package/dist/modules/access/access-declarations.d.ts.map +1 -0
  23. package/dist/modules/access/access-declarations.js +168 -0
  24. package/dist/modules/access/access-declarations.js.map +1 -0
  25. package/dist/modules/access/access-mutation.service.js +4 -4
  26. package/dist/modules/access/access-mutation.service.js.map +1 -1
  27. package/dist/modules/access/access-splice.d.ts +16 -1
  28. package/dist/modules/access/access-splice.d.ts.map +1 -1
  29. package/dist/modules/access/access-splice.js +40 -21
  30. package/dist/modules/access/access-splice.js.map +1 -1
  31. package/dist/modules/access/access.routes.d.ts.map +1 -1
  32. package/dist/modules/access/access.routes.js +51 -0
  33. package/dist/modules/access/access.routes.js.map +1 -1
  34. package/dist/modules/access/creator-access.d.ts +8 -0
  35. package/dist/modules/access/creator-access.d.ts.map +1 -1
  36. package/dist/modules/access/creator-access.js +21 -2
  37. package/dist/modules/access/creator-access.js.map +1 -1
  38. package/dist/modules/admin/admin-access.service.d.ts +1 -1
  39. package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
  40. package/dist/modules/admin/admin-access.service.js +3 -2
  41. package/dist/modules/admin/admin-access.service.js.map +1 -1
  42. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  43. package/dist/modules/auth/auth.routes.js +50 -1
  44. package/dist/modules/auth/auth.routes.js.map +1 -1
  45. package/dist/modules/auth/auth.service.d.ts +39 -10
  46. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  47. package/dist/modules/auth/auth.service.js +38 -16
  48. package/dist/modules/auth/auth.service.js.map +1 -1
  49. package/dist/modules/database/core-schema.d.ts +124 -0
  50. package/dist/modules/database/core-schema.d.ts.map +1 -1
  51. package/dist/modules/database/core-schema.js +45 -0
  52. package/dist/modules/database/core-schema.js.map +1 -1
  53. package/dist/modules/groups/group-provision.service.d.ts +130 -0
  54. package/dist/modules/groups/group-provision.service.d.ts.map +1 -0
  55. package/dist/modules/groups/group-provision.service.js +288 -0
  56. package/dist/modules/groups/group-provision.service.js.map +1 -0
  57. package/dist/modules/groups/groups.contract.d.ts +106 -0
  58. package/dist/modules/groups/groups.contract.d.ts.map +1 -0
  59. package/dist/modules/groups/groups.contract.js +36 -0
  60. package/dist/modules/groups/groups.contract.js.map +1 -0
  61. package/dist/modules/groups/groups.routes.d.ts +42 -0
  62. package/dist/modules/groups/groups.routes.d.ts.map +1 -0
  63. package/dist/modules/groups/groups.routes.js +379 -0
  64. package/dist/modules/groups/groups.routes.js.map +1 -0
  65. package/dist/modules/groups/groups.service.d.ts +60 -0
  66. package/dist/modules/groups/groups.service.d.ts.map +1 -0
  67. package/dist/modules/groups/groups.service.js +172 -0
  68. package/dist/modules/groups/groups.service.js.map +1 -0
  69. package/dist/modules/groups/index.d.ts +7 -0
  70. package/dist/modules/groups/index.d.ts.map +1 -0
  71. package/dist/modules/groups/index.js +6 -0
  72. package/dist/modules/groups/index.js.map +1 -0
  73. package/dist/modules/groups/join-proposals.d.ts +53 -0
  74. package/dist/modules/groups/join-proposals.d.ts.map +1 -0
  75. package/dist/modules/groups/join-proposals.js +67 -0
  76. package/dist/modules/groups/join-proposals.js.map +1 -0
  77. package/dist/modules/groups/join-requests.service.d.ts +81 -0
  78. package/dist/modules/groups/join-requests.service.d.ts.map +1 -0
  79. package/dist/modules/groups/join-requests.service.js +135 -0
  80. package/dist/modules/groups/join-requests.service.js.map +1 -0
  81. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +20 -0
  82. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
  83. package/dist/modules/secrets-vault/secrets-vault.routes.js +59 -11
  84. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  85. package/dist/modules/settings/deployment-settings.service.d.ts +129 -0
  86. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -0
  87. package/dist/modules/settings/deployment-settings.service.js +368 -0
  88. package/dist/modules/settings/deployment-settings.service.js.map +1 -0
  89. package/dist/modules/settings/setup.routes.d.ts +49 -0
  90. package/dist/modules/settings/setup.routes.d.ts.map +1 -0
  91. package/dist/modules/settings/setup.routes.js +292 -0
  92. package/dist/modules/settings/setup.routes.js.map +1 -0
  93. package/dist/modules/skills/index.d.ts +2 -1
  94. package/dist/modules/skills/index.d.ts.map +1 -1
  95. package/dist/modules/skills/index.js +1 -0
  96. package/dist/modules/skills/index.js.map +1 -1
  97. package/dist/modules/skills/pending-skills.service.d.ts +40 -0
  98. package/dist/modules/skills/pending-skills.service.d.ts.map +1 -0
  99. package/dist/modules/skills/pending-skills.service.js +147 -0
  100. package/dist/modules/skills/pending-skills.service.js.map +1 -0
  101. package/dist/modules/skills/skills.contract.d.ts +32 -3
  102. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  103. package/dist/modules/skills/skills.contract.js +1 -1
  104. package/dist/modules/skills/skills.routes.d.ts +3 -2
  105. package/dist/modules/skills/skills.routes.d.ts.map +1 -1
  106. package/dist/modules/skills/skills.routes.js +18 -1
  107. package/dist/modules/skills/skills.routes.js.map +1 -1
  108. package/dist/modules/skills/skills.service.d.ts +22 -0
  109. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  110. package/dist/modules/skills/skills.service.js +24 -12
  111. package/dist/modules/skills/skills.service.js.map +1 -1
  112. package/dist/modules/tool-manuals/index.d.ts +1 -1
  113. package/dist/modules/tool-manuals/index.d.ts.map +1 -1
  114. package/dist/modules/tool-manuals/index.js.map +1 -1
  115. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +47 -2
  116. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  117. package/dist/modules/tool-manuals/tool-manuals.contract.js +1 -1
  118. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +4 -0
  119. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  120. package/dist/modules/tool-manuals/tool-manuals.routes.js +23 -0
  121. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  122. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +3 -1
  123. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  124. package/dist/modules/tool-manuals/tool-manuals.service.js +84 -17
  125. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  126. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  127. package/dist/modules/tool-manuals/tool-manuals.tools.js +6 -2
  128. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  129. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  130. package/dist/modules/workflow/agent-tools/workflow.tools.js +4 -2
  131. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  132. package/dist/modules/workflow/event-bus.d.ts +11 -0
  133. package/dist/modules/workflow/event-bus.d.ts.map +1 -1
  134. package/dist/modules/workflow/event-bus.js +22 -0
  135. package/dist/modules/workflow/event-bus.js.map +1 -1
  136. package/dist/modules/workflow/file-lock.service.d.ts +9 -0
  137. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  138. package/dist/modules/workflow/file-lock.service.js +16 -0
  139. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  140. package/dist/modules/workflow/git/branch-name.d.ts +1 -1
  141. package/dist/modules/workflow/git/branch-name.d.ts.map +1 -1
  142. package/dist/modules/workflow/git/branch-name.js +1 -1
  143. package/dist/modules/workflow/git/branch-name.js.map +1 -1
  144. package/dist/modules/workflow/git/git.service.d.ts +31 -2
  145. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  146. package/dist/modules/workflow/git/git.service.js +144 -52
  147. package/dist/modules/workflow/git/git.service.js.map +1 -1
  148. package/dist/modules/workflow/git/pull-request.service.d.ts +12 -6
  149. package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
  150. package/dist/modules/workflow/git/pull-request.service.js +19 -9
  151. package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
  152. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  153. package/dist/modules/workflow/review-workflow/review-workflow.service.js +12 -3
  154. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  155. package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
  156. package/dist/modules/workflow/workflow.routes.js +95 -14
  157. package/dist/modules/workflow/workflow.routes.js.map +1 -1
  158. package/dist/modules/workflow/workflow.service.d.ts +92 -3
  159. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  160. package/dist/modules/workflow/workflow.service.js +381 -108
  161. package/dist/modules/workflow/workflow.service.js.map +1 -1
  162. package/dist/modules/workspace/bevel-ignore.d.ts +5 -0
  163. package/dist/modules/workspace/bevel-ignore.d.ts.map +1 -1
  164. package/dist/modules/workspace/bevel-ignore.js +5 -1
  165. package/dist/modules/workspace/bevel-ignore.js.map +1 -1
  166. package/dist/modules/workspace/kb-seed.interface.d.ts +1 -1
  167. package/dist/modules/workspace/kb-seed.service.d.ts +60 -5
  168. package/dist/modules/workspace/kb-seed.service.d.ts.map +1 -1
  169. package/dist/modules/workspace/kb-seed.service.js +228 -51
  170. package/dist/modules/workspace/kb-seed.service.js.map +1 -1
  171. package/dist/modules/workspace/workspace.routes.d.ts +2 -1
  172. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  173. package/dist/modules/workspace/workspace.routes.js +51 -18
  174. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  175. package/dist/modules/workspace/workspace.service.d.ts +16 -4
  176. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  177. package/dist/modules/workspace/workspace.service.js +36 -9
  178. package/dist/modules/workspace/workspace.service.js.map +1 -1
  179. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  180. package/dist/modules/workspace/workspace.tools.js +8 -3
  181. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  182. package/dist/shared/kb-layout.test.js +2 -2
  183. package/dist/test-setup.d.ts +2 -0
  184. package/dist/test-setup.d.ts.map +1 -0
  185. package/dist/test-setup.js +18 -0
  186. package/dist/test-setup.js.map +1 -0
  187. package/kb-template/.bevelignore +6 -2
  188. package/kb-template/AGENTS.md +272 -0
  189. package/kb-template/KnowledgeBase/How to get started.md +65 -0
  190. package/kb-template/access.md +36 -36
  191. package/migrations/0002_user_onboarding_done.sql +1 -0
  192. package/migrations/0003_deployment_settings.sql +9 -0
  193. package/migrations/meta/0001_snapshot.json +1486 -0
  194. package/migrations/meta/0002_snapshot.json +1493 -0
  195. package/migrations/meta/0003_snapshot.json +1552 -0
  196. package/migrations/meta/_journal.json +15 -1
  197. package/package.json +3 -3
  198. package/src/__tests__/branch-model.test.ts +79 -0
  199. package/src/__tests__/core-config.admin.test.ts +114 -0
  200. package/src/__tests__/database-url.test.ts +70 -0
  201. package/src/core/core-ports.ts +15 -0
  202. package/src/core/create-core-server.ts +55 -6
  203. package/src/core/create-core-services.ts +201 -36
  204. package/src/core-config.ts +139 -59
  205. package/src/modules/access/__tests__/access-declarations.test.ts +195 -0
  206. package/src/modules/access/__tests__/access-md-format.test.ts +191 -0
  207. package/src/modules/access/__tests__/access-splice.test.ts +21 -0
  208. package/src/modules/access/__tests__/access.routes.overrides.test.ts +214 -0
  209. package/src/modules/access/access-control.interface.ts +11 -0
  210. package/src/modules/access/access-control.service.ts +113 -15
  211. package/src/modules/access/access-declarations.ts +251 -0
  212. package/src/modules/access/access-mutation.service.ts +4 -4
  213. package/src/modules/access/access-splice.ts +64 -21
  214. package/src/modules/access/access.routes.ts +61 -0
  215. package/src/modules/access/creator-access.ts +22 -2
  216. package/src/modules/admin/admin-access.service.ts +4 -3
  217. package/src/modules/auth/__tests__/auth.service.test.ts +57 -0
  218. package/src/modules/auth/__tests__/onboarding-done.route.test.ts +147 -0
  219. package/src/modules/auth/auth.routes.ts +50 -1
  220. package/src/modules/auth/auth.service.ts +55 -17
  221. package/src/modules/database/core-schema.ts +46 -0
  222. package/src/modules/groups/__tests__/group-index.service.test.ts +225 -0
  223. package/src/modules/groups/__tests__/group-provision.service.test.ts +247 -0
  224. package/src/modules/groups/__tests__/groups.routes.test.ts +493 -0
  225. package/src/modules/groups/__tests__/join-proposals.test.ts +101 -0
  226. package/src/modules/groups/__tests__/join-requests.service.test.ts +175 -0
  227. package/src/modules/groups/group-provision.service.ts +328 -0
  228. package/src/modules/groups/groups.contract.ts +107 -0
  229. package/src/modules/groups/groups.routes.ts +433 -0
  230. package/src/modules/groups/groups.service.ts +193 -0
  231. package/src/modules/groups/index.ts +17 -0
  232. package/src/modules/groups/join-proposals.ts +114 -0
  233. package/src/modules/groups/join-requests.service.ts +185 -0
  234. package/src/modules/secrets-vault/__tests__/oauth-return-to.route.test.ts +283 -0
  235. package/src/modules/secrets-vault/secrets-vault.routes.ts +68 -12
  236. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +256 -0
  237. package/src/modules/settings/__tests__/setup.routes.test.ts +201 -0
  238. package/src/modules/settings/deployment-settings.service.ts +419 -0
  239. package/src/modules/settings/setup.routes.ts +317 -0
  240. package/src/modules/skills/__tests__/pending-skills.service.test.ts +240 -0
  241. package/src/modules/skills/__tests__/skills.service.test.ts +13 -13
  242. package/src/modules/skills/index.ts +3 -0
  243. package/src/modules/skills/pending-skills.service.ts +168 -0
  244. package/src/modules/skills/skills.contract.ts +34 -3
  245. package/src/modules/skills/skills.routes.ts +23 -2
  246. package/src/modules/skills/skills.service.ts +24 -16
  247. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +262 -0
  248. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +2 -2
  249. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +61 -23
  250. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +156 -156
  251. package/src/modules/tool-manuals/index.ts +2 -0
  252. package/src/modules/tool-manuals/tool-manuals.contract.ts +50 -2
  253. package/src/modules/tool-manuals/tool-manuals.routes.ts +21 -0
  254. package/src/modules/tool-manuals/tool-manuals.service.ts +86 -17
  255. package/src/modules/tool-manuals/tool-manuals.tools.ts +206 -202
  256. package/src/modules/workflow/__tests__/event-bus.test.ts +35 -0
  257. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +251 -5
  258. package/src/modules/workflow/agent-tools/workflow.tools.ts +4 -2
  259. package/src/modules/workflow/event-bus.ts +25 -0
  260. package/src/modules/workflow/file-lock.service.ts +17 -0
  261. package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +9 -49
  262. package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +101 -0
  263. package/src/modules/workflow/git/__tests__/git.service.commitFile.test.ts +219 -211
  264. package/src/modules/workflow/git/__tests__/git.service.fileContentsAtCommit.test.ts +132 -0
  265. package/src/modules/workflow/git/__tests__/git.service.logForFile.test.ts +4 -2
  266. package/src/modules/workflow/git/__tests__/git.service.postMergePull.test.ts +275 -272
  267. package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +5 -0
  268. package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +44 -0
  269. package/src/modules/workflow/git/branch-name.ts +5 -1
  270. package/src/modules/workflow/git/git.service.ts +159 -62
  271. package/src/modules/workflow/git/pull-request.service.ts +22 -11
  272. package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +4 -1
  273. package/src/modules/workflow/review-workflow/review-workflow.service.ts +12 -3
  274. package/src/modules/workflow/workflow.routes.ts +91 -13
  275. package/src/modules/workflow/workflow.service.ts +353 -13
  276. package/src/modules/workspace/__tests__/kb-seed.service.test.ts +255 -24
  277. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +236 -233
  278. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +179 -176
  279. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +320 -303
  280. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +337 -276
  281. package/src/modules/workspace/__tests__/workspace.tools.test.ts +14 -6
  282. package/src/modules/workspace/bevel-ignore.ts +66 -62
  283. package/src/modules/workspace/kb-seed.interface.ts +1 -1
  284. package/src/modules/workspace/kb-seed.service.ts +247 -53
  285. package/src/modules/workspace/workspace.routes.ts +54 -18
  286. package/src/modules/workspace/workspace.service.ts +45 -7
  287. package/src/modules/workspace/workspace.tools.ts +922 -917
  288. package/src/shared/__tests__/frontmatter-id.test.ts +53 -0
  289. package/src/shared/__tests__/join-request.test.ts +96 -0
  290. package/src/shared/__tests__/kb-layout.group.test.ts +45 -0
  291. package/src/shared/kb-layout.test.ts +2 -2
  292. package/src/test-setup.ts +18 -0
  293. package/kb-template/Agents/README.md +0 -38
  294. package/kb-template/CLAUDE.md +0 -419
  295. package/kb-template/Data/.gitkeep +0 -0
  296. package/kb-template/Data/README.md +0 -39
  297. package/kb-template/KnowledgeBase/README.md +0 -37
  298. package/kb-template/Pipelines/.gitkeep +0 -0
  299. package/kb-template/Pipelines/README.md +0 -38
  300. package/kb-template/Skills/.gitkeep +0 -0
  301. package/kb-template/Skills/README.md +0 -32
  302. package/kb-template/Tools/.gitkeep +0 -0
  303. package/kb-template/Tools/README.md +0 -42
  304. /package/kb-template/{Agents → Groups}/.gitkeep +0 -0
@@ -1,176 +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 { createWorkspaceRoutes } from '../workspace.routes.js';
13
- import type { ICreatorAccess } from '../../access/creator-access.js';
14
- import type { WorkspaceService } from '../workspace.service.js';
15
-
16
- const stubCreatorAccess: ICreatorAccess = {
17
- planForCreate: async () => null,
18
- grantInExtractedFile: async () => null,
19
- noteAccessFileWritten: () => {},
20
- };
21
-
22
- /**
23
- * Contract test for DELETE /workspace/:id/file on a *directory* target —
24
- * the recursive folder-delete branch.
25
- *
26
- * BEVA-132: deleting a folder that contains sub-folders left the (now-empty)
27
- * sub-directory shells on disk, so the explorer (which lists on-disk
28
- * directories, not just tracked files) kept showing the folder and it looked
29
- * undeletable. The route must sweep the whole empty subtree, not just the top
30
- * directory.
31
- */
32
-
33
- const USER_ID = 'user-1';
34
- const USER = { id: USER_ID, email: 'alice@example.com', name: 'Alice' };
35
- const WORKSPACE_ID = 'feature-branch';
36
-
37
- interface Harness {
38
- server: Server;
39
- baseUrl: string;
40
- workspaceDir: string;
41
- }
42
-
43
- async function makeHarness(): Promise<Harness> {
44
- const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'beva132-'));
45
-
46
- const accessControl = {
47
- canWrite: vi.fn(),
48
- canWriteBatch: vi.fn(),
49
- canDownload: vi.fn(),
50
- eligibleWriters: vi.fn(),
51
- eligibleWriterEmails: vi.fn(),
52
- invalidate: vi.fn(),
53
- findEmailByHash: vi.fn(),
54
- canWriteAtRef: vi.fn(),
55
- canWriteBatchAtRef: vi.fn(),
56
- eligibleWritersAtRef: vi.fn(),
57
- eligibleWritersForPathsAtRef: vi.fn(),
58
- } as unknown as IAccessControl;
59
-
60
- // deleteFile just removes the file from disk — the lock/commit machinery is
61
- // mocked out, so the on-disk effect of a per-file delete is all that the
62
- // recursive-cleanup logic under test reacts to.
63
- const workspaceServiceMock: Partial<WorkspaceService> = {
64
- getWorkspacePath: vi.fn(async () => workspaceDir),
65
- deleteFile: vi.fn(async (_id: string, relPath: string) => {
66
- await fs.rm(path.resolve(workspaceDir, relPath));
67
- }),
68
- };
69
- const workspaceService = workspaceServiceMock as WorkspaceService;
70
-
71
- const workflowServiceMock: Partial<IWorkflowService> = {
72
- getLock: vi.fn(async () => null),
73
- acquireLock: vi.fn(async () => ({ acquired: true, lock: {} as never })),
74
- releaseLock: vi.fn(async () => undefined as never),
75
- releaseLockNoCommit: vi.fn(async () => undefined as never),
76
- };
77
- const workflowService = workflowServiceMock as unknown as IWorkflowService;
78
-
79
- const authServiceMock: Partial<AuthService> = {
80
- getUserById: vi.fn(async () => USER),
81
- };
82
- const authService = authServiceMock as AuthService;
83
-
84
- const eventBus = { emit: vi.fn() } as unknown as WorkflowEventBus;
85
-
86
- const app = express();
87
- app.use(express.json());
88
- app.use('/api', (req, _res, next) => {
89
- (req as any).userId = USER_ID;
90
- next();
91
- });
92
- app.use('/api', createWorkspaceRoutes(
93
- workspaceService,
94
- authService,
95
- workflowService,
96
- eventBus,
97
- accessControl,
98
- 'knowledge-base',
99
- stubCreatorAccess,
100
- ));
101
-
102
- const server = await new Promise<Server>((resolve) => {
103
- const s = app.listen(0, () => resolve(s));
104
- });
105
- const addr = server.address();
106
- if (addr === null || typeof addr === 'string') {
107
- throw new Error(`Unexpected server.address() shape: ${JSON.stringify(addr)}`);
108
- }
109
- const port = (addr as AddressInfo).port;
110
- return { server, baseUrl: `http://127.0.0.1:${port}`, workspaceDir };
111
- }
112
-
113
- async function closeServer(server: Server): Promise<void> {
114
- await new Promise<void>((resolve, reject) =>
115
- server.close((err) => (err ? reject(err) : resolve())),
116
- );
117
- }
118
-
119
- async function exists(p: string): Promise<boolean> {
120
- try {
121
- await fs.stat(p);
122
- return true;
123
- } catch {
124
- return false;
125
- }
126
- }
127
-
128
- describe('DELETE /workspace/:id/file — recursive folder delete (BEVA-132)', () => {
129
- let h: Harness | null = null;
130
- afterEach(async () => {
131
- if (h) {
132
- await closeServer(h.server);
133
- await fs.rm(h.workspaceDir, { recursive: true, force: true });
134
- }
135
- h = null;
136
- });
137
-
138
- it('removes a folder whose children are sub-folders (nested files) off disk', async () => {
139
- h = await makeHarness();
140
- // parent/
141
- // sub/a.md
142
- // sub/deep/b.md
143
- // other/c.md
144
- await fs.mkdir(path.join(h.workspaceDir, 'parent/sub/deep'), { recursive: true });
145
- await fs.mkdir(path.join(h.workspaceDir, 'parent/other'), { recursive: true });
146
- await fs.writeFile(path.join(h.workspaceDir, 'parent/sub/a.md'), 'a');
147
- await fs.writeFile(path.join(h.workspaceDir, 'parent/sub/deep/b.md'), 'b');
148
- await fs.writeFile(path.join(h.workspaceDir, 'parent/other/c.md'), 'c');
149
-
150
- const res = await fetch(
151
- `${h.baseUrl}/api/workspace/${WORKSPACE_ID}/file?path=${encodeURIComponent('parent')}`,
152
- { method: 'DELETE' },
153
- );
154
- expect(res.status).toBe(200);
155
- const body = (await res.json()) as { status: string; count: number };
156
- expect(body.status).toBe('deleted');
157
- expect(body.count).toBe(3);
158
- // The whole subtree — including the empty sub-folder shells — is gone.
159
- expect(await exists(path.join(h.workspaceDir, 'parent'))).toBe(false);
160
- });
161
-
162
- it('removes a folder that contains only empty sub-folders (no tracked files)', async () => {
163
- h = await makeHarness();
164
- await fs.mkdir(path.join(h.workspaceDir, 'parent/emptyA/nested'), { recursive: true });
165
- await fs.mkdir(path.join(h.workspaceDir, 'parent/emptyB'), { recursive: true });
166
-
167
- const res = await fetch(
168
- `${h.baseUrl}/api/workspace/${WORKSPACE_ID}/file?path=${encodeURIComponent('parent')}`,
169
- { method: 'DELETE' },
170
- );
171
- expect(res.status).toBe(200);
172
- const body = (await res.json()) as { status: string; count: number };
173
- expect(body.count).toBe(0);
174
- expect(await exists(path.join(h.workspaceDir, 'parent'))).toBe(false);
175
- });
176
- });
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
+ });