@bevel-software/platform-core-backend 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (720) hide show
  1. package/LICENSE +202 -0
  2. package/dist/assets.d.ts +5 -0
  3. package/dist/assets.d.ts.map +1 -0
  4. package/dist/assets.js +23 -0
  5. package/dist/assets.js.map +1 -0
  6. package/dist/core/core-ports.d.ts +83 -0
  7. package/dist/core/core-ports.d.ts.map +1 -0
  8. package/dist/core/core-ports.js +12 -0
  9. package/dist/core/core-ports.js.map +1 -0
  10. package/dist/core/create-core-server.d.ts +62 -0
  11. package/dist/core/create-core-server.d.ts.map +1 -0
  12. package/dist/core/create-core-server.js +204 -0
  13. package/dist/core/create-core-server.js.map +1 -0
  14. package/dist/core/create-core-services.d.ts +95 -0
  15. package/dist/core/create-core-services.d.ts.map +1 -0
  16. package/dist/core/create-core-services.js +352 -0
  17. package/dist/core/create-core-services.js.map +1 -0
  18. package/dist/core-config.d.ts +119 -0
  19. package/dist/core-config.d.ts.map +1 -0
  20. package/dist/core-config.js +240 -0
  21. package/dist/core-config.js.map +1 -0
  22. package/dist/index.d.ts +36 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +36 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/modules/access/access-control.interface.d.ts +348 -0
  27. package/dist/modules/access/access-control.interface.d.ts.map +1 -0
  28. package/dist/modules/access/access-control.interface.js +2 -0
  29. package/dist/modules/access/access-control.interface.js.map +1 -0
  30. package/dist/modules/access/access-control.service.d.ts +323 -0
  31. package/dist/modules/access/access-control.service.d.ts.map +1 -0
  32. package/dist/modules/access/access-control.service.js +1431 -0
  33. package/dist/modules/access/access-control.service.js.map +1 -0
  34. package/dist/modules/access/access-errors.d.ts +34 -0
  35. package/dist/modules/access/access-errors.d.ts.map +1 -0
  36. package/dist/modules/access/access-errors.js +40 -0
  37. package/dist/modules/access/access-errors.js.map +1 -0
  38. package/dist/modules/access/access-mutation.service.d.ts +176 -0
  39. package/dist/modules/access/access-mutation.service.d.ts.map +1 -0
  40. package/dist/modules/access/access-mutation.service.js +307 -0
  41. package/dist/modules/access/access-mutation.service.js.map +1 -0
  42. package/dist/modules/access/access-splice.d.ts +98 -0
  43. package/dist/modules/access/access-splice.d.ts.map +1 -0
  44. package/dist/modules/access/access-splice.js +346 -0
  45. package/dist/modules/access/access-splice.js.map +1 -0
  46. package/dist/modules/access/access.routes.d.ts +10 -0
  47. package/dist/modules/access/access.routes.d.ts.map +1 -0
  48. package/dist/modules/access/access.routes.js +757 -0
  49. package/dist/modules/access/access.routes.js.map +1 -0
  50. package/dist/modules/access/creator-access.d.ts +113 -0
  51. package/dist/modules/access/creator-access.d.ts.map +1 -0
  52. package/dist/modules/access/creator-access.js +212 -0
  53. package/dist/modules/access/creator-access.js.map +1 -0
  54. package/dist/modules/access/kb-read-filter.d.ts +41 -0
  55. package/dist/modules/access/kb-read-filter.d.ts.map +1 -0
  56. package/dist/modules/access/kb-read-filter.js +60 -0
  57. package/dist/modules/access/kb-read-filter.js.map +1 -0
  58. package/dist/modules/access/roles-admin.service.d.ts +240 -0
  59. package/dist/modules/access/roles-admin.service.d.ts.map +1 -0
  60. package/dist/modules/access/roles-admin.service.js +630 -0
  61. package/dist/modules/access/roles-admin.service.js.map +1 -0
  62. package/dist/modules/access/roles-edit.d.ts +91 -0
  63. package/dist/modules/access/roles-edit.d.ts.map +1 -0
  64. package/dist/modules/access/roles-edit.js +232 -0
  65. package/dist/modules/access/roles-edit.js.map +1 -0
  66. package/dist/modules/access/roles-yaml-guard.d.ts +56 -0
  67. package/dist/modules/access/roles-yaml-guard.d.ts.map +1 -0
  68. package/dist/modules/access/roles-yaml-guard.js +79 -0
  69. package/dist/modules/access/roles-yaml-guard.js.map +1 -0
  70. package/dist/modules/admin/admin-access.routes.d.ts +18 -0
  71. package/dist/modules/admin/admin-access.routes.d.ts.map +1 -0
  72. package/dist/modules/admin/admin-access.routes.js +26 -0
  73. package/dist/modules/admin/admin-access.routes.js.map +1 -0
  74. package/dist/modules/admin/admin-access.service.d.ts +24 -0
  75. package/dist/modules/admin/admin-access.service.d.ts.map +1 -0
  76. package/dist/modules/admin/admin-access.service.js +41 -0
  77. package/dist/modules/admin/admin-access.service.js.map +1 -0
  78. package/dist/modules/admin/admin.interface.d.ts +11 -0
  79. package/dist/modules/admin/admin.interface.d.ts.map +1 -0
  80. package/dist/modules/admin/admin.interface.js +2 -0
  81. package/dist/modules/admin/admin.interface.js.map +1 -0
  82. package/dist/modules/auth/account-erasure.service.d.ts +80 -0
  83. package/dist/modules/auth/account-erasure.service.d.ts.map +1 -0
  84. package/dist/modules/auth/account-erasure.service.js +91 -0
  85. package/dist/modules/auth/account-erasure.service.js.map +1 -0
  86. package/dist/modules/auth/auth.middleware.d.ts +23 -0
  87. package/dist/modules/auth/auth.middleware.d.ts.map +1 -0
  88. package/dist/modules/auth/auth.middleware.js +68 -0
  89. package/dist/modules/auth/auth.middleware.js.map +1 -0
  90. package/dist/modules/auth/auth.routes.d.ts +25 -0
  91. package/dist/modules/auth/auth.routes.d.ts.map +1 -0
  92. package/dist/modules/auth/auth.routes.js +72 -0
  93. package/dist/modules/auth/auth.routes.js.map +1 -0
  94. package/dist/modules/auth/auth.service.d.ts +76 -0
  95. package/dist/modules/auth/auth.service.d.ts.map +1 -0
  96. package/dist/modules/auth/auth.service.js +168 -0
  97. package/dist/modules/auth/auth.service.js.map +1 -0
  98. package/dist/modules/code-mode/code-mode-names.d.ts +16 -0
  99. package/dist/modules/code-mode/code-mode-names.d.ts.map +1 -0
  100. package/dist/modules/code-mode/code-mode-names.js +30 -0
  101. package/dist/modules/code-mode/code-mode-names.js.map +1 -0
  102. package/dist/modules/code-mode/code-mode.tool.d.ts +13 -0
  103. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -0
  104. package/dist/modules/code-mode/code-mode.tool.js +112 -0
  105. package/dist/modules/code-mode/code-mode.tool.js.map +1 -0
  106. package/dist/modules/code-mode/index.d.ts +3 -0
  107. package/dist/modules/code-mode/index.d.ts.map +1 -0
  108. package/dist/modules/code-mode/index.js +3 -0
  109. package/dist/modules/code-mode/index.js.map +1 -0
  110. package/dist/modules/database/connection.d.ts +6 -0
  111. package/dist/modules/database/connection.d.ts.map +1 -0
  112. package/dist/modules/database/connection.js +12 -0
  113. package/dist/modules/database/connection.js.map +1 -0
  114. package/dist/modules/database/core-schema.d.ts +2273 -0
  115. package/dist/modules/database/core-schema.d.ts.map +1 -0
  116. package/dist/modules/database/core-schema.js +413 -0
  117. package/dist/modules/database/core-schema.js.map +1 -0
  118. package/dist/modules/database/migrate.d.ts +10 -0
  119. package/dist/modules/database/migrate.d.ts.map +1 -0
  120. package/dist/modules/database/migrate.js +40 -0
  121. package/dist/modules/database/migrate.js.map +1 -0
  122. package/dist/modules/database/schema.d.ts +12 -0
  123. package/dist/modules/database/schema.d.ts.map +1 -0
  124. package/dist/modules/database/schema.js +12 -0
  125. package/dist/modules/database/schema.js.map +1 -0
  126. package/dist/modules/diff/diff-paths.d.ts +2 -0
  127. package/dist/modules/diff/diff-paths.d.ts.map +1 -0
  128. package/dist/modules/diff/diff-paths.js +9 -0
  129. package/dist/modules/diff/diff-paths.js.map +1 -0
  130. package/dist/modules/diff/diff.config.d.ts +8 -0
  131. package/dist/modules/diff/diff.config.d.ts.map +1 -0
  132. package/dist/modules/diff/diff.config.js +13 -0
  133. package/dist/modules/diff/diff.config.js.map +1 -0
  134. package/dist/modules/diff/diff.interface.d.ts +73 -0
  135. package/dist/modules/diff/diff.interface.d.ts.map +1 -0
  136. package/dist/modules/diff/diff.interface.js +2 -0
  137. package/dist/modules/diff/diff.interface.js.map +1 -0
  138. package/dist/modules/diff/diff.routes.d.ts +35 -0
  139. package/dist/modules/diff/diff.routes.d.ts.map +1 -0
  140. package/dist/modules/diff/diff.routes.js +228 -0
  141. package/dist/modules/diff/diff.routes.js.map +1 -0
  142. package/dist/modules/diff/diff.service.d.ts +51 -0
  143. package/dist/modules/diff/diff.service.d.ts.map +1 -0
  144. package/dist/modules/diff/diff.service.js +489 -0
  145. package/dist/modules/diff/diff.service.js.map +1 -0
  146. package/dist/modules/diff/line-diff.d.ts +16 -0
  147. package/dist/modules/diff/line-diff.d.ts.map +1 -0
  148. package/dist/modules/diff/line-diff.js +67 -0
  149. package/dist/modules/diff/line-diff.js.map +1 -0
  150. package/dist/modules/mcp/mcp-auth.middleware.d.ts +27 -0
  151. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -0
  152. package/dist/modules/mcp/mcp-auth.middleware.js +116 -0
  153. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -0
  154. package/dist/modules/mcp/mcp-session-store.d.ts +74 -0
  155. package/dist/modules/mcp/mcp-session-store.d.ts.map +1 -0
  156. package/dist/modules/mcp/mcp-session-store.js +131 -0
  157. package/dist/modules/mcp/mcp-session-store.js.map +1 -0
  158. package/dist/modules/mcp/mcp.routes.d.ts +24 -0
  159. package/dist/modules/mcp/mcp.routes.d.ts.map +1 -0
  160. package/dist/modules/mcp/mcp.routes.js +229 -0
  161. package/dist/modules/mcp/mcp.routes.js.map +1 -0
  162. package/dist/modules/mcp/mcp.service.d.ts +198 -0
  163. package/dist/modules/mcp/mcp.service.d.ts.map +1 -0
  164. package/dist/modules/mcp/mcp.service.js +840 -0
  165. package/dist/modules/mcp/mcp.service.js.map +1 -0
  166. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +76 -0
  167. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -0
  168. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +291 -0
  169. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -0
  170. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +18 -0
  171. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -0
  172. package/dist/modules/mcp/oauth/oauth-consent.routes.js +52 -0
  173. package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -0
  174. package/dist/modules/mcp/oauth/oauth-state.d.ts +32 -0
  175. package/dist/modules/mcp/oauth/oauth-state.d.ts.map +1 -0
  176. package/dist/modules/mcp/oauth/oauth-state.js +34 -0
  177. package/dist/modules/mcp/oauth/oauth-state.js.map +1 -0
  178. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +92 -0
  179. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -0
  180. package/dist/modules/secrets-vault/db-secrets-vault.service.js +728 -0
  181. package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -0
  182. package/dist/modules/secrets-vault/index.d.ts +7 -0
  183. package/dist/modules/secrets-vault/index.d.ts.map +1 -0
  184. package/dist/modules/secrets-vault/index.js +6 -0
  185. package/dist/modules/secrets-vault/index.js.map +1 -0
  186. package/dist/modules/secrets-vault/mcp-oauth-discovery.service.d.ts +63 -0
  187. package/dist/modules/secrets-vault/mcp-oauth-discovery.service.d.ts.map +1 -0
  188. package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js +269 -0
  189. package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js.map +1 -0
  190. package/dist/modules/secrets-vault/secrets-variable-loader.d.ts +46 -0
  191. package/dist/modules/secrets-vault/secrets-variable-loader.d.ts.map +1 -0
  192. package/dist/modules/secrets-vault/secrets-variable-loader.js +87 -0
  193. package/dist/modules/secrets-vault/secrets-variable-loader.js.map +1 -0
  194. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +210 -0
  195. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -0
  196. package/dist/modules/secrets-vault/secrets-vault.contract.js +90 -0
  197. package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -0
  198. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +30 -0
  199. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -0
  200. package/dist/modules/secrets-vault/secrets-vault.routes.js +490 -0
  201. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -0
  202. package/dist/modules/skills/index.d.ts +5 -0
  203. package/dist/modules/skills/index.d.ts.map +1 -0
  204. package/dist/modules/skills/index.js +4 -0
  205. package/dist/modules/skills/index.js.map +1 -0
  206. package/dist/modules/skills/skills.contract.d.ts +58 -0
  207. package/dist/modules/skills/skills.contract.d.ts.map +1 -0
  208. package/dist/modules/skills/skills.contract.js +10 -0
  209. package/dist/modules/skills/skills.contract.js.map +1 -0
  210. package/dist/modules/skills/skills.routes.d.ts +15 -0
  211. package/dist/modules/skills/skills.routes.d.ts.map +1 -0
  212. package/dist/modules/skills/skills.routes.js +34 -0
  213. package/dist/modules/skills/skills.routes.js.map +1 -0
  214. package/dist/modules/skills/skills.service.d.ts +21 -0
  215. package/dist/modules/skills/skills.service.d.ts.map +1 -0
  216. package/dist/modules/skills/skills.service.js +202 -0
  217. package/dist/modules/skills/skills.service.js.map +1 -0
  218. package/dist/modules/skills/skills.tools.d.ts +15 -0
  219. package/dist/modules/skills/skills.tools.d.ts.map +1 -0
  220. package/dist/modules/skills/skills.tools.js +96 -0
  221. package/dist/modules/skills/skills.tools.js.map +1 -0
  222. package/dist/modules/tool-auth/external-api-key.errors.d.ts +14 -0
  223. package/dist/modules/tool-auth/external-api-key.errors.d.ts.map +1 -0
  224. package/dist/modules/tool-auth/external-api-key.errors.js +23 -0
  225. package/dist/modules/tool-auth/external-api-key.errors.js.map +1 -0
  226. package/dist/modules/tool-auth/external-api-key.interface.d.ts +90 -0
  227. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -0
  228. package/dist/modules/tool-auth/external-api-key.interface.js +2 -0
  229. package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -0
  230. package/dist/modules/tool-auth/external-api-key.service.d.ts +25 -0
  231. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -0
  232. package/dist/modules/tool-auth/external-api-key.service.js +164 -0
  233. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -0
  234. package/dist/modules/tool-auth/internal-token.service.d.ts +62 -0
  235. package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -0
  236. package/dist/modules/tool-auth/internal-token.service.js +85 -0
  237. package/dist/modules/tool-auth/internal-token.service.js.map +1 -0
  238. package/dist/modules/tool-auth/llm-usage-meter.d.ts +16 -0
  239. package/dist/modules/tool-auth/llm-usage-meter.d.ts.map +1 -0
  240. package/dist/modules/tool-auth/llm-usage-meter.js +6 -0
  241. package/dist/modules/tool-auth/llm-usage-meter.js.map +1 -0
  242. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +101 -0
  243. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -0
  244. package/dist/modules/tool-auth/tool-auth.middleware.js +166 -0
  245. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -0
  246. package/dist/modules/tool-helpers/index.d.ts +19 -0
  247. package/dist/modules/tool-helpers/index.d.ts.map +1 -0
  248. package/dist/modules/tool-helpers/index.js +18 -0
  249. package/dist/modules/tool-helpers/index.js.map +1 -0
  250. package/dist/modules/tool-helpers/tool-context.d.ts +35 -0
  251. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -0
  252. package/dist/modules/tool-helpers/tool-context.js +66 -0
  253. package/dist/modules/tool-helpers/tool-context.js.map +1 -0
  254. package/dist/modules/tool-helpers/tool-def.d.ts +33 -0
  255. package/dist/modules/tool-helpers/tool-def.d.ts.map +1 -0
  256. package/dist/modules/tool-helpers/tool-def.js +57 -0
  257. package/dist/modules/tool-helpers/tool-def.js.map +1 -0
  258. package/dist/modules/tool-helpers/tool-handler.d.ts +19 -0
  259. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -0
  260. package/dist/modules/tool-helpers/tool-handler.js +84 -0
  261. package/dist/modules/tool-helpers/tool-handler.js.map +1 -0
  262. package/dist/modules/tool-helpers/tool.contract.d.ts +64 -0
  263. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -0
  264. package/dist/modules/tool-helpers/tool.contract.js +24 -0
  265. package/dist/modules/tool-helpers/tool.contract.js.map +1 -0
  266. package/dist/modules/tool-helpers/validate-token.d.ts +31 -0
  267. package/dist/modules/tool-helpers/validate-token.d.ts.map +1 -0
  268. package/dist/modules/tool-helpers/validate-token.js +20 -0
  269. package/dist/modules/tool-helpers/validate-token.js.map +1 -0
  270. package/dist/modules/tool-manuals/index.d.ts +5 -0
  271. package/dist/modules/tool-manuals/index.d.ts.map +1 -0
  272. package/dist/modules/tool-manuals/index.js +5 -0
  273. package/dist/modules/tool-manuals/index.js.map +1 -0
  274. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +182 -0
  275. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -0
  276. package/dist/modules/tool-manuals/tool-manuals.contract.js +19 -0
  277. package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -0
  278. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +24 -0
  279. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -0
  280. package/dist/modules/tool-manuals/tool-manuals.routes.js +102 -0
  281. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -0
  282. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +108 -0
  283. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -0
  284. package/dist/modules/tool-manuals/tool-manuals.service.js +710 -0
  285. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -0
  286. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +35 -0
  287. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -0
  288. package/dist/modules/tool-manuals/tool-manuals.tools.js +157 -0
  289. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -0
  290. package/dist/modules/tool-registry/manual.routes.d.ts +6 -0
  291. package/dist/modules/tool-registry/manual.routes.d.ts.map +1 -0
  292. package/dist/modules/tool-registry/manual.routes.js +76 -0
  293. package/dist/modules/tool-registry/manual.routes.js.map +1 -0
  294. package/dist/modules/tool-registry/tool-registry.d.ts +23 -0
  295. package/dist/modules/tool-registry/tool-registry.d.ts.map +1 -0
  296. package/dist/modules/tool-registry/tool-registry.js +46 -0
  297. package/dist/modules/tool-registry/tool-registry.js.map +1 -0
  298. package/dist/modules/tool-registry/tool.contract.d.ts +48 -0
  299. package/dist/modules/tool-registry/tool.contract.d.ts.map +1 -0
  300. package/dist/modules/tool-registry/tool.contract.js +2 -0
  301. package/dist/modules/tool-registry/tool.contract.js.map +1 -0
  302. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +14 -0
  303. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -0
  304. package/dist/modules/workflow/agent-tools/workflow.tools.js +437 -0
  305. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -0
  306. package/dist/modules/workflow/event-bus.d.ts +107 -0
  307. package/dist/modules/workflow/event-bus.d.ts.map +1 -0
  308. package/dist/modules/workflow/event-bus.js +224 -0
  309. package/dist/modules/workflow/event-bus.js.map +1 -0
  310. package/dist/modules/workflow/events.routes.d.ts +80 -0
  311. package/dist/modules/workflow/events.routes.d.ts.map +1 -0
  312. package/dist/modules/workflow/events.routes.js +216 -0
  313. package/dist/modules/workflow/events.routes.js.map +1 -0
  314. package/dist/modules/workflow/file-change-notifier.d.ts +38 -0
  315. package/dist/modules/workflow/file-change-notifier.d.ts.map +1 -0
  316. package/dist/modules/workflow/file-change-notifier.js +22 -0
  317. package/dist/modules/workflow/file-change-notifier.js.map +1 -0
  318. package/dist/modules/workflow/file-lock.service.d.ts +57 -0
  319. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -0
  320. package/dist/modules/workflow/file-lock.service.js +202 -0
  321. package/dist/modules/workflow/file-lock.service.js.map +1 -0
  322. package/dist/modules/workflow/git/branch-name.d.ts +10 -0
  323. package/dist/modules/workflow/git/branch-name.d.ts.map +1 -0
  324. package/dist/modules/workflow/git/branch-name.js +76 -0
  325. package/dist/modules/workflow/git/branch-name.js.map +1 -0
  326. package/dist/modules/workflow/git/clone-config.d.ts +61 -0
  327. package/dist/modules/workflow/git/clone-config.d.ts.map +1 -0
  328. package/dist/modules/workflow/git/clone-config.js +69 -0
  329. package/dist/modules/workflow/git/clone-config.js.map +1 -0
  330. package/dist/modules/workflow/git/git-version.d.ts +34 -0
  331. package/dist/modules/workflow/git/git-version.d.ts.map +1 -0
  332. package/dist/modules/workflow/git/git-version.js +63 -0
  333. package/dist/modules/workflow/git/git-version.js.map +1 -0
  334. package/dist/modules/workflow/git/git.service.d.ts +427 -0
  335. package/dist/modules/workflow/git/git.service.d.ts.map +1 -0
  336. package/dist/modules/workflow/git/git.service.js +1987 -0
  337. package/dist/modules/workflow/git/git.service.js.map +1 -0
  338. package/dist/modules/workflow/git/mutex.d.ts +11 -0
  339. package/dist/modules/workflow/git/mutex.d.ts.map +1 -0
  340. package/dist/modules/workflow/git/mutex.js +23 -0
  341. package/dist/modules/workflow/git/mutex.js.map +1 -0
  342. package/dist/modules/workflow/git/pull-request.service.d.ts +124 -0
  343. package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -0
  344. package/dist/modules/workflow/git/pull-request.service.js +328 -0
  345. package/dist/modules/workflow/git/pull-request.service.js.map +1 -0
  346. package/dist/modules/workflow/locking-filesystem.d.ts +133 -0
  347. package/dist/modules/workflow/locking-filesystem.d.ts.map +1 -0
  348. package/dist/modules/workflow/locking-filesystem.js +400 -0
  349. package/dist/modules/workflow/locking-filesystem.js.map +1 -0
  350. package/dist/modules/workflow/pending-commits.service.d.ts +221 -0
  351. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -0
  352. package/dist/modules/workflow/pending-commits.service.js +364 -0
  353. package/dist/modules/workflow/pending-commits.service.js.map +1 -0
  354. package/dist/modules/workflow/pending-commits.worker.d.ts +148 -0
  355. package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -0
  356. package/dist/modules/workflow/pending-commits.worker.js +245 -0
  357. package/dist/modules/workflow/pending-commits.worker.js.map +1 -0
  358. package/dist/modules/workflow/read-only-filesystem.d.ts +24 -0
  359. package/dist/modules/workflow/read-only-filesystem.d.ts.map +1 -0
  360. package/dist/modules/workflow/read-only-filesystem.js +39 -0
  361. package/dist/modules/workflow/read-only-filesystem.js.map +1 -0
  362. package/dist/modules/workflow/recovery-bot.d.ts +31 -0
  363. package/dist/modules/workflow/recovery-bot.d.ts.map +1 -0
  364. package/dist/modules/workflow/recovery-bot.js +64 -0
  365. package/dist/modules/workflow/recovery-bot.js.map +1 -0
  366. package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts +137 -0
  367. package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts.map +1 -0
  368. package/dist/modules/workflow/review-workflow/review-workflow.interface.js +2 -0
  369. package/dist/modules/workflow/review-workflow/review-workflow.interface.js.map +1 -0
  370. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +26 -0
  371. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -0
  372. package/dist/modules/workflow/review-workflow/review-workflow.service.js +636 -0
  373. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -0
  374. package/dist/modules/workflow/sanitize-error.d.ts +29 -0
  375. package/dist/modules/workflow/sanitize-error.d.ts.map +1 -0
  376. package/dist/modules/workflow/sanitize-error.js +60 -0
  377. package/dist/modules/workflow/sanitize-error.js.map +1 -0
  378. package/dist/modules/workflow/session-ontology.policy.d.ts +52 -0
  379. package/dist/modules/workflow/session-ontology.policy.d.ts.map +1 -0
  380. package/dist/modules/workflow/session-ontology.policy.js +62 -0
  381. package/dist/modules/workflow/session-ontology.policy.js.map +1 -0
  382. package/dist/modules/workflow/session-ontology.service.d.ts +100 -0
  383. package/dist/modules/workflow/session-ontology.service.d.ts.map +1 -0
  384. package/dist/modules/workflow/session-ontology.service.js +143 -0
  385. package/dist/modules/workflow/session-ontology.service.js.map +1 -0
  386. package/dist/modules/workflow/workflow-hooks.d.ts +87 -0
  387. package/dist/modules/workflow/workflow-hooks.d.ts.map +1 -0
  388. package/dist/modules/workflow/workflow-hooks.js +35 -0
  389. package/dist/modules/workflow/workflow-hooks.js.map +1 -0
  390. package/dist/modules/workflow/workflow.errors.d.ts +171 -0
  391. package/dist/modules/workflow/workflow.errors.d.ts.map +1 -0
  392. package/dist/modules/workflow/workflow.errors.js +260 -0
  393. package/dist/modules/workflow/workflow.errors.js.map +1 -0
  394. package/dist/modules/workflow/workflow.routes.d.ts +26 -0
  395. package/dist/modules/workflow/workflow.routes.d.ts.map +1 -0
  396. package/dist/modules/workflow/workflow.routes.js +804 -0
  397. package/dist/modules/workflow/workflow.routes.js.map +1 -0
  398. package/dist/modules/workflow/workflow.service.d.ts +385 -0
  399. package/dist/modules/workflow/workflow.service.d.ts.map +1 -0
  400. package/dist/modules/workflow/workflow.service.js +1155 -0
  401. package/dist/modules/workflow/workflow.service.js.map +1 -0
  402. package/dist/modules/workspace/bevel-ignore.d.ts +25 -0
  403. package/dist/modules/workspace/bevel-ignore.d.ts.map +1 -0
  404. package/dist/modules/workspace/bevel-ignore.js +57 -0
  405. package/dist/modules/workspace/bevel-ignore.js.map +1 -0
  406. package/dist/modules/workspace/kb-seed.interface.d.ts +36 -0
  407. package/dist/modules/workspace/kb-seed.interface.d.ts.map +1 -0
  408. package/dist/modules/workspace/kb-seed.interface.js +2 -0
  409. package/dist/modules/workspace/kb-seed.interface.js.map +1 -0
  410. package/dist/modules/workspace/kb-seed.service.d.ts +70 -0
  411. package/dist/modules/workspace/kb-seed.service.d.ts.map +1 -0
  412. package/dist/modules/workspace/kb-seed.service.js +325 -0
  413. package/dist/modules/workspace/kb-seed.service.js.map +1 -0
  414. package/dist/modules/workspace/routine-write-policy.d.ts +49 -0
  415. package/dist/modules/workspace/routine-write-policy.d.ts.map +1 -0
  416. package/dist/modules/workspace/routine-write-policy.js +46 -0
  417. package/dist/modules/workspace/routine-write-policy.js.map +1 -0
  418. package/dist/modules/workspace/session-ontology.gate.d.ts +112 -0
  419. package/dist/modules/workspace/session-ontology.gate.d.ts.map +1 -0
  420. package/dist/modules/workspace/session-ontology.gate.js +161 -0
  421. package/dist/modules/workspace/session-ontology.gate.js.map +1 -0
  422. package/dist/modules/workspace/session-sink.d.ts +25 -0
  423. package/dist/modules/workspace/session-sink.d.ts.map +1 -0
  424. package/dist/modules/workspace/session-sink.js +8 -0
  425. package/dist/modules/workspace/session-sink.js.map +1 -0
  426. package/dist/modules/workspace/spill-store.d.ts +24 -0
  427. package/dist/modules/workspace/spill-store.d.ts.map +1 -0
  428. package/dist/modules/workspace/spill-store.js +87 -0
  429. package/dist/modules/workspace/spill-store.js.map +1 -0
  430. package/dist/modules/workspace/workspace.routes.d.ts +25 -0
  431. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -0
  432. package/dist/modules/workspace/workspace.routes.js +1046 -0
  433. package/dist/modules/workspace/workspace.routes.js.map +1 -0
  434. package/dist/modules/workspace/workspace.service.d.ts +339 -0
  435. package/dist/modules/workspace/workspace.service.d.ts.map +1 -0
  436. package/dist/modules/workspace/workspace.service.js +1221 -0
  437. package/dist/modules/workspace/workspace.service.js.map +1 -0
  438. package/dist/modules/workspace/workspace.tools.d.ts +19 -0
  439. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -0
  440. package/dist/modules/workspace/workspace.tools.js +767 -0
  441. package/dist/modules/workspace/workspace.tools.js.map +1 -0
  442. package/dist/shared/frontmatter-id.d.ts +35 -0
  443. package/dist/shared/frontmatter-id.d.ts.map +1 -0
  444. package/dist/shared/frontmatter-id.js +68 -0
  445. package/dist/shared/frontmatter-id.js.map +1 -0
  446. package/dist/shared/fs-walk.d.ts +9 -0
  447. package/dist/shared/fs-walk.d.ts.map +1 -0
  448. package/dist/shared/fs-walk.js +34 -0
  449. package/dist/shared/fs-walk.js.map +1 -0
  450. package/dist/shared/hash-email.d.ts +11 -0
  451. package/dist/shared/hash-email.d.ts.map +1 -0
  452. package/dist/shared/hash-email.js +14 -0
  453. package/dist/shared/hash-email.js.map +1 -0
  454. package/dist/shared/kb-layout.d.ts +38 -0
  455. package/dist/shared/kb-layout.d.ts.map +1 -0
  456. package/dist/shared/kb-layout.js +102 -0
  457. package/dist/shared/kb-layout.js.map +1 -0
  458. package/dist/shared/kb-layout.test.d.ts +2 -0
  459. package/dist/shared/kb-layout.test.d.ts.map +1 -0
  460. package/dist/shared/kb-layout.test.js +67 -0
  461. package/dist/shared/kb-layout.test.js.map +1 -0
  462. package/dist/shared/ssrf.d.ts +20 -0
  463. package/dist/shared/ssrf.d.ts.map +1 -0
  464. package/dist/shared/ssrf.js +89 -0
  465. package/dist/shared/ssrf.js.map +1 -0
  466. package/dist/shared/token-crypto.d.ts +16 -0
  467. package/dist/shared/token-crypto.d.ts.map +1 -0
  468. package/dist/shared/token-crypto.js +49 -0
  469. package/dist/shared/token-crypto.js.map +1 -0
  470. package/dist/shared/ttl-cache.d.ts +16 -0
  471. package/dist/shared/ttl-cache.d.ts.map +1 -0
  472. package/dist/shared/ttl-cache.js +27 -0
  473. package/dist/shared/ttl-cache.js.map +1 -0
  474. package/dist/shared/utcp-namespace.d.ts +46 -0
  475. package/dist/shared/utcp-namespace.d.ts.map +1 -0
  476. package/dist/shared/utcp-namespace.js +82 -0
  477. package/dist/shared/utcp-namespace.js.map +1 -0
  478. package/dist/version.d.ts +17 -0
  479. package/dist/version.d.ts.map +1 -0
  480. package/dist/version.js +19 -0
  481. package/dist/version.js.map +1 -0
  482. package/kb-template/.bevelignore +21 -0
  483. package/kb-template/Agents/.gitkeep +0 -0
  484. package/kb-template/Agents/README.md +38 -0
  485. package/kb-template/CLAUDE.md +419 -0
  486. package/kb-template/Data/.gitkeep +0 -0
  487. package/kb-template/Data/README.md +39 -0
  488. package/kb-template/KnowledgeBase/.gitkeep +0 -0
  489. package/kb-template/KnowledgeBase/README.md +37 -0
  490. package/kb-template/Pipelines/.gitkeep +0 -0
  491. package/kb-template/Pipelines/README.md +38 -0
  492. package/kb-template/Skills/.gitkeep +0 -0
  493. package/kb-template/Skills/README.md +32 -0
  494. package/kb-template/Tools/.gitkeep +0 -0
  495. package/kb-template/Tools/README.md +42 -0
  496. package/kb-template/access.md +36 -0
  497. package/migrations/0000_core_init.sql +243 -0
  498. package/migrations/meta/0000_snapshot.json +1480 -0
  499. package/migrations/meta/_journal.json +13 -0
  500. package/package.json +80 -0
  501. package/src/assets.ts +25 -0
  502. package/src/core/core-ports.ts +91 -0
  503. package/src/core/create-core-server.ts +338 -0
  504. package/src/core/create-core-services.ts +501 -0
  505. package/src/core-config.ts +257 -0
  506. package/src/index.ts +69 -0
  507. package/src/modules/access/__tests__/access-control.atref-batch.test.ts +98 -0
  508. package/src/modules/access/__tests__/access-control.read-batch.test.ts +132 -0
  509. package/src/modules/access/__tests__/access-control.service.test.ts +1011 -0
  510. package/src/modules/access/__tests__/access-mutation.service.test.ts +439 -0
  511. package/src/modules/access/__tests__/access-splice.test.ts +247 -0
  512. package/src/modules/access/__tests__/access.routes.revoke.test.ts +407 -0
  513. package/src/modules/access/__tests__/creator-access.test.ts +217 -0
  514. package/src/modules/access/__tests__/grant-sources.test.ts +204 -0
  515. package/src/modules/access/__tests__/kb-read-filter.test.ts +70 -0
  516. package/src/modules/access/__tests__/roles-admin.service.test.ts +469 -0
  517. package/src/modules/access/__tests__/roles-edit.test.ts +138 -0
  518. package/src/modules/access/__tests__/roles-yaml-guard.test.ts +70 -0
  519. package/src/modules/access/__tests__/roles.routes.test.ts +234 -0
  520. package/src/modules/access/access-control.interface.ts +383 -0
  521. package/src/modules/access/access-control.service.ts +1794 -0
  522. package/src/modules/access/access-errors.ts +60 -0
  523. package/src/modules/access/access-mutation.service.ts +369 -0
  524. package/src/modules/access/access-splice.ts +413 -0
  525. package/src/modules/access/access.routes.ts +894 -0
  526. package/src/modules/access/creator-access.ts +270 -0
  527. package/src/modules/access/kb-read-filter.ts +76 -0
  528. package/src/modules/access/roles-admin.service.ts +741 -0
  529. package/src/modules/access/roles-edit.ts +248 -0
  530. package/src/modules/access/roles-yaml-guard.ts +89 -0
  531. package/src/modules/admin/admin-access.routes.ts +29 -0
  532. package/src/modules/admin/admin-access.service.ts +38 -0
  533. package/src/modules/admin/admin.interface.ts +10 -0
  534. package/src/modules/auth/account-erasure.service.ts +183 -0
  535. package/src/modules/auth/auth.middleware.ts +78 -0
  536. package/src/modules/auth/auth.routes.ts +97 -0
  537. package/src/modules/auth/auth.service.ts +199 -0
  538. package/src/modules/code-mode/code-mode-names.ts +38 -0
  539. package/src/modules/code-mode/code-mode.tool.ts +117 -0
  540. package/src/modules/code-mode/index.ts +6 -0
  541. package/src/modules/database/connection.ts +15 -0
  542. package/src/modules/database/core-schema.ts +431 -0
  543. package/src/modules/database/migrate.ts +43 -0
  544. package/src/modules/database/schema.ts +11 -0
  545. package/src/modules/diff/__tests__/diff.routes.read-gate.test.ts +87 -0
  546. package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +150 -0
  547. package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +184 -0
  548. package/src/modules/diff/__tests__/diff.service.test.ts +202 -0
  549. package/src/modules/diff/diff-paths.ts +9 -0
  550. package/src/modules/diff/diff.config.ts +12 -0
  551. package/src/modules/diff/diff.interface.ts +78 -0
  552. package/src/modules/diff/diff.routes.ts +277 -0
  553. package/src/modules/diff/diff.service.ts +508 -0
  554. package/src/modules/diff/line-diff.ts +79 -0
  555. package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +287 -0
  556. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +249 -0
  557. package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +253 -0
  558. package/src/modules/mcp/__tests__/mcp-session-store.test.ts +151 -0
  559. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +150 -0
  560. package/src/modules/mcp/__tests__/mcp.routes.delete.test.ts +92 -0
  561. package/src/modules/mcp/__tests__/mcp.service.test.ts +414 -0
  562. package/src/modules/mcp/mcp-auth.middleware.ts +126 -0
  563. package/src/modules/mcp/mcp-session-store.ts +162 -0
  564. package/src/modules/mcp/mcp.routes.ts +263 -0
  565. package/src/modules/mcp/mcp.service.ts +973 -0
  566. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +401 -0
  567. package/src/modules/mcp/oauth/oauth-consent.routes.ts +60 -0
  568. package/src/modules/mcp/oauth/oauth-state.ts +61 -0
  569. package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +277 -0
  570. package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +206 -0
  571. package/src/modules/secrets-vault/__tests__/mcp-oauth-discovery.service.test.ts +178 -0
  572. package/src/modules/secrets-vault/__tests__/scopes-covered.test.ts +101 -0
  573. package/src/modules/secrets-vault/__tests__/secrets-variable-loader.test.ts +54 -0
  574. package/src/modules/secrets-vault/__tests__/tool-owner-gate.route.test.ts +143 -0
  575. package/src/modules/secrets-vault/db-secrets-vault.service.ts +792 -0
  576. package/src/modules/secrets-vault/index.ts +25 -0
  577. package/src/modules/secrets-vault/mcp-oauth-discovery.service.ts +289 -0
  578. package/src/modules/secrets-vault/secrets-variable-loader.ts +104 -0
  579. package/src/modules/secrets-vault/secrets-vault.contract.ts +270 -0
  580. package/src/modules/secrets-vault/secrets-vault.routes.ts +526 -0
  581. package/src/modules/skills/__tests__/skills.service.test.ts +176 -0
  582. package/src/modules/skills/index.ts +10 -0
  583. package/src/modules/skills/skills.contract.ts +54 -0
  584. package/src/modules/skills/skills.routes.ts +38 -0
  585. package/src/modules/skills/skills.service.ts +245 -0
  586. package/src/modules/skills/skills.tools.ts +121 -0
  587. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +358 -0
  588. package/src/modules/tool-auth/__tests__/internal-token.service.test.ts +79 -0
  589. package/src/modules/tool-auth/__tests__/manual-auth.middleware.test.ts +88 -0
  590. package/src/modules/tool-auth/external-api-key.errors.ts +25 -0
  591. package/src/modules/tool-auth/external-api-key.interface.ts +99 -0
  592. package/src/modules/tool-auth/external-api-key.service.ts +209 -0
  593. package/src/modules/tool-auth/internal-token.service.ts +124 -0
  594. package/src/modules/tool-auth/llm-usage-meter.ts +19 -0
  595. package/src/modules/tool-auth/tool-auth.middleware.ts +220 -0
  596. package/src/modules/tool-helpers/__tests__/phase0-loopback-spike.test.ts +129 -0
  597. package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +95 -0
  598. package/src/modules/tool-helpers/__tests__/tools-surface.test.ts +146 -0
  599. package/src/modules/tool-helpers/__tests__/validate-token.test.ts +75 -0
  600. package/src/modules/tool-helpers/index.ts +18 -0
  601. package/src/modules/tool-helpers/tool-context.ts +98 -0
  602. package/src/modules/tool-helpers/tool-def.ts +73 -0
  603. package/src/modules/tool-helpers/tool-handler.ts +91 -0
  604. package/src/modules/tool-helpers/tool.contract.ts +85 -0
  605. package/src/modules/tool-helpers/validate-token.ts +50 -0
  606. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +200 -0
  607. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +499 -0
  608. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +156 -0
  609. package/src/modules/tool-manuals/index.ts +12 -0
  610. package/src/modules/tool-manuals/tool-manuals.contract.ts +183 -0
  611. package/src/modules/tool-manuals/tool-manuals.routes.ts +110 -0
  612. package/src/modules/tool-manuals/tool-manuals.service.ts +781 -0
  613. package/src/modules/tool-manuals/tool-manuals.tools.ts +202 -0
  614. package/src/modules/tool-registry/__tests__/tool-registry.test.ts +64 -0
  615. package/src/modules/tool-registry/manual.routes.ts +87 -0
  616. package/src/modules/tool-registry/tool-registry.ts +55 -0
  617. package/src/modules/tool-registry/tool.contract.ts +52 -0
  618. package/src/modules/workflow/__tests__/event-bus.test.ts +322 -0
  619. package/src/modules/workflow/__tests__/file-change-notifier.test.ts +51 -0
  620. package/src/modules/workflow/__tests__/format-affected-owners.test.ts +67 -0
  621. package/src/modules/workflow/__tests__/locking-filesystem.test.ts +445 -0
  622. package/src/modules/workflow/__tests__/pending-commits.canonical.test.ts +36 -0
  623. package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +243 -0
  624. package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +308 -0
  625. package/src/modules/workflow/__tests__/sanitize-error.test.ts +67 -0
  626. package/src/modules/workflow/__tests__/session-ontology.policy.test.ts +62 -0
  627. package/src/modules/workflow/__tests__/session-ontology.service.test.ts +200 -0
  628. package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +232 -0
  629. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +380 -0
  630. package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +340 -0
  631. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +192 -0
  632. package/src/modules/workflow/agent-tools/workflow.tools.ts +502 -0
  633. package/src/modules/workflow/event-bus.ts +262 -0
  634. package/src/modules/workflow/events.routes.ts +266 -0
  635. package/src/modules/workflow/file-change-notifier.ts +54 -0
  636. package/src/modules/workflow/file-lock.service.ts +269 -0
  637. package/src/modules/workflow/git/__tests__/branch-name.test.ts +86 -0
  638. package/src/modules/workflow/git/__tests__/branchAuthor.test.ts +80 -0
  639. package/src/modules/workflow/git/__tests__/clone-config.test.ts +15 -0
  640. package/src/modules/workflow/git/__tests__/git-test-helpers.ts +47 -0
  641. package/src/modules/workflow/git/__tests__/git-version.failure.test.ts +24 -0
  642. package/src/modules/workflow/git/__tests__/git-version.test.ts +49 -0
  643. package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +389 -0
  644. package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +124 -0
  645. package/src/modules/workflow/git/__tests__/git.service.commitChanges.test.ts +105 -0
  646. package/src/modules/workflow/git/__tests__/git.service.commitFile.test.ts +211 -0
  647. package/src/modules/workflow/git/__tests__/git.service.createBranch.test.ts +154 -0
  648. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +320 -0
  649. package/src/modules/workflow/git/__tests__/git.service.diffFileAtCommit.test.ts +202 -0
  650. package/src/modules/workflow/git/__tests__/git.service.diffFileBetweenBranches.test.ts +453 -0
  651. package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +198 -0
  652. package/src/modules/workflow/git/__tests__/git.service.listTrackedFiles.test.ts +125 -0
  653. package/src/modules/workflow/git/__tests__/git.service.logForFile.test.ts +219 -0
  654. package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +145 -0
  655. package/src/modules/workflow/git/__tests__/git.service.pendingChanges.test.ts +240 -0
  656. package/src/modules/workflow/git/__tests__/git.service.postMergePull.test.ts +272 -0
  657. package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +232 -0
  658. package/src/modules/workflow/git/__tests__/git.service.readFileAtRef.test.ts +101 -0
  659. package/src/modules/workflow/git/__tests__/git.service.status.test.ts +127 -0
  660. package/src/modules/workflow/git/__tests__/mutex.test.ts +78 -0
  661. package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +225 -0
  662. package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +101 -0
  663. package/src/modules/workflow/git/branch-name.ts +70 -0
  664. package/src/modules/workflow/git/clone-config.ts +71 -0
  665. package/src/modules/workflow/git/git-version.ts +70 -0
  666. package/src/modules/workflow/git/git.service.ts +2248 -0
  667. package/src/modules/workflow/git/mutex.ts +22 -0
  668. package/src/modules/workflow/git/pull-request.service.ts +447 -0
  669. package/src/modules/workflow/locking-filesystem.ts +489 -0
  670. package/src/modules/workflow/pending-commits.service.ts +444 -0
  671. package/src/modules/workflow/pending-commits.worker.ts +361 -0
  672. package/src/modules/workflow/read-only-filesystem.ts +54 -0
  673. package/src/modules/workflow/recovery-bot.ts +73 -0
  674. package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +560 -0
  675. package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +176 -0
  676. package/src/modules/workflow/review-workflow/__tests__/merge-gate.test.ts +258 -0
  677. package/src/modules/workflow/review-workflow/review-workflow.interface.ts +199 -0
  678. package/src/modules/workflow/review-workflow/review-workflow.service.ts +806 -0
  679. package/src/modules/workflow/sanitize-error.ts +62 -0
  680. package/src/modules/workflow/session-ontology.policy.ts +70 -0
  681. package/src/modules/workflow/session-ontology.service.ts +178 -0
  682. package/src/modules/workflow/workflow-hooks.ts +101 -0
  683. package/src/modules/workflow/workflow.errors.ts +285 -0
  684. package/src/modules/workflow/workflow.routes.ts +858 -0
  685. package/src/modules/workflow/workflow.service.ts +1555 -0
  686. package/src/modules/workspace/__tests__/kb-seed.service.test.ts +213 -0
  687. package/src/modules/workspace/__tests__/routine-write-policy.test.ts +68 -0
  688. package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +237 -0
  689. package/src/modules/workspace/__tests__/spill-store.test.ts +43 -0
  690. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +233 -0
  691. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +176 -0
  692. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +303 -0
  693. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +276 -0
  694. package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +120 -0
  695. package/src/modules/workspace/__tests__/workspace.service.test.ts +565 -0
  696. package/src/modules/workspace/__tests__/workspace.tools.test.ts +504 -0
  697. package/src/modules/workspace/bevel-ignore.ts +62 -0
  698. package/src/modules/workspace/kb-seed.interface.ts +36 -0
  699. package/src/modules/workspace/kb-seed.service.ts +361 -0
  700. package/src/modules/workspace/routine-write-policy.ts +79 -0
  701. package/src/modules/workspace/session-ontology.gate.ts +189 -0
  702. package/src/modules/workspace/session-sink.ts +25 -0
  703. package/src/modules/workspace/spill-store.ts +87 -0
  704. package/src/modules/workspace/workspace.routes.ts +1114 -0
  705. package/src/modules/workspace/workspace.service.ts +1343 -0
  706. package/src/modules/workspace/workspace.tools.ts +885 -0
  707. package/src/shared/__tests__/frontmatter-id.test.ts +88 -0
  708. package/src/shared/__tests__/fs-walk.test.ts +42 -0
  709. package/src/shared/__tests__/ssrf.test.ts +53 -0
  710. package/src/shared/__tests__/utcp-namespace.test.ts +19 -0
  711. package/src/shared/frontmatter-id.ts +72 -0
  712. package/src/shared/fs-walk.ts +30 -0
  713. package/src/shared/hash-email.ts +14 -0
  714. package/src/shared/kb-layout.test.ts +88 -0
  715. package/src/shared/kb-layout.ts +101 -0
  716. package/src/shared/ssrf.ts +78 -0
  717. package/src/shared/token-crypto.ts +54 -0
  718. package/src/shared/ttl-cache.ts +27 -0
  719. package/src/shared/utcp-namespace.ts +88 -0
  720. package/src/version.ts +19 -0
@@ -0,0 +1,792 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { and, desc, eq, inArray, isNull, or } from 'drizzle-orm';
3
+ import type { Database } from '../database/connection.js';
4
+ import { secrets } from '../database/schema.js';
5
+ import { TokenCrypto } from '../../shared/token-crypto.js';
6
+ import { assertSafeFetchUrl } from '../../shared/ssrf.js';
7
+ import {
8
+ type ISecretsVaultService,
9
+ type SecretSummary,
10
+ type SecretConfigStatus,
11
+ type PutStaticSecretInput,
12
+ type PutSharedStaticSecretInput,
13
+ type CreateOAuthSecretInput,
14
+ type OAuthProviderConfig,
15
+ type VariableScopeResolver,
16
+ InvalidSecretError,
17
+ SecretNotFoundError,
18
+ SecretOAuthError,
19
+ } from './secrets-vault.contract.js';
20
+
21
+ const MAX_KEY_LEN = 200;
22
+ const MAX_LABEL_LEN = 200;
23
+ const MAX_VALUE_LEN = 100_000;
24
+ /** Refresh an OAuth token this many ms before its stated expiry. */
25
+ const REFRESH_SKEW_MS = 60_000;
26
+ /** Upper bound on a token-endpoint round-trip, so a hung provider can't block callers. */
27
+ const TOKEN_REQUEST_TIMEOUT_MS = 10_000;
28
+
29
+ /** The token material stored (encrypted) for an `oauth` secret. */
30
+ interface OAuthTokenSet {
31
+ access_token: string;
32
+ refresh_token?: string;
33
+ /** Epoch ms; absent when the provider gave no `expires_in`. */
34
+ expires_at?: number;
35
+ token_type?: string;
36
+ /**
37
+ * Space-delimited scopes the provider actually granted (echoed on the token
38
+ * response). The durable record of what this token can do, so a later check can
39
+ * tell whether it still covers a tool whose required scopes have grown. Absent
40
+ * on tokens minted before this was captured (treated as "covers nothing").
41
+ */
42
+ scope?: string;
43
+ }
44
+
45
+ interface OAuthBlob {
46
+ clientSecret?: string;
47
+ tokens?: OAuthTokenSet;
48
+ /**
49
+ * PKCE code_verifier generated at `begin*` time, consumed by the next
50
+ * `completeOAuth` on this row. Stored encrypted with the rest of the blob —
51
+ * it's a one-time secret binding the pending consent to this server.
52
+ */
53
+ pendingVerifier?: string;
54
+ }
55
+
56
+ /**
57
+ * Postgres-backed `ISecretsVaultService`. Secret material is encrypted at rest
58
+ * with `TokenCrypto` (AES-256-GCM); a DB leak alone yields only ciphertext. Per-
59
+ * user isolation is enforced by including `userId` in every WHERE clause — a
60
+ * `get`/`remove` for another user's row matches zero rows and reads as "not
61
+ * found", exactly like a row that doesn't exist.
62
+ *
63
+ * Two provisioning tiers share the `secrets` table (see the schema): per-user
64
+ * rows (`user_id` set) and shared/admin rows (`user_id NULL`). At `resolve` time
65
+ * the injected `scopeOf` decides which tier a variable reads — an `admin` var
66
+ * reads the one shared row; a `user` var reads only the caller's row.
67
+ */
68
+ export class DbSecretsVaultService implements ISecretsVaultService {
69
+ private cryptoInstance: TokenCrypto | null = null;
70
+
71
+ constructor(
72
+ private readonly db: Database,
73
+ /** 32-byte AES key (hex/base64); empty disables secret read/write with a clear error. */
74
+ private readonly encKey: string,
75
+ private readonly now: () => number = Date.now,
76
+ /**
77
+ * The provisioning scope of a UTCP-namespaced key (`<manual>_<VAR>`). Bound
78
+ * to the tool-manuals catalog at composition. Absent ⇒ everything is `admin`
79
+ * (shared) — the safe default (never falls through to a per-user row for an
80
+ * unclassified var).
81
+ */
82
+ private readonly scopeOf?: VariableScopeResolver,
83
+ ) {}
84
+
85
+ /** Lazily build the cipher — an empty key fails loudly only when a secret is actually touched. */
86
+ private crypto(): TokenCrypto {
87
+ if (!this.cryptoInstance) {
88
+ if (!this.encKey) {
89
+ throw new InvalidSecretError(
90
+ 'Secrets require an encryption key — set CONNECTOR_CONFIG_ENC_KEY (or SHAREPOINT_TOKEN_ENC_KEY).',
91
+ );
92
+ }
93
+ this.cryptoInstance = new TokenCrypto(this.encKey);
94
+ }
95
+ return this.cryptoInstance;
96
+ }
97
+
98
+ async list(userId: string): Promise<SecretSummary[]> {
99
+ const rows = await this.db
100
+ .select()
101
+ .from(secrets)
102
+ .where(eq(secrets.userId, userId))
103
+ .orderBy(desc(secrets.updatedAt));
104
+ return rows.map((r) => this.toSummary(r));
105
+ }
106
+
107
+ async getById(userId: string, id: string): Promise<SecretSummary | null> {
108
+ const row = await this.row(userId, id);
109
+ return row ? this.toSummary(row) : null;
110
+ }
111
+
112
+ async putStatic(input: PutStaticSecretInput): Promise<SecretSummary> {
113
+ const userId = this.requireUserId(input.userId);
114
+ const key = this.requireKey(input.key);
115
+ if (typeof input.value !== 'string' || input.value.length === 0) {
116
+ throw new InvalidSecretError('value is required');
117
+ }
118
+ if (input.value.length > MAX_VALUE_LEN) {
119
+ throw new InvalidSecretError(`value exceeds ${MAX_VALUE_LEN} characters`);
120
+ }
121
+ const label = this.normaliseLabel(input.label);
122
+ const valueEncrypted = this.crypto().encrypt(input.value);
123
+
124
+ // Upsert on (user_id, key): re-saving a key replaces its value in place.
125
+ const [row] = await this.db
126
+ .insert(secrets)
127
+ .values({ userId, key, kind: 'static', label, valueEncrypted, oauthMeta: null })
128
+ .onConflictDoUpdate({
129
+ target: [secrets.userId, secrets.key],
130
+ set: { kind: 'static', label, valueEncrypted, oauthMeta: null, updatedAt: new Date() },
131
+ })
132
+ .returning();
133
+ return this.toSummary(row);
134
+ }
135
+
136
+ async createOAuth(input: CreateOAuthSecretInput): Promise<SecretSummary> {
137
+ const userId = this.requireUserId(input.userId);
138
+ const key = this.requireKey(input.key);
139
+ const provider = this.requireProvider(input.provider);
140
+ const label = this.normaliseLabel(input.label);
141
+
142
+ const blob: OAuthBlob = { clientSecret: provider.clientSecret };
143
+ const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
144
+ const oauthMeta = this.publicProviderMeta(provider);
145
+
146
+ const [row] = await this.db
147
+ .insert(secrets)
148
+ .values({ userId, key, kind: 'oauth', label, valueEncrypted, oauthMeta })
149
+ .onConflictDoUpdate({
150
+ target: [secrets.userId, secrets.key],
151
+ // Replacing an oauth secret's provider config drops any existing tokens
152
+ // (they belonged to the old client) — the user must re-authorize.
153
+ set: { kind: 'oauth', label, valueEncrypted, oauthMeta, updatedAt: new Date() },
154
+ })
155
+ .returning();
156
+ return this.toSummary(row);
157
+ }
158
+
159
+ async remove(userId: string, id: string): Promise<void> {
160
+ const res = await this.db
161
+ .delete(secrets)
162
+ .where(and(eq(secrets.id, id), eq(secrets.userId, userId)))
163
+ .returning({ id: secrets.id });
164
+ if (res.length === 0) throw new SecretNotFoundError(id);
165
+ }
166
+
167
+ async putSharedStatic(input: PutSharedStaticSecretInput): Promise<SecretSummary> {
168
+ const key = this.requireKey(input.key);
169
+ if (typeof input.value !== 'string' || input.value.length === 0) {
170
+ throw new InvalidSecretError('value is required');
171
+ }
172
+ if (input.value.length > MAX_VALUE_LEN) {
173
+ throw new InvalidSecretError(`value exceeds ${MAX_VALUE_LEN} characters`);
174
+ }
175
+ const label = this.normaliseLabel(input.label);
176
+ const valueEncrypted = this.crypto().encrypt(input.value);
177
+
178
+ // Upsert the ONE shared row for this key (user_id NULL). The conflict target
179
+ // is the partial unique index `secrets_shared_key_unq` — hence `targetWhere`.
180
+ const [row] = await this.db
181
+ .insert(secrets)
182
+ .values({ userId: null, key, kind: 'static', label, valueEncrypted, oauthMeta: null })
183
+ .onConflictDoUpdate({
184
+ target: secrets.key,
185
+ targetWhere: isNull(secrets.userId),
186
+ set: { kind: 'static', label, valueEncrypted, oauthMeta: null, updatedAt: new Date() },
187
+ })
188
+ .returning();
189
+ return this.toSummary(row);
190
+ }
191
+
192
+ async removeShared(key: string): Promise<void> {
193
+ const res = await this.db
194
+ .delete(secrets)
195
+ .where(and(isNull(secrets.userId), eq(secrets.key, key)))
196
+ .returning({ id: secrets.id });
197
+ if (res.length === 0) throw new SecretNotFoundError(key);
198
+ }
199
+
200
+ async removeUserByKey(userId: string, key: string): Promise<void> {
201
+ const res = await this.db
202
+ .delete(secrets)
203
+ .where(and(eq(secrets.userId, userId), eq(secrets.key, key)))
204
+ .returning({ id: secrets.id });
205
+ if (res.length === 0) throw new SecretNotFoundError(key);
206
+ }
207
+
208
+ async statusFor(userId: string, keys: string[]): Promise<SecretConfigStatus[]> {
209
+ if (keys.length === 0) return [];
210
+ const rows = await this.db
211
+ .select({
212
+ key: secrets.key,
213
+ userId: secrets.userId,
214
+ kind: secrets.kind,
215
+ valueEncrypted: secrets.valueEncrypted,
216
+ })
217
+ .from(secrets)
218
+ .where(and(inArray(secrets.key, keys), or(isNull(secrets.userId), eq(secrets.userId, userId))));
219
+ return keys.map((key) => {
220
+ const userRow = rows.find((r) => r.key === key && r.userId === userId);
221
+ // Decrypt+parse the oauth row's token set once, then derive both fields from it.
222
+ const tokens = userRow?.kind === 'oauth' ? this.readTokensSafe(userRow.valueEncrypted) : undefined;
223
+ return {
224
+ key,
225
+ adminConfigured: rows.some((r) => r.key === key && r.userId === null),
226
+ userConfigured: !!userRow,
227
+ // Only meaningful for an oauth row: has the caller completed sign-in?
228
+ userAuthorized: userRow?.kind === 'oauth' ? Boolean(tokens?.access_token) : undefined,
229
+ // Only meaningful for an oauth row: the scopes the caller's token was granted,
230
+ // so a coverage check can tell whether it still covers the tool's live scopes.
231
+ grantedScopes: userRow?.kind === 'oauth' ? tokens?.scope : undefined,
232
+ };
233
+ });
234
+ }
235
+
236
+ /**
237
+ * Decrypt+parse an oauth row's token set once, or undefined if the blob can't be
238
+ * read — a corrupted row or rotated encKey must degrade to "not authorized", not throw.
239
+ * Callers derive `authorized` / `grantedScopes` from this so decrypt happens once per row.
240
+ */
241
+ private readTokensSafe(valueEncrypted: string): OAuthTokenSet | undefined {
242
+ try {
243
+ return this.readBlob(valueEncrypted).tokens;
244
+ } catch {
245
+ return undefined;
246
+ }
247
+ }
248
+
249
+ async beginOAuth(userId: string, id: string, redirectUri: string, state: string): Promise<string> {
250
+ const row = await this.requireRow(userId, id);
251
+ if (row.kind !== 'oauth') throw new SecretOAuthError('Secret is not an OAuth secret');
252
+ const meta = this.readMeta(row.oauthMeta);
253
+
254
+ // PKCE (S256): same as the tool path — mint the verifier, stash it on the
255
+ // row (so `completeOAuth` echoes it at the token exchange), and put only
256
+ // the derived challenge in the consent URL. A provider registered pkce
257
+ // would otherwise fail the exchange from this standalone flow.
258
+ let pendingVerifier: string | undefined;
259
+ if (meta.pkce) {
260
+ pendingVerifier = randomBytes(32).toString('base64url');
261
+ const blob = this.readBlob(row.valueEncrypted);
262
+ const next: OAuthBlob = { ...blob, pendingVerifier };
263
+ await this.db
264
+ .update(secrets)
265
+ .set({ valueEncrypted: this.crypto().encrypt(JSON.stringify(next)), updatedAt: new Date() })
266
+ .where(and(eq(secrets.id, id), eq(secrets.userId, userId)));
267
+ }
268
+
269
+ const url = new URL(meta.authorizationUrl);
270
+ url.searchParams.set('response_type', 'code');
271
+ url.searchParams.set('client_id', meta.clientId);
272
+ url.searchParams.set('redirect_uri', redirectUri);
273
+ url.searchParams.set('state', state);
274
+ if (meta.scopes && meta.scopes.length) url.searchParams.set('scope', meta.scopes.join(' '));
275
+ if (pendingVerifier) {
276
+ url.searchParams.set('code_challenge', sha256base64url(pendingVerifier));
277
+ url.searchParams.set('code_challenge_method', 'S256');
278
+ }
279
+ if (meta.resource) url.searchParams.set('resource', meta.resource);
280
+ // Extra provider params must not clobber the control params we just set — most
281
+ // importantly the signed `state` and `redirect_uri` (CSRF / code-interception).
282
+ const reserved = new Set([
283
+ 'response_type',
284
+ 'client_id',
285
+ 'redirect_uri',
286
+ 'state',
287
+ 'scope',
288
+ 'code_challenge',
289
+ 'code_challenge_method',
290
+ 'resource',
291
+ ]);
292
+ for (const [k, v] of Object.entries(meta.authParams ?? {})) {
293
+ if (!reserved.has(k)) url.searchParams.set(k, v);
294
+ }
295
+ return url.toString();
296
+ }
297
+
298
+ async completeOAuth(userId: string, id: string, code: string, redirectUri: string): Promise<void> {
299
+ const row = await this.requireRow(userId, id);
300
+ if (row.kind !== 'oauth') throw new SecretOAuthError('Secret is not an OAuth secret');
301
+ const meta = this.readMeta(row.oauthMeta);
302
+ const blob = this.readBlob(row.valueEncrypted);
303
+
304
+ const body = new URLSearchParams({
305
+ grant_type: 'authorization_code',
306
+ code,
307
+ redirect_uri: redirectUri,
308
+ client_id: meta.clientId,
309
+ });
310
+ if (blob.clientSecret) body.set('client_secret', blob.clientSecret);
311
+ // PKCE: echo the verifier generated at begin time; audience-bind per RFC 8707.
312
+ if (blob.pendingVerifier) body.set('code_verifier', blob.pendingVerifier);
313
+ if (meta.resource) body.set('resource', meta.resource);
314
+
315
+ const tokens = await this.tokenRequest(meta.tokenUrl, body);
316
+ // The verifier is one-time — never carried past the exchange it was minted for.
317
+ const next: OAuthBlob = { clientSecret: blob.clientSecret, tokens };
318
+ await this.db
319
+ .update(secrets)
320
+ .set({ valueEncrypted: this.crypto().encrypt(JSON.stringify(next)), updatedAt: new Date() })
321
+ .where(and(eq(secrets.id, id), eq(secrets.userId, userId)));
322
+ }
323
+
324
+ async putSharedOAuthClientSecret(input: {
325
+ key: string;
326
+ clientSecret: string;
327
+ provider: OAuthProviderConfig;
328
+ }): Promise<void> {
329
+ const key = this.requireKey(input.key);
330
+ if (typeof input.clientSecret !== 'string' || !input.clientSecret) {
331
+ throw new InvalidSecretError('clientSecret is required');
332
+ }
333
+ // Validate the provider config the same way createOAuth does (SSRF-safe https
334
+ // URLs). Store the client secret AND the provider meta together on the shared
335
+ // row so a later `.tool` edit can't redirect the secret to another endpoint.
336
+ const provider = this.requireProvider({ ...input.provider, clientSecret: input.clientSecret });
337
+ const blob: OAuthBlob = { clientSecret: provider.clientSecret };
338
+ const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
339
+ const oauthMeta = this.publicProviderMeta(provider);
340
+ await this.db
341
+ .insert(secrets)
342
+ .values({ userId: null, key, kind: 'oauth', label: null, valueEncrypted, oauthMeta })
343
+ .onConflictDoUpdate({
344
+ target: secrets.key,
345
+ targetWhere: isNull(secrets.userId),
346
+ set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
347
+ });
348
+ }
349
+
350
+ async beginToolOAuthByKey(input: {
351
+ userId: string;
352
+ key: string;
353
+ redirectUri: string;
354
+ state: string;
355
+ scopes?: string[];
356
+ }): Promise<{ id: string; url: string }> {
357
+ const userId = this.requireUserId(input.userId);
358
+ const key = this.requireKey(input.key);
359
+ // The shared row holds the owner-set client secret + the provider meta it was
360
+ // set for. Both are read from HERE, never from the live `.tool`.
361
+ const [shared] = await this.db
362
+ .select()
363
+ .from(secrets)
364
+ .where(and(isNull(secrets.userId), eq(secrets.key, key)))
365
+ .limit(1);
366
+ if (!shared || shared.kind !== 'oauth') {
367
+ throw new SecretOAuthError("This tool's owner hasn't finished setting this up");
368
+ }
369
+ const sharedBlob = this.readBlob(shared.valueEncrypted);
370
+ const meta = this.readMeta(shared.oauthMeta);
371
+ // A missing secret is "not set up yet" ONLY for confidential clients — a
372
+ // dynamically-registered PUBLIC client (PKCE-only) never has one.
373
+ if (!sharedBlob.clientSecret && !meta.publicClient) {
374
+ throw new SecretOAuthError("This tool's owner hasn't finished setting this up");
375
+ }
376
+
377
+ // Carry forward any tokens the caller already holds so re-entering the flow
378
+ // (or abandoning it) can't de-authorize a still-valid prior grant — only
379
+ // `completeOAuth` replaces tokens, after a successful callback.
380
+ const [existingUserRow] = await this.db
381
+ .select()
382
+ .from(secrets)
383
+ .where(and(eq(secrets.userId, userId), eq(secrets.key, key)))
384
+ .limit(1);
385
+ // Only reuse tokens if (a) they decrypt cleanly — a corrupted row or rotated
386
+ // encKey must degrade to "start fresh", not crash the recovery flow — and (b)
387
+ // they were minted under the SAME provider config we're about to (re)write, or
388
+ // a refresh_token for a stale client id/secret would silently fail once carried
389
+ // into this row after an owner rotates the shared secret.
390
+ let existingTokens: OAuthTokenSet | undefined;
391
+ if (
392
+ existingUserRow?.kind === 'oauth' &&
393
+ JSON.stringify(existingUserRow.oauthMeta) === JSON.stringify(shared.oauthMeta)
394
+ ) {
395
+ try {
396
+ existingTokens = this.readBlob(existingUserRow.valueEncrypted).tokens;
397
+ } catch {
398
+ existingTokens = undefined;
399
+ }
400
+ }
401
+
402
+ // PKCE (S256): mint the verifier now, stash it on the caller's row, and put
403
+ // only the derived challenge in the consent URL. `completeOAuth` echoes the
404
+ // verifier at the token exchange and drops it.
405
+ const pendingVerifier = meta.pkce ? randomBytes(32).toString('base64url') : undefined;
406
+
407
+ // Provision (or reset) the caller's own oauth row for this key from the shared
408
+ // provider meta + secret, preserving any existing tokens. Keyed `<manual>_<VAR>`
409
+ // so `resolve` (scope 'user') returns the token once sign-in completes.
410
+ const blob: OAuthBlob = { clientSecret: sharedBlob.clientSecret, tokens: existingTokens, pendingVerifier };
411
+ const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
412
+ const [row] = await this.db
413
+ .insert(secrets)
414
+ .values({ userId, key, kind: 'oauth', label: shared.label, valueEncrypted, oauthMeta: shared.oauthMeta })
415
+ .onConflictDoUpdate({
416
+ target: [secrets.userId, secrets.key],
417
+ set: { kind: 'oauth', valueEncrypted, oauthMeta: shared.oauthMeta, updatedAt: new Date() },
418
+ })
419
+ .returning();
420
+
421
+ // Build the consent URL exactly as beginOAuth does, from the stored meta —
422
+ // EXCEPT the requested scopes, which the caller may override from the live tool
423
+ // file (`input.scopes`) so an owner adding a permission takes effect without
424
+ // re-setting the secret. Client id, addresses, and secret stay owner-pinned.
425
+ const requestedScopes = input.scopes && input.scopes.length ? input.scopes : meta.scopes;
426
+ const url = new URL(meta.authorizationUrl);
427
+ url.searchParams.set('response_type', 'code');
428
+ url.searchParams.set('client_id', meta.clientId);
429
+ url.searchParams.set('redirect_uri', input.redirectUri);
430
+ url.searchParams.set('state', input.state);
431
+ if (requestedScopes && requestedScopes.length) url.searchParams.set('scope', requestedScopes.join(' '));
432
+ if (pendingVerifier) {
433
+ url.searchParams.set('code_challenge', sha256base64url(pendingVerifier));
434
+ url.searchParams.set('code_challenge_method', 'S256');
435
+ }
436
+ if (meta.resource) url.searchParams.set('resource', meta.resource);
437
+ const reserved = new Set([
438
+ 'response_type',
439
+ 'client_id',
440
+ 'redirect_uri',
441
+ 'state',
442
+ 'scope',
443
+ 'code_challenge',
444
+ 'code_challenge_method',
445
+ 'resource',
446
+ ]);
447
+ for (const [k, v] of Object.entries(meta.authParams ?? {})) {
448
+ if (!reserved.has(k)) url.searchParams.set(k, v);
449
+ }
450
+ return { id: row.id, url: url.toString() };
451
+ }
452
+
453
+ async resolve(userId: string, key: string): Promise<string | null> {
454
+ // Scope-driven: an `admin` var reads the ONE shared row (user_id NULL) and
455
+ // ignores the caller; a `user` var reads only the caller's row. Default is
456
+ // `admin` so an unclassified var never falls through to a per-user row.
457
+ const scope = (await this.scopeOf?.(key)) ?? 'admin';
458
+ const scopeFilter = scope === 'user' ? eq(secrets.userId, userId) : isNull(secrets.userId);
459
+ const [row] = await this.db
460
+ .select()
461
+ .from(secrets)
462
+ .where(and(scopeFilter, eq(secrets.key, key)))
463
+ .limit(1);
464
+ if (!row) return null;
465
+
466
+ if (row.kind === 'static') {
467
+ try {
468
+ return this.crypto().decrypt(row.valueEncrypted);
469
+ } catch {
470
+ return null;
471
+ }
472
+ }
473
+
474
+ // oauth: return a valid access token, refreshing on demand.
475
+ let blob: OAuthBlob;
476
+ try {
477
+ blob = this.readBlob(row.valueEncrypted);
478
+ } catch {
479
+ return null;
480
+ }
481
+ const tokens = blob.tokens;
482
+ if (!tokens?.access_token) return null; // not authorized yet
483
+
484
+ const expired = typeof tokens.expires_at === 'number' && tokens.expires_at - REFRESH_SKEW_MS <= this.now();
485
+ if (!expired) return tokens.access_token;
486
+ if (!tokens.refresh_token) return tokens.access_token; // best-effort; may be stale
487
+
488
+ const meta = this.readMeta(row.oauthMeta);
489
+ const body = new URLSearchParams({
490
+ grant_type: 'refresh_token',
491
+ refresh_token: tokens.refresh_token,
492
+ client_id: meta.clientId,
493
+ });
494
+ if (blob.clientSecret) body.set('client_secret', blob.clientSecret);
495
+ if (meta.resource) body.set('resource', meta.resource);
496
+
497
+ let refreshed: OAuthTokenSet;
498
+ try {
499
+ refreshed = await this.tokenRequest(meta.tokenUrl, body);
500
+ } catch (err) {
501
+ // A DEFINITIVE provider rejection (400/401 — invalid_grant and friends)
502
+ // means the grant is dead: no future refresh will revive it. Wipe the
503
+ // token set (keeping the client secret) so `statusFor` reports
504
+ // not-authorized and every fail-closed surface — the pre-call check,
505
+ // /connect, the listing filter — routes the user to re-authorize instead
506
+ // of letting calls fail opaquely at the provider forever.
507
+ if (
508
+ err instanceof SecretOAuthError &&
509
+ (err.providerStatus === 400 || err.providerStatus === 401)
510
+ ) {
511
+ const next: OAuthBlob = { clientSecret: blob.clientSecret };
512
+ // Guarded on the ciphertext we read, so a concurrent refresh that
513
+ // already persisted ROTATED tokens can't be wiped by our stale
514
+ // failure. When that guard trips (0 rows), the failure was against a
515
+ // dead pre-rotation refresh token — re-read and serve the fresh grant
516
+ // instead of reporting not-authorized.
517
+ const wiped = await this.db
518
+ .update(secrets)
519
+ .set({ valueEncrypted: this.crypto().encrypt(JSON.stringify(next)), updatedAt: new Date() })
520
+ .where(and(eq(secrets.id, row.id), eq(secrets.valueEncrypted, row.valueEncrypted)))
521
+ .returning({ id: secrets.id });
522
+ if (Array.isArray(wiped) && wiped.length === 0) {
523
+ const [current] = await this.db
524
+ .select({ valueEncrypted: secrets.valueEncrypted })
525
+ .from(secrets)
526
+ .where(eq(secrets.id, row.id))
527
+ .limit(1);
528
+ try {
529
+ const fresh = current ? this.readBlob(current.valueEncrypted) : null;
530
+ return fresh?.tokens?.access_token ?? null;
531
+ } catch {
532
+ return null;
533
+ }
534
+ }
535
+ return null;
536
+ }
537
+ // Transient failure (timeout, network, 5xx) — return the stale token so
538
+ // the caller gets a clear 401 from the provider rather than a silent
539
+ // missing var, and the next call retries the refresh.
540
+ return tokens.access_token;
541
+ }
542
+ // Some providers omit the refresh_token on refresh — keep the old one.
543
+ if (!refreshed.refresh_token) refreshed.refresh_token = tokens.refresh_token;
544
+ // Likewise, a refresh response often omits `scope` — keep the granted scopes
545
+ // recorded at sign-in so coverage checks don't regress to "unknown".
546
+ if (!refreshed.scope) refreshed.scope = tokens.scope;
547
+ const next: OAuthBlob = { clientSecret: blob.clientSecret, tokens: refreshed };
548
+
549
+ // Optimistic concurrency: only persist if the stored ciphertext is unchanged,
550
+ // so a concurrent refresh on the same row doesn't clobber (Microsoft pattern).
551
+ await this.db
552
+ .update(secrets)
553
+ .set({ valueEncrypted: this.crypto().encrypt(JSON.stringify(next)), updatedAt: new Date() })
554
+ .where(and(eq(secrets.id, row.id), eq(secrets.valueEncrypted, row.valueEncrypted)));
555
+ return refreshed.access_token;
556
+ }
557
+
558
+ async putSharedOAuthProvider(input: {
559
+ key: string;
560
+ label?: string | null;
561
+ provider: OAuthProviderConfig;
562
+ }): Promise<void> {
563
+ const key = this.requireKey(input.key);
564
+ // Same validation as the owner-typed path, but the secret is OPTIONAL —
565
+ // auto-discovery registers PUBLIC (PKCE-only) clients that never get one.
566
+ const provider = this.requireProvider(input.provider);
567
+ const label = this.normaliseLabel(input.label);
568
+ const blob: OAuthBlob = { clientSecret: provider.clientSecret };
569
+ const valueEncrypted = this.crypto().encrypt(JSON.stringify(blob));
570
+ const oauthMeta = this.publicProviderMeta(provider);
571
+ await this.db
572
+ .insert(secrets)
573
+ .values({ userId: null, key, kind: 'oauth', label, valueEncrypted, oauthMeta })
574
+ // Preserve an existing row's label on re-upsert (matches
575
+ // `putSharedOAuthClientSecret`): the optional input label is only for the
576
+ // FIRST write, so a later re-registration can't null out an owner label.
577
+ .onConflictDoUpdate({
578
+ target: secrets.key,
579
+ targetWhere: isNull(secrets.userId),
580
+ set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
581
+ });
582
+ }
583
+
584
+ async getSharedOAuthProvider(key: string): Promise<OAuthProviderConfig | null> {
585
+ const [shared] = await this.db
586
+ .select()
587
+ .from(secrets)
588
+ .where(and(isNull(secrets.userId), eq(secrets.key, key)))
589
+ .limit(1);
590
+ if (!shared || shared.kind !== 'oauth') return null;
591
+ try {
592
+ return this.readMeta(shared.oauthMeta);
593
+ } catch {
594
+ return null;
595
+ }
596
+ }
597
+
598
+ // ---- helpers --------------------------------------------------------------
599
+
600
+ private async tokenRequest(tokenUrl: string, body: URLSearchParams): Promise<OAuthTokenSet> {
601
+ // Re-validate at fetch time (defense-in-depth): the URL was checked on input,
602
+ // but this also guards the refresh path and any future caller that skipped it.
603
+ try {
604
+ assertSafeFetchUrl(tokenUrl, { requireHttps: true, label: 'tokenUrl' });
605
+ } catch (err) {
606
+ throw new SecretOAuthError(err instanceof Error ? err.message : 'tokenUrl is not allowed');
607
+ }
608
+ let res: Response;
609
+ try {
610
+ res = await fetch(tokenUrl, {
611
+ method: 'POST',
612
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
613
+ body: body.toString(),
614
+ // Don't follow redirects: a validated host could 302 to an internal target,
615
+ // which would post the code/secret past the SSRF check (redirect-based SSRF).
616
+ redirect: 'error',
617
+ // Bound the call so a slow/hung provider can't block completeOAuth/resolve.
618
+ signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),
619
+ });
620
+ } catch (err) {
621
+ const msg =
622
+ err instanceof Error && err.name === 'TimeoutError'
623
+ ? `Token endpoint timed out after ${TOKEN_REQUEST_TIMEOUT_MS}ms`
624
+ : `Token endpoint request failed: ${err instanceof Error ? err.message : String(err)}`;
625
+ throw new SecretOAuthError(msg);
626
+ }
627
+ if (!res.ok) {
628
+ throw new SecretOAuthError(`Token endpoint returned HTTP ${res.status}`, res.status);
629
+ }
630
+ const json = (await res.json()) as Record<string, unknown>;
631
+ const accessToken = typeof json.access_token === 'string' ? json.access_token : '';
632
+ if (!accessToken) throw new SecretOAuthError('Token endpoint response had no access_token');
633
+ const expiresIn = typeof json.expires_in === 'number' ? json.expires_in : undefined;
634
+ return {
635
+ access_token: accessToken,
636
+ refresh_token: typeof json.refresh_token === 'string' ? json.refresh_token : undefined,
637
+ expires_at: expiresIn !== undefined ? this.now() + expiresIn * 1000 : undefined,
638
+ token_type: typeof json.token_type === 'string' ? json.token_type : undefined,
639
+ scope: typeof json.scope === 'string' ? json.scope : undefined,
640
+ };
641
+ }
642
+
643
+ private async row(userId: string, id: string): Promise<typeof secrets.$inferSelect | undefined> {
644
+ const [row] = await this.db
645
+ .select()
646
+ .from(secrets)
647
+ .where(and(eq(secrets.id, id), eq(secrets.userId, userId)))
648
+ .limit(1);
649
+ return row;
650
+ }
651
+
652
+ private async requireRow(userId: string, id: string): Promise<typeof secrets.$inferSelect> {
653
+ const row = await this.row(userId, id);
654
+ if (!row) throw new SecretNotFoundError(id);
655
+ return row;
656
+ }
657
+
658
+ private readBlob(valueEncrypted: string): OAuthBlob {
659
+ const parsed = JSON.parse(this.crypto().decrypt(valueEncrypted));
660
+ return (parsed && typeof parsed === 'object' ? parsed : {}) as OAuthBlob;
661
+ }
662
+
663
+ private readMeta(raw: unknown): Required<Pick<OAuthProviderConfig, 'authorizationUrl' | 'tokenUrl' | 'clientId'>> &
664
+ Pick<OAuthProviderConfig, 'scopes' | 'authParams' | 'pkce' | 'publicClient' | 'resource'> {
665
+ const meta = (raw ?? {}) as Record<string, unknown>;
666
+ const authorizationUrl = typeof meta.authorizationUrl === 'string' ? meta.authorizationUrl : '';
667
+ const tokenUrl = typeof meta.tokenUrl === 'string' ? meta.tokenUrl : '';
668
+ const clientId = typeof meta.clientId === 'string' ? meta.clientId : '';
669
+ if (!authorizationUrl || !tokenUrl || !clientId) {
670
+ throw new SecretOAuthError('OAuth secret is missing provider configuration');
671
+ }
672
+ return {
673
+ authorizationUrl,
674
+ tokenUrl,
675
+ clientId,
676
+ scopes: Array.isArray(meta.scopes) ? meta.scopes.map(String) : undefined,
677
+ authParams:
678
+ meta.authParams && typeof meta.authParams === 'object'
679
+ ? (meta.authParams as Record<string, string>)
680
+ : undefined,
681
+ pkce: meta.pkce === true,
682
+ publicClient: meta.publicClient === true,
683
+ resource: typeof meta.resource === 'string' && meta.resource ? meta.resource : undefined,
684
+ };
685
+ }
686
+
687
+ private publicProviderMeta(p: OAuthProviderConfig): Record<string, unknown> {
688
+ return {
689
+ authorizationUrl: p.authorizationUrl,
690
+ tokenUrl: p.tokenUrl,
691
+ clientId: p.clientId,
692
+ scopes: p.scopes ?? [],
693
+ authParams: p.authParams ?? {},
694
+ ...(p.pkce ? { pkce: true } : {}),
695
+ ...(p.publicClient ? { publicClient: true } : {}),
696
+ ...(p.resource ? { resource: p.resource } : {}),
697
+ };
698
+ }
699
+
700
+ private requireProvider(p: unknown): OAuthProviderConfig {
701
+ if (!p || typeof p !== 'object') throw new InvalidSecretError('provider is required');
702
+ const cfg = p as Record<string, unknown>;
703
+ const authorizationUrl = this.requireUrl(cfg.authorizationUrl, 'authorizationUrl');
704
+ const tokenUrl = this.requireUrl(cfg.tokenUrl, 'tokenUrl');
705
+ const clientId = typeof cfg.clientId === 'string' ? cfg.clientId.trim() : '';
706
+ if (!clientId) throw new InvalidSecretError('clientId is required');
707
+ const scopes = Array.isArray(cfg.scopes) ? cfg.scopes.map(String) : undefined;
708
+ const authParams =
709
+ cfg.authParams && typeof cfg.authParams === 'object'
710
+ ? (cfg.authParams as Record<string, string>)
711
+ : undefined;
712
+ return {
713
+ authorizationUrl,
714
+ tokenUrl,
715
+ clientId,
716
+ clientSecret: typeof cfg.clientSecret === 'string' ? cfg.clientSecret : undefined,
717
+ scopes,
718
+ authParams,
719
+ pkce: cfg.pkce === true,
720
+ publicClient: cfg.publicClient === true,
721
+ // Never fetched (it rides as a request param), but validate it like the
722
+ // endpoints anyway — it names the remote server and must be a sane https URL.
723
+ resource:
724
+ typeof cfg.resource === 'string' && cfg.resource.trim()
725
+ ? this.requireUrl(cfg.resource, 'resource')
726
+ : undefined,
727
+ };
728
+ }
729
+
730
+ private requireUrl(value: unknown, field: string): string {
731
+ if (typeof value !== 'string' || !value.trim()) throw new InvalidSecretError(`${field} is required`);
732
+ const trimmed = value.trim();
733
+ // OAuth endpoints must be https (client secret + tokens travel here) and
734
+ // must not point at internal/loopback/metadata hosts (SSRF): `tokenUrl` is
735
+ // fetched server-side, and blocking `authorizationUrl` too costs nothing.
736
+ try {
737
+ assertSafeFetchUrl(trimmed, { requireHttps: true, label: field });
738
+ } catch (err) {
739
+ throw new InvalidSecretError(err instanceof Error ? err.message : `${field} must be a valid https URL`);
740
+ }
741
+ return trimmed;
742
+ }
743
+
744
+ private requireUserId(userId: unknown): string {
745
+ const trimmed = (typeof userId === 'string' ? userId : '').trim();
746
+ if (!trimmed) throw new InvalidSecretError('userId is required');
747
+ return trimmed;
748
+ }
749
+
750
+ private requireKey(key: unknown): string {
751
+ if (typeof key !== 'string') throw new InvalidSecretError('key must be a string');
752
+ const trimmed = key.trim();
753
+ if (!trimmed) throw new InvalidSecretError('key is required');
754
+ if (trimmed.length > MAX_KEY_LEN) throw new InvalidSecretError(`key exceeds ${MAX_KEY_LEN} characters`);
755
+ // Keys are UTCP variable names — restrict to what the substitutor accepts
756
+ // (`${[a-zA-Z0-9_]+}`) so a saved secret can actually be referenced.
757
+ if (!/^[a-zA-Z0-9_]+$/.test(trimmed)) {
758
+ throw new InvalidSecretError('key may only contain letters, digits, and underscores');
759
+ }
760
+ return trimmed;
761
+ }
762
+
763
+ private normaliseLabel(value: unknown): string | null {
764
+ if (value === undefined || value === null) return null;
765
+ if (typeof value !== 'string') throw new InvalidSecretError('label must be a string');
766
+ const trimmed = value.trim();
767
+ if (!trimmed) return null;
768
+ if (trimmed.length > MAX_LABEL_LEN) throw new InvalidSecretError(`label exceeds ${MAX_LABEL_LEN} characters`);
769
+ return trimmed;
770
+ }
771
+
772
+ private toSummary(row: typeof secrets.$inferSelect): SecretSummary {
773
+ const kind = (row.kind === 'oauth' ? 'oauth' : 'static') as 'static' | 'oauth';
774
+ const summary: SecretSummary = {
775
+ id: row.id,
776
+ key: row.key,
777
+ kind,
778
+ label: row.label,
779
+ createdAt: row.createdAt,
780
+ updatedAt: row.updatedAt,
781
+ };
782
+ if (kind === 'oauth') {
783
+ summary.authorized = Boolean(this.readTokensSafe(row.valueEncrypted)?.access_token);
784
+ }
785
+ return summary;
786
+ }
787
+ }
788
+
789
+ /** PKCE S256: base64url(sha256(verifier)). */
790
+ function sha256base64url(verifier: string): string {
791
+ return createHash('sha256').update(verifier).digest('base64url');
792
+ }