@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
@@ -0,0 +1,49 @@
1
+ import express from 'express';
2
+ import type { IAdminAccessService } from '../admin/admin.interface.js';
3
+ import { DeploymentSettingsService } from './deployment-settings.service.js';
4
+ import '../auth/auth.middleware.js';
5
+ /**
6
+ * First-run setup — the deployment's own configuration, for the things that
7
+ * used to be environment-only.
8
+ *
9
+ * WHY THESE ROUTES ARE AUTHENTICATED BUT NOT KB-DEPENDENT: the whole point is
10
+ * to be reachable on a deployment that has no knowledge base yet. That works
11
+ * because the bootstrap admin (`ADMIN_EMAIL`) is recognised without consulting
12
+ * `roles.yaml` — `AdminAccessService` short-circuits on it before any clone is
13
+ * attempted — so the one person who can finish setup can always sign in, and
14
+ * nobody else is let near it.
15
+ */
16
+ export declare function createSetupRoutes(settings: DeploymentSettingsService, adminAccess: IAdminAccessService): express.Router;
17
+ /**
18
+ * Whether the STORED configuration answers everything the deployment needs.
19
+ *
20
+ * The KB needs a URL and a token — the username and the directory name both
21
+ * have working defaults, so neither can block a start. The branch model has no
22
+ * default ON PURPOSE: guessing `main` would silently point a deployment at the
23
+ * wrong branch and let the protected-branch guards apply to nothing, which is
24
+ * worse than asking.
25
+ *
26
+ * SSO is deliberately absent. It is configuration, not a prerequisite — a
27
+ * deployment signs in perfectly well without it, and gating on it would lock
28
+ * an admin out of the screen where they would set it up.
29
+ */
30
+ export declare function settingsAnswered(settings: DeploymentSettingsService): boolean;
31
+ /**
32
+ * Whether THIS PROCESS can actually serve — which is not the same question,
33
+ * and conflating them opened the gate onto a broken app.
34
+ *
35
+ * The branch model is applied once, during boot: services take `DEFAULT_BRANCH`
36
+ * at construction and the browser is served it before it renders. Saving it
37
+ * therefore answers the question without changing the answer this process
38
+ * holds — so a deployment configured through the setup screen reported itself
39
+ * complete while every workspace call still failed with
40
+ * `Invalid branch name ""`.
41
+ *
42
+ * Requiring the model to be IN EFFECT keeps the gate shut until the restart
43
+ * that puts it there. {@link awaitingRestart} is what tells the screen to ask
44
+ * for one rather than claim a field is missing.
45
+ */
46
+ export declare function isComplete(settings: DeploymentSettingsService): boolean;
47
+ /** Answered, but not yet in effect: everything is stored, the process is stale. */
48
+ export declare function awaitingRestart(settings: DeploymentSettingsService): boolean;
49
+ //# sourceMappingURL=setup.routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/settings/setup.routes.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAG9B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EACL,yBAAyB,EAG1B,MAAM,kCAAkC,CAAC;AAM1C,OAAO,4BAA4B,CAAC;AAIpC;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,yBAAyB,EACnC,WAAW,EAAE,mBAAmB,GAC/B,OAAO,CAAC,MAAM,CAqNhB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,OAAO,CAQ7E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,yBAAyB,GAAG,OAAO,CAEvE;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,yBAAyB,GAAG,OAAO,CAE5E"}
@@ -0,0 +1,292 @@
1
+ import express from 'express';
2
+ import { execFile } from 'node:child_process';
3
+ import { promisify } from 'node:util';
4
+ import { SettingsValidationError, validateHttpsRemote, } from './deployment-settings.service.js';
5
+ import { configureBranchModel, isBranchModelConfigured, validateBranchModel, } from '@bevel-software/platform-shared';
6
+ import '../auth/auth.middleware.js'; // Express Request augmentation
7
+ const execFileAsync = promisify(execFile);
8
+ /**
9
+ * First-run setup — the deployment's own configuration, for the things that
10
+ * used to be environment-only.
11
+ *
12
+ * WHY THESE ROUTES ARE AUTHENTICATED BUT NOT KB-DEPENDENT: the whole point is
13
+ * to be reachable on a deployment that has no knowledge base yet. That works
14
+ * because the bootstrap admin (`ADMIN_EMAIL`) is recognised without consulting
15
+ * `roles.yaml` — `AdminAccessService` short-circuits on it before any clone is
16
+ * attempted — so the one person who can finish setup can always sign in, and
17
+ * nobody else is let near it.
18
+ */
19
+ export function createSetupRoutes(settings, adminAccess) {
20
+ const router = express.Router();
21
+ const requireAdmin = async (req, res, next) => {
22
+ if (!(await adminAccess.isAdmin(req.userEmail))) {
23
+ res.status(403).json({ error: 'Admins only' });
24
+ return;
25
+ }
26
+ next();
27
+ };
28
+ /**
29
+ * Whether the deployment is usable, and — for an admin — what is missing.
30
+ *
31
+ * Deliberately readable by ANY signed-in user, because everyone needs the
32
+ * answer: a non-admin who arrives mid-setup gets told the deployment is
33
+ * still being configured instead of a broken file tree. Only the admin
34
+ * branch carries the settings themselves, and no branch ever carries a
35
+ * secret's value.
36
+ */
37
+ router.get('/setup/status', async (req, res) => {
38
+ const complete = isComplete(settings);
39
+ if (!(await adminAccess.isAdmin(req.userEmail))) {
40
+ res.json({ complete, isAdmin: false });
41
+ return;
42
+ }
43
+ res.json({
44
+ complete,
45
+ awaitingRestart: awaitingRestart(settings),
46
+ isAdmin: true,
47
+ settings: settings.describe(),
48
+ });
49
+ });
50
+ /**
51
+ * Save settings. Validated and written as ONE batch — a repository URL
52
+ * stored without the token that reads it is a deployment that fails at its
53
+ * first clone, so a partial write is never better than none.
54
+ */
55
+ router.post('/setup/settings', requireAdmin, async (req, res) => {
56
+ const body = (req.body ?? {});
57
+ const entries = {};
58
+ for (const [key, value] of Object.entries(body.settings ?? {})) {
59
+ if (typeof value !== 'string') {
60
+ res.status(400).json({ error: `"${key}" must be a string.` });
61
+ return;
62
+ }
63
+ entries[key] = value;
64
+ }
65
+ try {
66
+ const { restartRequired } = await settings.save(entries, req.userId ?? null);
67
+ /**
68
+ * Apply the branch model to THIS process, so pressing Save finishes
69
+ * setup instead of asking for a restart.
70
+ *
71
+ * Only when it was not already configured — reconfiguring a live
72
+ * deployment mid-flight would swap the branches out from under sessions
73
+ * that are using them, which is a different and much less welcome
74
+ * feature. Going from "none" to "some" has nobody to disturb: the app is
75
+ * gated shut until exactly this moment.
76
+ *
77
+ * The services that need it read it when they use it rather than
78
+ * capturing it at construction, which is what makes applying it here
79
+ * enough.
80
+ */
81
+ if (!isBranchModelConfigured()) {
82
+ const model = {
83
+ defaultBranch: settings.resolve('defaultBranch'),
84
+ protectedBranches: settings.resolve('protectedBranches'),
85
+ };
86
+ if (!validateBranchModel(model))
87
+ configureBranchModel(model);
88
+ }
89
+ res.json({
90
+ ok: true,
91
+ restartRequired,
92
+ complete: isComplete(settings),
93
+ awaitingRestart: awaitingRestart(settings),
94
+ settings: settings.describe(),
95
+ });
96
+ }
97
+ catch (err) {
98
+ if (err instanceof SettingsValidationError) {
99
+ res.status(400).json({ error: 'Some settings need fixing.', problems: err.problems });
100
+ return;
101
+ }
102
+ // Logged in full, returned generic: a driver message here would hand back
103
+ // the schema or the connection string.
104
+ console.error('[setup] save failed:', err instanceof Error ? err.message : String(err));
105
+ res.status(500).json({ error: 'Could not save these settings.' });
106
+ }
107
+ });
108
+ /**
109
+ * Try the credentials against the real remote, BEFORE anything is saved.
110
+ *
111
+ * This is the reason the screen is worth more than the environment variables
112
+ * it replaces. `ls-remote` is the cheapest operation that proves all three
113
+ * values at once — the URL resolves, the token authenticates, and the
114
+ * username is the one this host expects — and it answers in a second instead
115
+ * of surfacing as a failed clone at some later, unrelated moment.
116
+ *
117
+ * Values are taken from the request when supplied so an admin can test what
118
+ * they typed rather than what is stored, and fall back to what is in effect
119
+ * (which is how "test the token I saved last week" works).
120
+ */
121
+ router.post('/setup/test-connection', requireAdmin, async (req, res) => {
122
+ const body = (req.body ?? {});
123
+ const supplied = (key) => {
124
+ const value = body[key];
125
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
126
+ };
127
+ const url = supplied('kbRepoUrl') ?? settings.resolve('kbRepoUrl');
128
+ // Unlike the token, the username is not a secret and carries a default —
129
+ // it is which name the host expects beside the token, not a credential.
130
+ const username = supplied('gitUsername') ?? (settings.resolve('gitUsername') || 'x-access-token');
131
+ if (!url) {
132
+ res.status(400).json({ ok: false, error: 'Enter the repository URL first.' });
133
+ return;
134
+ }
135
+ /**
136
+ * THE CONFIGURED TOKEN ONLY EVER GOES TO THE CONFIGURED REPOSITORY.
137
+ *
138
+ * The stored token is deliberately unreadable — `describe()` omits it, so
139
+ * an admin can replace it but never see it. Falling back to it for whatever
140
+ * URL the request names would hand that value straight back: point the test
141
+ * at a host you control, read it out of the request. Admin-gated, but the
142
+ * whole point of not returning it is that being an admin is not the same as
143
+ * being allowed to hold it.
144
+ *
145
+ * So a request that names a DIFFERENT repository has to bring its own
146
+ * credential. Testing what is already configured — the "does the token I
147
+ * saved last week still work?" case — is unaffected.
148
+ */
149
+ const suppliedToken = supplied('gitToken');
150
+ const testingConfiguredRepo = url === settings.resolve('kbRepoUrl');
151
+ if (!suppliedToken && !testingConfiguredRepo) {
152
+ res.status(400).json({
153
+ ok: false,
154
+ error: 'Enter the access token for that repository — the saved one is only used with the repository it was saved for.',
155
+ });
156
+ return;
157
+ }
158
+ const token = suppliedToken ?? (testingConfiguredRepo ? settings.resolve('gitToken') : '');
159
+ // The SAME rule the setting is validated by, applied before the value ever
160
+ // reaches git. Without it this endpoint is argument injection: a value
161
+ // beginning `--upload-pack=` makes git run a command of the caller's
162
+ // choosing, and `ext::sh -c …` is a transport whose entire purpose is to
163
+ // execute one. Both are admin-only, but "admin" is not "may run arbitrary
164
+ // commands as the server process".
165
+ const urlProblem = validateHttpsRemote(url);
166
+ if (urlProblem) {
167
+ res.status(400).json({ ok: false, error: urlProblem });
168
+ return;
169
+ }
170
+ if (!/^[A-Za-z0-9._-]+$/.test(username)) {
171
+ // Interpolated into the credential-helper snippet below.
172
+ res.status(400).json({ ok: false, error: 'The username contains unsupported characters.' });
173
+ return;
174
+ }
175
+ try {
176
+ // The helper reads the token from the environment at call time, so it
177
+ // never appears in argv (and so never in a process listing or a crash
178
+ // dump). Same shape the real clone uses.
179
+ const args = ['-c', 'credential.helper=', ...(token
180
+ ? ['-c', `credential.helper=!f() { echo "username=${username}"; echo "password=$BEVEL_TEST_TOKEN"; }; f`]
181
+ : []),
182
+ // `--end-of-options` on top of the validation above: belt and braces,
183
+ // so nothing that arrives here can ever be read as a flag.
184
+ 'ls-remote', '--heads', '--end-of-options', url];
185
+ const { stdout } = await execFileAsync('git', args, {
186
+ timeout: 20_000,
187
+ env: {
188
+ ...process.env,
189
+ BEVEL_TEST_TOKEN: token,
190
+ // Never let git stop for a prompt: without this a bad credential
191
+ // hangs the request until the timeout instead of failing.
192
+ GIT_TERMINAL_PROMPT: '0',
193
+ GIT_ASKPASS: 'echo',
194
+ },
195
+ });
196
+ const lines = stdout.split('\n');
197
+ // `<sha>\trefs/heads/<name>` — tag refs and the bare HEAD row are not
198
+ // branches, so they are filtered rather than sliced blindly.
199
+ const branches = lines
200
+ .filter((line) => !line.startsWith('ref:'))
201
+ .map((line) => line.split('\t')[1]?.trim())
202
+ .filter((ref) => !!ref && ref.startsWith('refs/heads/'))
203
+ .map((ref) => ref.slice('refs/heads/'.length));
204
+ // `ref: refs/heads/<name>\tHEAD` — what the remote calls its own trunk.
205
+ const defaultBranch = lines
206
+ .find((line) => line.startsWith('ref:') && line.trimEnd().endsWith('HEAD'))
207
+ ?.slice('ref: refs/heads/'.length)
208
+ .split('\t')[0]
209
+ ?.trim() || null;
210
+ res.json({
211
+ ok: true,
212
+ // An EMPTY repository is a success, not a failure — seeding one is a
213
+ // supported path, and saying "no branches yet" beats an error that
214
+ // reads like the credentials are wrong.
215
+ empty: branches.length === 0,
216
+ branches,
217
+ defaultBranch,
218
+ });
219
+ }
220
+ catch (err) {
221
+ res.status(200).json({ ok: false, error: explainGitFailure(err, token) });
222
+ }
223
+ });
224
+ return router;
225
+ }
226
+ /**
227
+ * Whether the STORED configuration answers everything the deployment needs.
228
+ *
229
+ * The KB needs a URL and a token — the username and the directory name both
230
+ * have working defaults, so neither can block a start. The branch model has no
231
+ * default ON PURPOSE: guessing `main` would silently point a deployment at the
232
+ * wrong branch and let the protected-branch guards apply to nothing, which is
233
+ * worse than asking.
234
+ *
235
+ * SSO is deliberately absent. It is configuration, not a prerequisite — a
236
+ * deployment signs in perfectly well without it, and gating on it would lock
237
+ * an admin out of the screen where they would set it up.
238
+ */
239
+ export function settingsAnswered(settings) {
240
+ const kb = Boolean(settings.resolve('kbRepoUrl') && settings.resolve('gitToken'));
241
+ const branches = validateBranchModel({
242
+ defaultBranch: settings.resolve('defaultBranch'),
243
+ protectedBranches: settings.resolve('protectedBranches'),
244
+ }) === null;
245
+ return kb && branches;
246
+ }
247
+ /**
248
+ * Whether THIS PROCESS can actually serve — which is not the same question,
249
+ * and conflating them opened the gate onto a broken app.
250
+ *
251
+ * The branch model is applied once, during boot: services take `DEFAULT_BRANCH`
252
+ * at construction and the browser is served it before it renders. Saving it
253
+ * therefore answers the question without changing the answer this process
254
+ * holds — so a deployment configured through the setup screen reported itself
255
+ * complete while every workspace call still failed with
256
+ * `Invalid branch name ""`.
257
+ *
258
+ * Requiring the model to be IN EFFECT keeps the gate shut until the restart
259
+ * that puts it there. {@link awaitingRestart} is what tells the screen to ask
260
+ * for one rather than claim a field is missing.
261
+ */
262
+ export function isComplete(settings) {
263
+ return settingsAnswered(settings) && isBranchModelConfigured();
264
+ }
265
+ /** Answered, but not yet in effect: everything is stored, the process is stale. */
266
+ export function awaitingRestart(settings) {
267
+ return settingsAnswered(settings) && !isBranchModelConfigured();
268
+ }
269
+ /**
270
+ * Turn git's stderr into something an admin can act on. Deliberately narrow:
271
+ * the raw text is echoed only when it matches nothing known, and the token is
272
+ * scrubbed from it first — `ls-remote` failures have been known to quote the
273
+ * credential back.
274
+ */
275
+ function explainGitFailure(err, token) {
276
+ const raw = err instanceof Error ? `${err.message}` : String(err);
277
+ const text = token ? raw.replaceAll(token, '***') : raw;
278
+ if (/timed out|ETIMEDOUT/i.test(text)) {
279
+ return 'The host did not answer in time. Check the URL, and that this server can reach it.';
280
+ }
281
+ if (/Authentication failed|could not read Username|invalid credentials|403/i.test(text)) {
282
+ return 'The host rejected those credentials. Check the token, and that the username matches the host (GitHub x-access-token, GitLab oauth2, Bitbucket x-token-auth).';
283
+ }
284
+ if (/not found|repository .* does not exist|404/i.test(text)) {
285
+ return 'No repository at that URL — or the token cannot see it.';
286
+ }
287
+ if (/could not resolve host|unable to access|SSL|certificate/i.test(text)) {
288
+ return 'Could not reach that host from this server. Check the URL and any network egress rules.';
289
+ }
290
+ return text.split('\n').slice(0, 3).join(' ').slice(0, 400);
291
+ }
292
+ //# sourceMappingURL=setup.routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.routes.js","sourceRoot":"","sources":["../../../src/modules/settings/setup.routes.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  export { SkillService } from './skills.service.js';
2
+ export { PendingSkillsService } from './pending-skills.service.js';
2
3
  export { registerSkillsTools } from './skills.tools.js';
3
4
  export { createSkillsRoutes } from './skills.routes.js';
4
- export type { ISkillService, Skill, SkillSummary, SkillFileContent, GetSkillResult, } from './skills.contract.js';
5
+ export type { ISkillService, IPendingSkillService, Skill, SkillSummary, SkillFileContent, PendingSkill, GetSkillResult, } from './skills.contract.js';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modules/skills/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,YAAY,EACV,aAAa,EACb,KAAK,EACL,YAAY,EACZ,gBAAgB,EAChB,cAAc,GACf,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modules/skills/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,KAAK,EACL,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,cAAc,GACf,MAAM,sBAAsB,CAAC"}
@@ -1,4 +1,5 @@
1
1
  export { SkillService } from './skills.service.js';
2
+ export { PendingSkillsService } from './pending-skills.service.js';
2
3
  export { registerSkillsTools } from './skills.tools.js';
3
4
  export { createSkillsRoutes } from './skills.routes.js';
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modules/skills/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modules/skills/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { type IWorkflowService } from '@bevel-software/platform-shared';
2
+ import { type WorkspaceService } from '../workspace/workspace.service.js';
3
+ import type { IAccessControl } from '../access/access-control.interface.js';
4
+ import type { IPendingSkillService, ISkillService, PendingSkill } from './skills.contract.js';
5
+ /**
6
+ * Skills that exist only on an open change request — proposed, not released.
7
+ *
8
+ * The catalog (`SkillService`) is pinned to the default branch, which is the
9
+ * right answer for everything that LOADS a skill and the wrong one for the
10
+ * person who just proposed it: a change request that ADDS a skill folder has
11
+ * nothing on the default branch to hang itself off, so until it merged the
12
+ * proposal was invisible to its author and to the people who had to approve it.
13
+ * This service is that missing half, and it is deliberately a separate surface
14
+ * rather than a widening of the catalog — an unapproved skill must never become
15
+ * loadable just because it became visible.
16
+ *
17
+ * WHO MAY SEE ONE is narrower than who may see the catalog: the author, and
18
+ * whoever could approve it. "Could approve it" is not re-stated here as a
19
+ * group-admin check — it is `canWrite` on the SKILL.md the request would
20
+ * create. The new folder carries no `access.md` of its own, so that verdict
21
+ * inherits from the group folder, which IS the group's admin; asking the access
22
+ * tree keeps this surface and the merge gate answering with one voice, and it
23
+ * keeps working for skills nested in a category subfolder that carries rules of
24
+ * its own.
25
+ *
26
+ * Everything degrades to "nothing pending" rather than throwing: this hangs off
27
+ * the library's list load, and a review surface that cannot answer must not
28
+ * take the shelf down with it.
29
+ */
30
+ export declare class PendingSkillsService implements IPendingSkillService {
31
+ private readonly workspaceService;
32
+ private readonly accessControl;
33
+ private readonly skillService;
34
+ private readonly workflow;
35
+ constructor(workspaceService: WorkspaceService, accessControl: IAccessControl, skillService: ISkillService, workflow: IWorkflowService);
36
+ listPendingSkills(userEmail: string): Promise<PendingSkill[]>;
37
+ /** The file at a branch, or null when it is absent/unreadable there. */
38
+ private readAt;
39
+ }
40
+ //# sourceMappingURL=pending-skills.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-skills.service.d.ts","sourceRoot":"","sources":["../../../src/modules/skills/pending-skills.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAwB,KAAK,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAChG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAI5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAI9F;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,oBAAqB,YAAW,oBAAoB;IAE7D,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAHR,gBAAgB,EAAE,gBAAgB,EAClC,aAAa,EAAE,cAAc,EAC7B,YAAY,EAAE,aAAa,EAC3B,QAAQ,EAAE,gBAAgB;IAGvC,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAqFnE,wEAAwE;YAC1D,MAAM;CAWrB"}
@@ -0,0 +1,147 @@
1
+ import { DEFAULT_BRANCH, GROUPS_DIR, } from '@bevel-software/platform-shared';
2
+ import { workspaceIdForBranch } from '../workspace/workspace.service.js';
3
+ import { hashEmail } from '../../shared/hash-email.js';
4
+ import { resolveDeclaredId } from '../../shared/frontmatter-id.js';
5
+ import { isSafeSkillName, parseSkillFrontmatter } from './skills.service.js';
6
+ const SKILL_DOC = 'SKILL.md';
7
+ /**
8
+ * Skills that exist only on an open change request — proposed, not released.
9
+ *
10
+ * The catalog (`SkillService`) is pinned to the default branch, which is the
11
+ * right answer for everything that LOADS a skill and the wrong one for the
12
+ * person who just proposed it: a change request that ADDS a skill folder has
13
+ * nothing on the default branch to hang itself off, so until it merged the
14
+ * proposal was invisible to its author and to the people who had to approve it.
15
+ * This service is that missing half, and it is deliberately a separate surface
16
+ * rather than a widening of the catalog — an unapproved skill must never become
17
+ * loadable just because it became visible.
18
+ *
19
+ * WHO MAY SEE ONE is narrower than who may see the catalog: the author, and
20
+ * whoever could approve it. "Could approve it" is not re-stated here as a
21
+ * group-admin check — it is `canWrite` on the SKILL.md the request would
22
+ * create. The new folder carries no `access.md` of its own, so that verdict
23
+ * inherits from the group folder, which IS the group's admin; asking the access
24
+ * tree keeps this surface and the merge gate answering with one voice, and it
25
+ * keeps working for skills nested in a category subfolder that carries rules of
26
+ * its own.
27
+ *
28
+ * Everything degrades to "nothing pending" rather than throwing: this hangs off
29
+ * the library's list load, and a review surface that cannot answer must not
30
+ * take the shelf down with it.
31
+ */
32
+ export class PendingSkillsService {
33
+ workspaceService;
34
+ accessControl;
35
+ skillService;
36
+ workflow;
37
+ constructor(workspaceService, accessControl, skillService, workflow) {
38
+ this.workspaceService = workspaceService;
39
+ this.accessControl = accessControl;
40
+ this.skillService = skillService;
41
+ this.workflow = workflow;
42
+ }
43
+ async listPendingSkills(userEmail) {
44
+ const email = userEmail.trim().toLowerCase();
45
+ if (!email)
46
+ return [];
47
+ let crs;
48
+ try {
49
+ crs = (await this.workflow.listChangeRequests()).filter((c) => c.state === 'open');
50
+ }
51
+ catch {
52
+ return [];
53
+ }
54
+ if (crs.length === 0)
55
+ return [];
56
+ // The released set, UNFILTERED by the caller's read access. A skill someone
57
+ // else can read and this caller cannot is still released — counting it as
58
+ // pending would invent a review that is not happening, and would leak the
59
+ // fact that the folder exists.
60
+ let released;
61
+ try {
62
+ released = new Set((await this.skillService.listSkills()).map((s) => s.path));
63
+ }
64
+ catch {
65
+ return [];
66
+ }
67
+ const candidates = crs.flatMap((cr) => cr.touchedNodePaths
68
+ .filter((p) => isSkillDoc(p) && !released.has(folderOf(p)))
69
+ .map((p) => ({ cr, docPath: p, folder: folderOf(p) })));
70
+ if (candidates.length === 0)
71
+ return [];
72
+ const wsId = workspaceIdForBranch(DEFAULT_BRANCH);
73
+ const authorHash = hashEmail(email);
74
+ // One access load for every candidate. Resolved on the DEFAULT branch —
75
+ // the tree as it stands is what says who owns the group the skill is
76
+ // arriving into; the branch's own copy is written by the proposer and must
77
+ // not get a say in who reviews them.
78
+ const writable = await this.accessControl
79
+ .canWriteBatch(wsId, email, candidates.map((c) => c.docPath))
80
+ .catch(() => new Map());
81
+ // The branch copies are read through the default branch's clone at
82
+ // `origin/<branch>`, so a proposal never needs a clone of its own. Refresh
83
+ // first, best-effort: a request pushed seconds ago is worth one fetch, and
84
+ // a failed fetch should degrade to "read what we have".
85
+ await this.workspaceService.ensureRemotesFetched(wsId).catch(() => undefined);
86
+ const out = [];
87
+ for (const { cr, docPath, folder } of candidates) {
88
+ const isAuthor = !!cr.authorId && cr.authorId === authorHash;
89
+ // Fail closed, matching the catalog: only an explicit `true` admits a
90
+ // reviewer. A missing entry (the batch skipped the path, the tree could
91
+ // not be read) means denied, never shown.
92
+ if (!isAuthor && writable.get(docPath) !== true)
93
+ continue;
94
+ const raw = await this.readAt(wsId, cr.branch, docPath);
95
+ // Nothing to describe: the request touched the path but the file is gone
96
+ // at its head (proposed then withdrawn within the branch), or unreadable.
97
+ if (raw === null)
98
+ continue;
99
+ const fm = parseSkillFrontmatter(raw);
100
+ const folderName = folder.split('/').pop() ?? folder;
101
+ const declared = resolveDeclaredId(fm.frontmatter, folderName);
102
+ out.push({
103
+ name: isSafeSkillName(declared) ? declared : folderName,
104
+ description: fm.description,
105
+ version: fm.version,
106
+ path: folder,
107
+ changeRequestNumber: cr.number,
108
+ branch: cr.branch,
109
+ authorName: cr.appAuthor?.name ?? cr.author.name ?? 'Someone',
110
+ createdAt: cr.createdAt,
111
+ isAuthor,
112
+ });
113
+ }
114
+ // Oldest first — the one that has been waiting longest is the one that
115
+ // needs answering, and a list that reorders as requests arrive moves under
116
+ // the pointer.
117
+ return out.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
118
+ }
119
+ /** The file at a branch, or null when it is absent/unreadable there. */
120
+ async readAt(wsId, branch, repoRelPath) {
121
+ try {
122
+ return await this.workspaceService.readFileAtRef(wsId, `origin/${branch}`, repoRelPath);
123
+ }
124
+ catch {
125
+ return null;
126
+ }
127
+ }
128
+ }
129
+ /**
130
+ * Is this touched path a skill's own SKILL.md?
131
+ *
132
+ * `Groups/<group>/<skill>/SKILL.md` is the shallowest shape, hence four
133
+ * segments; deeper ones are skills nested in a category subfolder, which the
134
+ * catalog already supports. `Groups/<group>/SKILL.md` is NOT a skill — a group
135
+ * folder is not itself one.
136
+ */
137
+ function isSkillDoc(repoRelPath) {
138
+ const segments = repoRelPath.split('/');
139
+ return (segments.length >= 4 &&
140
+ segments[0] === GROUPS_DIR &&
141
+ segments[segments.length - 1] === SKILL_DOC);
142
+ }
143
+ /** The skill folder holding a SKILL.md. */
144
+ function folderOf(skillDocPath) {
145
+ return skillDocPath.slice(0, -(SKILL_DOC.length + 1));
146
+ }
147
+ //# sourceMappingURL=pending-skills.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-skills.service.js","sourceRoot":"","sources":["../../../src/modules/skills/pending-skills.service.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Skills are reusable specialist instructions living under `Skills/<name>/SKILL.md`
2
+ * Skills are reusable specialist instructions living under `Groups/<group>/<name>/SKILL.md`
3
3
  * in the KB repo. Discovery/loading is pinned to the DEFAULT branch — the catalog
4
4
  * is a single global, released set (a skill on a draft isn't discoverable until
5
5
  * merged). Progressive disclosure: `listSkills` = name + description (level 1),
@@ -11,7 +11,7 @@ export interface SkillSummary {
11
11
  name: string;
12
12
  description: string;
13
13
  version?: string;
14
- /** Repo-root-relative skill folder, e.g. `Skills/rfi`. */
14
+ /** Repo-root-relative skill folder, e.g. `Groups/Everyone/rfi`. */
15
15
  path: string;
16
16
  }
17
17
  export interface Skill extends SkillSummary {
@@ -19,7 +19,7 @@ export interface Skill extends SkillSummary {
19
19
  body: string;
20
20
  /** Pre-approved tools from the `allowed-tools` frontmatter, if declared. */
21
21
  allowedTools?: string[];
22
- /** Repo-root-relative bundled file paths, e.g. `Skills/rfi/scripts/build_xlsx.py`. */
22
+ /** Repo-root-relative bundled file paths, e.g. `Groups/Everyone/rfi/scripts/build_xlsx.py`. */
23
23
  files: string[];
24
24
  }
25
25
  export interface SkillFileContent {
@@ -43,6 +43,35 @@ export type GetSkillResult = {
43
43
  ok: false;
44
44
  error: 'not_found' | 'forbidden' | 'invalid_file';
45
45
  };
46
+ /**
47
+ * A skill that exists ONLY on an open change request's branch — proposed, not
48
+ * released. It is deliberately NOT a `SkillSummary` the catalog returns: the
49
+ * catalog is what agents load, and a skill nobody has approved must not be
50
+ * loadable. This is a review surface only.
51
+ *
52
+ * Who may see one is narrower than who may see the catalog: its author, and
53
+ * whoever could approve it. See `PendingSkillsService` for why that predicate
54
+ * is the access tree's answer rather than a group-admin check spelled out
55
+ * again here.
56
+ */
57
+ export interface PendingSkill extends SkillSummary {
58
+ /** The open change request that would release it. */
59
+ changeRequestNumber: number;
60
+ branch: string;
61
+ /** Display name of whoever opened the request (person or agent). */
62
+ authorName: string;
63
+ createdAt: string;
64
+ /** True when the caller opened the request themselves. */
65
+ isAuthor: boolean;
66
+ }
67
+ export interface IPendingSkillService {
68
+ /**
69
+ * Skills awaiting approval that `userEmail` is entitled to see — the ones
70
+ * they proposed, and the ones they could approve. Never throws: a review
71
+ * surface failing must not take the library down with it.
72
+ */
73
+ listPendingSkills(userEmail: string): Promise<PendingSkill[]>;
74
+ }
46
75
  export interface ISkillService {
47
76
  /**
48
77
  * The default-branch skill catalog. When `userEmail` is given, filtered to