@bevel-software/platform-core-backend 0.4.1 → 0.5.1

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 (305) hide show
  1. package/THIRD-PARTY-NOTICES.md +6118 -0
  2. package/dist/core/core-ports.d.ts +15 -0
  3. package/dist/core/core-ports.d.ts.map +1 -1
  4. package/dist/core/core-ports.js.map +1 -1
  5. package/dist/core/create-core-server.d.ts.map +1 -1
  6. package/dist/core/create-core-server.js +40 -6
  7. package/dist/core/create-core-server.js.map +1 -1
  8. package/dist/core/create-core-services.d.ts +17 -1
  9. package/dist/core/create-core-services.d.ts.map +1 -1
  10. package/dist/core/create-core-services.js +153 -29
  11. package/dist/core/create-core-services.js.map +1 -1
  12. package/dist/core-config.d.ts +70 -21
  13. package/dist/core-config.d.ts.map +1 -1
  14. package/dist/core-config.js +134 -58
  15. package/dist/core-config.js.map +1 -1
  16. package/dist/modules/access/access-control.interface.d.ts +6 -0
  17. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  18. package/dist/modules/access/access-control.service.d.ts +74 -5
  19. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  20. package/dist/modules/access/access-control.service.js +116 -15
  21. package/dist/modules/access/access-control.service.js.map +1 -1
  22. package/dist/modules/access/access-declarations.d.ts +93 -0
  23. package/dist/modules/access/access-declarations.d.ts.map +1 -0
  24. package/dist/modules/access/access-declarations.js +168 -0
  25. package/dist/modules/access/access-declarations.js.map +1 -0
  26. package/dist/modules/access/access-mutation.service.js +4 -4
  27. package/dist/modules/access/access-mutation.service.js.map +1 -1
  28. package/dist/modules/access/access-splice.d.ts +16 -1
  29. package/dist/modules/access/access-splice.d.ts.map +1 -1
  30. package/dist/modules/access/access-splice.js +40 -21
  31. package/dist/modules/access/access-splice.js.map +1 -1
  32. package/dist/modules/access/access.routes.d.ts.map +1 -1
  33. package/dist/modules/access/access.routes.js +51 -0
  34. package/dist/modules/access/access.routes.js.map +1 -1
  35. package/dist/modules/access/creator-access.d.ts +8 -0
  36. package/dist/modules/access/creator-access.d.ts.map +1 -1
  37. package/dist/modules/access/creator-access.js +21 -2
  38. package/dist/modules/access/creator-access.js.map +1 -1
  39. package/dist/modules/admin/admin-access.service.d.ts +1 -1
  40. package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
  41. package/dist/modules/admin/admin-access.service.js +3 -2
  42. package/dist/modules/admin/admin-access.service.js.map +1 -1
  43. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  44. package/dist/modules/auth/auth.routes.js +50 -1
  45. package/dist/modules/auth/auth.routes.js.map +1 -1
  46. package/dist/modules/auth/auth.service.d.ts +39 -10
  47. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  48. package/dist/modules/auth/auth.service.js +38 -16
  49. package/dist/modules/auth/auth.service.js.map +1 -1
  50. package/dist/modules/database/core-schema.d.ts +124 -0
  51. package/dist/modules/database/core-schema.d.ts.map +1 -1
  52. package/dist/modules/database/core-schema.js +45 -0
  53. package/dist/modules/database/core-schema.js.map +1 -1
  54. package/dist/modules/groups/group-provision.service.d.ts +130 -0
  55. package/dist/modules/groups/group-provision.service.d.ts.map +1 -0
  56. package/dist/modules/groups/group-provision.service.js +288 -0
  57. package/dist/modules/groups/group-provision.service.js.map +1 -0
  58. package/dist/modules/groups/groups.contract.d.ts +106 -0
  59. package/dist/modules/groups/groups.contract.d.ts.map +1 -0
  60. package/dist/modules/groups/groups.contract.js +36 -0
  61. package/dist/modules/groups/groups.contract.js.map +1 -0
  62. package/dist/modules/groups/groups.routes.d.ts +42 -0
  63. package/dist/modules/groups/groups.routes.d.ts.map +1 -0
  64. package/dist/modules/groups/groups.routes.js +379 -0
  65. package/dist/modules/groups/groups.routes.js.map +1 -0
  66. package/dist/modules/groups/groups.service.d.ts +60 -0
  67. package/dist/modules/groups/groups.service.d.ts.map +1 -0
  68. package/dist/modules/groups/groups.service.js +172 -0
  69. package/dist/modules/groups/groups.service.js.map +1 -0
  70. package/dist/modules/groups/index.d.ts +7 -0
  71. package/dist/modules/groups/index.d.ts.map +1 -0
  72. package/dist/modules/groups/index.js +6 -0
  73. package/dist/modules/groups/index.js.map +1 -0
  74. package/dist/modules/groups/join-proposals.d.ts +53 -0
  75. package/dist/modules/groups/join-proposals.d.ts.map +1 -0
  76. package/dist/modules/groups/join-proposals.js +67 -0
  77. package/dist/modules/groups/join-proposals.js.map +1 -0
  78. package/dist/modules/groups/join-requests.service.d.ts +81 -0
  79. package/dist/modules/groups/join-requests.service.d.ts.map +1 -0
  80. package/dist/modules/groups/join-requests.service.js +135 -0
  81. package/dist/modules/groups/join-requests.service.js.map +1 -0
  82. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +20 -0
  83. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
  84. package/dist/modules/secrets-vault/secrets-vault.routes.js +59 -11
  85. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  86. package/dist/modules/settings/deployment-settings.service.d.ts +129 -0
  87. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -0
  88. package/dist/modules/settings/deployment-settings.service.js +368 -0
  89. package/dist/modules/settings/deployment-settings.service.js.map +1 -0
  90. package/dist/modules/settings/setup.routes.d.ts +49 -0
  91. package/dist/modules/settings/setup.routes.d.ts.map +1 -0
  92. package/dist/modules/settings/setup.routes.js +292 -0
  93. package/dist/modules/settings/setup.routes.js.map +1 -0
  94. package/dist/modules/skills/index.d.ts +2 -1
  95. package/dist/modules/skills/index.d.ts.map +1 -1
  96. package/dist/modules/skills/index.js +1 -0
  97. package/dist/modules/skills/index.js.map +1 -1
  98. package/dist/modules/skills/pending-skills.service.d.ts +40 -0
  99. package/dist/modules/skills/pending-skills.service.d.ts.map +1 -0
  100. package/dist/modules/skills/pending-skills.service.js +147 -0
  101. package/dist/modules/skills/pending-skills.service.js.map +1 -0
  102. package/dist/modules/skills/skills.contract.d.ts +32 -3
  103. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  104. package/dist/modules/skills/skills.contract.js +1 -1
  105. package/dist/modules/skills/skills.routes.d.ts +3 -2
  106. package/dist/modules/skills/skills.routes.d.ts.map +1 -1
  107. package/dist/modules/skills/skills.routes.js +18 -1
  108. package/dist/modules/skills/skills.routes.js.map +1 -1
  109. package/dist/modules/skills/skills.service.d.ts +22 -0
  110. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  111. package/dist/modules/skills/skills.service.js +24 -12
  112. package/dist/modules/skills/skills.service.js.map +1 -1
  113. package/dist/modules/tool-manuals/index.d.ts +1 -1
  114. package/dist/modules/tool-manuals/index.d.ts.map +1 -1
  115. package/dist/modules/tool-manuals/index.js.map +1 -1
  116. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +47 -2
  117. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  118. package/dist/modules/tool-manuals/tool-manuals.contract.js +1 -1
  119. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +4 -0
  120. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  121. package/dist/modules/tool-manuals/tool-manuals.routes.js +23 -0
  122. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  123. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +3 -1
  124. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  125. package/dist/modules/tool-manuals/tool-manuals.service.js +84 -17
  126. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  127. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  128. package/dist/modules/tool-manuals/tool-manuals.tools.js +6 -2
  129. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  130. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  131. package/dist/modules/workflow/agent-tools/workflow.tools.js +4 -2
  132. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  133. package/dist/modules/workflow/event-bus.d.ts +11 -0
  134. package/dist/modules/workflow/event-bus.d.ts.map +1 -1
  135. package/dist/modules/workflow/event-bus.js +22 -0
  136. package/dist/modules/workflow/event-bus.js.map +1 -1
  137. package/dist/modules/workflow/file-lock.service.d.ts +9 -0
  138. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  139. package/dist/modules/workflow/file-lock.service.js +16 -0
  140. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  141. package/dist/modules/workflow/git/branch-name.d.ts +1 -1
  142. package/dist/modules/workflow/git/branch-name.d.ts.map +1 -1
  143. package/dist/modules/workflow/git/branch-name.js +1 -1
  144. package/dist/modules/workflow/git/branch-name.js.map +1 -1
  145. package/dist/modules/workflow/git/git.service.d.ts +31 -2
  146. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  147. package/dist/modules/workflow/git/git.service.js +144 -52
  148. package/dist/modules/workflow/git/git.service.js.map +1 -1
  149. package/dist/modules/workflow/git/pull-request.service.d.ts +12 -6
  150. package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
  151. package/dist/modules/workflow/git/pull-request.service.js +19 -9
  152. package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
  153. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  154. package/dist/modules/workflow/review-workflow/review-workflow.service.js +12 -3
  155. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  156. package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
  157. package/dist/modules/workflow/workflow.routes.js +95 -14
  158. package/dist/modules/workflow/workflow.routes.js.map +1 -1
  159. package/dist/modules/workflow/workflow.service.d.ts +92 -3
  160. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  161. package/dist/modules/workflow/workflow.service.js +381 -108
  162. package/dist/modules/workflow/workflow.service.js.map +1 -1
  163. package/dist/modules/workspace/bevel-ignore.d.ts +5 -0
  164. package/dist/modules/workspace/bevel-ignore.d.ts.map +1 -1
  165. package/dist/modules/workspace/bevel-ignore.js +5 -1
  166. package/dist/modules/workspace/bevel-ignore.js.map +1 -1
  167. package/dist/modules/workspace/kb-seed.interface.d.ts +1 -1
  168. package/dist/modules/workspace/kb-seed.service.d.ts +60 -5
  169. package/dist/modules/workspace/kb-seed.service.d.ts.map +1 -1
  170. package/dist/modules/workspace/kb-seed.service.js +228 -51
  171. package/dist/modules/workspace/kb-seed.service.js.map +1 -1
  172. package/dist/modules/workspace/workspace.routes.d.ts +2 -1
  173. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  174. package/dist/modules/workspace/workspace.routes.js +51 -18
  175. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  176. package/dist/modules/workspace/workspace.service.d.ts +16 -4
  177. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  178. package/dist/modules/workspace/workspace.service.js +36 -9
  179. package/dist/modules/workspace/workspace.service.js.map +1 -1
  180. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  181. package/dist/modules/workspace/workspace.tools.js +8 -3
  182. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  183. package/dist/shared/kb-layout.test.js +2 -2
  184. package/dist/test-setup.d.ts +2 -0
  185. package/dist/test-setup.d.ts.map +1 -0
  186. package/dist/test-setup.js +18 -0
  187. package/dist/test-setup.js.map +1 -0
  188. package/kb-template/.bevelignore +6 -2
  189. package/kb-template/AGENTS.md +272 -0
  190. package/kb-template/KnowledgeBase/How to get started.md +65 -0
  191. package/kb-template/access.md +36 -36
  192. package/migrations/0002_user_onboarding_done.sql +1 -0
  193. package/migrations/0003_deployment_settings.sql +9 -0
  194. package/migrations/meta/0001_snapshot.json +1486 -0
  195. package/migrations/meta/0002_snapshot.json +1493 -0
  196. package/migrations/meta/0003_snapshot.json +1552 -0
  197. package/migrations/meta/_journal.json +15 -1
  198. package/package.json +5 -4
  199. package/src/__tests__/branch-model.test.ts +79 -0
  200. package/src/__tests__/core-config.admin.test.ts +114 -0
  201. package/src/__tests__/database-url.test.ts +70 -0
  202. package/src/core/core-ports.ts +15 -0
  203. package/src/core/create-core-server.ts +55 -6
  204. package/src/core/create-core-services.ts +201 -36
  205. package/src/core-config.ts +139 -59
  206. package/src/modules/access/__tests__/access-declarations.test.ts +195 -0
  207. package/src/modules/access/__tests__/access-md-format.test.ts +191 -0
  208. package/src/modules/access/__tests__/access-splice.test.ts +21 -0
  209. package/src/modules/access/__tests__/access.routes.overrides.test.ts +214 -0
  210. package/src/modules/access/access-control.interface.ts +11 -0
  211. package/src/modules/access/access-control.service.ts +113 -15
  212. package/src/modules/access/access-declarations.ts +251 -0
  213. package/src/modules/access/access-mutation.service.ts +4 -4
  214. package/src/modules/access/access-splice.ts +64 -21
  215. package/src/modules/access/access.routes.ts +61 -0
  216. package/src/modules/access/creator-access.ts +22 -2
  217. package/src/modules/admin/admin-access.service.ts +4 -3
  218. package/src/modules/auth/__tests__/auth.service.test.ts +57 -0
  219. package/src/modules/auth/__tests__/onboarding-done.route.test.ts +147 -0
  220. package/src/modules/auth/auth.routes.ts +50 -1
  221. package/src/modules/auth/auth.service.ts +55 -17
  222. package/src/modules/database/core-schema.ts +46 -0
  223. package/src/modules/groups/__tests__/group-index.service.test.ts +225 -0
  224. package/src/modules/groups/__tests__/group-provision.service.test.ts +247 -0
  225. package/src/modules/groups/__tests__/groups.routes.test.ts +493 -0
  226. package/src/modules/groups/__tests__/join-proposals.test.ts +101 -0
  227. package/src/modules/groups/__tests__/join-requests.service.test.ts +175 -0
  228. package/src/modules/groups/group-provision.service.ts +328 -0
  229. package/src/modules/groups/groups.contract.ts +107 -0
  230. package/src/modules/groups/groups.routes.ts +433 -0
  231. package/src/modules/groups/groups.service.ts +193 -0
  232. package/src/modules/groups/index.ts +17 -0
  233. package/src/modules/groups/join-proposals.ts +114 -0
  234. package/src/modules/groups/join-requests.service.ts +185 -0
  235. package/src/modules/secrets-vault/__tests__/oauth-return-to.route.test.ts +283 -0
  236. package/src/modules/secrets-vault/secrets-vault.routes.ts +68 -12
  237. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +256 -0
  238. package/src/modules/settings/__tests__/setup.routes.test.ts +201 -0
  239. package/src/modules/settings/deployment-settings.service.ts +419 -0
  240. package/src/modules/settings/setup.routes.ts +317 -0
  241. package/src/modules/skills/__tests__/pending-skills.service.test.ts +240 -0
  242. package/src/modules/skills/__tests__/skills.service.test.ts +13 -13
  243. package/src/modules/skills/index.ts +3 -0
  244. package/src/modules/skills/pending-skills.service.ts +168 -0
  245. package/src/modules/skills/skills.contract.ts +34 -3
  246. package/src/modules/skills/skills.routes.ts +23 -2
  247. package/src/modules/skills/skills.service.ts +24 -16
  248. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +262 -0
  249. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +2 -2
  250. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +61 -23
  251. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +156 -156
  252. package/src/modules/tool-manuals/index.ts +2 -0
  253. package/src/modules/tool-manuals/tool-manuals.contract.ts +50 -2
  254. package/src/modules/tool-manuals/tool-manuals.routes.ts +21 -0
  255. package/src/modules/tool-manuals/tool-manuals.service.ts +86 -17
  256. package/src/modules/tool-manuals/tool-manuals.tools.ts +206 -202
  257. package/src/modules/workflow/__tests__/event-bus.test.ts +35 -0
  258. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +251 -5
  259. package/src/modules/workflow/agent-tools/workflow.tools.ts +4 -2
  260. package/src/modules/workflow/event-bus.ts +25 -0
  261. package/src/modules/workflow/file-lock.service.ts +17 -0
  262. package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +9 -49
  263. package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +101 -0
  264. package/src/modules/workflow/git/__tests__/git.service.commitFile.test.ts +219 -211
  265. package/src/modules/workflow/git/__tests__/git.service.fileContentsAtCommit.test.ts +132 -0
  266. package/src/modules/workflow/git/__tests__/git.service.logForFile.test.ts +4 -2
  267. package/src/modules/workflow/git/__tests__/git.service.postMergePull.test.ts +275 -272
  268. package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +5 -0
  269. package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +44 -0
  270. package/src/modules/workflow/git/branch-name.ts +5 -1
  271. package/src/modules/workflow/git/git.service.ts +159 -62
  272. package/src/modules/workflow/git/pull-request.service.ts +22 -11
  273. package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +4 -1
  274. package/src/modules/workflow/review-workflow/review-workflow.service.ts +12 -3
  275. package/src/modules/workflow/workflow.routes.ts +91 -13
  276. package/src/modules/workflow/workflow.service.ts +353 -13
  277. package/src/modules/workspace/__tests__/kb-seed.service.test.ts +255 -24
  278. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +236 -233
  279. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +179 -176
  280. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +320 -303
  281. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +337 -276
  282. package/src/modules/workspace/__tests__/workspace.tools.test.ts +14 -6
  283. package/src/modules/workspace/bevel-ignore.ts +66 -62
  284. package/src/modules/workspace/kb-seed.interface.ts +1 -1
  285. package/src/modules/workspace/kb-seed.service.ts +247 -53
  286. package/src/modules/workspace/workspace.routes.ts +54 -18
  287. package/src/modules/workspace/workspace.service.ts +45 -7
  288. package/src/modules/workspace/workspace.tools.ts +922 -917
  289. package/src/shared/__tests__/frontmatter-id.test.ts +53 -0
  290. package/src/shared/__tests__/join-request.test.ts +96 -0
  291. package/src/shared/__tests__/kb-layout.group.test.ts +45 -0
  292. package/src/shared/kb-layout.test.ts +2 -2
  293. package/src/test-setup.ts +18 -0
  294. package/kb-template/Agents/README.md +0 -38
  295. package/kb-template/CLAUDE.md +0 -419
  296. package/kb-template/Data/.gitkeep +0 -0
  297. package/kb-template/Data/README.md +0 -39
  298. package/kb-template/KnowledgeBase/README.md +0 -37
  299. package/kb-template/Pipelines/.gitkeep +0 -0
  300. package/kb-template/Pipelines/README.md +0 -38
  301. package/kb-template/Skills/.gitkeep +0 -0
  302. package/kb-template/Skills/README.md +0 -32
  303. package/kb-template/Tools/.gitkeep +0 -0
  304. package/kb-template/Tools/README.md +0 -42
  305. /package/kb-template/{Agents → Groups}/.gitkeep +0 -0
@@ -56,6 +56,59 @@ describe('dedupeById', () => {
56
56
  expect(out.map((x) => x.at)).toEqual([1, 2]); // first `a` kept, second dropped
57
57
  expect(onDup).toHaveBeenCalledWith('a', 3);
58
58
  });
59
+
60
+ /**
61
+ * REGRESSION GUARD for the "one folder = one space" model.
62
+ *
63
+ * Spaces are grouping folders under the catalog root, and a tool or skill
64
+ * shared by two spaces is expected to be duplicated as two files with
65
+ * DISTINCT ids. Nothing enforces that convention — `seen` is a single
66
+ * global Set keyed on the declared id, with no notion of the folder the
67
+ * item came from. So the moment two spaces hold a same-named item, one of
68
+ * them silently disappears from the catalog.
69
+ *
70
+ * These tests pin that behaviour so it is a documented, observable
71
+ * property rather than a surprise. If dedup ever becomes folder-scoped,
72
+ * they are the tests that should change.
73
+ */
74
+ test('same id in two different space folders: the second folder loses its copy', () => {
75
+ const onDup = vi.fn();
76
+ // Services sort by (name, path) before deduping, so path order is stable.
77
+ const items = [
78
+ { name: 'notion', path: 'Tools/Everyone/notion.tool' },
79
+ { name: 'notion', path: 'Tools/Finance/notion.tool' },
80
+ ];
81
+ const out = dedupeById(items, (x) => x.name, (x, id) => onDup(id, x.path));
82
+
83
+ expect(out).toHaveLength(1);
84
+ expect(out[0].path).toBe('Tools/Everyone/notion.tool');
85
+ // The Finance space loses its tool entirely — only a server-side warning
86
+ // marks it, which is why the convention needs distinct ids per space.
87
+ expect(onDup).toHaveBeenCalledWith('notion', 'Tools/Finance/notion.tool');
88
+ });
89
+
90
+ test('distinct ids per space folder keep BOTH copies — the supported pattern', () => {
91
+ const onDup = vi.fn();
92
+ const items = [
93
+ { name: 'everyone_notion', path: 'Tools/Everyone/notion.tool' },
94
+ { name: 'finance_notion', path: 'Tools/Finance/notion.tool' },
95
+ ];
96
+ const out = dedupeById(items, (x) => x.name, (x, id) => onDup(id, x.path));
97
+
98
+ expect(out).toHaveLength(2);
99
+ expect(onDup).not.toHaveBeenCalled();
100
+ });
101
+
102
+ test('the surviving copy is the alphabetically-first path, not scan order', () => {
103
+ // Deterministic winner matters: an unstable one means a space's tool
104
+ // appears or vanishes depending on filesystem iteration order.
105
+ const items = [
106
+ { name: 'x', path: 'Tools/Zulu/x.tool' },
107
+ { name: 'x', path: 'Tools/Alpha/x.tool' },
108
+ ].sort((a, b) => a.name.localeCompare(b.name) || a.path.localeCompare(b.path));
109
+
110
+ expect(dedupeById(items, (i) => i.name)[0].path).toBe('Tools/Alpha/x.tool');
111
+ });
59
112
  });
60
113
 
61
114
  describe('setFrontmatterField (shared, line-based)', () => {
@@ -0,0 +1,96 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { isJoinBranchFor, joinBranchFor, kebabGroupName } from '@bevel-software/platform-shared';
3
+ import { assertValidBranchName } from '../../modules/workflow/git/branch-name.js';
4
+
5
+ /**
6
+ * The join-branch convention. Two properties matter, and they pull in
7
+ * different directions:
8
+ *
9
+ * - UNIQUENESS (the write side): distinct requesters and distinct groups must
10
+ * never share a branch, or one person's commit lands on another's request.
11
+ * Both inputs the name is built from are lossy; the requester tag fixes it.
12
+ * - EXACT GROUP IDENTITY (the read side): the settle decision is "the diff
13
+ * is empty", so matching a branch to the WRONG group reads an unchanged
14
+ * file, sees an empty diff, and destroys somebody else's request. The
15
+ * group tag is recomputed from the exact name so shape-matching can never
16
+ * cross groups; only the requester half matches by shape (the reader has
17
+ * no email to recompute it from).
18
+ */
19
+ const TAG = '[0-9a-z]{7}';
20
+
21
+ describe('joinBranchFor', () => {
22
+ it('produces a valid branch name that follows the draft convention', () => {
23
+ const branch = joinBranchFor('ali@bevel.software', 'GTM');
24
+ expect(branch).toMatch(new RegExp(`^ali/join-gtm-${TAG}-${TAG}$`));
25
+ expect(() => assertValidBranchName(branch)).not.toThrow();
26
+ });
27
+
28
+ it('is deterministic, and case-insensitive in the email only', () => {
29
+ expect(joinBranchFor('Ali@Bevel.Software', 'GTM')).toBe(joinBranchFor('ali@bevel.software', 'GTM'));
30
+ });
31
+
32
+ it('separates requesters who share a localpart across domains', () => {
33
+ // Regression: without the requester tag both collapse to one branch, so
34
+ // the second requester's grant commit lands on the first one's branch.
35
+ expect(joinBranchFor('ali@bevel.software', 'GTM')).not.toBe(
36
+ joinBranchFor('ali@other.com', 'GTM'),
37
+ );
38
+ });
39
+
40
+ it('separates groups whose names kebab to the same slug', () => {
41
+ expect(joinBranchFor('ali@bevel.software', 'Finance!')).not.toBe(
42
+ joinBranchFor('ali@bevel.software', 'Finance'),
43
+ );
44
+ // Case is part of a group's identity (the folder name is case-sensitive).
45
+ expect(joinBranchFor('ali@bevel.software', 'finance')).not.toBe(
46
+ joinBranchFor('ali@bevel.software', 'Finance'),
47
+ );
48
+ });
49
+
50
+ it('still yields a valid, matchable branch for a group with no alphanumerics', () => {
51
+ const branch = joinBranchFor('ali@bevel.software', '!!!');
52
+ expect(kebabGroupName('!!!')).toBe('');
53
+ expect(branch).toMatch(new RegExp(`^ali/join-${TAG}-${TAG}$`));
54
+ expect(() => assertValidBranchName(branch)).not.toThrow();
55
+ expect(isJoinBranchFor(branch, '!!!')).toBe(true);
56
+ });
57
+ });
58
+
59
+ describe('isJoinBranchFor', () => {
60
+ it("recognises somebody ELSE's join branch (the requester tag is not recomputed)", () => {
61
+ expect(isJoinBranchFor(joinBranchFor('juan@bevel.software', 'GTM'), 'GTM')).toBe(true);
62
+ expect(isJoinBranchFor(joinBranchFor('ali@other.com', 'GTM'), 'GTM')).toBe(true);
63
+ });
64
+
65
+ it('rejects a branch for a different group', () => {
66
+ expect(isJoinBranchFor(joinBranchFor('ali@bevel.software', 'Finance'), 'GTM')).toBe(false);
67
+ });
68
+
69
+ it('NEVER crosses slug-colliding groups — the destructive-settle regression', () => {
70
+ // `Finance!`, `finance` and `Finance` all slug to `finance`. A listing
71
+ // for one that matched another's branch would read that branch's copy of
72
+ // ITS OWN access.md (unchanged there), see an empty diff, and settle —
73
+ // closing a change request and deleting a branch belonging to a
74
+ // different group. The recomputed group tag is what forbids the match.
75
+ const forBang = joinBranchFor('ali@bevel.software', 'Finance!');
76
+ expect(isJoinBranchFor(forBang, 'finance')).toBe(false);
77
+ expect(isJoinBranchFor(forBang, 'Finance')).toBe(false);
78
+ expect(isJoinBranchFor(forBang, 'Finance!')).toBe(true);
79
+ expect(isJoinBranchFor(joinBranchFor('ali@bevel.software', 'finance'), 'Finance!')).toBe(false);
80
+ });
81
+
82
+ it('rejects ordinary draft branches and near-misses', () => {
83
+ const good = joinBranchFor('ali@bevel.software', 'GTM');
84
+ const [, suffix] = good.split('/');
85
+ for (const branch of [
86
+ 'ali/gtm',
87
+ 'ali/join-gtm', // no tags — the pre-tag shape
88
+ 'ali/join-gtm-abc1234', // one tag only
89
+ `ali/${suffix.toUpperCase()}`, // tags are lowercase base36
90
+ `ali/${suffix}/extra`,
91
+ suffix, // no author segment
92
+ ]) {
93
+ expect(isJoinBranchFor(branch, 'GTM'), branch).toBe(false);
94
+ }
95
+ });
96
+ });
@@ -0,0 +1,45 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { groupOfPath, GROUPS_DIR } from '@bevel-software/platform-shared';
3
+
4
+ /**
5
+ * `groupOfPath` is the whole of the grouping contract — the sidebar, the
6
+ * catalog buckets and the access story all read a group off a path with it.
7
+ * The cases that matter are the ones where "has a group" and "is under the
8
+ * group root" come apart.
9
+ */
10
+ describe('groupOfPath', () => {
11
+ it('reads the group from the Groups root', () => {
12
+ expect(groupOfPath(`${GROUPS_DIR}/GTM/heyreach-campaign/SKILL.md`)).toBe('GTM');
13
+ expect(groupOfPath(`${GROUPS_DIR}/GTM/heyreach.tool`)).toBe('GTM');
14
+ expect(groupOfPath(`${GROUPS_DIR}/Engineering/review/architecture-review`)).toBe(
15
+ 'Engineering',
16
+ );
17
+ });
18
+
19
+ it('returns null for content directly under the root — it has no group folder', () => {
20
+ // The regression this guards: `Groups/slack.tool` must NOT report a group
21
+ // called "slack.tool", and `Groups/GTM` must not report itself.
22
+ expect(groupOfPath(`${GROUPS_DIR}/slack.tool`)).toBeNull();
23
+ expect(groupOfPath(`${GROUPS_DIR}/GTM`)).toBeNull();
24
+ expect(groupOfPath(GROUPS_DIR)).toBeNull();
25
+ });
26
+
27
+ it('returns null outside the group root', () => {
28
+ expect(groupOfPath('KnowledgeBase/Product/Knowledge/Foo.md')).toBeNull();
29
+ expect(groupOfPath('Data/Engineering/Knowledge/Bar.md')).toBeNull();
30
+ // The retired pre-merge roots are ordinary non-group folders now.
31
+ expect(groupOfPath('Skills/GTM/heyreach-campaign')).toBeNull();
32
+ expect(groupOfPath('Tools/NewsAgent/serper.tool')).toBeNull();
33
+ expect(groupOfPath('access.md')).toBeNull();
34
+ expect(groupOfPath('')).toBeNull();
35
+ });
36
+
37
+ it('is not fooled by a prefix match on the root name', () => {
38
+ expect(groupOfPath('GroupsOld/GTM/x')).toBeNull();
39
+ });
40
+
41
+ it('tolerates leading and doubled separators', () => {
42
+ expect(groupOfPath(`/${GROUPS_DIR}/GTM/x`)).toBe('GTM');
43
+ expect(groupOfPath(`${GROUPS_DIR}//GTM//x`)).toBe('GTM');
44
+ });
45
+ });
@@ -41,8 +41,8 @@ describe('ontologyOf', () => {
41
41
  expect(ontologyOf('KnowledgeBase/Ops/Knowledge/x.md')).toBe('KnowledgeBase/Ops');
42
42
  });
43
43
 
44
- it('returns null for Skills and root config (neutral)', () => {
45
- expect(ontologyOf('Skills/some-skill/SKILL.md')).toBeNull();
44
+ it('returns null for Groups and root config (neutral)', () => {
45
+ expect(ontologyOf('Groups/some-skill/SKILL.md')).toBeNull();
46
46
  expect(ontologyOf('access.md')).toBeNull();
47
47
  expect(ontologyOf('roles.yaml')).toBeNull();
48
48
  });
@@ -0,0 +1,18 @@
1
+ import { branchModelFromEnv, configureBranchModel } from '@bevel-software/platform-shared';
2
+
3
+ /**
4
+ * Apply the branch model before any suite imports something that reads it.
5
+ *
6
+ * The shared module no longer reads `process.env` at import time — both sides
7
+ * configure it during boot instead (`createCoreServices` here, a fetch of
8
+ * `/api/config` in the browser). Suites exercise pieces of the server without
9
+ * going through `createCoreServices`, so the same call has to happen for them.
10
+ *
11
+ * It must live in `setupFiles` rather than `vitest.config.ts`: the config is
12
+ * evaluated in its own module graph, so a call there configures a copy of the
13
+ * shared module that no test ever sees.
14
+ *
15
+ * The values come from the environment the config pins, which keeps the one
16
+ * place the historical two-branch pair is written.
17
+ */
18
+ configureBranchModel(branchModelFromEnv());
@@ -1,38 +0,0 @@
1
- # Agents/
2
-
3
- `.agent` files — **role definitions for agents**, and the main way anyone
4
- (a person's own assistant or a background pipeline) connects to the platform.
5
- Connecting "as" an `.agent` mints exactly that agent's tool surface — e.g. the
6
- KB's MCP config is generated per `.agent`, not from everything the identity
7
- can read.
8
-
9
- ## Structure
10
-
11
- ```text
12
- Agents/
13
- ├── default.agent ← the safe floor: basic KB access only
14
- ├── developer.agent ← an admin-authored role: everything developers get
15
- └── <role-or-pipeline>.agent
16
- ```
17
-
18
- One file per role or per pipeline agent. (Exact file format: TBD.)
19
-
20
- ## What an `.agent` defines
21
-
22
- - The **target harness** (e.g. Claude Code) and model / reasoning settings.
23
- - The **skills** and **tools** composed in — selections from `Skills/` and
24
- `Tools/`. The execution layer compiles these into the harness's native
25
- plugin format and installs it.
26
- - Permissions/allowlists, hooks, MCP servers, env vars, session defaults,
27
- and injected instructions (the agent's charter).
28
- - **References** to an identity and a budget — never credentials themselves.
29
-
30
- ## Rules
31
-
32
- - An `.agent` only **narrows** what its identity's permissions already allow —
33
- it never widens access. Identity remains the security boundary; `.agent`
34
- is configuration.
35
- - **No secret values** in these files — secrets are referenced by vault name.
36
- - This folder sits under access control + change requests: editing a role
37
- file changes the whole role's tooling, and is reviewed like any other
38
- change.