@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,806 @@
1
+ import { and, asc, eq } from 'drizzle-orm';
2
+ import type {
3
+ AuthUser,
4
+ CancelPrResult,
5
+ FileApprovalEntry,
6
+ FileApprovalState,
7
+ MergePrResult,
8
+ PostPrCommentInput,
9
+ PrReviewComment,
10
+ PullRequestFile,
11
+ PullRequestState,
12
+ } from '@bevel-software/platform-shared';
13
+ import type { Database } from '../../database/connection.js';
14
+ import { changeRequests, prComments, prFileApprovals, prMergeLog } from '../../database/schema.js';
15
+ import type { IAccessControl } from '../../access/access-control.interface.js';
16
+ import { isAccessMdPath } from '../../access/access-control.service.js';
17
+ import type { GitService } from '../git/git.service.js';
18
+ import {
19
+ ChangeRequestConflictsError,
20
+ WorkflowDomainError,
21
+ WorkflowValidationError,
22
+ } from '../workflow.errors.js';
23
+ import type { WorkspaceService } from '../../workspace/workspace.service.js';
24
+ import { hashEmail } from '../../../shared/hash-email.js';
25
+ import type {
26
+ IReviewWorkflowService,
27
+ MergeGateInput,
28
+ MergeGateResult,
29
+ } from './review-workflow.interface.js';
30
+
31
+ // Merge commit (not squash): change requests can carry many meaningful commits
32
+ // (e.g. a bulk node upload split across files) and the KB's history is the audit
33
+ // trail — squashing would collapse them into one. A merge commit preserves every
34
+ // commit on the branch.
35
+ const MERGE_METHOD = 'merge' as const;
36
+ const EVERYONE_CANONICAL = 'everyone';
37
+ /** Repo-relative path of the access-config file the resolver gates as Admin-only. */
38
+ const ROLES_YAML = 'roles.yaml';
39
+
40
+ function redactTokens(msg: string): string {
41
+ const tokens = [process.env.GITHUB_TOKEN, process.env.GH_TOKEN].filter(
42
+ (t): t is string => !!t && t.length > 0,
43
+ );
44
+ return tokens.reduce((m, t) => m.replaceAll(t, '***'), msg);
45
+ }
46
+
47
+ class CommentAuthError extends WorkflowDomainError {
48
+ constructor(reason: 'not-found' | 'forbidden') {
49
+ super(
50
+ reason === 'not-found' ? 'Comment not found' : 'You can only edit your own comments',
51
+ reason === 'not-found' ? 404 : 403,
52
+ );
53
+ this.name = 'CommentAuthError';
54
+ }
55
+ }
56
+
57
+ class MergeBlockedError extends WorkflowDomainError {
58
+ constructor(reasons: string[]) {
59
+ super(`Merge gate rejected: ${reasons.join('; ') || 'unknown'}`, 422, {
60
+ mergeBlockedReasons: reasons,
61
+ });
62
+ this.name = 'MergeBlockedError';
63
+ }
64
+ }
65
+
66
+ class MergeExecutionError extends WorkflowDomainError {
67
+ constructor(reason: string) {
68
+ super(`Merge failed: ${reason}`, 502);
69
+ this.name = 'MergeExecutionError';
70
+ }
71
+ }
72
+
73
+ class ApprovalAuthError extends WorkflowDomainError {
74
+ constructor(reason: 'not-eligible' | 'no-eligible-approvers' | 'path-not-in-pr') {
75
+ const { status, msg } = {
76
+ 'not-eligible': { status: 403, msg: "You don't have write access on this file's path" },
77
+ 'no-eligible-approvers': { status: 422, msg: 'No one is eligible to approve this file — broaden the access.md rules covering it first' },
78
+ 'path-not-in-pr': { status: 404, msg: 'Path is not part of this PR' },
79
+ }[reason];
80
+ super(msg, status);
81
+ this.name = 'ApprovalAuthError';
82
+ }
83
+ }
84
+
85
+ class BypassAuthError extends WorkflowDomainError {
86
+ constructor() {
87
+ super(
88
+ "Only admins can merge with bypass. You need write access to roles.yaml on the base branch to skip approval warnings.",
89
+ 403,
90
+ );
91
+ this.name = 'BypassAuthError';
92
+ }
93
+ }
94
+
95
+ class CancelStateError extends WorkflowDomainError {
96
+ constructor(reason: 'already-applied' | 'already-cancelled') {
97
+ const msg =
98
+ reason === 'already-applied'
99
+ ? 'This change request was already applied.'
100
+ : 'This change request is already cancelled.';
101
+ super(msg, 422);
102
+ this.name = 'CancelStateError';
103
+ }
104
+ }
105
+
106
+ class CancelAuthError extends WorkflowDomainError {
107
+ constructor() {
108
+ super(
109
+ "You can't cancel this change request — only the author or an admin can.",
110
+ 403,
111
+ );
112
+ this.name = 'CancelAuthError';
113
+ }
114
+ }
115
+
116
+ function assertValidPrNumber(n: number): void {
117
+ if (!Number.isInteger(n) || n <= 0) {
118
+ throw new WorkflowValidationError('PR number must be a positive integer');
119
+ }
120
+ }
121
+
122
+ function assertValidBody(body: string): void {
123
+ const trimmed = body.trim();
124
+ if (!trimmed) throw new WorkflowValidationError('comment body is required');
125
+ if (trimmed.length > 64_000) {
126
+ throw new WorkflowValidationError('comment body exceeds 64k character limit');
127
+ }
128
+ }
129
+
130
+ function assertValidLine(line: number | undefined): void {
131
+ if (line === undefined) return;
132
+ if (!Number.isInteger(line) || line < 1 || line > 1_000_000) {
133
+ throw new WorkflowValidationError('line must be a positive integer');
134
+ }
135
+ }
136
+
137
+ // RFC 4122 — accepts any variant/version so legacy v1 ids still pass. Stricter
138
+ // than "any 32 hex" because Drizzle/PG reject mis-shaped strings with a 500,
139
+ // and we want a 4xx on malformed input from URL params / body fields.
140
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
141
+ function assertValidUuid(value: unknown, fieldName: string): asserts value is string {
142
+ if (typeof value !== 'string' || !UUID_RE.test(value)) {
143
+ throw new WorkflowValidationError(`${fieldName} must be a valid UUID`);
144
+ }
145
+ }
146
+
147
+ /**
148
+ * The access-config files the resolver treats as Admin-only to write
149
+ * (`roles.yaml` decides admin membership; any `access.md` decides per-path
150
+ * grants). These are NOT `.md` KB nodes but ARE the most security-critical
151
+ * files in the repo, so the approval gate must bind them too — without this a
152
+ * `roles.yaml`-only change request rides into a protected branch with no
153
+ * approval and no admin check, letting its author self-promote to Admin.
154
+ * Mirrors the resolver's own `relativePath === 'roles.yaml' || isAccessMdPath`
155
+ * special-cases (access-control.service.ts). Paths are repo-relative, the same
156
+ * form GitHub reports a PR's changed files in.
157
+ */
158
+ function isAccessConfigPath(p: string): boolean {
159
+ return p === ROLES_YAML || isAccessMdPath(p);
160
+ }
161
+
162
+ /**
163
+ * Approval enforcement binds markdown KB nodes AND the access-config files
164
+ * (`roles.yaml`, `access.md`) that have someone eligible to approve them per
165
+ * the access tree. Files with no eligible approvers, and other non-md files,
166
+ * are outside the gate — they neither warn nor block. Shared between the gate
167
+ * and any call-site that needs the "does this participate in approvals"
168
+ * question answered consistently.
169
+ */
170
+ function isGateRelevant(a: FileApprovalState): boolean {
171
+ const hasEligible =
172
+ a.eligibleApprovers.roles.length > 0 || a.eligibleApprovers.users.length > 0;
173
+ const lower = a.path.toLowerCase();
174
+ return hasEligible && (lower.endsWith('.md') || isAccessConfigPath(a.path));
175
+ }
176
+
177
+ /** Human-friendly label for the eligible-approver set, used in gate warnings. */
178
+ function eligibleLabel(a: FileApprovalState): string {
179
+ const parts: string[] = [];
180
+ if (a.eligibleApprovers.roles.length) parts.push(a.eligibleApprovers.roles.join(', '));
181
+ if (a.eligibleApprovers.users.length) {
182
+ parts.push(
183
+ a.eligibleApprovers.users
184
+ .map((u) => (u.name ? `${u.name} <${u.email}>` : u.email))
185
+ .join(', '),
186
+ );
187
+ }
188
+ return parts.join('; ') || 'someone with write access';
189
+ }
190
+
191
+ function assertValidPath(p: unknown): asserts p is string | undefined {
192
+ if (p === undefined || p === null) return;
193
+ if (typeof p !== 'string') throw new WorkflowValidationError('path must be a string');
194
+ if (!p) throw new WorkflowValidationError('path, if provided, must be non-empty');
195
+ if (p.length > 4096) throw new WorkflowValidationError('path exceeds 4096 character limit');
196
+ if (p.includes('\0')) throw new WorkflowValidationError('path contains NUL');
197
+ }
198
+
199
+ function toDTO(row: typeof prComments.$inferSelect): PrReviewComment {
200
+ return {
201
+ id: row.id,
202
+ author: { email: row.authorEmail, name: row.authorName },
203
+ body: row.body,
204
+ path: row.path ?? undefined,
205
+ line: row.line ?? undefined,
206
+ headSha: row.headSha,
207
+ parentId: row.parentId ?? undefined,
208
+ createdAt: row.createdAt.toISOString(),
209
+ updatedAt: row.updatedAt ? row.updatedAt.toISOString() : undefined,
210
+ };
211
+ }
212
+
213
+ export class ReviewWorkflowService implements IReviewWorkflowService {
214
+ constructor(
215
+ private readonly db: Database,
216
+ private readonly accessControl: IAccessControl,
217
+ private readonly workspaceService: WorkspaceService,
218
+ private readonly git: GitService,
219
+ ) {}
220
+
221
+ async listComments(prNumber: number): Promise<PrReviewComment[]> {
222
+ assertValidPrNumber(prNumber);
223
+ const rows = await this.db
224
+ .select()
225
+ .from(prComments)
226
+ .where(eq(prComments.prNumber, prNumber))
227
+ .orderBy(asc(prComments.createdAt));
228
+ return rows.map(toDTO);
229
+ }
230
+
231
+ async postComment(
232
+ prNumber: number,
233
+ user: AuthUser,
234
+ input: PostPrCommentInput,
235
+ headSha: string,
236
+ ): Promise<PrReviewComment> {
237
+ assertValidPrNumber(prNumber);
238
+ assertValidBody(input.body);
239
+ assertValidPath(input.path);
240
+ assertValidLine(input.line);
241
+ if (!headSha) throw new WorkflowValidationError('head sha is required');
242
+
243
+ // Inline (line-level) comments must have a path; general comments may have neither.
244
+ if (input.line !== undefined && !input.path) {
245
+ throw new WorkflowValidationError('line comments require a file path');
246
+ }
247
+
248
+ // Validate parentId references a real comment on the same PR. UUID format
249
+ // check first — PG rejects mis-shaped ids with a 500 from its uuid type,
250
+ // so we convert that to a structured 4xx before hitting the DB. We skip a
251
+ // depth check — trees deeper than two levels are rare and the UI flattens
252
+ // them into a single reply chain for display.
253
+ if (input.parentId !== undefined && input.parentId !== null) {
254
+ assertValidUuid(input.parentId, 'parentId');
255
+ const parent = await this.db
256
+ .select()
257
+ .from(prComments)
258
+ .where(
259
+ and(eq(prComments.id, input.parentId), eq(prComments.prNumber, prNumber)),
260
+ )
261
+ .limit(1);
262
+ if (parent.length === 0) {
263
+ throw new WorkflowValidationError('parent comment not found on this PR');
264
+ }
265
+ }
266
+
267
+ const [row] = await this.db
268
+ .insert(prComments)
269
+ .values({
270
+ prNumber,
271
+ authorEmail: user.email.trim().toLowerCase(),
272
+ authorName: user.name,
273
+ path: input.path ?? null,
274
+ line: input.line ?? null,
275
+ headSha,
276
+ body: input.body.trim(),
277
+ parentId: input.parentId ?? null,
278
+ })
279
+ .returning();
280
+ return toDTO(row);
281
+ }
282
+
283
+ async editComment(
284
+ commentId: string,
285
+ prNumber: number,
286
+ user: AuthUser,
287
+ body: string,
288
+ ): Promise<PrReviewComment> {
289
+ assertValidPrNumber(prNumber);
290
+ assertValidUuid(commentId, 'comment id');
291
+ assertValidBody(body);
292
+ // Scope lookup to (id, prNumber) so a guessed id from another PR 404s
293
+ // rather than leaking existence / touching a foreign comment.
294
+ const existing = await this.db
295
+ .select()
296
+ .from(prComments)
297
+ .where(and(eq(prComments.id, commentId), eq(prComments.prNumber, prNumber)))
298
+ .limit(1);
299
+ if (existing.length === 0) throw new CommentAuthError('not-found');
300
+ if (existing[0].authorEmail !== user.email.trim().toLowerCase()) {
301
+ throw new CommentAuthError('forbidden');
302
+ }
303
+ const [row] = await this.db
304
+ .update(prComments)
305
+ .set({ body: body.trim(), updatedAt: new Date() })
306
+ .where(and(eq(prComments.id, commentId), eq(prComments.prNumber, prNumber)))
307
+ .returning();
308
+ return toDTO(row);
309
+ }
310
+
311
+ async deleteComment(commentId: string, prNumber: number, user: AuthUser): Promise<void> {
312
+ assertValidPrNumber(prNumber);
313
+ assertValidUuid(commentId, 'comment id');
314
+ const existing = await this.db
315
+ .select()
316
+ .from(prComments)
317
+ .where(and(eq(prComments.id, commentId), eq(prComments.prNumber, prNumber)))
318
+ .limit(1);
319
+ if (existing.length === 0) throw new CommentAuthError('not-found');
320
+ if (existing[0].authorEmail !== user.email.trim().toLowerCase()) {
321
+ throw new CommentAuthError('forbidden');
322
+ }
323
+ await this.db
324
+ .delete(prComments)
325
+ .where(and(eq(prComments.id, commentId), eq(prComments.prNumber, prNumber)));
326
+ }
327
+
328
+ async getApprovalStates(
329
+ prNumber: number,
330
+ files: PullRequestFile[],
331
+ headSha: string,
332
+ baseBranch: string,
333
+ prAuthorIdHash: string | null,
334
+ workspaceId?: string,
335
+ viewerEmail?: string,
336
+ ): Promise<FileApprovalState[]> {
337
+ assertValidPrNumber(prNumber);
338
+
339
+ // Eligible approvers are resolved against `origin/<baseBranch>` — the
340
+ // PR's target. Reading from the PR head would let the PR author grant
341
+ // themselves approval rights via access.md edits in their own branch;
342
+ // reading from the working tree would let any user with edit access to
343
+ // access.md do the same locally. Both are privilege-escalation paths.
344
+ // Origin's protected branches are fast-forward-only (they're gated by
345
+ // their own PR review), so they're the authoritative source.
346
+ //
347
+ // Without a workspace we can't do the git-show lookup, so return
348
+ // entries with empty eligibility and let the caller decide.
349
+ const baseRef = `origin/${baseBranch}`;
350
+ const paths = files.map((f) => f.path);
351
+ let eligibilityByPath: Map<
352
+ string,
353
+ {
354
+ roles: string[];
355
+ users: { name: string; email: string }[];
356
+ emails: Set<string>;
357
+ excludedEmails?: Set<string>;
358
+ }
359
+ > = new Map();
360
+ // Viewer-can-approve runs the same gate the Approve route applies, but
361
+ // batched per PR. Default is false — both when the access tree can't be
362
+ // resolved (no workspace, fetch failure, no roles.yaml on base) and when
363
+ // the caller is unauthenticated. The frontend can still surface the
364
+ // eligible roles/users list; it just won't render an Approve button.
365
+ let viewerCanApproveByPath: Map<string, boolean> = new Map();
366
+ if (workspaceId) {
367
+ await this.workspaceService.ensureRemotesFetched(workspaceId).catch(() => undefined);
368
+ try {
369
+ const resolved = await this.accessControl.eligibleWritersForPathsAtRef(
370
+ workspaceId,
371
+ baseRef,
372
+ paths,
373
+ );
374
+ if (resolved) eligibilityByPath = resolved;
375
+ } catch (err) {
376
+ console.warn(
377
+ `[review-workflow] eligibleWritersForPathsAtRef failed for PR #${prNumber} (base=${baseBranch}):`,
378
+ err,
379
+ );
380
+ }
381
+
382
+ if (viewerEmail) {
383
+ try {
384
+ const batch = await this.accessControl.canWriteBatchAtRef(
385
+ workspaceId,
386
+ baseRef,
387
+ viewerEmail,
388
+ paths,
389
+ );
390
+ if (batch) viewerCanApproveByPath = batch;
391
+ } catch (err) {
392
+ console.warn(
393
+ `[review-workflow] canWriteBatchAtRef failed for PR #${prNumber} viewer=${viewerEmail}:`,
394
+ err,
395
+ );
396
+ }
397
+ }
398
+ }
399
+
400
+ // One round-trip for all approvals on this PR. Filtering + staleness check
401
+ // happens in memory — trivial at the expected row counts.
402
+ const rows = await this.db
403
+ .select()
404
+ .from(prFileApprovals)
405
+ .where(eq(prFileApprovals.prNumber, prNumber));
406
+
407
+ return files.map((file): FileApprovalState => {
408
+ const eligible = eligibilityByPath.get(file.path) ?? {
409
+ roles: [],
410
+ users: [] as { name: string; email: string }[],
411
+ emails: new Set<string>(),
412
+ };
413
+ const pathRows = rows.filter((r) => r.path === file.path);
414
+
415
+ const approvedBy: FileApprovalEntry[] = pathRows.map((r) => ({
416
+ email: r.approverEmail,
417
+ name: r.approverName,
418
+ approvedAt: r.approvedAt.toISOString(),
419
+ isStale: r.headSha !== headSha,
420
+ isSelfApproval: prAuthorIdHash ? hashEmail(r.approverEmail) === prAuthorIdHash : false,
421
+ }));
422
+
423
+ // A file is approved when any eligible writer (per the access tree on
424
+ // `origin/<baseBranch>`) has a non-stale approval against the current
425
+ // head SHA. Files with no eligible writers can't be approved and are
426
+ // silently excluded from the merge gate by `isGateRelevant`.
427
+ // `everyone` write grant means any signed-in approver qualifies — except
428
+ // those carved out by a denial at any tier (`deny email` / `deny role` /
429
+ // `deny everyone`), captured in `excludedEmails`. Otherwise only the
430
+ // explicitly-eligible writers in `emails` can approve.
431
+ const everyoneCanApprove = eligible.roles.includes(EVERYONE_CANONICAL);
432
+ const excludedEmails = eligible.excludedEmails ?? new Set<string>();
433
+ const hasEligibleApproval =
434
+ (everyoneCanApprove || eligible.emails.size > 0) &&
435
+ pathRows.some(
436
+ (r) => {
437
+ const approverEmail = r.approverEmail.toLowerCase();
438
+ if (r.headSha !== headSha) return false;
439
+ return everyoneCanApprove
440
+ ? !excludedEmails.has(approverEmail)
441
+ : eligible.emails.has(approverEmail);
442
+ },
443
+ );
444
+
445
+ return {
446
+ path: file.path,
447
+ eligibleApprovers: { roles: eligible.roles, users: eligible.users },
448
+ approvedBy,
449
+ isApproved: hasEligibleApproval,
450
+ viewerCanApprove: viewerCanApproveByPath.get(file.path) === true,
451
+ };
452
+ });
453
+ }
454
+
455
+ async approveFile(
456
+ prNumber: number,
457
+ path: string,
458
+ user: AuthUser,
459
+ files: PullRequestFile[],
460
+ headSha: string,
461
+ baseBranch: string,
462
+ prAuthorIdHash: string | null,
463
+ workspaceId: string,
464
+ ): Promise<FileApprovalState[]> {
465
+ assertValidPrNumber(prNumber);
466
+ assertValidPath(path);
467
+ if (!headSha) throw new WorkflowValidationError('head sha is required');
468
+ if (!baseBranch) throw new WorkflowValidationError('base branch is required');
469
+
470
+ if (!files.some((f) => f.path === path)) {
471
+ throw new ApprovalAuthError('path-not-in-pr');
472
+ }
473
+
474
+ // Resolve eligibility against `origin/<baseBranch>` — the canonical
475
+ // access tree for the PR's target. Reading from the PR head or the
476
+ // working tree would let users grant themselves approval rights via
477
+ // local edits to access.md. Fetch remotes first so the ref resolves
478
+ // on a clone that hasn't seen recent base updates.
479
+ await this.workspaceService.ensureRemotesFetched(workspaceId).catch(() => undefined);
480
+ const canApprove = await this.accessControl.canWriteAtRef(
481
+ workspaceId,
482
+ `origin/${baseBranch}`,
483
+ user.email,
484
+ path,
485
+ );
486
+ if (canApprove === null) throw new ApprovalAuthError('no-eligible-approvers');
487
+ if (!canApprove) throw new ApprovalAuthError('not-eligible');
488
+ const callerEmail = user.email.trim().toLowerCase();
489
+
490
+ // Unique index on (prNumber, path, approverEmail, headSha) makes this
491
+ // idempotent — `onConflictDoNothing` turns a double-click into a no-op.
492
+ await this.db
493
+ .insert(prFileApprovals)
494
+ .values({
495
+ prNumber,
496
+ path,
497
+ approverEmail: callerEmail,
498
+ approverName: user.name,
499
+ headSha,
500
+ })
501
+ .onConflictDoNothing();
502
+
503
+ return this.getApprovalStates(prNumber, files, headSha, baseBranch, prAuthorIdHash, workspaceId);
504
+ }
505
+
506
+ evaluateMergeGate(input: MergeGateInput): MergeGateResult {
507
+ const reasons: string[] = [];
508
+ const warnings: string[] = [];
509
+
510
+ if (input.state === 'merged') {
511
+ reasons.push('This pull request has already been merged.');
512
+ } else if (input.state === 'closed') {
513
+ reasons.push('This pull request is closed.');
514
+ }
515
+
516
+ // Empty approvals = empty files array = nothing to approve. That's not
517
+ // mergeable either — a PR that touches no files shouldn't be opened in
518
+ // the first place, let alone merged.
519
+ if (input.approvals.length === 0 && input.state === 'open') {
520
+ reasons.push('This pull request has no file changes to approve.');
521
+ }
522
+
523
+ // Ownership enforcement only binds markdown KB nodes. Non-md files (TS,
524
+ // JSON, images, etc.) and ownerless md files are silent — they neither
525
+ // warn nor block. For the remaining gate-relevant files, "owner hasn't
526
+ // approved the current head" is surfaced as a *warning* the caller can
527
+ // bypass explicitly, not a hard block.
528
+ for (const a of input.approvals) {
529
+ if (!isGateRelevant(a)) continue;
530
+ if (a.isApproved) continue;
531
+
532
+ const hasStale = a.approvedBy.some((e) => e.isStale);
533
+ const label = eligibleLabel(a);
534
+ if (hasStale) {
535
+ warnings.push(`${label} need to re-approve ${a.path} after the latest push.`);
536
+ } else {
537
+ warnings.push(`Waiting on approval for ${a.path} from ${label}.`);
538
+ }
539
+ }
540
+
541
+ return { mergeable: reasons.length === 0, reasons, warnings };
542
+ }
543
+
544
+ async mergePr(
545
+ prNumber: number,
546
+ user: AuthUser,
547
+ headSha: string,
548
+ approvals: FileApprovalState[],
549
+ state: PullRequestState,
550
+ prTitle: string,
551
+ baseBranch: string,
552
+ workspaceId: string,
553
+ opts: { bypass?: boolean } = {},
554
+ ): Promise<MergePrResult> {
555
+ assertValidPrNumber(prNumber);
556
+ if (!headSha) throw new WorkflowValidationError('head sha is required');
557
+ if (!baseBranch) throw new WorkflowValidationError('base branch is required');
558
+ if (!workspaceId) throw new WorkflowValidationError('workspace id is required');
559
+
560
+ // Server-side re-validation — never trust the frontend's cached gate.
561
+ // Hard blocks always refuse. Soft warnings refuse unless the caller opted
562
+ // into bypass; with bypass, the bypassed warnings get inlined in the merge
563
+ // commit body so git history captures the decision.
564
+ const gate = this.evaluateMergeGate({ prNumber, state, approvals });
565
+ if (!gate.mergeable) throw new MergeBlockedError(gate.reasons);
566
+ if (gate.warnings.length > 0 && !opts.bypass) {
567
+ throw new MergeBlockedError(gate.warnings);
568
+ }
569
+
570
+ // Bypass authority: admin-only. Resolved against `origin/<baseBranch>`
571
+ // (same authoritative source the approval gate uses) so a PR author
572
+ // can't grant themselves bypass rights by editing roles.yaml in their
573
+ // own branch. Using `canWriteAtRef(..., 'roles.yaml')` because
574
+ // `canWriteResolved` short-circuits that path to "is the caller in the
575
+ // Admin role"; that's exactly the predicate we want here. A null result
576
+ // (no usable access config on origin/<base>) is treated as "no admin can
577
+ // be identified, no bypass" — the safe default for a misconfigured tree.
578
+ if (opts.bypass && gate.warnings.length > 0) {
579
+ const isAdmin = await this.accessControl.canWriteAtRef(
580
+ workspaceId,
581
+ `origin/${baseBranch}`,
582
+ user.email,
583
+ 'roles.yaml',
584
+ );
585
+ if (isAdmin !== true) throw new BypassAuthError();
586
+ }
587
+
588
+ // Authoritative branches come from the CR row, not the passed baseBranch
589
+ // (which only drove the gate) — the merge acts on what the row records.
590
+ const [cr] = await this.db
591
+ .select()
592
+ .from(changeRequests)
593
+ .where(eq(changeRequests.number, prNumber))
594
+ .limit(1);
595
+ if (!cr) throw new WorkflowValidationError(`Change request #${prNumber} not found`);
596
+ // Re-validate lifecycle against the AUTHORITATIVE row, not the caller's
597
+ // `state` (which only drove the gate). A stale or duplicate request must not
598
+ // re-run the git merge on a CR that's already merged or closed.
599
+ if (cr.state !== 'open') {
600
+ throw new MergeBlockedError([
601
+ cr.state === 'merged'
602
+ ? 'This change request has already been merged.'
603
+ : 'This change request is closed.',
604
+ ]);
605
+ }
606
+
607
+ const triggeredByEmail = user.email.trim().toLowerCase();
608
+ const [logRow] = await this.db
609
+ .insert(prMergeLog)
610
+ .values({
611
+ prNumber,
612
+ triggeredByEmail,
613
+ triggeredByName: user.name,
614
+ headShaAtMerge: headSha,
615
+ mergeMethod: MERGE_METHOD,
616
+ succeeded: false,
617
+ })
618
+ .returning({ id: prMergeLog.id });
619
+
620
+ // Attribution lives on the merge commit itself (authored as the human
621
+ // triggerer). When bypass is used, the bypassed warnings are appended so the
622
+ // decision survives in git history — no separate audit table needed.
623
+ const subject = `${prTitle} (#${prNumber})`;
624
+ const bypassFooter =
625
+ opts.bypass && gate.warnings.length > 0
626
+ ? `\n\nApproval requirements bypassed:\n${gate.warnings.map((w) => `- ${w}`).join('\n')}`
627
+ : '';
628
+ const body = `Merged via Bevel by ${user.name} <${user.email}>${bypassFooter}`;
629
+
630
+ // The actual merge: local `git merge --no-ff` on the base branch's workspace,
631
+ // pushed to origin. Provider-agnostic — no PR API. A merge failure flips the
632
+ // log to `succeeded: false` and surfaces MergeExecutionError; a conflict is a
633
+ // caller-resolvable precondition (ChangeRequestConflictsError, 409).
634
+ const baseWorkspace = await this.workspaceService.getOrCreateForBranch(cr.targetBranch);
635
+ let mergeResult: Awaited<ReturnType<GitService['mergeChangeRequest']>>;
636
+ try {
637
+ mergeResult = await this.git.mergeChangeRequest(
638
+ baseWorkspace.id,
639
+ cr.sourceBranch,
640
+ cr.targetBranch,
641
+ { subject, body },
642
+ user,
643
+ );
644
+ } catch (err) {
645
+ const redacted = redactTokens(err instanceof Error ? err.message : String(err));
646
+ await this.db
647
+ .update(prMergeLog)
648
+ .set({ succeeded: false, completedAt: new Date(), error: redacted })
649
+ .where(eq(prMergeLog.id, logRow.id));
650
+ throw new MergeExecutionError(redacted);
651
+ }
652
+
653
+ if (mergeResult.kind === 'conflicts') {
654
+ await this.db
655
+ .update(prMergeLog)
656
+ .set({
657
+ succeeded: false,
658
+ completedAt: new Date(),
659
+ error: `conflicts: ${mergeResult.paths.join(', ')}`,
660
+ })
661
+ .where(eq(prMergeLog.id, logRow.id));
662
+ throw new ChangeRequestConflictsError(cr.sourceBranch, cr.targetBranch, mergeResult.paths);
663
+ }
664
+
665
+ // Merged. Record CR + log success together; do the CR-state update FIRST so
666
+ // the merge is durably reflected even if the log write hiccups. Gate the
667
+ // write on the row STILL being `open` (CAS): if a concurrent merge/close won
668
+ // the race between our lifecycle re-check above and here, `.returning()` is
669
+ // empty and we refuse to clobber its terminal state instead of overwriting.
670
+ const completedAt = new Date();
671
+ const [updatedCr] = await this.db
672
+ .update(changeRequests)
673
+ .set({ state: 'merged', mergedSha: mergeResult.sha, closedAt: completedAt, updatedAt: completedAt })
674
+ .where(and(eq(changeRequests.id, cr.id), eq(changeRequests.state, 'open')))
675
+ .returning({ id: changeRequests.id });
676
+ if (!updatedCr) {
677
+ // A concurrent merge won the CAS between our lifecycle re-check and here.
678
+ // Our own git merge was a harmless idempotent no-op, but this attempt did
679
+ // NOT finalize the CR. Finalize this log row with an explanatory error and
680
+ // a completedAt so it doesn't linger as a phantom `succeeded=false,
681
+ // error=null` entry that a "failed merges" audit query would misread.
682
+ const raceError = 'Change request was merged by a concurrent request; this attempt did not finalize it.';
683
+ await this.db
684
+ .update(prMergeLog)
685
+ .set({ succeeded: false, completedAt, error: raceError })
686
+ .where(eq(prMergeLog.id, logRow.id));
687
+ throw new MergeExecutionError(raceError);
688
+ }
689
+ await this.db
690
+ .update(prMergeLog)
691
+ .set({ succeeded: true, completedAt })
692
+ .where(eq(prMergeLog.id, logRow.id));
693
+
694
+ return {
695
+ prNumber,
696
+ sha: mergeResult.sha,
697
+ mergedAt: completedAt.toISOString(),
698
+ };
699
+ }
700
+
701
+ async cancelPr(
702
+ prNumber: number,
703
+ user: AuthUser,
704
+ state: PullRequestState,
705
+ authorIdHash: string | null,
706
+ baseBranch: string,
707
+ workspaceId: string,
708
+ ): Promise<CancelPrResult> {
709
+ assertValidPrNumber(prNumber);
710
+ if (!baseBranch) throw new WorkflowValidationError('base branch is required');
711
+ if (!workspaceId) throw new WorkflowValidationError('workspace id is required');
712
+
713
+ // Refuse early on terminal states so the gh call only fires for genuinely
714
+ // open PRs. Two distinct 422s — frontend distinguishes "already applied"
715
+ // from "already cancelled" in friendlyGitError to drive different copy.
716
+ if (state === 'merged') throw new CancelStateError('already-applied');
717
+ if (state === 'closed') throw new CancelStateError('already-cancelled');
718
+
719
+ // Authorization: author OR admin. Author check is a pure hash compare; the
720
+ // admin check shells through to the access tree on origin/<base>. Same
721
+ // canWriteAtRef call mergePr's bypass uses — never resolve against the PR
722
+ // head or working tree (privilege-escalation paths). Fetch remotes first so
723
+ // the ref resolves on a clone that hasn't seen recent base updates.
724
+ const viewerIsAuthor = !!(authorIdHash && authorIdHash === hashEmail(user.email));
725
+ let viewerIsAdmin = false;
726
+ if (!viewerIsAuthor) {
727
+ // Fetch failure is non-authoritative: skip the admin check rather than
728
+ // resolve roles.yaml against a stale local ref (could grant admin to
729
+ // someone who was just demoted on origin).
730
+ let fetchOk = true;
731
+ try {
732
+ await this.workspaceService.ensureRemotesFetched(workspaceId);
733
+ } catch {
734
+ fetchOk = false;
735
+ }
736
+ if (fetchOk) {
737
+ const isAdmin = await this.accessControl.canWriteAtRef(
738
+ workspaceId,
739
+ `origin/${baseBranch}`,
740
+ user.email,
741
+ 'roles.yaml',
742
+ );
743
+ viewerIsAdmin = isAdmin === true;
744
+ }
745
+ }
746
+ if (!viewerIsAuthor && !viewerIsAdmin) throw new CancelAuthError();
747
+
748
+ // Close = flip the row to `closed`. Guard on `state = 'open'` so a race
749
+ // (someone merged/closed it between the pre-check and here) updates zero
750
+ // rows; we then re-read to return the precise already-applied /
751
+ // already-cancelled error the client renders distinct copy for. The source
752
+ // branch is intentionally left intact — the author may still want it.
753
+ const now = new Date();
754
+ const updated = await this.db
755
+ .update(changeRequests)
756
+ .set({ state: 'closed', closedAt: now, updatedAt: now })
757
+ .where(and(eq(changeRequests.number, prNumber), eq(changeRequests.state, 'open')))
758
+ .returning({ id: changeRequests.id });
759
+
760
+ if (updated.length === 0) {
761
+ const [row] = await this.db
762
+ .select({ state: changeRequests.state })
763
+ .from(changeRequests)
764
+ .where(eq(changeRequests.number, prNumber))
765
+ .limit(1);
766
+ throw new CancelStateError(row?.state === 'merged' ? 'already-applied' : 'already-cancelled');
767
+ }
768
+
769
+ return {
770
+ prNumber,
771
+ cancelledAt: now.toISOString(),
772
+ };
773
+ }
774
+
775
+ async unapproveFile(
776
+ prNumber: number,
777
+ path: string,
778
+ user: AuthUser,
779
+ files: PullRequestFile[],
780
+ headSha: string,
781
+ baseBranch: string,
782
+ prAuthorIdHash: string | null,
783
+ workspaceId: string,
784
+ ): Promise<FileApprovalState[]> {
785
+ assertValidPrNumber(prNumber);
786
+ assertValidPath(path);
787
+ if (!headSha) throw new WorkflowValidationError('head sha is required');
788
+
789
+ const callerEmail = user.email.trim().toLowerCase();
790
+
791
+ // Only revoke the caller's OWN approval — never someone else's. Filter on
792
+ // (PR, path, approverEmail) without pinning the SHA so a user revoking
793
+ // after a force-push can drop their stale row in one click.
794
+ await this.db
795
+ .delete(prFileApprovals)
796
+ .where(
797
+ and(
798
+ eq(prFileApprovals.prNumber, prNumber),
799
+ eq(prFileApprovals.path, path),
800
+ eq(prFileApprovals.approverEmail, callerEmail),
801
+ ),
802
+ );
803
+
804
+ return this.getApprovalStates(prNumber, files, headSha, baseBranch, prAuthorIdHash, workspaceId);
805
+ }
806
+ }