@bevel-software/platform-core-backend 0.19.0 → 0.20.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 (255) hide show
  1. package/dist/core/create-core-server.d.ts.map +1 -1
  2. package/dist/core/create-core-server.js +22 -0
  3. package/dist/core/create-core-server.js.map +1 -1
  4. package/dist/core/create-core-services.d.ts +3 -0
  5. package/dist/core/create-core-services.d.ts.map +1 -1
  6. package/dist/core/create-core-services.js +10 -1
  7. package/dist/core/create-core-services.js.map +1 -1
  8. package/dist/modules/audit/agent-audit.service.d.ts +54 -0
  9. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -0
  10. package/dist/modules/audit/agent-audit.service.js +290 -0
  11. package/dist/modules/audit/agent-audit.service.js.map +1 -0
  12. package/dist/modules/audit/agent-rest-audit.middleware.d.ts +40 -0
  13. package/dist/modules/audit/agent-rest-audit.middleware.d.ts.map +1 -0
  14. package/dist/modules/audit/agent-rest-audit.middleware.js +75 -0
  15. package/dist/modules/audit/agent-rest-audit.middleware.js.map +1 -0
  16. package/dist/modules/audit/audit.contract.d.ts +145 -0
  17. package/dist/modules/audit/audit.contract.d.ts.map +1 -0
  18. package/dist/modules/audit/audit.contract.js +35 -0
  19. package/dist/modules/audit/audit.contract.js.map +1 -0
  20. package/dist/modules/audit/audit.routes.d.ts +21 -0
  21. package/dist/modules/audit/audit.routes.d.ts.map +1 -0
  22. package/dist/modules/audit/audit.routes.js +157 -0
  23. package/dist/modules/audit/audit.routes.js.map +1 -0
  24. package/dist/modules/audit/event-classifier.d.ts +52 -0
  25. package/dist/modules/audit/event-classifier.d.ts.map +1 -0
  26. package/dist/modules/audit/event-classifier.js +123 -0
  27. package/dist/modules/audit/event-classifier.js.map +1 -0
  28. package/dist/modules/audit/index.d.ts +7 -0
  29. package/dist/modules/audit/index.d.ts.map +1 -0
  30. package/dist/modules/audit/index.js +7 -0
  31. package/dist/modules/audit/index.js.map +1 -0
  32. package/dist/modules/audit/request-audit.d.ts +57 -0
  33. package/dist/modules/audit/request-audit.d.ts.map +1 -0
  34. package/dist/modules/audit/request-audit.js +117 -0
  35. package/dist/modules/audit/request-audit.js.map +1 -0
  36. package/dist/modules/database/core-schema.d.ts +414 -0
  37. package/dist/modules/database/core-schema.d.ts.map +1 -1
  38. package/dist/modules/database/core-schema.js +99 -0
  39. package/dist/modules/database/core-schema.js.map +1 -1
  40. package/dist/modules/diff/diff.service.d.ts.map +1 -1
  41. package/dist/modules/diff/diff.service.js +6 -1
  42. package/dist/modules/diff/diff.service.js.map +1 -1
  43. package/dist/modules/kb-fs/git-guarded-filesystem.d.ts +9 -1
  44. package/dist/modules/kb-fs/git-guarded-filesystem.d.ts.map +1 -1
  45. package/dist/modules/kb-fs/git-guarded-filesystem.js +16 -3
  46. package/dist/modules/kb-fs/git-guarded-filesystem.js.map +1 -1
  47. package/dist/modules/mcp/mcp-auth.middleware.d.ts +1 -0
  48. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  49. package/dist/modules/mcp/mcp-auth.middleware.js +32 -0
  50. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  51. package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
  52. package/dist/modules/mcp/mcp.routes.js +8 -1
  53. package/dist/modules/mcp/mcp.routes.js.map +1 -1
  54. package/dist/modules/mcp/mcp.service.d.ts +9 -1
  55. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  56. package/dist/modules/mcp/mcp.service.js +48 -5
  57. package/dist/modules/mcp/mcp.service.js.map +1 -1
  58. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +59 -0
  59. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  60. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +180 -8
  61. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  62. package/dist/modules/settings/deployment-settings.service.d.ts +10 -2
  63. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  64. package/dist/modules/settings/deployment-settings.service.js +32 -6
  65. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  66. package/dist/modules/tool-auth/external-api-key.interface.d.ts +21 -5
  67. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  68. package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
  69. package/dist/modules/tool-auth/external-api-key.service.d.ts +1 -0
  70. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  71. package/dist/modules/tool-auth/external-api-key.service.js +22 -9
  72. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  73. package/dist/modules/tool-auth/internal-token.service.d.ts +10 -0
  74. package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
  75. package/dist/modules/tool-auth/internal-token.service.js +1 -0
  76. package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
  77. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +8 -0
  78. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  79. package/dist/modules/tool-auth/tool-auth.middleware.js +9 -1
  80. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  81. package/dist/modules/workspace/workspace.service.d.ts +8 -0
  82. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  83. package/dist/modules/workspace/workspace.service.js +9 -0
  84. package/dist/modules/workspace/workspace.service.js.map +1 -1
  85. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  86. package/dist/modules/workspace/workspace.tools.js +49 -13
  87. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  88. package/dist/shared/git-internals.d.ts +7 -1
  89. package/dist/shared/git-internals.d.ts.map +1 -1
  90. package/dist/shared/git-internals.js +67 -9
  91. package/dist/shared/git-internals.js.map +1 -1
  92. package/migrations/0012_agent_audit.sql +41 -0
  93. package/migrations/0013_agent_identity_and_key_soft_delete.sql +29 -0
  94. package/migrations/meta/0012_snapshot.json +2247 -0
  95. package/migrations/meta/0013_snapshot.json +2259 -0
  96. package/migrations/meta/_journal.json +14 -0
  97. package/package.json +4 -4
  98. package/src/core/create-core-server.ts +28 -0
  99. package/src/core/create-core-services.ts +13 -0
  100. package/src/modules/audit/__tests__/agent-audit.service.test.ts +370 -0
  101. package/src/modules/audit/__tests__/agent-rest-audit.middleware.test.ts +113 -0
  102. package/src/modules/audit/__tests__/audit.routes.test.ts +201 -0
  103. package/src/modules/audit/__tests__/event-classifier.test.ts +121 -0
  104. package/src/modules/audit/agent-audit.service.ts +316 -0
  105. package/src/modules/audit/agent-rest-audit.middleware.ts +83 -0
  106. package/src/modules/audit/audit.contract.ts +164 -0
  107. package/src/modules/audit/audit.routes.ts +168 -0
  108. package/src/modules/audit/event-classifier.ts +155 -0
  109. package/src/modules/audit/index.ts +14 -0
  110. package/src/modules/audit/request-audit.ts +141 -0
  111. package/src/modules/database/core-schema.ts +104 -0
  112. package/src/modules/diff/diff.service.ts +6 -1
  113. package/src/modules/kb-fs/git-guarded-filesystem.ts +16 -3
  114. package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +183 -13
  115. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +60 -2
  116. package/src/modules/mcp/__tests__/mcp.routes.local-token.test.ts +24 -4
  117. package/src/modules/mcp/__tests__/mcp.routes.stateless.test.ts +1 -1
  118. package/src/modules/mcp/__tests__/mcp.service.test.ts +120 -3
  119. package/src/modules/mcp/mcp-auth.middleware.ts +31 -0
  120. package/src/modules/mcp/mcp.routes.ts +11 -1
  121. package/src/modules/mcp/mcp.service.ts +62 -4
  122. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +184 -6
  123. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +35 -0
  124. package/src/modules/settings/deployment-settings.service.ts +47 -8
  125. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +29 -8
  126. package/src/modules/tool-auth/__tests__/internal-token.service.test.ts +6 -0
  127. package/src/modules/tool-auth/external-api-key.interface.ts +22 -5
  128. package/src/modules/tool-auth/external-api-key.service.ts +25 -8
  129. package/src/modules/tool-auth/internal-token.service.ts +11 -0
  130. package/src/modules/tool-auth/tool-auth.middleware.ts +17 -1
  131. package/src/modules/workspace/__tests__/git-internals.security.test.ts +135 -44
  132. package/src/modules/workspace/workspace.service.ts +9 -0
  133. package/src/modules/workspace/workspace.tools.ts +47 -11
  134. package/src/shared/__tests__/git-internals.test.ts +41 -0
  135. package/src/shared/git-internals.ts +61 -6
  136. package/dist/modules/access/access-errors.d.ts +0 -34
  137. package/dist/modules/access/access-errors.d.ts.map +0 -1
  138. package/dist/modules/access/access-errors.js +0 -40
  139. package/dist/modules/access/access-errors.js.map +0 -1
  140. package/dist/modules/access/access-splice.d.ts +0 -140
  141. package/dist/modules/access/access-splice.d.ts.map +0 -1
  142. package/dist/modules/access/access-splice.js +0 -389
  143. package/dist/modules/access/access-splice.js.map +0 -1
  144. package/dist/modules/access/group-files.d.ts +0 -83
  145. package/dist/modules/access/group-files.d.ts.map +0 -1
  146. package/dist/modules/access/group-files.js +0 -167
  147. package/dist/modules/access/group-files.js.map +0 -1
  148. package/dist/modules/access/kb-read-filter.d.ts +0 -41
  149. package/dist/modules/access/kb-read-filter.d.ts.map +0 -1
  150. package/dist/modules/access/kb-read-filter.js +0 -60
  151. package/dist/modules/access/kb-read-filter.js.map +0 -1
  152. package/dist/modules/access/render-roles-yaml.d.ts +0 -22
  153. package/dist/modules/access/render-roles-yaml.d.ts.map +0 -1
  154. package/dist/modules/access/render-roles-yaml.js +0 -56
  155. package/dist/modules/access/render-roles-yaml.js.map +0 -1
  156. package/dist/modules/access/roles-yaml-guard.d.ts +0 -56
  157. package/dist/modules/access/roles-yaml-guard.d.ts.map +0 -1
  158. package/dist/modules/access/roles-yaml-guard.js +0 -79
  159. package/dist/modules/access/roles-yaml-guard.js.map +0 -1
  160. package/dist/modules/diff/diff-paths.d.ts +0 -2
  161. package/dist/modules/diff/diff-paths.d.ts.map +0 -1
  162. package/dist/modules/diff/diff-paths.js +0 -9
  163. package/dist/modules/diff/diff-paths.js.map +0 -1
  164. package/dist/modules/groups/group-provision.service.d.ts +0 -130
  165. package/dist/modules/groups/group-provision.service.d.ts.map +0 -1
  166. package/dist/modules/groups/group-provision.service.js +0 -288
  167. package/dist/modules/groups/group-provision.service.js.map +0 -1
  168. package/dist/modules/groups/groups.contract.d.ts +0 -106
  169. package/dist/modules/groups/groups.contract.d.ts.map +0 -1
  170. package/dist/modules/groups/groups.contract.js +0 -36
  171. package/dist/modules/groups/groups.contract.js.map +0 -1
  172. package/dist/modules/groups/groups.routes.d.ts +0 -42
  173. package/dist/modules/groups/groups.routes.d.ts.map +0 -1
  174. package/dist/modules/groups/groups.routes.js +0 -379
  175. package/dist/modules/groups/groups.routes.js.map +0 -1
  176. package/dist/modules/groups/groups.service.d.ts +0 -60
  177. package/dist/modules/groups/groups.service.d.ts.map +0 -1
  178. package/dist/modules/groups/groups.service.js +0 -172
  179. package/dist/modules/groups/groups.service.js.map +0 -1
  180. package/dist/modules/groups/index.d.ts +0 -7
  181. package/dist/modules/groups/index.d.ts.map +0 -1
  182. package/dist/modules/groups/index.js +0 -6
  183. package/dist/modules/groups/index.js.map +0 -1
  184. package/dist/modules/groups/join-proposals.d.ts +0 -53
  185. package/dist/modules/groups/join-proposals.d.ts.map +0 -1
  186. package/dist/modules/groups/join-proposals.js +0 -67
  187. package/dist/modules/groups/join-proposals.js.map +0 -1
  188. package/dist/modules/groups/join-requests.service.d.ts +0 -81
  189. package/dist/modules/groups/join-requests.service.d.ts.map +0 -1
  190. package/dist/modules/groups/join-requests.service.js +0 -135
  191. package/dist/modules/groups/join-requests.service.js.map +0 -1
  192. package/dist/modules/mcp/mcp-session-store.d.ts +0 -74
  193. package/dist/modules/mcp/mcp-session-store.d.ts.map +0 -1
  194. package/dist/modules/mcp/mcp-session-store.js +0 -131
  195. package/dist/modules/mcp/mcp-session-store.js.map +0 -1
  196. package/dist/modules/workflow/file-change-notifier.d.ts +0 -38
  197. package/dist/modules/workflow/file-change-notifier.d.ts.map +0 -1
  198. package/dist/modules/workflow/file-change-notifier.js +0 -22
  199. package/dist/modules/workflow/file-change-notifier.js.map +0 -1
  200. package/dist/modules/workflow/git/branch-name.d.ts +0 -10
  201. package/dist/modules/workflow/git/branch-name.d.ts.map +0 -1
  202. package/dist/modules/workflow/git/branch-name.js +0 -76
  203. package/dist/modules/workflow/git/branch-name.js.map +0 -1
  204. package/dist/modules/workflow/git/clone-config.d.ts +0 -61
  205. package/dist/modules/workflow/git/clone-config.d.ts.map +0 -1
  206. package/dist/modules/workflow/git/clone-config.js +0 -69
  207. package/dist/modules/workflow/git/clone-config.js.map +0 -1
  208. package/dist/modules/workflow/git/mutex.d.ts +0 -11
  209. package/dist/modules/workflow/git/mutex.d.ts.map +0 -1
  210. package/dist/modules/workflow/git/mutex.js +0 -23
  211. package/dist/modules/workflow/git/mutex.js.map +0 -1
  212. package/dist/modules/workflow/locking-filesystem.d.ts +0 -137
  213. package/dist/modules/workflow/locking-filesystem.d.ts.map +0 -1
  214. package/dist/modules/workflow/locking-filesystem.js +0 -553
  215. package/dist/modules/workflow/locking-filesystem.js.map +0 -1
  216. package/dist/modules/workflow/read-only-filesystem.d.ts +0 -24
  217. package/dist/modules/workflow/read-only-filesystem.d.ts.map +0 -1
  218. package/dist/modules/workflow/read-only-filesystem.js +0 -39
  219. package/dist/modules/workflow/read-only-filesystem.js.map +0 -1
  220. package/dist/modules/workflow/workflow.errors.d.ts +0 -197
  221. package/dist/modules/workflow/workflow.errors.d.ts.map +0 -1
  222. package/dist/modules/workflow/workflow.errors.js +0 -298
  223. package/dist/modules/workflow/workflow.errors.js.map +0 -1
  224. package/dist/modules/workspace/bevel-ignore.d.ts +0 -30
  225. package/dist/modules/workspace/bevel-ignore.d.ts.map +0 -1
  226. package/dist/modules/workspace/bevel-ignore.js +0 -61
  227. package/dist/modules/workspace/bevel-ignore.js.map +0 -1
  228. package/dist/modules/workspace/kb-seed.interface.d.ts +0 -36
  229. package/dist/modules/workspace/kb-seed.interface.d.ts.map +0 -1
  230. package/dist/modules/workspace/kb-seed.interface.js +0 -2
  231. package/dist/modules/workspace/kb-seed.interface.js.map +0 -1
  232. package/dist/modules/workspace/kb-seed.service.d.ts +0 -125
  233. package/dist/modules/workspace/kb-seed.service.d.ts.map +0 -1
  234. package/dist/modules/workspace/kb-seed.service.js +0 -535
  235. package/dist/modules/workspace/kb-seed.service.js.map +0 -1
  236. package/dist/modules/workspace/plugins-migration.d.ts +0 -50
  237. package/dist/modules/workspace/plugins-migration.d.ts.map +0 -1
  238. package/dist/modules/workspace/plugins-migration.js +0 -379
  239. package/dist/modules/workspace/plugins-migration.js.map +0 -1
  240. package/dist/shared/fs-errors.d.ts +0 -8
  241. package/dist/shared/fs-errors.d.ts.map +0 -1
  242. package/dist/shared/fs-errors.js +0 -11
  243. package/dist/shared/fs-errors.js.map +0 -1
  244. package/dist/shared/fs-walk.d.ts +0 -23
  245. package/dist/shared/fs-walk.d.ts.map +0 -1
  246. package/dist/shared/fs-walk.js +0 -42
  247. package/dist/shared/fs-walk.js.map +0 -1
  248. package/dist/shared/hash-email.d.ts +0 -11
  249. package/dist/shared/hash-email.d.ts.map +0 -1
  250. package/dist/shared/hash-email.js +0 -14
  251. package/dist/shared/hash-email.js.map +0 -1
  252. package/dist/shared/kb-walk.d.ts +0 -52
  253. package/dist/shared/kb-walk.d.ts.map +0 -1
  254. package/dist/shared/kb-walk.js +0 -76
  255. package/dist/shared/kb-walk.js.map +0 -1
@@ -1,288 +0,0 @@
1
- /**
2
- * Group provisioning — the ONE privileged door for bringing a `Groups/<name>/`
3
- * folder into existence, and (via {@link GroupProvisionService.deleteGroup})
4
- * for taking one back out of it.
5
- *
6
- * Creating a group is the single operation the access model cannot govern
7
- * from inside: the folder that will carry the rules does not exist yet, and
8
- * the root's own rule (`write: Admin`) says no. The old answer was a
9
- * carve-out inside the generic write gate — every write path could claim an
10
- * unused name under `Groups/`, and the gate had to re-derive "is this that
11
- * one blessed case?" on every check. This service replaces that: the generic
12
- * gate is uniformly strict again, and the privilege lives here, named,
13
- * behind its own endpoint.
14
- *
15
- * What a provision IS: one exclusive write of the new folder's `access.md`
16
- * (flag `wx`, so two concurrent creators race for the fs, not for the
17
- * overwrite), then one SYNCHRONOUS commit+push. Synchronous on purpose — the
18
- * write gate reads rules at HEAD, so a creation whose access.md were still
19
- * sitting in the async commit queue would 403 the very next thing its
20
- * creator does (writing the first skill into it).
21
- *
22
- * Two shapes, two templates:
23
- *
24
- * - A NAMED group: discoverable by design. The access.md's own frontmatter
25
- * reads `everyone` — anyone may open the FILE, see the group listed, and
26
- * ask to join — while the BODY (the folder's actual rules) names only
27
- * the creator under read, write and owner.
28
- * - A PERSONAL folder (`personal-<user-id>`): private by design. No
29
- * frontmatter grant at all, so nobody else can even see it exists; the
30
- * body names its owner. Created lazily (ensure semantics) on the first
31
- * personal skill.
32
- */
33
- import fs from 'node:fs/promises';
34
- import path from 'node:path';
35
- import { DEFAULT_BRANCH, GROUPS_DIR, PERSONAL_GROUP_PREFIX, isPersonalGroupFolder, personalGroupFolderName, } from '@bevel-software/platform-shared';
36
- import { creatorPrincipal } from '../access/creator-access.js';
37
- import { spliceGrant } from '../access/access-splice.js';
38
- import { WorkspaceMutex } from '../workflow/git/mutex.js';
39
- /** A refusal the route can pass through: message + HTTP status. */
40
- export class GroupProvisionError extends Error {
41
- status;
42
- constructor(message, status) {
43
- super(message);
44
- this.status = status;
45
- this.name = 'GroupProvisionError';
46
- }
47
- }
48
- export class GroupProvisionService {
49
- workspaceService;
50
- commits;
51
- accessControl;
52
- kbDirName;
53
- events;
54
- /**
55
- * Serialises creations by NORMALIZED (lowercased) folder name. The wx write
56
- * arbitrates same-path races, but on a case-sensitive filesystem `GTM` and
57
- * `gtm` are different paths — without this lock two concurrent requests
58
- * could both pass the collision check and both land, breaking the
59
- * case-insensitive uniqueness the check promises.
60
- */
61
- creations = new WorkspaceMutex();
62
- constructor(workspaceService, commits, accessControl, kbDirName, events) {
63
- this.workspaceService = workspaceService;
64
- this.commits = commits;
65
- this.accessControl = accessControl;
66
- this.kbDirName = kbDirName;
67
- this.events = events;
68
- }
69
- /**
70
- * Create `Groups/<name>/` for `user`. Throws `GroupProvisionError` 422 on a
71
- * name the filesystem or the model cannot carry, 409 when the name is taken
72
- * (case-insensitively — the workspaces live on case-insensitive
73
- * filesystems too, where `GTM` and `gtm` are one folder).
74
- */
75
- async createGroup(user, rawName) {
76
- const name = rawName.trim();
77
- if (!name)
78
- throw new GroupProvisionError('A group needs a name.', 422);
79
- // eslint-disable-next-line no-control-regex -- NUL and control chars are
80
- // exactly what a filesystem path cannot carry; refusing them here keeps
81
- // the refusal a 422 instead of the fs layer's 500.
82
- if (/[/\\\u0000-\u001f\u007f]/.test(name) || name === '.' || name === '..' || name.startsWith('.')) {
83
- throw new GroupProvisionError('A group name can\'t contain / or \\ or control characters, or start with a dot.', 422);
84
- }
85
- if (name.toLowerCase().startsWith(PERSONAL_GROUP_PREFIX)) {
86
- // Reserved: the personal-folder namespace. A group squatting there
87
- // would collide with somebody's future personal folder.
88
- throw new GroupProvisionError(`Group names starting with "${PERSONAL_GROUP_PREFIX}" are reserved.`, 422);
89
- }
90
- return this.creations.run(`group:${name.toLowerCase()}`, async () => {
91
- const existing = await this.existingFolder(name);
92
- if (existing !== null) {
93
- throw new GroupProvisionError(`A group named "${existing}" already exists.`, 409);
94
- }
95
- await this.provision(user, name, groupAccessMd(user));
96
- return { folder: name, created: true };
97
- });
98
- }
99
- /**
100
- * Ensure the caller's personal folder exists — idempotent, keyed to the
101
- * stable user id. Returns `created: false` when it is already there.
102
- */
103
- async ensurePersonalGroup(user) {
104
- const folder = personalGroupFolderName(user.id);
105
- return this.creations.run(`group:${folder.toLowerCase()}`, async () => {
106
- if ((await this.existingFolder(folder)) !== null) {
107
- return { folder, created: false };
108
- }
109
- try {
110
- await this.provision(user, folder, personalAccessMd(user));
111
- }
112
- catch (err) {
113
- // ENSURE semantics even under a race the lock cannot see (another
114
- // process, a checkout that appeared between check and write): the
115
- // folder existing is this method's success case, never its error.
116
- if (err instanceof GroupProvisionError && err.status === 409) {
117
- return { folder, created: false };
118
- }
119
- throw err;
120
- }
121
- return { folder, created: true };
122
- });
123
- }
124
- /**
125
- * Delete `Groups/<name>/` — the whole folder, its skills and tools
126
- * included, in ONE commit. MECHANISM only: the route owns the
127
- * authorization (the caller must hold the `owner` verb on the folder;
128
- * this service never re-derives it), exactly as `createGroup` leaves
129
- * "any signed-in user" to its endpoint.
130
- *
131
- * Shape mirrors a provision run in reverse, with the same failure
132
- * contract: the folder is PARKED (renamed to a dot-prefixed sibling the
133
- * scanners ignore) rather than removed, the deletion is committed
134
- * synchronously, and only a landed commit lets the parked bytes go. A
135
- * refused commit renames the folder back, so a failed delete leaves the
136
- * group exactly as it was — never half-gone on disk while origin still
137
- * carries it.
138
- *
139
- * Serialised on the same per-name lock creations use, so a delete can
140
- * never interleave with a re-creation of the same name.
141
- */
142
- async deleteGroup(user, rawName) {
143
- const name = rawName.trim();
144
- if (!name)
145
- throw new GroupProvisionError('A group needs a name.', 422);
146
- if (isPersonalGroupFolder(name)) {
147
- // Personal folders are not groups (the catalog never lists them), and
148
- // nobody deletes somebody's private shelf through the group door.
149
- throw new GroupProvisionError('Unknown group', 404);
150
- }
151
- return this.creations.run(`group:${name.toLowerCase()}`, async () => {
152
- const existing = await this.existingFolder(name);
153
- // Exact match only — the catalog hands the route the on-disk casing,
154
- // so a mismatch means the group is gone (or was never there).
155
- if (existing !== name)
156
- throw new GroupProvisionError('Unknown group', 404);
157
- const wsId = await this.readyWorkspaceId();
158
- const wsDir = await this.workspaceService.getWorkspacePath(wsId);
159
- const groupsDir = path.join(wsDir, this.kbDirName, GROUPS_DIR);
160
- const folderDir = path.join(groupsDir, name);
161
- // Dot-prefixed ⇒ invisible to the group scanner and the collision
162
- // check for the whole window the commit is in flight.
163
- const parkedDir = path.join(groupsDir, `.${name}.deleting`);
164
- await fs.rm(parkedDir, { recursive: true, force: true }); // a stale park from a crashed run
165
- await fs.rename(folderDir, parkedDir);
166
- try {
167
- // Inline and `systemAuthorized`, for `provision`'s reasons in
168
- // reverse: the gate reads rules at HEAD, and the endpoint has
169
- // already authorized the delete (owner verdict), so the per-user
170
- // push gate — which would re-read an access.md this very commit
171
- // removes — is skipped for exactly this commit. `commitFile` is
172
- // path-scoped (`git add -- <path>`), and a folder path stages every
173
- // deletion under it: one commit, one removed group.
174
- await this.commits.runPendingCommit(wsId, DEFAULT_BRANCH, `${this.kbDirName}/${GROUPS_DIR}/${name}`, user, { systemAuthorized: true });
175
- }
176
- catch (err) {
177
- // The commit did not land: put the folder back, so a failed delete
178
- // is a no-op rather than a group that exists at origin but not here.
179
- try {
180
- await fs.rename(parkedDir, folderDir);
181
- }
182
- catch {
183
- /* the park survives for the next attempt — better than masking the real error */
184
- }
185
- throw err;
186
- }
187
- await fs.rm(parkedDir, { recursive: true, force: true }).catch(() => { });
188
- // The folder's rules left the access model — drop the resolver cache
189
- // so the very next check runs against a tree without them.
190
- this.accessControl.invalidate(wsId);
191
- this.events?.emit({ kind: 'fs-tree-changed', workspaceId: wsId, branch: DEFAULT_BRANCH });
192
- });
193
- }
194
- /** The taken name (in its on-disk casing) colliding with `name`, or null. */
195
- async existingFolder(name) {
196
- const wsId = await this.readyWorkspaceId();
197
- const wsDir = await this.workspaceService.getWorkspacePath(wsId);
198
- let children;
199
- try {
200
- children = await fs.readdir(path.join(wsDir, this.kbDirName, GROUPS_DIR));
201
- }
202
- catch {
203
- return null; // no Groups/ root yet — nothing can collide
204
- }
205
- const lower = name.toLowerCase();
206
- return children.find((c) => c.toLowerCase() === lower) ?? null;
207
- }
208
- async provision(user, folder, accessMd) {
209
- const wsId = await this.readyWorkspaceId();
210
- const wsRelPath = `${this.kbDirName}/${GROUPS_DIR}/${folder}/access.md`;
211
- try {
212
- // Exclusive create — the fs is the arbiter of a same-name race, not
213
- // the (stale-able) existence check above.
214
- await this.workspaceService.writeFile(wsId, wsRelPath, accessMd, { failIfExists: true });
215
- }
216
- catch (err) {
217
- if (err.status === 409) {
218
- throw new GroupProvisionError(`A group named "${folder}" already exists.`, 409);
219
- }
220
- throw err;
221
- }
222
- try {
223
- // Inline, not enqueued: the gate reads rules at HEAD, so the folder is
224
- // only real once this commit lands. `runPendingCommit` is the same
225
- // commit+push (with pull-rebase recovery) the queue worker runs.
226
- // `systemAuthorized`: the push gate reads access at origin, where this
227
- // folder does not exist yet and the root says `write: Admin` — the very
228
- // rule this endpoint exists to carve through. The endpoint has already
229
- // authorized the write (any signed-in user, unused name, exclusive
230
- // create), so the per-user gate is skipped for exactly this commit.
231
- await this.commits.runPendingCommit(wsId, DEFAULT_BRANCH, wsRelPath, user, {
232
- systemAuthorized: true,
233
- });
234
- }
235
- catch (err) {
236
- // The commit did not land: roll the seeded file back off the disk,
237
- // best-effort, so a retry doesn't find a half-made group and report
238
- // "already exists" for something that never got committed. Only OUR
239
- // file and — when that leaves it empty — the folder; never recursive,
240
- // so a concurrent writer's bytes can't be collateral.
241
- try {
242
- const wsDir = await this.workspaceService.getWorkspacePath(wsId);
243
- const folderDir = path.join(wsDir, this.kbDirName, GROUPS_DIR, folder);
244
- await fs.rm(path.join(folderDir, 'access.md'), { force: true });
245
- await fs.rmdir(folderDir).catch(() => { });
246
- }
247
- catch {
248
- /* leave it for the next attempt's wx conflict — better than masking the real error */
249
- }
250
- throw err;
251
- }
252
- // The folder's rules changed the access model — drop the resolver cache
253
- // so the very next check (the creator's first skill write) sees them.
254
- this.accessControl.invalidate(wsId);
255
- this.events?.emit({ kind: 'fs-tree-changed', workspaceId: wsId, branch: DEFAULT_BRANCH });
256
- }
257
- async readyWorkspaceId() {
258
- const ws = await this.workspaceService.getOrCreateForBranch(DEFAULT_BRANCH);
259
- return ws.id;
260
- }
261
- }
262
- /**
263
- * A named group's access.md: discoverable file (frontmatter `read: everyone`
264
- * — anyone may see the group listed and ask to join), creator-run folder
265
- * (body read/write/owner name the creator). The `read: []` placeholder makes
266
- * the body parse as rules from the first byte, so every later splice targets
267
- * the body rather than the frontmatter.
268
- */
269
- export function groupAccessMd(creator) {
270
- return withCreatorGrants('---\nread:\n - everyone\n---\nread: []\n', creator);
271
- }
272
- /**
273
- * A personal folder's access.md: PRIVATE — no frontmatter grant, so the
274
- * folder is invisible to everyone but its owner; the body names the owner
275
- * under read, write and owner.
276
- */
277
- export function personalAccessMd(creator) {
278
- return withCreatorGrants('read: []\n', creator);
279
- }
280
- function withCreatorGrants(base, creator) {
281
- const principal = creatorPrincipal(creator);
282
- let out = base;
283
- for (const verb of ['read', 'write', 'owner']) {
284
- out = spliceGrant(out, verb, principal, { allowScalar: false, target: 'folder' }).text;
285
- }
286
- return out;
287
- }
288
- //# sourceMappingURL=group-provision.service.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"group-provision.service.js","sourceRoot":"","sources":["../../../src/modules/groups/group-provision.service.ts"],"names":[],"mappings":"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"}
@@ -1,106 +0,0 @@
1
- /**
2
- * Groups — the folders under `Groups/` that carry a team's skills and the
3
- * tools those skills need. A group is a FOLDER, never a role: one access
4
- * boundary, one place, derived from the path exactly the way `groupOfPath`
5
- * derives it.
6
- *
7
- * A group EXISTS exactly when its folder carries an `access.md` — the file
8
- * the provisioning endpoint seeds. A bare directory under `Groups/` is not a
9
- * group (git cannot record an empty folder, so deleted groups leave ghost
10
- * directories on live checkouts).
11
- *
12
- * Enumeration then follows the KB's read model with NO special cases: an
13
- * existing group appears for a caller exactly when the access resolution says
14
- * something about it —
15
- *
16
- * - MEMBER: the caller can read the folder itself (`canRead`).
17
- * - MANAGER: the caller can write the folder's `access.md` (`canWrite`,
18
- * admin-rescued).
19
- * - DISCOVERABLE: the caller can read the `access.md` FILE — which, in the
20
- * new body-governed format, is granted by the file's own
21
- * `read: everyone` frontmatter (see `accessMdSelfEntries`). The group
22
- * shows locked, and the caller may ask to join.
23
- *
24
- * A group where all three verdicts are false is ABSENT from the response —
25
- * name, counts and principals never leave the backend. Making a group secret
26
- * is therefore an ordinary access edit: drop the `everyone` grant from its
27
- * access.md frontmatter.
28
- *
29
- * Requesting access is a CHANGE REQUEST, not a bespoke system: the join
30
- * route commits "add me to the body's `read:` list" on a draft branch and
31
- * opens a CR that the folder's writers approve by merging (or by granting
32
- * via Manage access, which makes the CR moot). To agents these are plain
33
- * change requests; only the UI dresses them up.
34
- */
35
- /** Access-rule principals as the resolver hands them back. */
36
- export interface ResolvedPrincipals {
37
- roles: string[];
38
- users: {
39
- name: string;
40
- email: string;
41
- }[];
42
- }
43
- export interface ResolvedReaders extends ResolvedPrincipals {
44
- restricted: boolean;
45
- }
46
- /** One group as `GET /api/groups` reports it, resolved for ONE caller. */
47
- export interface GroupSummary {
48
- /** Group folder name, e.g. `GTM`. */
49
- name: string;
50
- /** Repo-relative constituent folders, e.g. `['Groups/GTM']`. */
51
- folders: string[];
52
- /**
53
- * Per-caller: the caller can read the FOLDER (membership). Every returned
54
- * group has at least one of `canRead` / `canWrite` / discoverability; a
55
- * group with none is not returned at all.
56
- */
57
- canRead: boolean;
58
- /**
59
- * Per-caller; true ⇒ may manage access. Resolved as write on the folder's
60
- * `access.md`, which admin-rescues — so a platform Admin locked OUT of
61
- * reading still sees the group and gets the self-service way back in.
62
- */
63
- canWrite: boolean;
64
- /**
65
- * Per-caller: the caller holds the `owner` verb on the FOLDER — resolved
66
- * from the `owner:` lists alone, no admin rescue. Deleting the group is the
67
- * owner's verb (the DELETE route enforces the same verdict); managers who
68
- * merely write the access.md do not get it.
69
- */
70
- isOwner: boolean;
71
- /** Caller-INDEPENDENT total (the group's whole content, not the caller's slice). */
72
- skillCount: number;
73
- toolCount: number;
74
- /** For display: "Run by …" (fallback chain: owners → writers → 'the workspace admins'). */
75
- owners: ResolvedPrincipals;
76
- writers: ResolvedPrincipals;
77
- readers: ResolvedReaders;
78
- /**
79
- * The caller has an OPEN join change request for this group (their
80
- * deterministic join branch has an open CR). Always false for a member.
81
- */
82
- hasRequested: boolean;
83
- /** The open join CR's number when `hasRequested` (deep-links the UI). */
84
- requestNumber: number | null;
85
- }
86
- /**
87
- * The caller-INDEPENDENT slice of a group — what `catalog()` computes once and
88
- * caches. The per-caller verdicts (`canRead`/`canWrite`, and whether the group
89
- * appears at all) are resolved per request in the route.
90
- */
91
- export interface GroupCatalogEntry {
92
- name: string;
93
- folders: string[];
94
- skillCount: number;
95
- toolCount: number;
96
- owners: ResolvedPrincipals;
97
- writers: ResolvedPrincipals;
98
- readers: ResolvedReaders;
99
- }
100
- export interface IGroupIndexService {
101
- /** Caller-independent catalog part (folders, counts, principals) — cached 60s. */
102
- catalog(): Promise<GroupCatalogEntry[]>;
103
- /** Drop the cached catalog (call after a default-branch change under a group root). */
104
- invalidate(): void;
105
- }
106
- //# sourceMappingURL=groups.contract.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"groups.contract.d.ts","sourceRoot":"","sources":["../../../src/modules/groups/groups.contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,8DAA8D;AAC9D,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,eAAgB,SAAQ,kBAAkB;IACzD,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,oFAAoF;IACpF,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,2FAA2F;IAC3F,MAAM,EAAE,kBAAkB,CAAC;IAC3B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,OAAO,EAAE,eAAe,CAAC;IACzB;;;OAGG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB,yEAAyE;IACzE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,OAAO,EAAE,eAAe,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,kFAAkF;IAClF,OAAO,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC;IACxC,uFAAuF;IACvF,UAAU,IAAI,IAAI,CAAC;CACpB"}
@@ -1,36 +0,0 @@
1
- /**
2
- * Groups — the folders under `Groups/` that carry a team's skills and the
3
- * tools those skills need. A group is a FOLDER, never a role: one access
4
- * boundary, one place, derived from the path exactly the way `groupOfPath`
5
- * derives it.
6
- *
7
- * A group EXISTS exactly when its folder carries an `access.md` — the file
8
- * the provisioning endpoint seeds. A bare directory under `Groups/` is not a
9
- * group (git cannot record an empty folder, so deleted groups leave ghost
10
- * directories on live checkouts).
11
- *
12
- * Enumeration then follows the KB's read model with NO special cases: an
13
- * existing group appears for a caller exactly when the access resolution says
14
- * something about it —
15
- *
16
- * - MEMBER: the caller can read the folder itself (`canRead`).
17
- * - MANAGER: the caller can write the folder's `access.md` (`canWrite`,
18
- * admin-rescued).
19
- * - DISCOVERABLE: the caller can read the `access.md` FILE — which, in the
20
- * new body-governed format, is granted by the file's own
21
- * `read: everyone` frontmatter (see `accessMdSelfEntries`). The group
22
- * shows locked, and the caller may ask to join.
23
- *
24
- * A group where all three verdicts are false is ABSENT from the response —
25
- * name, counts and principals never leave the backend. Making a group secret
26
- * is therefore an ordinary access edit: drop the `everyone` grant from its
27
- * access.md frontmatter.
28
- *
29
- * Requesting access is a CHANGE REQUEST, not a bespoke system: the join
30
- * route commits "add me to the body's `read:` list" on a draft branch and
31
- * opens a CR that the folder's writers approve by merging (or by granting
32
- * via Manage access, which makes the CR moot). To agents these are plain
33
- * change requests; only the UI dresses them up.
34
- */
35
- export {};
36
- //# sourceMappingURL=groups.contract.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"groups.contract.js","sourceRoot":"","sources":["../../../src/modules/groups/groups.contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG"}
@@ -1,42 +0,0 @@
1
- import express from 'express';
2
- import '../auth/auth.middleware.js';
3
- import { type AuthUser, type IWorkflowService } from '@bevel-software/platform-shared';
4
- import type { IAccessControl } from '../access/access-control.interface.js';
5
- import type { WorkspaceService } from '../workspace/workspace.service.js';
6
- import { type GroupProvisionService } from './group-provision.service.js';
7
- import type { JoinRequestsService } from './join-requests.service.js';
8
- import type { IGroupIndexService } from './groups.contract.js';
9
- /**
10
- * Browser-facing (JWT) group routes, mounted behind `authMiddleware`:
11
- *
12
- * GET /api/groups → { groups: GroupSummary[] }
13
- * DELETE /api/groups/:name → { ok } (owners)
14
- * POST /api/groups/:name/join-request → { ok, number } (opens a CR)
15
- * GET /api/groups/:name/join-requests → { requests } (managers)
16
- * POST /api/groups/:name/join-requests/:n/reconcile → { closed }
17
- *
18
- * Enumeration is three verdicts per (caller, group), all of them ordinary
19
- * access resolution — no special cases, no side tables:
20
- *
21
- * member canRead on the group FOLDER
22
- * manager canWrite on the folder's access.md (admin-rescued)
23
- * discoverable canRead on the access.md FILE — in the body-governed format
24
- * its own `read: everyone` frontmatter grants this
25
- *
26
- * All three false ⇒ the group is absent from the response entirely.
27
- *
28
- * A join request is a plain change request whose branch edits the group's
29
- * `access.md`. Managers do NOT merge it: they read its individual proposals
30
- * (see `join-proposals.ts`), grant the ones they accept through the ordinary
31
- * access path, and the request retires itself once its rules are a subset of
32
- * the default branch's — reconciled here, lazily on listing and eagerly right
33
- * after a grant. Nothing about the lifecycle is stored; it is derived from
34
- * two copies of one file.
35
- *
36
- * Auth gating is explicit and uniform: `authMiddleware` at the mount, PLUS a
37
- * `req.userEmail` check in every handler (the skills-routes pattern) so a
38
- * middleware change can never silently un-gate one. Nothing here is reachable
39
- * with an agent connection key or a manual-auth bearer.
40
- */
41
- export declare function createGroupsRoutes(groupIndex: IGroupIndexService, accessControl: IAccessControl, workflow: IWorkflowService, workspaceService: WorkspaceService, joinRequests: JoinRequestsService, provision: GroupProvisionService, kbDirName: string, resolveUser: (req: express.Request) => Promise<AuthUser | null>): express.Router;
42
- //# sourceMappingURL=groups.routes.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"groups.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/groups/groups.routes.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,4BAA4B,CAAC;AACpC,OAAO,EAGL,KAAK,QAAQ,EAEb,KAAK,gBAAgB,EACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAG5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,OAAO,EAAuB,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC/F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,KAAK,EAGV,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,kBAAkB,EAC9B,aAAa,EAAE,cAAc,EAC7B,QAAQ,EAAE,gBAAgB,EAC1B,gBAAgB,EAAE,gBAAgB,EAClC,YAAY,EAAE,mBAAmB,EACjC,SAAS,EAAE,qBAAqB,EAChC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,GAC9D,OAAO,CAAC,MAAM,CAiXhB"}