@bevel-software/platform-core-backend 0.14.0 → 0.15.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 (385) hide show
  1. package/THIRD-PARTY-NOTICES.md +6 -6
  2. package/dist/core/catalog-cache-invalidation.d.ts +29 -0
  3. package/dist/core/catalog-cache-invalidation.d.ts.map +1 -0
  4. package/dist/core/catalog-cache-invalidation.js +71 -0
  5. package/dist/core/catalog-cache-invalidation.js.map +1 -0
  6. package/dist/core/create-core-server.d.ts.map +1 -1
  7. package/dist/core/create-core-server.js +99 -8
  8. package/dist/core/create-core-server.js.map +1 -1
  9. package/dist/core/create-core-services.d.ts +18 -1
  10. package/dist/core/create-core-services.d.ts.map +1 -1
  11. package/dist/core/create-core-services.js +96 -43
  12. package/dist/core/create-core-services.js.map +1 -1
  13. package/dist/modules/access/access-control.interface.d.ts +18 -6
  14. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  15. package/dist/modules/access/access-control.service.d.ts +26 -1
  16. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  17. package/dist/modules/access/access-control.service.js +213 -71
  18. package/dist/modules/access/access-control.service.js.map +1 -1
  19. package/dist/modules/access/access.routes.d.ts.map +1 -1
  20. package/dist/modules/access/access.routes.js +59 -7
  21. package/dist/modules/access/access.routes.js.map +1 -1
  22. package/dist/modules/access-model/access-grammar.d.ts +51 -1
  23. package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
  24. package/dist/modules/access-model/access-grammar.js +95 -1
  25. package/dist/modules/access-model/access-grammar.js.map +1 -1
  26. package/dist/modules/access-model/group-files.d.ts.map +1 -1
  27. package/dist/modules/access-model/group-files.js +4 -1
  28. package/dist/modules/access-model/group-files.js.map +1 -1
  29. package/dist/modules/access-model/plugin-principals.d.ts +49 -0
  30. package/dist/modules/access-model/plugin-principals.d.ts.map +1 -0
  31. package/dist/modules/access-model/plugin-principals.js +125 -0
  32. package/dist/modules/access-model/plugin-principals.js.map +1 -0
  33. package/dist/modules/database/core-schema.d.ts +326 -0
  34. package/dist/modules/database/core-schema.d.ts.map +1 -1
  35. package/dist/modules/database/core-schema.js +52 -0
  36. package/dist/modules/database/core-schema.js.map +1 -1
  37. package/dist/modules/kb-sync/index.d.ts +6 -0
  38. package/dist/modules/kb-sync/index.d.ts.map +1 -0
  39. package/dist/modules/kb-sync/index.js +5 -0
  40. package/dist/modules/kb-sync/index.js.map +1 -0
  41. package/dist/modules/kb-sync/kb-sync.interface.d.ts +77 -0
  42. package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -0
  43. package/dist/modules/kb-sync/kb-sync.interface.js +2 -0
  44. package/dist/modules/kb-sync/kb-sync.interface.js.map +1 -0
  45. package/dist/modules/kb-sync/kb-sync.routes.d.ts +70 -0
  46. package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -0
  47. package/dist/modules/kb-sync/kb-sync.routes.js +135 -0
  48. package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -0
  49. package/dist/modules/kb-sync/kb-sync.service.d.ts +45 -0
  50. package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -0
  51. package/dist/modules/kb-sync/kb-sync.service.js +183 -0
  52. package/dist/modules/kb-sync/kb-sync.service.js.map +1 -0
  53. package/dist/modules/kb-sync/sync-auth.d.ts +59 -0
  54. package/dist/modules/kb-sync/sync-auth.d.ts.map +1 -0
  55. package/dist/modules/kb-sync/sync-auth.js +62 -0
  56. package/dist/modules/kb-sync/sync-auth.js.map +1 -0
  57. package/dist/modules/kb-sync/sync-payload.d.ts +32 -0
  58. package/dist/modules/kb-sync/sync-payload.d.ts.map +1 -0
  59. package/dist/modules/kb-sync/sync-payload.js +105 -0
  60. package/dist/modules/kb-sync/sync-payload.js.map +1 -0
  61. package/dist/modules/marketplace/git-http.routes.d.ts +42 -0
  62. package/dist/modules/marketplace/git-http.routes.d.ts.map +1 -0
  63. package/dist/modules/marketplace/git-http.routes.js +190 -0
  64. package/dist/modules/marketplace/git-http.routes.js.map +1 -0
  65. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts +23 -0
  66. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts.map +1 -0
  67. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js +59 -0
  68. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js.map +1 -0
  69. package/dist/modules/marketplace/github-facade/github-facade-codes.store.d.ts +59 -0
  70. package/dist/modules/marketplace/github-facade/github-facade-codes.store.d.ts.map +1 -0
  71. package/dist/modules/marketplace/github-facade/github-facade-codes.store.js +96 -0
  72. package/dist/modules/marketplace/github-facade/github-facade-codes.store.js.map +1 -0
  73. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts +99 -0
  74. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts.map +1 -0
  75. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js +147 -0
  76. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js.map +1 -0
  77. package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts +47 -0
  78. package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts.map +1 -0
  79. package/dist/modules/marketplace/github-facade/github-facade.routes.js +228 -0
  80. package/dist/modules/marketplace/github-facade/github-facade.routes.js.map +1 -0
  81. package/dist/modules/marketplace/github-facade/github-facade.service.d.ts +109 -0
  82. package/dist/modules/marketplace/github-facade/github-facade.service.d.ts.map +1 -0
  83. package/dist/modules/marketplace/github-facade/github-facade.service.js +172 -0
  84. package/dist/modules/marketplace/github-facade/github-facade.service.js.map +1 -0
  85. package/dist/modules/marketplace/github-facade/index.d.ts +6 -0
  86. package/dist/modules/marketplace/github-facade/index.d.ts.map +1 -0
  87. package/dist/modules/marketplace/github-facade/index.js +6 -0
  88. package/dist/modules/marketplace/github-facade/index.js.map +1 -0
  89. package/dist/modules/marketplace/index.d.ts +4 -0
  90. package/dist/modules/marketplace/index.d.ts.map +1 -0
  91. package/dist/modules/marketplace/index.js +4 -0
  92. package/dist/modules/marketplace/index.js.map +1 -0
  93. package/dist/modules/marketplace/marketplace-repo.service.d.ts +122 -0
  94. package/dist/modules/marketplace/marketplace-repo.service.d.ts.map +1 -0
  95. package/dist/modules/marketplace/marketplace-repo.service.js +300 -0
  96. package/dist/modules/marketplace/marketplace-repo.service.js.map +1 -0
  97. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  98. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +4 -0
  99. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  100. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +14 -8
  101. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
  102. package/dist/modules/mcp/oauth/oauth-consent.routes.js +13 -1
  103. package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
  104. package/dist/modules/mcp/oauth/oauth-state.d.ts +13 -2
  105. package/dist/modules/mcp/oauth/oauth-state.d.ts.map +1 -1
  106. package/dist/modules/mcp/oauth/oauth-state.js +3 -1
  107. package/dist/modules/mcp/oauth/oauth-state.js.map +1 -1
  108. package/dist/modules/plugins/compile/compile-marketplace.d.ts +97 -0
  109. package/dist/modules/plugins/compile/compile-marketplace.d.ts.map +1 -0
  110. package/dist/modules/plugins/compile/compile-marketplace.js +240 -0
  111. package/dist/modules/plugins/compile/compile-marketplace.js.map +1 -0
  112. package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts +66 -0
  113. package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts.map +1 -0
  114. package/dist/modules/plugins/compile/marketplace-compiler.service.js +132 -0
  115. package/dist/modules/plugins/compile/marketplace-compiler.service.js.map +1 -0
  116. package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts +37 -0
  117. package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts.map +1 -0
  118. package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js +135 -0
  119. package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js.map +1 -0
  120. package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts +45 -0
  121. package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts.map +1 -0
  122. package/dist/modules/plugins/discovery/bundle-dialect/registry.js +147 -0
  123. package/dist/modules/plugins/discovery/bundle-dialect/registry.js.map +1 -0
  124. package/dist/modules/plugins/discovery/kb-plugin-source.d.ts +37 -0
  125. package/dist/modules/plugins/discovery/kb-plugin-source.d.ts.map +1 -0
  126. package/dist/modules/plugins/discovery/kb-plugin-source.js +107 -0
  127. package/dist/modules/plugins/discovery/kb-plugin-source.js.map +1 -0
  128. package/dist/modules/plugins/discovery/native.source.d.ts +15 -0
  129. package/dist/modules/plugins/discovery/native.source.d.ts.map +1 -0
  130. package/dist/modules/plugins/discovery/native.source.js +102 -0
  131. package/dist/modules/plugins/discovery/native.source.js.map +1 -0
  132. package/dist/modules/plugins/discovery/plugin-source.d.ts +96 -0
  133. package/dist/modules/plugins/discovery/plugin-source.d.ts.map +1 -0
  134. package/dist/modules/plugins/discovery/plugin-source.js +2 -0
  135. package/dist/modules/plugins/discovery/plugin-source.js.map +1 -0
  136. package/dist/modules/plugins/index.d.ts +10 -1
  137. package/dist/modules/plugins/index.d.ts.map +1 -1
  138. package/dist/modules/plugins/index.js +8 -0
  139. package/dist/modules/plugins/index.js.map +1 -1
  140. package/dist/modules/plugins/plugin-links.d.ts +66 -0
  141. package/dist/modules/plugins/plugin-links.d.ts.map +1 -0
  142. package/dist/modules/plugins/plugin-links.js +160 -0
  143. package/dist/modules/plugins/plugin-links.js.map +1 -0
  144. package/dist/modules/plugins/plugin-links.service.d.ts +114 -0
  145. package/dist/modules/plugins/plugin-links.service.d.ts.map +1 -0
  146. package/dist/modules/plugins/plugin-links.service.js +304 -0
  147. package/dist/modules/plugins/plugin-links.service.js.map +1 -0
  148. package/dist/modules/plugins/plugin-provision.service.d.ts +39 -3
  149. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
  150. package/dist/modules/plugins/plugin-provision.service.js +152 -52
  151. package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
  152. package/dist/modules/plugins/plugin-rename.service.d.ts +69 -0
  153. package/dist/modules/plugins/plugin-rename.service.d.ts.map +1 -0
  154. package/dist/modules/plugins/plugin-rename.service.js +245 -0
  155. package/dist/modules/plugins/plugin-rename.service.js.map +1 -0
  156. package/dist/modules/plugins/plugins.contract.d.ts +27 -1
  157. package/dist/modules/plugins/plugins.contract.d.ts.map +1 -1
  158. package/dist/modules/plugins/plugins.routes.d.ts +7 -1
  159. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  160. package/dist/modules/plugins/plugins.routes.js +120 -12
  161. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  162. package/dist/modules/plugins/plugins.service.d.ts +50 -3
  163. package/dist/modules/plugins/plugins.service.d.ts.map +1 -1
  164. package/dist/modules/plugins/plugins.service.js +108 -47
  165. package/dist/modules/plugins/plugins.service.js.map +1 -1
  166. package/dist/modules/settings/deployment-settings.service.d.ts +9 -0
  167. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  168. package/dist/modules/settings/deployment-settings.service.js +82 -2
  169. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  170. package/dist/modules/settings/setup.routes.d.ts +26 -0
  171. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  172. package/dist/modules/settings/setup.routes.js +9 -1
  173. package/dist/modules/settings/setup.routes.js.map +1 -1
  174. package/dist/modules/skills/index.d.ts +1 -0
  175. package/dist/modules/skills/index.d.ts.map +1 -1
  176. package/dist/modules/skills/index.js +1 -0
  177. package/dist/modules/skills/index.js.map +1 -1
  178. package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
  179. package/dist/modules/skills/pending-skills.service.js +9 -4
  180. package/dist/modules/skills/pending-skills.service.js.map +1 -1
  181. package/dist/modules/skills/skill-access-requests.routes.d.ts +38 -0
  182. package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -0
  183. package/dist/modules/skills/skill-access-requests.routes.js +168 -0
  184. package/dist/modules/skills/skill-access-requests.routes.js.map +1 -0
  185. package/dist/modules/skills/skills.contract.d.ts +31 -0
  186. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  187. package/dist/modules/skills/skills.routes.d.ts +28 -3
  188. package/dist/modules/skills/skills.routes.d.ts.map +1 -1
  189. package/dist/modules/skills/skills.routes.js +42 -3
  190. package/dist/modules/skills/skills.routes.js.map +1 -1
  191. package/dist/modules/skills/skills.service.d.ts +4 -0
  192. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  193. package/dist/modules/skills/skills.service.js +83 -22
  194. package/dist/modules/skills/skills.service.js.map +1 -1
  195. package/dist/modules/tool-auth/external-api-key.interface.d.ts +18 -1
  196. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  197. package/dist/modules/tool-auth/external-api-key.interface.js +2 -1
  198. package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
  199. package/dist/modules/tool-auth/external-api-key.service.d.ts +13 -4
  200. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  201. package/dist/modules/tool-auth/external-api-key.service.js +26 -9
  202. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  203. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +36 -0
  204. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
  205. package/dist/modules/tool-manuals/mcp-json-discovery.js +130 -92
  206. package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
  207. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +6 -1
  208. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  209. package/dist/modules/tool-manuals/tool-manuals.service.js +18 -25
  210. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  211. package/dist/modules/workflow/git/git.service.d.ts +40 -2
  212. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  213. package/dist/modules/workflow/git/git.service.js +227 -54
  214. package/dist/modules/workflow/git/git.service.js.map +1 -1
  215. package/dist/modules/workflow/git/pull-request.service.d.ts +6 -0
  216. package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
  217. package/dist/modules/workflow/git/pull-request.service.js +8 -0
  218. package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
  219. package/dist/modules/workflow/workflow.service.d.ts +52 -1
  220. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  221. package/dist/modules/workflow/workflow.service.js +242 -9
  222. package/dist/modules/workflow/workflow.service.js.map +1 -1
  223. package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts +35 -0
  224. package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts.map +1 -0
  225. package/dist/modules/workspace/startup/steps/plugin-manifests.step.js +121 -0
  226. package/dist/modules/workspace/startup/steps/plugin-manifests.step.js.map +1 -0
  227. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  228. package/dist/modules/workspace/startup/steps/seed-tree.js +46 -2
  229. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  230. package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
  231. package/dist/modules/workspace/startup/steps/template-files.step.js +112 -25
  232. package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
  233. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  234. package/dist/modules/workspace/workspace.routes.js +9 -9
  235. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  236. package/dist/modules/workspace/workspace.service.d.ts +33 -0
  237. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  238. package/dist/modules/workspace/workspace.service.js +110 -16
  239. package/dist/modules/workspace/workspace.service.js.map +1 -1
  240. package/dist/shared/domain-errors.d.ts +20 -0
  241. package/dist/shared/domain-errors.d.ts.map +1 -1
  242. package/dist/shared/domain-errors.js +29 -0
  243. package/dist/shared/domain-errors.js.map +1 -1
  244. package/dist/shared/fs-errors.d.ts +8 -0
  245. package/dist/shared/fs-errors.d.ts.map +1 -0
  246. package/dist/shared/fs-errors.js +11 -0
  247. package/dist/shared/fs-errors.js.map +1 -0
  248. package/dist/shared/fs-walk.d.ts +20 -6
  249. package/dist/shared/fs-walk.d.ts.map +1 -1
  250. package/dist/shared/fs-walk.js +35 -27
  251. package/dist/shared/fs-walk.js.map +1 -1
  252. package/dist/shared/kb-walk.d.ts +52 -0
  253. package/dist/shared/kb-walk.d.ts.map +1 -0
  254. package/dist/shared/kb-walk.js +76 -0
  255. package/dist/shared/kb-walk.js.map +1 -0
  256. package/dist/shared/path-order.d.ts +18 -0
  257. package/dist/shared/path-order.d.ts.map +1 -0
  258. package/dist/shared/path-order.js +35 -0
  259. package/dist/shared/path-order.js.map +1 -0
  260. package/dist/shared/ttl-cache.d.ts +28 -1
  261. package/dist/shared/ttl-cache.d.ts.map +1 -1
  262. package/dist/shared/ttl-cache.js +33 -1
  263. package/dist/shared/ttl-cache.js.map +1 -1
  264. package/kb-template/.bevelignore +1 -1
  265. package/kb-template/AGENTS.md +95 -30
  266. package/kb-template/Skills/.gitkeep +0 -0
  267. package/migrations/0005_github_facade.sql +25 -0
  268. package/migrations/meta/0005_snapshot.json +1717 -0
  269. package/migrations/meta/_journal.json +7 -0
  270. package/package.json +6 -6
  271. package/src/__tests__/kb-layout-config.test.ts +110 -0
  272. package/src/core/__tests__/catalog-cache-invalidation.test.ts +146 -0
  273. package/src/core/catalog-cache-invalidation.ts +92 -0
  274. package/src/core/create-core-server.ts +123 -7
  275. package/src/core/create-core-services.ts +162 -41
  276. package/src/modules/access/__tests__/access-control.invalidate-race.test.ts +86 -0
  277. package/src/modules/access/__tests__/access-control.service.test.ts +19 -3
  278. package/src/modules/access/__tests__/access-plugin-principals.test.ts +368 -0
  279. package/src/modules/access/__tests__/groups-admin.service.test.ts +8 -0
  280. package/src/modules/access/access-control.interface.ts +20 -9
  281. package/src/modules/access/access-control.service.ts +240 -78
  282. package/src/modules/access/access.routes.ts +96 -14
  283. package/src/modules/access-model/__tests__/access-grammar.test.ts +18 -0
  284. package/src/modules/access-model/__tests__/plugin-token-canonical.test.ts +40 -0
  285. package/src/modules/access-model/access-grammar.ts +114 -2
  286. package/src/modules/access-model/group-files.ts +4 -0
  287. package/src/modules/access-model/plugin-principals.ts +137 -0
  288. package/src/modules/database/core-schema.ts +54 -0
  289. package/src/modules/kb-sync/__tests__/kb-sync.routes.test.ts +297 -0
  290. package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +295 -0
  291. package/src/modules/kb-sync/__tests__/sync-auth.test.ts +118 -0
  292. package/src/modules/kb-sync/__tests__/sync-payload.test.ts +148 -0
  293. package/src/modules/kb-sync/index.ts +19 -0
  294. package/src/modules/kb-sync/kb-sync.interface.ts +79 -0
  295. package/src/modules/kb-sync/kb-sync.routes.ts +197 -0
  296. package/src/modules/kb-sync/kb-sync.service.ts +201 -0
  297. package/src/modules/kb-sync/sync-auth.ts +118 -0
  298. package/src/modules/kb-sync/sync-payload.ts +143 -0
  299. package/src/modules/marketplace/__tests__/github-facade.test.ts +443 -0
  300. package/src/modules/marketplace/__tests__/marketplace-git.test.ts +179 -0
  301. package/src/modules/marketplace/git-http.routes.ts +193 -0
  302. package/src/modules/marketplace/github-facade/github-facade-admin.routes.ts +78 -0
  303. package/src/modules/marketplace/github-facade/github-facade-codes.store.ts +145 -0
  304. package/src/modules/marketplace/github-facade/github-facade-credentials.service.ts +208 -0
  305. package/src/modules/marketplace/github-facade/github-facade.routes.ts +268 -0
  306. package/src/modules/marketplace/github-facade/github-facade.service.ts +221 -0
  307. package/src/modules/marketplace/github-facade/index.ts +25 -0
  308. package/src/modules/marketplace/index.ts +3 -0
  309. package/src/modules/marketplace/marketplace-repo.service.ts +337 -0
  310. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +3 -0
  311. package/src/modules/mcp/oauth/oauth-consent.routes.ts +24 -2
  312. package/src/modules/mcp/oauth/oauth-state.ts +15 -3
  313. package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +163 -0
  314. package/src/modules/plugins/__tests__/compile-marketplace.test.ts +294 -0
  315. package/src/modules/plugins/__tests__/plugin-index.service.test.ts +56 -12
  316. package/src/modules/plugins/__tests__/plugin-links.service.test.ts +287 -0
  317. package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +205 -5
  318. package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +394 -0
  319. package/src/modules/plugins/__tests__/plugins.routes.test.ts +80 -33
  320. package/src/modules/plugins/compile/compile-marketplace.ts +403 -0
  321. package/src/modules/plugins/compile/marketplace-compiler.service.ts +150 -0
  322. package/src/modules/plugins/discovery/__tests__/kb-plugin-source.test.ts +329 -0
  323. package/src/modules/plugins/discovery/bundle-dialect/bundle.source.ts +140 -0
  324. package/src/modules/plugins/discovery/bundle-dialect/registry.ts +175 -0
  325. package/src/modules/plugins/discovery/kb-plugin-source.ts +114 -0
  326. package/src/modules/plugins/discovery/native.source.ts +122 -0
  327. package/src/modules/plugins/discovery/plugin-source.ts +99 -0
  328. package/src/modules/plugins/index.ts +10 -0
  329. package/src/modules/plugins/plugin-links.service.ts +363 -0
  330. package/src/modules/plugins/plugin-links.ts +184 -0
  331. package/src/modules/plugins/plugin-provision.service.ts +160 -54
  332. package/src/modules/plugins/plugin-rename.service.ts +298 -0
  333. package/src/modules/plugins/plugins.contract.ts +28 -1
  334. package/src/modules/plugins/plugins.routes.ts +128 -11
  335. package/src/modules/plugins/plugins.service.ts +106 -53
  336. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +57 -0
  337. package/src/modules/settings/__tests__/setup.routes.sync-status.test.ts +77 -0
  338. package/src/modules/settings/deployment-settings.service.ts +95 -2
  339. package/src/modules/settings/setup.routes.ts +24 -0
  340. package/src/modules/skills/__tests__/skill-access-requests.routes.test.ts +128 -0
  341. package/src/modules/skills/__tests__/skills.service.test.ts +85 -0
  342. package/src/modules/skills/index.ts +1 -0
  343. package/src/modules/skills/pending-skills.service.ts +7 -5
  344. package/src/modules/skills/skill-access-requests.routes.ts +199 -0
  345. package/src/modules/skills/skills.contract.ts +32 -0
  346. package/src/modules/skills/skills.routes.ts +59 -3
  347. package/src/modules/skills/skills.service.ts +84 -22
  348. package/src/modules/tool-auth/external-api-key.interface.ts +20 -1
  349. package/src/modules/tool-auth/external-api-key.service.ts +31 -13
  350. package/src/modules/tool-manuals/__tests__/mcp-json-discovery.test.ts +24 -1
  351. package/src/modules/tool-manuals/__tests__/tool-manuals.cli.test.ts +2 -0
  352. package/src/modules/tool-manuals/mcp-json-discovery.ts +154 -88
  353. package/src/modules/tool-manuals/tool-manuals.service.ts +14 -23
  354. package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +1 -1
  355. package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +1 -0
  356. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +2 -2
  357. package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +119 -2
  358. package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +437 -0
  359. package/src/modules/workflow/git/__tests__/git.service.postMergePull.test.ts +3 -3
  360. package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +47 -4
  361. package/src/modules/workflow/git/__tests__/git.service.syncFromRemote.test.ts +253 -0
  362. package/src/modules/workflow/git/git.service.ts +239 -56
  363. package/src/modules/workflow/git/pull-request.service.ts +9 -0
  364. package/src/modules/workflow/workflow.service.ts +260 -9
  365. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +6 -7
  366. package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +93 -0
  367. package/src/modules/workspace/__tests__/workspace.service.missing-branch.test.ts +28 -0
  368. package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +42 -12
  369. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +219 -4
  370. package/src/modules/workspace/startup/steps/plugin-manifests.step.ts +129 -0
  371. package/src/modules/workspace/startup/steps/seed-tree.ts +44 -2
  372. package/src/modules/workspace/startup/steps/template-files.step.ts +131 -27
  373. package/src/modules/workspace/workspace.routes.ts +9 -17
  374. package/src/modules/workspace/workspace.service.ts +107 -16
  375. package/src/shared/__tests__/filename.test.ts +0 -0
  376. package/src/shared/__tests__/fs-walk.test.ts +22 -1
  377. package/src/shared/__tests__/kb-walk.test.ts +107 -0
  378. package/src/shared/__tests__/path-order.test.ts +24 -0
  379. package/src/shared/__tests__/ttl-cache.test.ts +55 -0
  380. package/src/shared/domain-errors.ts +29 -0
  381. package/src/shared/fs-errors.ts +10 -0
  382. package/src/shared/fs-walk.ts +38 -23
  383. package/src/shared/kb-walk.ts +95 -0
  384. package/src/shared/path-order.ts +33 -0
  385. package/src/shared/ttl-cache.ts +33 -1
@@ -0,0 +1,268 @@
1
+ import express from 'express';
2
+ import type { AuthUser } from '@bevel-software/platform-shared';
3
+ import '../../auth/auth.middleware.js'; // Express Request.userId / userEmail augmentation
4
+ import '../../tool-auth/external-api-key.interface.js'; // Express Request augmentation (req.externalApiKeyId)
5
+ import type { MarketplaceRepoService } from '../marketplace-repo.service.js';
6
+ import type { MarketplaceKeyResolver } from '../git-http.routes.js';
7
+ import { GitHubFacadeRequestError, type GitHubFacade } from './github-facade.service.js';
8
+
9
+ export interface GitHubFacadeRoutesDeps {
10
+ facade: GitHubFacade;
11
+ keys: MarketplaceKeyResolver;
12
+ repo: MarketplaceRepoService;
13
+ /**
14
+ * The owner and name claude.ai derives from the marketplace URL people
15
+ * paste — the SAME URL Claude Code clones: `https://<host>/git/marketplace.git`
16
+ * reads as owner `git`, repository `marketplace`. One address for every
17
+ * Claude surface.
18
+ */
19
+ owner: string;
20
+ repoName: string;
21
+ /**
22
+ * The deployment's public address. Only its ORIGIN is ever emitted: a
23
+ * configured URL may carry userinfo or a path, and neither belongs in a
24
+ * body handed to a third party.
25
+ */
26
+ publicUrl: string;
27
+ }
28
+
29
+ /**
30
+ * The GitHub Enterprise surface a consumer (claude.ai, Cowork) talks to when
31
+ * a person adds this deployment's marketplace from their own settings —
32
+ * observed call for call against a lookalike host, and nothing beyond it:
33
+ *
34
+ * GET /login/oauth/authorize the "connect your account" redirect
35
+ * POST /login/oauth/access_token code → token (our client id + secret)
36
+ * GET /api/v3/repos/:owner/:repo repository metadata
37
+ * GET …/commits?per_page=1 the head commit — of THIS person's tree
38
+ * GET …/zipball/:ref that tree, zipped
39
+ *
40
+ * Mounted at the app root, ahead of the SPA and the `/api` JWT mounts: the
41
+ * first two are hit by a bare browser and by Anthropic's backend, the rest
42
+ * carry the person's token as a Bearer. The token is a connection key, so a
43
+ * revoked link fails here the same way it fails on the git remote.
44
+ *
45
+ * Errors are GitHub-shaped too (`{ message }`, and the OAuth error body on
46
+ * the token endpoint, in whichever encoding the client negotiated): the
47
+ * caller is a GitHub client and reads them as one.
48
+ */
49
+ export function createGitHubFacadeRoutes(deps: GitHubFacadeRoutesDeps): express.Router {
50
+ const router = express.Router();
51
+ const { facade, keys, repo, owner, repoName } = deps;
52
+ const origin = new URL(deps.publicUrl).origin;
53
+ const fullName = `${owner}/${repoName}`;
54
+
55
+ router.get('/login/oauth/authorize', async (req, res) => {
56
+ try {
57
+ res.redirect(302, await facade.authorizeRedirect(req.query as Record<string, unknown>));
58
+ } catch (err) {
59
+ answerError(res, err);
60
+ }
61
+ });
62
+
63
+ // GitHub's token endpoint takes JSON or a form and answers in the shape
64
+ // `Accept` asks for — success and failure alike; claude.ai sends JSON and
65
+ // asks for JSON.
66
+ router.post(
67
+ '/login/oauth/access_token',
68
+ express.json({ limit: '16kb' }),
69
+ express.urlencoded({ extended: false, limit: '16kb' }),
70
+ async (req, res) => {
71
+ try {
72
+ negotiated(req, res, 200, await facade.exchangeCode((req.body ?? {}) as Record<string, unknown>));
73
+ } catch (err) {
74
+ if (err instanceof GitHubFacadeRequestError) {
75
+ negotiated(req, res, err.status, { error: err.code, error_description: err.message });
76
+ return;
77
+ }
78
+ answerError(res, err);
79
+ }
80
+ },
81
+ );
82
+
83
+ // --- the REST API, as far as the observed contract goes ---------------------
84
+
85
+ const api = express.Router();
86
+
87
+ api.use(async (req, res, next) => {
88
+ const token = bearerOf(req);
89
+ if (!token || !keys.looksLikeExternalApiKey(token)) {
90
+ unauthorized(res);
91
+ return;
92
+ }
93
+ let resolved: { tokenId: string; user: AuthUser } | null;
94
+ try {
95
+ resolved = await keys.verifyAndLoadToken(token);
96
+ } catch (err) {
97
+ console.error('[github-facade] key verification failed:', err);
98
+ res.status(500).json({ message: 'Authentication backend unavailable' });
99
+ return;
100
+ }
101
+ if (!resolved) {
102
+ unauthorized(res);
103
+ return;
104
+ }
105
+ req.userId = resolved.user.id;
106
+ req.userEmail = resolved.user.email;
107
+ req.externalApiKeyId = resolved.tokenId;
108
+ next();
109
+ });
110
+
111
+ const isOurs = (req: express.Request) => req.params.owner === owner && req.params.repo === repoName;
112
+ const notFound = (res: express.Response) => res.status(404).json({ message: 'Not Found' });
113
+
114
+ api.get('/repos/:owner/:repo', (req, res) => {
115
+ if (!isOurs(req)) return void notFound(res);
116
+ res.json(repositoryBody(fullName, owner, repoName, origin));
117
+ });
118
+
119
+ api.get('/repos/:owner/:repo/commits', async (req, res) => {
120
+ if (!isOurs(req)) return void notFound(res);
121
+ try {
122
+ const { sha } = await repo.headFor({ id: req.userId!, email: req.userEmail! });
123
+ const commit = await repo.describeCommit(sha);
124
+ res.json([commitBody(commit, fullName, origin)]);
125
+ } catch (err) {
126
+ answerError(res, err);
127
+ }
128
+ });
129
+
130
+ api.get('/repos/:owner/:repo/zipball/:ref', async (req, res) => {
131
+ if (!isOurs(req)) return void notFound(res);
132
+ try {
133
+ const { namespace, sha: head } = await repo.headFor({ id: req.userId!, email: req.userEmail! });
134
+ const ref = req.params.ref;
135
+ // Only THIS person's tree, at its head or a commit behind it: the object
136
+ // store is shared across everyone, so a sha alone must never be enough
137
+ // to read a tree compiled for someone else.
138
+ const sha = ref === 'HEAD' || ref === 'main' ? head : ref;
139
+ if (!(await repo.contains(namespace, sha))) return void notFound(res);
140
+ res.status(200);
141
+ res.setHeader('Content-Type', 'application/zip');
142
+ res.setHeader('Content-Disposition', `attachment; filename=${owner}-${repoName}-${sha.slice(0, 7)}.zip`);
143
+ const archive = repo.archiveZip(sha, `${owner}-${repoName}-${sha.slice(0, 7)}`);
144
+ const stderr: Buffer[] = [];
145
+ archive.stderr?.on('data', (chunk: Buffer) => stderr.push(chunk));
146
+ const fail = (reason: string) => {
147
+ console.error(`[github-facade] zipball ${sha.slice(0, 7)} failed: ${reason}`);
148
+ // Headers are already out once the stream started; the only honest
149
+ // answer then is a cut connection, which the client sees as a failed
150
+ // download rather than a truncated archive it might unpack.
151
+ if (!res.headersSent) res.status(500).json({ message: 'Archive failed' });
152
+ else res.destroy();
153
+ };
154
+ archive.on('error', (err: Error) => fail(err.message));
155
+ // A client that goes away mid-download must not leave git running —
156
+ // and that kill is the ONE signal exit that is not a failure: git dying
157
+ // to any other signal left a truncated archive on the wire.
158
+ let stopped = false;
159
+ const stop = () => {
160
+ if (archive.exitCode === null && !archive.killed) {
161
+ stopped = true;
162
+ archive.kill();
163
+ }
164
+ };
165
+ archive.on('close', (code, signal) => {
166
+ if (code === 0 || stopped) return;
167
+ fail(`git archive exited ${code ?? `on ${signal}`}: ${Buffer.concat(stderr).toString().trim()}`);
168
+ });
169
+ req.on('aborted', stop);
170
+ res.on('close', stop);
171
+ archive.stdout!.pipe(res);
172
+ } catch (err) {
173
+ answerError(res, err);
174
+ }
175
+ });
176
+
177
+ api.use((_req, res) => notFound(res));
178
+ router.use('/api/v3', api);
179
+
180
+ return router;
181
+ }
182
+
183
+ function bearerOf(req: express.Request): string | null {
184
+ const header = req.headers.authorization;
185
+ if (!header) return null;
186
+ const [scheme, ...rest] = header.split(' ');
187
+ if (!scheme || !/^(bearer|token)$/i.test(scheme)) return null;
188
+ return rest.join(' ').trim() || null;
189
+ }
190
+
191
+ function unauthorized(res: express.Response): void {
192
+ res.setHeader('WWW-Authenticate', 'Bearer realm="hexis-marketplace"');
193
+ res.status(401).json({ message: 'Bad credentials' });
194
+ }
195
+
196
+ /** The token endpoint's reply, in the encoding the client asked for. */
197
+ function negotiated(
198
+ req: express.Request,
199
+ res: express.Response,
200
+ status: number,
201
+ payload: Record<string, string>,
202
+ ): void {
203
+ const accept = req.headers.accept ?? '';
204
+ const wantsForm = accept.includes('application/x-www-form-urlencoded') && !accept.includes('json');
205
+ res.status(status);
206
+ if (wantsForm) {
207
+ res.type('application/x-www-form-urlencoded').send(new URLSearchParams(payload).toString());
208
+ return;
209
+ }
210
+ res.json(payload);
211
+ }
212
+
213
+ function answerError(res: express.Response, err: unknown): void {
214
+ if (err instanceof GitHubFacadeRequestError) {
215
+ res.status(err.status).json({ message: err.message });
216
+ return;
217
+ }
218
+ console.error('[github-facade]', err);
219
+ res.status(500).json({ message: 'Internal error' });
220
+ }
221
+
222
+ /** A repository as GitHub describes one — the fields a marketplace sync reads. */
223
+ function repositoryBody(fullName: string, owner: string, name: string, origin: string) {
224
+ const html = `${origin}/${fullName}`;
225
+ return {
226
+ id: 1,
227
+ node_id: 'R_hexis_marketplace',
228
+ name,
229
+ full_name: fullName,
230
+ private: true,
231
+ owner: { login: owner, id: 1, type: 'Organization' },
232
+ html_url: html,
233
+ description: 'The skills you may read, compiled as native plugins.',
234
+ fork: false,
235
+ url: `${origin}/api/v3/repos/${fullName}`,
236
+ clone_url: `${html}.git`,
237
+ default_branch: 'main',
238
+ visibility: 'private',
239
+ archived: false,
240
+ disabled: false,
241
+ permissions: { admin: false, maintain: false, push: false, triage: false, pull: true },
242
+ };
243
+ }
244
+
245
+ function commitBody(
246
+ commit: { sha: string; message: string; authorName: string; authorEmail: string; date: string; tree: string },
247
+ fullName: string,
248
+ origin: string,
249
+ ) {
250
+ const base = `${origin}/api/v3/repos/${fullName}`;
251
+ const who = { name: commit.authorName, email: commit.authorEmail, date: commit.date };
252
+ return {
253
+ sha: commit.sha,
254
+ node_id: `C_${commit.sha}`,
255
+ commit: {
256
+ author: who,
257
+ committer: who,
258
+ message: commit.message,
259
+ tree: { sha: commit.tree, url: `${base}/git/trees/${commit.tree}` },
260
+ comment_count: 0,
261
+ },
262
+ url: `${base}/commits/${commit.sha}`,
263
+ html_url: `${origin}/${fullName}/commit/${commit.sha}`,
264
+ author: null,
265
+ committer: null,
266
+ parents: [],
267
+ };
268
+ }
@@ -0,0 +1,221 @@
1
+ import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import type { MintedExternalApiKey } from '../../tool-auth/external-api-key.interface.js';
3
+ import { signAuthRequest, type McpAuthRequestState } from '../../mcp/oauth/oauth-state.js';
4
+ import type { GitHubFacadeCredentialsService } from './github-facade-credentials.service.js';
5
+ import type { GitHubFacadeCodeStore } from './github-facade-codes.store.js';
6
+
7
+ /**
8
+ * The KIND stored on a connection key minted through the facade, and the
9
+ * plaintext prefix that kind is minted with. `gho_` is what a GitHub OAuth
10
+ * token looks like — the shape a consumer expecting a GitHub Enterprise host
11
+ * accepts; the key is otherwise an ordinary connection key — hashed at rest,
12
+ * revocable from the person's external-agent page, accepted by every surface
13
+ * a connection key is. The kind, not the label, is what tells such a key
14
+ * apart: a label is the person's to edit.
15
+ */
16
+ export const GITHUB_LINK_KEY_KIND = 'github-link';
17
+ export const GITHUB_LINK_KEY_PREFIX = 'gho_';
18
+
19
+ /**
20
+ * A product that talks to this deployment as if it were a GitHub Enterprise
21
+ * Server. The facade itself is consumer-neutral — GitHub's OAuth pair and
22
+ * REST subset are the contract — and a consumer is the one thing that is not:
23
+ * where its callback lives (the only place a code may be sent), what the
24
+ * consent page calls it, and how the keys it holds are labelled for the
25
+ * person. Claude is the first; another product with a "connect your GitHub
26
+ * Enterprise" flow is one more entry, not one more module.
27
+ */
28
+ export interface GitHubFacadeConsumer {
29
+ id: string;
30
+ /** What the consent page says is asking. */
31
+ name: string;
32
+ /** Hostnames a redirect_uri may point at. */
33
+ redirectHosts: readonly string[];
34
+ /** The label on the connection keys minted for this consumer. */
35
+ keyLabel: string;
36
+ }
37
+
38
+ /** claude.ai and Cowork: the consumer this facade was built against. */
39
+ export const CLAUDE_CONSUMER: GitHubFacadeConsumer = {
40
+ id: 'claude',
41
+ name: 'Claude',
42
+ redirectHosts: ['claude.ai'],
43
+ keyLabel: 'Claude (claude.ai and Cowork)',
44
+ };
45
+
46
+ const CODE_TTL_MS = 10 * 60_000;
47
+ const CODE_BYTES = 10;
48
+
49
+ export interface GitHubFacadeKeyMinter {
50
+ mint(userId: string, label: string, options?: { kind?: string }): Promise<MintedExternalApiKey>;
51
+ }
52
+
53
+ export interface GitHubFacadeDeps {
54
+ credentials: GitHubFacadeCredentialsService;
55
+ codes: GitHubFacadeCodeStore;
56
+ keys: GitHubFacadeKeyMinter;
57
+ /** Who may connect through the facade. */
58
+ consumers: readonly GitHubFacadeConsumer[];
59
+ /** HMAC secret for the signed authorize state — the same one the MCP flow uses. */
60
+ stateSecret: string;
61
+ /** SPA base URL — where the browser is sent to sign in and approve. */
62
+ publicFrontendUrl: string;
63
+ }
64
+
65
+ export class GitHubFacadeRequestError extends Error {
66
+ constructor(
67
+ readonly status: number,
68
+ readonly code: string,
69
+ message: string,
70
+ ) {
71
+ super(message);
72
+ this.name = 'GitHubFacadeRequestError';
73
+ }
74
+ }
75
+
76
+ /**
77
+ * The "connect your GitHub Enterprise account" flow, as a consumer such as claude.ai drives it
78
+ * against a GitHub Enterprise host — with hexis standing where GitHub would.
79
+ *
80
+ * 1. The browser lands on `/login/oauth/authorize?client_id&redirect_uri&state`.
81
+ * We check the client id is ours and the redirect goes to claude.ai,
82
+ * pack the request into the SAME signed state the MCP authorization
83
+ * flow uses, and send the browser to the SPA's `/connect` page — where a
84
+ * hexis session (or a sign-in) attaches a person to the request.
85
+ * 2. Finish on that page comes back through the consent routes with the
86
+ * state; {@link completeConsent} stores a one-time code bound to that
87
+ * person and returns the callback URL carrying it.
88
+ * 3. Anthropic's backend posts the code with our client id and secret to
89
+ * `/login/oauth/access_token`; {@link exchangeCode} turns it into a
90
+ * connection key for that person — the token every later fetch carries.
91
+ *
92
+ * Nothing here is remembered in the process: credentials and codes are read
93
+ * from their stores on every step, so the replica that issued a code and the
94
+ * replica that exchanges it need not be the same one.
95
+ */
96
+ export class GitHubFacade {
97
+ constructor(private readonly deps: GitHubFacadeDeps) {}
98
+
99
+ /** Where to send the browser for an authorize request, or a 4xx to answer with. */
100
+ async authorizeRedirect(query: Record<string, unknown>): Promise<string> {
101
+ const clientId = str(query.client_id);
102
+ const redirectUri = str(query.redirect_uri);
103
+ const state = str(query.state);
104
+ const creds = await this.deps.credentials.ensure();
105
+ if (!clientId || !safeEqual(clientId, creds.clientId)) {
106
+ throw new GitHubFacadeRequestError(400, 'unknown_client', 'Unknown client_id.');
107
+ }
108
+ if (!this.consumerFor(redirectUri)) {
109
+ throw new GitHubFacadeRequestError(400, 'invalid_redirect', 'redirect_uri does not belong to a known consumer.');
110
+ }
111
+ const signed = signAuthRequest(this.deps.stateSecret, {
112
+ c: clientId,
113
+ r: redirectUri,
114
+ s: state || undefined,
115
+ gh: true,
116
+ });
117
+ const url = new URL('/connect', this.deps.publicFrontendUrl);
118
+ url.searchParams.set('oauth', signed);
119
+ return url.toString();
120
+ }
121
+
122
+ /** True when a verified state is one of ours (a GitHub-shaped link, not an MCP client). */
123
+ isFacadeRequest(st: McpAuthRequestState): boolean {
124
+ return st.gh === true;
125
+ }
126
+
127
+ /** What the consent page should say is asking, for one of our states. */
128
+ clientNameFor(st: McpAuthRequestState): string {
129
+ return this.consumerFor(st.r)?.name ?? 'an external product';
130
+ }
131
+
132
+ /**
133
+ * The person approved on `/connect`: a one-time code for them, and the
134
+ * claude.ai callback to send the browser to. Supersedes any live code the
135
+ * same person already had for this client.
136
+ */
137
+ async completeConsent(userId: string, st: McpAuthRequestState): Promise<{ redirectTo: string }> {
138
+ const creds = await this.deps.credentials.ensure();
139
+ if (!safeEqual(st.c, creds.clientId) || !this.consumerFor(st.r)) {
140
+ throw new GitHubFacadeRequestError(400, 'invalid_request', 'The authorization request is not one of ours.');
141
+ }
142
+ const code = randomBytes(CODE_BYTES).toString('hex');
143
+ await this.deps.codes.put({
144
+ codeHash: hashCode(code),
145
+ userId,
146
+ clientId: st.c,
147
+ redirectUri: st.r,
148
+ expiresAt: new Date(Date.now() + CODE_TTL_MS),
149
+ });
150
+ const url = new URL(st.r);
151
+ url.searchParams.set('code', code);
152
+ if (st.s) url.searchParams.set('state', st.s);
153
+ return { redirectTo: url.toString() };
154
+ }
155
+
156
+ /**
157
+ * The token exchange. Client id and secret must be ours and the code live;
158
+ * every check runs BEFORE the code is spent, so a request that is wrong
159
+ * about something else (its redirect, say) can be corrected and retried.
160
+ * Spending is one conditional update in the store — the first exchange
161
+ * wins, on whichever replica, and only the winner mints.
162
+ */
163
+ async exchangeCode(body: Record<string, unknown>): Promise<{
164
+ access_token: string;
165
+ token_type: 'bearer';
166
+ scope: '';
167
+ }> {
168
+ const creds = await this.deps.credentials.ensure();
169
+ const clientId = str(body.client_id);
170
+ const clientSecret = str(body.client_secret);
171
+ const code = str(body.code);
172
+ if (!clientId || !clientSecret || !safeEqual(clientId, creds.clientId) || !safeEqual(clientSecret, creds.clientSecret)) {
173
+ throw new GitHubFacadeRequestError(401, 'incorrect_client_credentials', 'The client_id and/or client_secret passed are incorrect.');
174
+ }
175
+ const pending = code ? await this.deps.codes.peek(hashCode(code)) : null;
176
+ if (!pending || pending.clientId !== clientId) {
177
+ throw new GitHubFacadeRequestError(400, 'bad_verification_code', 'The code passed is incorrect or expired.');
178
+ }
179
+ const redirectUri = str(body.redirect_uri);
180
+ if (redirectUri && redirectUri !== pending.redirectUri) {
181
+ throw new GitHubFacadeRequestError(400, 'redirect_uri_mismatch', 'The redirect_uri does not match the authorization.');
182
+ }
183
+ const spent = await this.deps.codes.consume(pending.codeHash);
184
+ if (!spent) {
185
+ throw new GitHubFacadeRequestError(400, 'bad_verification_code', 'The code passed is incorrect or expired.');
186
+ }
187
+ // The consumer is the one the code was issued for — the redirect the
188
+ // person approved names it — so the key is labelled for that product.
189
+ const consumer = this.consumerFor(spent.redirectUri);
190
+ const minted = await this.deps.keys.mint(spent.userId, consumer?.keyLabel ?? 'GitHub-compatible link', {
191
+ kind: GITHUB_LINK_KEY_KIND,
192
+ });
193
+ return { access_token: minted.plaintext, token_type: 'bearer', scope: '' };
194
+ }
195
+
196
+ /** The consumer whose callback a redirect_uri is, or null — https only, host exactly. */
197
+ private consumerFor(redirectUri: string): GitHubFacadeConsumer | null {
198
+ let parsed: URL;
199
+ try {
200
+ parsed = new URL(redirectUri);
201
+ } catch {
202
+ return null;
203
+ }
204
+ if (parsed.protocol !== 'https:') return null;
205
+ return this.deps.consumers.find((c) => c.redirectHosts.includes(parsed.hostname)) ?? null;
206
+ }
207
+ }
208
+
209
+ function hashCode(code: string): string {
210
+ return createHash('sha256').update(code).digest('hex');
211
+ }
212
+
213
+ function str(v: unknown): string {
214
+ return typeof v === 'string' ? v : '';
215
+ }
216
+
217
+ function safeEqual(a: string, b: string): boolean {
218
+ const ab = Buffer.from(a);
219
+ const bb = Buffer.from(b);
220
+ return ab.length === bb.length && timingSafeEqual(ab, bb);
221
+ }
@@ -0,0 +1,25 @@
1
+ export {
2
+ GitHubFacadeCredentialsService,
3
+ DbGitHubFacadeCredentialsStore,
4
+ MemoryGitHubFacadeCredentialsStore,
5
+ GitHubFacadeUnavailableError,
6
+ type GitHubFacadeCredentials,
7
+ type GitHubFacadeCredentialsStore,
8
+ } from './github-facade-credentials.service.js';
9
+ export {
10
+ DbGitHubFacadeCodeStore,
11
+ MemoryGitHubFacadeCodeStore,
12
+ type GitHubFacadeCodeStore,
13
+ type PendingFacadeCode,
14
+ } from './github-facade-codes.store.js';
15
+ export {
16
+ GitHubFacade,
17
+ GitHubFacadeRequestError,
18
+ GITHUB_LINK_KEY_KIND,
19
+ GITHUB_LINK_KEY_PREFIX,
20
+ CLAUDE_CONSUMER,
21
+ type GitHubFacadeConsumer,
22
+ type GitHubFacadeDeps,
23
+ } from './github-facade.service.js';
24
+ export { createGitHubFacadeRoutes, type GitHubFacadeRoutesDeps } from './github-facade.routes.js';
25
+ export { createGitHubFacadeAdminRoutes, type GitHubFacadeAdminRoutesDeps } from './github-facade-admin.routes.js';
@@ -0,0 +1,3 @@
1
+ export { MarketplaceRepoService, type MarketplaceCompiler, type EnsureResult } from './marketplace-repo.service.js';
2
+ export { createMarketplaceGitRoutes, type MarketplaceKeyResolver } from './git-http.routes.js';
3
+ export * from './github-facade/index.js';