@bevel-software/platform-core-backend 0.11.1 → 0.11.2

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 (312) hide show
  1. package/dist/core/create-core-services.d.ts +1 -1
  2. package/dist/core/create-core-services.d.ts.map +1 -1
  3. package/dist/core/create-core-services.js +2 -2
  4. package/dist/core/create-core-services.js.map +1 -1
  5. package/dist/modules/access/access-control.service.d.ts +1 -253
  6. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  7. package/dist/modules/access/access-control.service.js +3 -632
  8. package/dist/modules/access/access-control.service.js.map +1 -1
  9. package/dist/modules/access/access-declarations.d.ts +2 -2
  10. package/dist/modules/access/access-declarations.d.ts.map +1 -1
  11. package/dist/modules/access/access-declarations.js +2 -2
  12. package/dist/modules/access/access-declarations.js.map +1 -1
  13. package/dist/modules/access/access-mutation.service.d.ts +3 -3
  14. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  15. package/dist/modules/access/access-mutation.service.js +3 -3
  16. package/dist/modules/access/access-mutation.service.js.map +1 -1
  17. package/dist/modules/access/access.routes.js +4 -4
  18. package/dist/modules/access/access.routes.js.map +1 -1
  19. package/dist/modules/access/admin-locked-commit.d.ts +2 -2
  20. package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
  21. package/dist/modules/access/admin-locked-commit.js +2 -2
  22. package/dist/modules/access/admin-locked-commit.js.map +1 -1
  23. package/dist/modules/access/admin-route-helpers.js +1 -1
  24. package/dist/modules/access/admin-route-helpers.js.map +1 -1
  25. package/dist/modules/access/capability-registry.js +1 -1
  26. package/dist/modules/access/capability-registry.js.map +1 -1
  27. package/dist/modules/access/creator-access.d.ts +1 -45
  28. package/dist/modules/access/creator-access.d.ts.map +1 -1
  29. package/dist/modules/access/creator-access.js +4 -24
  30. package/dist/modules/access/creator-access.js.map +1 -1
  31. package/dist/modules/access/groups-admin.routes.js +1 -1
  32. package/dist/modules/access/groups-admin.routes.js.map +1 -1
  33. package/dist/modules/access/groups-admin.service.d.ts +1 -1
  34. package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
  35. package/dist/modules/access/groups-admin.service.js +6 -5
  36. package/dist/modules/access/groups-admin.service.js.map +1 -1
  37. package/dist/modules/access/groups-edit.js +2 -2
  38. package/dist/modules/access/groups-edit.js.map +1 -1
  39. package/dist/modules/access/reference-scan.js +1 -1
  40. package/dist/modules/access/reference-scan.js.map +1 -1
  41. package/dist/modules/access/roles-admin.service.d.ts +1 -1
  42. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  43. package/dist/modules/access/roles-admin.service.js +8 -7
  44. package/dist/modules/access/roles-admin.service.js.map +1 -1
  45. package/dist/modules/access/roles-edit.js +1 -1
  46. package/dist/modules/access/roles-edit.js.map +1 -1
  47. package/dist/modules/access/synced-groups-committer.js +4 -4
  48. package/dist/modules/access/synced-groups-committer.js.map +1 -1
  49. package/dist/modules/access/synced-groups-writer.js +2 -2
  50. package/dist/modules/access/synced-groups-writer.js.map +1 -1
  51. package/dist/modules/access-model/access-errors.d.ts +34 -0
  52. package/dist/modules/access-model/access-errors.d.ts.map +1 -0
  53. package/dist/modules/access-model/access-errors.js +40 -0
  54. package/dist/modules/access-model/access-errors.js.map +1 -0
  55. package/dist/modules/access-model/access-grammar.d.ts +266 -0
  56. package/dist/modules/access-model/access-grammar.d.ts.map +1 -0
  57. package/dist/modules/access-model/access-grammar.js +644 -0
  58. package/dist/modules/access-model/access-grammar.js.map +1 -0
  59. package/dist/modules/access-model/access-splice.d.ts +140 -0
  60. package/dist/modules/access-model/access-splice.d.ts.map +1 -0
  61. package/dist/modules/access-model/access-splice.js +389 -0
  62. package/dist/modules/access-model/access-splice.js.map +1 -0
  63. package/dist/modules/access-model/creator.d.ts +54 -0
  64. package/dist/modules/access-model/creator.d.ts.map +1 -0
  65. package/dist/modules/access-model/creator.js +30 -0
  66. package/dist/modules/access-model/creator.js.map +1 -0
  67. package/dist/modules/access-model/group-files.d.ts +83 -0
  68. package/dist/modules/access-model/group-files.d.ts.map +1 -0
  69. package/dist/modules/access-model/group-files.js +167 -0
  70. package/dist/modules/access-model/group-files.js.map +1 -0
  71. package/dist/modules/access-model/kb-read-filter.d.ts +41 -0
  72. package/dist/modules/access-model/kb-read-filter.d.ts.map +1 -0
  73. package/dist/modules/access-model/kb-read-filter.js +60 -0
  74. package/dist/modules/access-model/kb-read-filter.js.map +1 -0
  75. package/dist/modules/access-model/render-roles-yaml.d.ts +22 -0
  76. package/dist/modules/access-model/render-roles-yaml.d.ts.map +1 -0
  77. package/dist/modules/access-model/render-roles-yaml.js +56 -0
  78. package/dist/modules/access-model/render-roles-yaml.js.map +1 -0
  79. package/dist/modules/access-model/roles-yaml-guard.d.ts +56 -0
  80. package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -0
  81. package/dist/modules/access-model/roles-yaml-guard.js +79 -0
  82. package/dist/modules/access-model/roles-yaml-guard.js.map +1 -0
  83. package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
  84. package/dist/modules/admin/admin-access.service.js +1 -1
  85. package/dist/modules/admin/admin-access.service.js.map +1 -1
  86. package/dist/modules/diff/diff.routes.js +4 -4
  87. package/dist/modules/diff/diff.routes.js.map +1 -1
  88. package/dist/modules/diff/diff.service.d.ts +1 -1
  89. package/dist/modules/diff/diff.service.d.ts.map +1 -1
  90. package/dist/modules/kb-fs/branch-name.d.ts +10 -0
  91. package/dist/modules/kb-fs/branch-name.d.ts.map +1 -0
  92. package/dist/modules/kb-fs/branch-name.js +76 -0
  93. package/dist/modules/kb-fs/branch-name.js.map +1 -0
  94. package/dist/modules/kb-fs/clone-config.d.ts +61 -0
  95. package/dist/modules/kb-fs/clone-config.d.ts.map +1 -0
  96. package/dist/modules/kb-fs/clone-config.js +69 -0
  97. package/dist/modules/kb-fs/clone-config.js.map +1 -0
  98. package/dist/modules/kb-fs/file-change-notifier.d.ts +38 -0
  99. package/dist/modules/kb-fs/file-change-notifier.d.ts.map +1 -0
  100. package/dist/modules/kb-fs/file-change-notifier.js +22 -0
  101. package/dist/modules/kb-fs/file-change-notifier.js.map +1 -0
  102. package/dist/modules/kb-fs/locking-filesystem.d.ts +137 -0
  103. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -0
  104. package/dist/modules/kb-fs/locking-filesystem.js +553 -0
  105. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -0
  106. package/dist/modules/kb-fs/mutex.d.ts +11 -0
  107. package/dist/modules/kb-fs/mutex.d.ts.map +1 -0
  108. package/dist/modules/kb-fs/mutex.js +23 -0
  109. package/dist/modules/kb-fs/mutex.js.map +1 -0
  110. package/dist/modules/kb-fs/read-only-filesystem.d.ts +24 -0
  111. package/dist/modules/kb-fs/read-only-filesystem.d.ts.map +1 -0
  112. package/dist/modules/kb-fs/read-only-filesystem.js +39 -0
  113. package/dist/modules/kb-fs/read-only-filesystem.js.map +1 -0
  114. package/dist/modules/plugins/join-proposals.d.ts +1 -1
  115. package/dist/modules/plugins/join-proposals.d.ts.map +1 -1
  116. package/dist/modules/plugins/join-proposals.js +1 -1
  117. package/dist/modules/plugins/join-proposals.js.map +1 -1
  118. package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
  119. package/dist/modules/plugins/join-requests.service.js +1 -1
  120. package/dist/modules/plugins/join-requests.service.js.map +1 -1
  121. package/dist/modules/plugins/plugin-provision.service.js +3 -3
  122. package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
  123. package/dist/modules/plugins/plugins.routes.js +2 -2
  124. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  125. package/dist/modules/plugins/plugins.service.js +1 -1
  126. package/dist/modules/plugins/plugins.service.js.map +1 -1
  127. package/dist/modules/secrets-vault/secrets-vault.routes.js +1 -1
  128. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  129. package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
  130. package/dist/modules/skills/pending-skills.service.js +1 -1
  131. package/dist/modules/skills/pending-skills.service.js.map +1 -1
  132. package/dist/modules/skills/skills.service.js +1 -1
  133. package/dist/modules/skills/skills.service.js.map +1 -1
  134. package/dist/modules/tool-helpers/tool-context.d.ts +2 -2
  135. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
  136. package/dist/modules/tool-helpers/tool-context.js +4 -4
  137. package/dist/modules/tool-helpers/tool-context.js.map +1 -1
  138. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +1 -1
  139. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -1
  140. package/dist/modules/tool-manuals/mcp-server-edit.service.js +1 -1
  141. package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
  142. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +1 -1
  143. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  144. package/dist/modules/tool-manuals/tool-manuals.routes.js +1 -1
  145. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  146. package/dist/modules/tool-manuals/tool-manuals.service.js +1 -1
  147. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  148. package/dist/modules/tool-manuals/tool-manuals.tools.js +1 -1
  149. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  150. package/dist/modules/workflow/agent-tools/workflow.tools.js +1 -1
  151. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  152. package/dist/modules/workflow/file-lock.service.js +1 -1
  153. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  154. package/dist/modules/workflow/git/git.service.d.ts +2 -2
  155. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  156. package/dist/modules/workflow/git/git.service.js +5 -5
  157. package/dist/modules/workflow/git/git.service.js.map +1 -1
  158. package/dist/modules/workflow/git/pull-request.service.js +1 -1
  159. package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
  160. package/dist/modules/workflow/review-workflow/review-workflow.service.js +2 -2
  161. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  162. package/dist/modules/workflow/session-ontology.service.js +1 -1
  163. package/dist/modules/workflow/session-ontology.service.js.map +1 -1
  164. package/dist/modules/workflow/workflow.routes.js +2 -2
  165. package/dist/modules/workflow/workflow.routes.js.map +1 -1
  166. package/dist/modules/workflow/workflow.service.d.ts +1 -1
  167. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  168. package/dist/modules/workflow/workflow.service.js +4 -4
  169. package/dist/modules/workflow/workflow.service.js.map +1 -1
  170. package/dist/modules/workspace/startup/kb-startup-runner.js +1 -1
  171. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
  172. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +1 -1
  173. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -1
  174. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +1 -1
  175. package/dist/modules/workspace/startup/steps/roles-yaml.step.js +2 -2
  176. package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -1
  177. package/dist/modules/workspace/startup/steps/seed-tree.js +1 -1
  178. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  179. package/dist/modules/workspace/workspace.routes.d.ts +1 -1
  180. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  181. package/dist/modules/workspace/workspace.routes.js +5 -4
  182. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  183. package/dist/modules/workspace/workspace.service.d.ts +2 -9
  184. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  185. package/dist/modules/workspace/workspace.service.js +14 -15
  186. package/dist/modules/workspace/workspace.service.js.map +1 -1
  187. package/dist/modules/workspace/workspace.tools.js +3 -3
  188. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  189. package/dist/shared/domain-errors.d.ts +202 -0
  190. package/dist/shared/domain-errors.d.ts.map +1 -0
  191. package/dist/shared/domain-errors.js +303 -0
  192. package/dist/shared/domain-errors.js.map +1 -0
  193. package/dist/shared/workspace-id.d.ts +27 -0
  194. package/dist/shared/workspace-id.d.ts.map +1 -0
  195. package/dist/shared/workspace-id.js +36 -0
  196. package/dist/shared/workspace-id.js.map +1 -0
  197. package/package.json +3 -3
  198. package/src/core/create-core-services.ts +2 -2
  199. package/src/modules/access/__tests__/access-control.service.test.ts +1 -1
  200. package/src/modules/access/__tests__/access-groups.test.ts +6 -5
  201. package/src/modules/access/__tests__/access-md-format.test.ts +3 -3
  202. package/src/modules/access/__tests__/access-mutation.service.test.ts +1 -1
  203. package/src/modules/access/__tests__/admin-locked-commit.test.ts +1 -1
  204. package/src/modules/access/__tests__/admin-route-helpers.test.ts +1 -1
  205. package/src/modules/access/__tests__/roles-admin.service.test.ts +2 -2
  206. package/src/modules/access/__tests__/roles-edit.test.ts +1 -1
  207. package/src/modules/access/__tests__/synced-groups-committer.test.ts +1 -1
  208. package/src/modules/access/__tests__/synced-groups-writer.test.ts +1 -1
  209. package/src/modules/access/access-control.service.ts +23 -768
  210. package/src/modules/access/access-declarations.ts +2 -2
  211. package/src/modules/access/access-mutation.service.ts +3 -3
  212. package/src/modules/access/access.routes.ts +5 -5
  213. package/src/modules/access/admin-locked-commit.ts +2 -2
  214. package/src/modules/access/admin-route-helpers.ts +1 -1
  215. package/src/modules/access/capability-registry.ts +1 -1
  216. package/src/modules/access/creator-access.ts +9 -72
  217. package/src/modules/access/groups-admin.routes.ts +1 -1
  218. package/src/modules/access/groups-admin.service.ts +6 -7
  219. package/src/modules/access/groups-edit.ts +2 -2
  220. package/src/modules/access/reference-scan.ts +1 -1
  221. package/src/modules/access/roles-admin.service.ts +8 -8
  222. package/src/modules/access/roles-edit.ts +1 -1
  223. package/src/modules/access/synced-groups-committer.ts +4 -4
  224. package/src/modules/access/synced-groups-writer.ts +2 -2
  225. package/src/modules/access-model/__tests__/access-grammar.test.ts +31 -0
  226. package/src/modules/{access → access-model}/__tests__/access-splice.test.ts +268 -268
  227. package/src/modules/{access → access-model}/access-errors.ts +1 -1
  228. package/src/modules/access-model/access-grammar.ts +778 -0
  229. package/src/modules/{access → access-model}/access-splice.ts +1 -1
  230. package/src/modules/access-model/creator.ts +79 -0
  231. package/src/modules/{access → access-model}/group-files.ts +1 -1
  232. package/src/modules/{access → access-model}/render-roles-yaml.ts +1 -1
  233. package/src/modules/{access → access-model}/roles-yaml-guard.ts +2 -2
  234. package/src/modules/admin/admin-access.service.ts +2 -1
  235. package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +2 -2
  236. package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +3 -2
  237. package/src/modules/diff/__tests__/diff.service.test.ts +3 -2
  238. package/src/modules/diff/diff.routes.ts +4 -4
  239. package/src/modules/diff/diff.service.ts +1 -1
  240. package/src/modules/{workflow/git → kb-fs}/__tests__/branch-name.test.ts +1 -1
  241. package/src/modules/{workflow → kb-fs}/__tests__/locking-filesystem.test.ts +1 -1
  242. package/src/modules/{workflow/git → kb-fs}/branch-name.ts +1 -1
  243. package/src/modules/{workflow → kb-fs}/locking-filesystem.ts +2 -2
  244. package/src/modules/plugins/__tests__/join-requests.service.test.ts +2 -4
  245. package/src/modules/plugins/__tests__/plugin-index.service.test.ts +1 -1
  246. package/src/modules/plugins/__tests__/plugins.routes.test.ts +1 -1
  247. package/src/modules/plugins/join-proposals.ts +1 -1
  248. package/src/modules/plugins/join-requests.service.ts +2 -1
  249. package/src/modules/plugins/plugin-provision.service.ts +3 -3
  250. package/src/modules/plugins/plugins.routes.ts +2 -2
  251. package/src/modules/plugins/plugins.service.ts +1 -1
  252. package/src/modules/secrets-vault/secrets-vault.routes.ts +1 -1
  253. package/src/modules/skills/__tests__/skills.service.test.ts +1 -1
  254. package/src/modules/skills/pending-skills.service.ts +2 -1
  255. package/src/modules/skills/skills.service.ts +1 -1
  256. package/src/modules/tool-helpers/tool-context.ts +6 -5
  257. package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +1 -1
  258. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +1 -1
  259. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +1 -1
  260. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +1 -1
  261. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +1 -1
  262. package/src/modules/tool-manuals/mcp-server-edit.service.ts +2 -1
  263. package/src/modules/tool-manuals/tool-manuals.routes.ts +2 -1
  264. package/src/modules/tool-manuals/tool-manuals.service.ts +1 -1
  265. package/src/modules/tool-manuals/tool-manuals.tools.ts +1 -1
  266. package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +1 -1
  267. package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +1 -1
  268. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
  269. package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +1 -1
  270. package/src/modules/workflow/agent-tools/workflow.tools.ts +1 -1
  271. package/src/modules/workflow/file-lock.service.ts +1 -1
  272. package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +1 -1
  273. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +1 -1
  274. package/src/modules/workflow/git/__tests__/git.service.diffFileAtCommit.test.ts +1 -1
  275. package/src/modules/workflow/git/__tests__/git.service.diffFileBetweenBranches.test.ts +1 -1
  276. package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +1 -1
  277. package/src/modules/workflow/git/git.service.ts +5 -5
  278. package/src/modules/workflow/git/pull-request.service.ts +1 -1
  279. package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
  280. package/src/modules/workflow/review-workflow/review-workflow.service.ts +2 -2
  281. package/src/modules/workflow/session-ontology.service.ts +1 -1
  282. package/src/modules/workflow/workflow.routes.ts +2 -2
  283. package/src/modules/workflow/workflow.service.ts +5 -5
  284. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +1 -1
  285. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +1 -1
  286. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +2 -2
  287. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +1 -1
  288. package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +2 -5
  289. package/src/modules/workspace/__tests__/workspace.service.test.ts +13 -1
  290. package/src/modules/workspace/__tests__/workspace.tools.test.ts +1 -1
  291. package/src/modules/workspace/startup/kb-startup-runner.ts +1 -1
  292. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +1 -1
  293. package/src/modules/workspace/startup/steps/groups-to-plugins.step.ts +1 -1
  294. package/src/modules/workspace/startup/steps/roles-yaml.step.ts +2 -2
  295. package/src/modules/workspace/startup/steps/seed-tree.ts +1 -1
  296. package/src/modules/workspace/workspace.routes.ts +6 -5
  297. package/src/modules/workspace/workspace.service.ts +14 -18
  298. package/src/modules/workspace/workspace.tools.ts +3 -3
  299. package/src/shared/__tests__/join-request.test.ts +1 -1
  300. package/src/shared/__tests__/workspace-id.test.ts +17 -0
  301. package/src/{modules/workflow/workflow.errors.ts → shared/domain-errors.ts} +6 -1
  302. package/src/shared/workspace-id.ts +36 -0
  303. /package/src/modules/{access → access-model}/__tests__/kb-read-filter.test.ts +0 -0
  304. /package/src/modules/{access → access-model}/__tests__/roles-yaml-guard.test.ts +0 -0
  305. /package/src/modules/{access → access-model}/kb-read-filter.ts +0 -0
  306. /package/src/modules/{workflow/git → kb-fs}/__tests__/clone-config.test.ts +0 -0
  307. /package/src/modules/{workflow → kb-fs}/__tests__/file-change-notifier.test.ts +0 -0
  308. /package/src/modules/{workflow/git → kb-fs}/__tests__/mutex.test.ts +0 -0
  309. /package/src/modules/{workflow/git → kb-fs}/clone-config.ts +0 -0
  310. /package/src/modules/{workflow → kb-fs}/file-change-notifier.ts +0 -0
  311. /package/src/modules/{workflow/git → kb-fs}/mutex.ts +0 -0
  312. /package/src/modules/{workflow → kb-fs}/read-only-filesystem.ts +0 -0
@@ -15,9 +15,9 @@ import type {
15
15
  import type { WorkspaceService } from '../../workspace/workspace.service.js';
16
16
  import type { WorkflowHooks, CommitValidationContext } from '../workflow-hooks.js';
17
17
  import type { IAccessControl } from '../../access/access-control.interface.js';
18
- import { AccessDeniedError } from '../../access/access-errors.js';
19
- import { WorkspaceMutex } from './mutex.js';
20
- import { cloneTrackingConfigArgs, SAFE_IMPLICIT_FETCH_ARGS } from './clone-config.js';
18
+ import { AccessDeniedError } from '../../access-model/access-errors.js';
19
+ import { WorkspaceMutex } from '../../kb-fs/mutex.js';
20
+ import { cloneTrackingConfigArgs, SAFE_IMPLICIT_FETCH_ARGS } from '../../kb-fs/clone-config.js';
21
21
  import {
22
22
  assertValidBranchName,
23
23
  assertValidRelativePath,
@@ -25,13 +25,13 @@ import {
25
25
  isOwnSuggestionsBranch,
26
26
  isProtectedBranch,
27
27
  PROTECTED_BRANCHES,
28
- } from './branch-name.js';
28
+ } from '../../kb-fs/branch-name.js';
29
29
  import {
30
30
  BranchAuthorshipError,
31
31
  WorkflowValidationError,
32
32
  ProtectedBranchError,
33
33
  PullRebaseConflictError,
34
- } from '../workflow.errors.js';
34
+ } from '../../../shared/domain-errors.js';
35
35
 
36
36
  const execFileAsync = promisify(execFile);
37
37
 
@@ -13,7 +13,7 @@ import { changeRequests } from '../../database/schema.js';
13
13
  import type { WorkspaceService } from '../../workspace/workspace.service.js';
14
14
  import type { IAccessControl } from '../../access/access-control.interface.js';
15
15
  import type { GitService } from './git.service.js';
16
- import { WorkflowValidationError } from '../workflow.errors.js';
16
+ import { WorkflowValidationError } from '../../../shared/domain-errors.js';
17
17
  import { hashEmail } from '../../../shared/hash-email.js';
18
18
 
19
19
  const LIST_PR_CACHE_TTL_MS = 30_000;
@@ -6,7 +6,7 @@ import type { WorkspaceService } from '../../../workspace/workspace.service.js';
6
6
  import type { GitService } from '../../git/git.service.js';
7
7
  import type { IAccessControl } from '../../../access/access-control.interface.js';
8
8
  import { hashEmail } from '../../../../shared/hash-email.js';
9
- import { WorkflowValidationError } from '../../workflow.errors.js';
9
+ import { WorkflowValidationError } from '../../../../shared/domain-errors.js';
10
10
 
11
11
  // Cancel is now a DB state flip (no `gh`): `update(change_requests) … WHERE
12
12
  // number = ? AND state = 'open'`. This spy stands in for the update's
@@ -13,13 +13,13 @@ import type {
13
13
  import type { Database } from '../../database/connection.js';
14
14
  import { changeRequests, prComments, prFileApprovals, prMergeLog } from '../../database/schema.js';
15
15
  import type { IAccessControl } from '../../access/access-control.interface.js';
16
- import { isAccessMdPath } from '../../access/access-control.service.js';
16
+ import { isAccessMdPath } from '../../access-model/access-grammar.js';
17
17
  import type { GitService } from '../git/git.service.js';
18
18
  import {
19
19
  ChangeRequestConflictsError,
20
20
  WorkflowDomainError,
21
21
  WorkflowValidationError,
22
- } from '../workflow.errors.js';
22
+ } from '../../../shared/domain-errors.js';
23
23
  import type { WorkspaceService } from '../../workspace/workspace.service.js';
24
24
  import { hashEmail } from '../../../shared/hash-email.js';
25
25
  import type {
@@ -37,7 +37,7 @@ import { ontologyOf } from '../../shared/kb-layout.js';
37
37
  import type { Database } from '../database/connection.js';
38
38
  import { sessionOntologyTouches } from '../database/schema.js';
39
39
  import { recordTouch, decideWrite, type WriteDecision } from './session-ontology.policy.js';
40
- import { WorkspaceMutex } from './git/mutex.js';
40
+ import { WorkspaceMutex } from '../kb-fs/mutex.js';
41
41
 
42
42
  export type OperationDecision =
43
43
  | { allow: true; touched: string[] }
@@ -27,9 +27,9 @@ import type {
27
27
  } from '@bevel-software/platform-shared';
28
28
  import type { AuthService } from '../auth/auth.service.js';
29
29
  import type { WorkspaceService } from '../workspace/workspace.service.js';
30
- import { branchForWorkspaceId } from '../workspace/workspace.service.js';
30
+ import { branchForWorkspaceId } from '../../shared/workspace-id.js';
31
31
  import type { WorkflowEventBus } from './event-bus.js';
32
- import { WorkflowDomainError } from './workflow.errors.js';
32
+ import { WorkflowDomainError } from '../../shared/domain-errors.js';
33
33
  import '../auth/auth.middleware.js'; // Express Request augmentation
34
34
 
35
35
  function toHttpError(
@@ -49,14 +49,14 @@ import type { GitService } from './git/git.service.js';
49
49
  import type { PullRequestService } from './git/pull-request.service.js';
50
50
  import type { IReviewWorkflowService } from './review-workflow/review-workflow.interface.js';
51
51
  import type { WorkspaceService } from '../workspace/workspace.service.js';
52
- import { workspaceIdForBranch } from '../workspace/workspace.service.js';
52
+ import { workspaceIdForBranch } from '../../shared/workspace-id.js';
53
53
  import type { IAccessControl } from '../access/access-control.interface.js';
54
54
  import { FileLockService } from './file-lock.service.js';
55
55
  import { PendingCommitsService } from './pending-commits.service.js';
56
56
  import type { WorkflowEventBus } from './event-bus.js';
57
- import type { FileChangeNotifier } from './file-change-notifier.js';
57
+ import type { FileChangeNotifier } from '../kb-fs/file-change-notifier.js';
58
58
  import { WorkflowHooks } from './workflow-hooks.js';
59
- import { WorkspaceMutex } from './git/mutex.js';
59
+ import { WorkspaceMutex } from '../kb-fs/mutex.js';
60
60
  import { hashEmail } from '../../shared/hash-email.js';
61
61
  import {
62
62
  ChangeRequestConflictsError,
@@ -66,9 +66,9 @@ import {
66
66
  PushNeedsAgentResolutionError,
67
67
  WorkflowDomainError,
68
68
  WorkflowValidationError,
69
- } from './workflow.errors.js';
69
+ } from '../../shared/domain-errors.js';
70
70
  import { RECOVERY_BOT_EMAIL, RECOVERY_BOT_NAME } from './recovery-bot.js';
71
- import { AccessDeniedError } from '../access/access-errors.js';
71
+ import { AccessDeniedError } from '../access-model/access-errors.js';
72
72
 
73
73
  const execFileAsync = promisify(execFile);
74
74
 
@@ -4,7 +4,7 @@ import express from 'express';
4
4
  import { describe, it, expect, afterEach, vi } from 'vitest';
5
5
  import type { IWorkflowService } from '@bevel-software/platform-shared';
6
6
  import type { IAccessControl } from '../../access/access-control.interface.js';
7
- import type { ICreatorAccess } from '../../access/creator-access.js';
7
+ import type { ICreatorAccess } from '../../access-model/creator.js';
8
8
  import type { WorkflowEventBus } from '../../workflow/event-bus.js';
9
9
  import type { AuthService } from '../../auth/auth.service.js';
10
10
  import type { IAdminAccessService } from '../../admin/admin.interface.js';
@@ -11,7 +11,7 @@ import type { WorkflowEventBus } from '../../workflow/event-bus.js';
11
11
  import type { AuthService } from '../../auth/auth.service.js';
12
12
  import type { IAdminAccessService } from '../../admin/admin.interface.js';
13
13
  import { createWorkspaceRoutes } from '../workspace.routes.js';
14
- import type { ICreatorAccess } from '../../access/creator-access.js';
14
+ import type { ICreatorAccess } from '../../access-model/creator.js';
15
15
  import type { WorkspaceService } from '../workspace.service.js';
16
16
 
17
17
  const stubCreatorAccess: ICreatorAccess = {
@@ -6,10 +6,10 @@ import type { IWorkflowService } from '@bevel-software/platform-shared';
6
6
  import type { IAccessControl } from '../../access/access-control.interface.js';
7
7
  import type { WorkflowEventBus } from '../../workflow/event-bus.js';
8
8
  import type { AuthService } from '../../auth/auth.service.js';
9
- import { AccessConfigError } from '../../access/access-errors.js';
9
+ import { AccessConfigError } from '../../access-model/access-errors.js';
10
10
  import type { IAdminAccessService } from '../../admin/admin.interface.js';
11
11
  import { createWorkspaceRoutes } from '../workspace.routes.js';
12
- import type { ICreatorAccess } from '../../access/creator-access.js';
12
+ import type { ICreatorAccess } from '../../access-model/creator.js';
13
13
  import { FolderTooLargeError, type WorkspaceService } from '../workspace.service.js';
14
14
 
15
15
  const stubCreatorAccess: ICreatorAccess = {
@@ -4,7 +4,7 @@ import express from 'express';
4
4
  import { describe, it, expect, afterEach, vi } from 'vitest';
5
5
  import type { IWorkflowService } from '@bevel-software/platform-shared';
6
6
  import type { IAccessControl } from '../../access/access-control.interface.js';
7
- import type { ICreatorAccess } from '../../access/creator-access.js';
7
+ import type { ICreatorAccess } from '../../access-model/creator.js';
8
8
  import type { IAdminAccessService } from '../../admin/admin.interface.js';
9
9
  import type { WorkflowEventBus } from '../../workflow/event-bus.js';
10
10
  import type { AuthService } from '../../auth/auth.service.js';
@@ -2,11 +2,8 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
2
  import fs from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import os from 'node:os';
5
- import {
6
- WorkspaceService,
7
- workspaceIdForBranch,
8
- type ReadTreeFilter,
9
- } from '../workspace.service.js';
5
+ import { WorkspaceService, type ReadTreeFilter } from '../workspace.service.js';
6
+ import { workspaceIdForBranch } from '../../../shared/workspace-id.js';
10
7
 
11
8
  const KB = 'knowledge-base';
12
9
 
@@ -4,7 +4,8 @@ import { promisify } from 'node:util';
4
4
  import fs from 'node:fs/promises';
5
5
  import path from 'node:path';
6
6
  import os from 'node:os';
7
- import { WorkspaceService, workspaceIdForBranch } from '../workspace.service.js';
7
+ import { WorkspaceService } from '../workspace.service.js';
8
+ import { workspaceIdForBranch } from '../../../shared/workspace-id.js';
8
9
 
9
10
  const execFileAsync = promisify(execFile);
10
11
 
@@ -62,6 +63,17 @@ describe('WorkspaceService — branch-keyed identity', () => {
62
63
  expect(workspaceIdForBranch('alice/feature')).toBe('alice%2Ffeature');
63
64
  });
64
65
 
66
+ it('a malformed workspace id (bad percent-escape) rejects as a 400 BranchNameError, not a 500', async () => {
67
+ const svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base');
68
+ // `%zz` fails decodeURIComponent, falls back to itself, and `%` never
69
+ // passes the branch-name rules — the cold-path bootstrap must surface
70
+ // that as the status-carrying domain error, not a wrapped plain Error.
71
+ await expect(svc.readFile('%zz', 'roles.yaml')).rejects.toMatchObject({
72
+ name: 'BranchNameError',
73
+ status: 400,
74
+ });
75
+ });
76
+
65
77
  it('returns workspace info derived from the branch — no .workspace.json on disk', async () => {
66
78
  const { workspaceId, workspaceDir } = await seedBranchWorkspace(root, 'target-company-state');
67
79
  const svc = new WorkspaceService(root, 'https://github.com/Bevel-Software/knowledge-base.git', 'knowledge-base');
@@ -16,7 +16,7 @@ import { RoutineWritePolicyService } from '../routine-write-policy.js';
16
16
  import { WorkflowHooks } from '../../workflow/workflow-hooks.js';
17
17
  import { SpillStore } from '../spill-store.js';
18
18
  import type { IAccessControl } from '../../access/access-control.interface.js';
19
- import { assertValidBranchName } from '../../workflow/git/branch-name.js';
19
+ import { assertValidBranchName } from '../../kb-fs/branch-name.js';
20
20
 
21
21
  const KB_DIR = 'knowledge-base';
22
22
 
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { isBranchModelConfigured } from '@bevel-software/platform-shared';
4
- import { workspaceIdForBranch } from '../workspace.service.js';
4
+ import { workspaceIdForBranch } from '../../../shared/workspace-id.js';
5
5
  import type { KbBranch, OnServerStart, ServerStartContext } from './on-server-start.js';
6
6
  import { git, lsRemoteHeads, redactSecret, stampIdentity, withTempDir } from './kb-git.js';
7
7
 
@@ -8,7 +8,7 @@ import { KbStartupRunner } from '../../kb-startup-runner.js';
8
8
  import type { OnServerStart, ServerStartContext, StepResult } from '../../on-server-start.js';
9
9
  import { GroupsToPluginsStep } from '../groups-to-plugins.step.js';
10
10
  import { RolesYamlStep } from '../roles-yaml.step.js';
11
- import { renderRolesYaml } from '../../../../access/render-roles-yaml.js';
11
+ import { renderRolesYaml } from '../../../../access-model/render-roles-yaml.js';
12
12
  import { TemplateFilesStep } from '../template-files.step.js';
13
13
  import { buildSeedTree } from '../seed-tree.js';
14
14
  import { defaultKbTemplateDir } from '../../../../../assets.js';
@@ -13,7 +13,7 @@ import {
13
13
  } from '@bevel-software/platform-shared';
14
14
  import type { ToolManualDescriptor } from '../../../tool-manuals/tool-manuals.contract.js';
15
15
  import { normalizeToolManual } from '../../../tool-manuals/tool-manuals.service.js';
16
- import { parseOwnAccessEntries } from '../../../access/access-control.service.js';
16
+ import { parseOwnAccessEntries } from '../../../access-model/access-grammar.js';
17
17
  import { containsVariableReference } from '../../../../shared/variable-refs.js';
18
18
  import { IGNORE_FILENAME } from '../../bevel-ignore.js';
19
19
  import type { KbBranch, OnServerStart, ServerStartContext, StepResult } from '../on-server-start.js';
@@ -1,12 +1,12 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { renderRolesYaml } from '../../../access/render-roles-yaml.js';
3
+ import { renderRolesYaml } from '../../../access-model/render-roles-yaml.js';
4
4
  import type { OnServerStart, ServerStartContext, StepResult } from '../on-server-start.js';
5
5
 
6
6
  /**
7
7
  * Write `roles.yaml` on any protected branch missing it, generated from the
8
8
  * configured seed admins (`ADMIN_EMAIL`) via the shared validated renderer
9
- * (../../../access/render-roles-yaml.ts — a malformed email stops the boot
9
+ * (../../../access-model/render-roles-yaml.ts — a malformed email stops the boot
10
10
  * with a message naming the fix). Never part of the template: a template copy
11
11
  * would seed repos with a stale hard-coded Admin list.
12
12
  *
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { renderRolesYaml } from '../../../access/render-roles-yaml.js';
3
+ import { renderRolesYaml } from '../../../access-model/render-roles-yaml.js';
4
4
  import { TEMPLATE_SOURCE_FALLBACKS, reservedRootDirs, templateSource } from './template-files.step.js';
5
5
 
6
6
  /**
@@ -13,15 +13,16 @@ import {
13
13
  PIPELINES_DIR,
14
14
  PLUGINS_DIR,
15
15
  } from '@bevel-software/platform-shared';
16
- import { branchForWorkspaceId, FolderTooLargeError, type ReadTreeFilter } from './workspace.service.js';
16
+ import { FolderTooLargeError, type ReadTreeFilter } from './workspace.service.js';
17
+ import { branchForWorkspaceId } from '../../shared/workspace-id.js';
17
18
  import type { WorkspaceService } from './workspace.service.js';
18
19
  import type { AuthService } from '../auth/auth.service.js';
19
20
  import type { IAccessControl } from '../access/access-control.interface.js';
20
- import { resolveReadableMap, toKbRelative } from '../access/kb-read-filter.js';
21
- import type { ICreatorAccess } from '../access/creator-access.js';
22
- import { isRolesYamlPath, assertRolesYamlParsable } from '../access/roles-yaml-guard.js';
21
+ import { resolveReadableMap, toKbRelative } from '../access-model/kb-read-filter.js';
22
+ import type { ICreatorAccess } from '../access-model/creator.js';
23
+ import { isRolesYamlPath, assertRolesYamlParsable } from '../access-model/roles-yaml-guard.js';
23
24
  import type { WorkflowEventBus } from '../workflow/event-bus.js';
24
- import { WorkflowDomainError } from '../workflow/workflow.errors.js';
25
+ import { WorkflowDomainError } from '../../shared/domain-errors.js';
25
26
  import '../auth/auth.middleware.js'; // Express Request augmentation
26
27
 
27
28
  const MAX_UPLOAD_BYTES = 50 * 1024 * 1024; // 50 MB
@@ -6,8 +6,10 @@ import AdmZip from 'adm-zip';
6
6
  import type { AuthUser, IWorkspaceService, WorkspaceInfo, FileTreeEntry } from '@bevel-software/platform-shared';
7
7
  import { assertValidRelativePath, validateFilename, DEFAULT_BRANCH } from '@bevel-software/platform-shared';
8
8
  import { BevelIgnoreStack } from './bevel-ignore.js';
9
- import { assertValidBranchName } from '../workflow/git/branch-name.js';
10
- import { cloneTrackingConfigArgs, SAFE_IMPLICIT_FETCH_ARGS } from '../workflow/git/clone-config.js';
9
+ import { workspaceIdForBranch, branchForWorkspaceId } from '../../shared/workspace-id.js';
10
+ import { WorkflowDomainError } from '../../shared/domain-errors.js';
11
+ import { assertValidBranchName } from '../kb-fs/branch-name.js';
12
+ import { cloneTrackingConfigArgs, SAFE_IMPLICIT_FETCH_ARGS } from '../kb-fs/clone-config.js';
11
13
  import type { IDiffService } from '../diff/diff.interface.js';
12
14
 
13
15
  /**
@@ -85,25 +87,12 @@ function redactError(err: unknown): string {
85
87
 
86
88
  const FETCH_CACHE_TTL_MS = 30_000;
87
89
 
88
- /**
89
- * Map a branch name to the directory that contains its workspace clone.
90
- * `encodeURIComponent` handles `/` in branch names (e.g. `alice/foo`) by
91
- * encoding it to `%2F` — single-segment-safe on every supported filesystem.
92
- */
93
- export function workspaceIdForBranch(branch: string): string {
94
- return encodeURIComponent(branch);
95
- }
96
-
97
- export function branchForWorkspaceId(workspaceId: string): string {
98
- return decodeURIComponent(workspaceId);
99
- }
100
-
101
90
  /**
102
91
  * Per-directory read filter for the file tree. Given a batch of
103
92
  * workspace-relative entry paths (e.g. `staging-repo/Product/Knowledge/x.md`),
104
93
  * returns a verdict map keyed by those paths (`path → readable`). Injected by
105
94
  * the route from the access service so `WorkspaceService` stays access-agnostic
106
- * (it gets a function, not the access module). See `modules/access/kb-read-filter.ts`.
95
+ * (it gets a function, not the access module). See `modules/access-model/kb-read-filter.ts`.
107
96
  */
108
97
  export type ReadTreeFilter = (wsRelPaths: string[]) => Promise<Map<string, boolean>>;
109
98
 
@@ -408,7 +397,7 @@ export class WorkspaceService implements IWorkspaceService {
408
397
 
409
398
  /**
410
399
  * Collapse the clone's tracking config to a single fetch refspec and a single
411
- * upstream ref for `branch` (see `workflow/git/clone-config.ts`). A clone that
400
+ * upstream ref for `branch` (see `kb-fs/clone-config.ts`). A clone that
412
401
  * accumulated a second `remote.origin.fetch` refspec or `branch.<b>.merge`
413
402
  * value makes git refuse to refresh it — "Cannot rebase onto multiple
414
403
  * branches" — which is how a post-merge pull of the target branch fails.
@@ -724,7 +713,7 @@ export class WorkspaceService implements IWorkspaceService {
724
713
 
725
714
  // This driver runs outside the git layer's per-workspace mutex, so it may
726
715
  // only run the safe implicit-fetch shape — see `SAFE_IMPLICIT_FETCH_ARGS`
727
- // in `workflow/git/clone-config.ts` for the full rationale.
716
+ // in `kb-fs/clone-config.ts` for the full rationale.
728
717
  const promise = execFileAsync(
729
718
  'git',
730
719
  ['-C', repoDir, ...SAFE_IMPLICIT_FETCH_ARGS],
@@ -1268,6 +1257,13 @@ export class WorkspaceService implements IWorkspaceService {
1268
1257
  // Bootstrap reported success but didn't register — defensive fallback.
1269
1258
  return workspaceDir;
1270
1259
  } catch (err) {
1260
+ // Domain errors pass through intact: they carry the status the routes
1261
+ // map (BranchNameError → 400), which is how a malformed or bogus
1262
+ // workspace id stays a client error instead of the wrap below erasing
1263
+ // the type and surfacing as a 500. A malformed id (bad percent-escape)
1264
+ // decodes to itself, and `%` never passes the branch-name rules, so it
1265
+ // lands here as a BranchNameError like any other invalid name.
1266
+ if (err instanceof WorkflowDomainError) throw err;
1271
1267
  const reason = err instanceof Error ? err.message : String(err);
1272
1268
  throw new Error(`Invalid workspace ID "${workspaceId}": ${reason}`);
1273
1269
  }
@@ -16,13 +16,13 @@ import {
16
16
  import type { IRoutineWritePolicy } from './routine-write-policy.js';
17
17
  import type { ToolHandlerFactory } from '../tool-helpers/tool-handler.js';
18
18
  import { requireInternalSource, requireExternalSource } from '../tool-auth/tool-auth.middleware.js';
19
- import { workspaceIdForBranch } from './workspace.service.js';
19
+ import { workspaceIdForBranch } from '../../shared/workspace-id.js';
20
20
  // Leaf-level shared primitive (same exception `workspace.service.ts` already
21
21
  // relies on) — not a workflow service, so this stays inside the module boundary.
22
- import { assertValidBranchName } from '../workflow/git/branch-name.js';
22
+ import { assertValidBranchName } from '../kb-fs/branch-name.js';
23
23
  import type { ISessionSink } from './session-sink.js';
24
24
  import type { IAccessControl } from '../access/access-control.interface.js';
25
- import { toKbRelative, resolveReadableMap } from '../access/kb-read-filter.js';
25
+ import { toKbRelative, resolveReadableMap } from '../access-model/kb-read-filter.js';
26
26
  import type { SpillStore } from './spill-store.js';
27
27
 
28
28
  /** A directory entry as returned by `LocalFilesystem.readdir`. */
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { isJoinBranchFor, joinBranchFor, kebabPluginName } from '@bevel-software/platform-shared';
3
- import { assertValidBranchName } from '../../modules/workflow/git/branch-name.js';
3
+ import { assertValidBranchName } from '../../modules/kb-fs/branch-name.js';
4
4
 
5
5
  /**
6
6
  * The join-branch convention. Two properties matter, and they pull in
@@ -0,0 +1,17 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { branchForWorkspaceId, workspaceIdForBranch } from '../workspace-id.js';
3
+
4
+ describe('workspace-id mapping', () => {
5
+ it('round-trips branch names, slashes included', () => {
6
+ for (const branch of ['current-company-state', 'alice/draft', 'a b', '100%']) {
7
+ expect(branchForWorkspaceId(workspaceIdForBranch(branch))).toBe(branch);
8
+ }
9
+ });
10
+
11
+ it('never throws on a malformed id — it names a branch that does not exist', () => {
12
+ for (const malformed of ['%', '100%', '%2', '%zz', 'a%2Gb']) {
13
+ expect(() => branchForWorkspaceId(malformed)).not.toThrow();
14
+ expect(branchForWorkspaceId(malformed)).toBe(malformed);
15
+ }
16
+ });
17
+ });
@@ -1,9 +1,14 @@
1
1
  /**
2
2
  * Workflow domain errors. This is the single source of truth — every error
3
- * raised by the workflow module (branches, changes, change requests, access
3
+ * raised by the workflow domain (branches, changes, change requests, access
4
4
  * control, locks) lives here. Route handlers shape responses via the
5
5
  * `.status` + `.payload` contract on the base class.
6
6
  *
7
+ * Lives in `src/shared` (not `modules/workflow`) because the classes are
8
+ * consumed across module boundaries — access, diff, plugins and workspace all
9
+ * catch or raise them — and `src/shared` is the layer with no module imports.
10
+ * Only a type from `@bevel-software/platform-shared` comes in; nothing else.
11
+ *
7
12
  * Naming: classes use workflow vocabulary. `WorkflowDomainError` is the
8
13
  * abstract base; specific subclasses describe the workflow concept that
9
14
  * went wrong (`BranchNameError`, `ProtectedBranchError`, etc.). Where a
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The branch ↔ workspace-directory naming convention. Workspaces are
3
+ * per-branch (one on-disk clone per branch name), and the workspace id IS the
4
+ * URL-encoded branch name — deterministic both ways, so every caller
5
+ * (backend, frontend, agent) can compute one from the other without a DB
6
+ * round-trip. Consumed by nearly every module that touches a workspace, so it
7
+ * lives in `backend/src/shared` rather than inside the workspace module.
8
+ *
9
+ * Pure string handling — no IO, no DB.
10
+ */
11
+
12
+ /**
13
+ * Map a branch name to the directory that contains its workspace clone.
14
+ * `encodeURIComponent` handles `/` in branch names (e.g. `alice/foo`) by
15
+ * encoding it to `%2F` — single-segment-safe on every supported filesystem.
16
+ */
17
+ export function workspaceIdForBranch(branch: string): string {
18
+ return encodeURIComponent(branch);
19
+ }
20
+
21
+ /**
22
+ * Map a workspace id back to its branch name. Workspace ids come in from
23
+ * request paths and from directory names on disk, so malformed input (a lone
24
+ * `%`, `%` without two hex digits) must not throw — `decodeURIComponent`
25
+ * would raise a `URIError` that surfaces as an unhandled 500 in every route
26
+ * that resolves an id. A malformed id can never equal a valid encoding, so
27
+ * returning it unchanged is safe: it simply names a branch that does not
28
+ * exist, and the caller's own lookup fails with its normal not-found path.
29
+ */
30
+ export function branchForWorkspaceId(workspaceId: string): string {
31
+ try {
32
+ return decodeURIComponent(workspaceId);
33
+ } catch {
34
+ return workspaceId;
35
+ }
36
+ }
File without changes