@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,247 @@
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ import {
4
+ spliceGrant,
5
+ spliceRevoke,
6
+ validatePrincipal,
7
+ AccessSpliceError,
8
+ type Principal,
9
+ } from '../access-splice.js';
10
+ import { parseAccessEntry, canonicalEmail } from '../access-control.service.js';
11
+
12
+ const felix: Principal = { kind: 'user', email: 'felix.kissel@example.com', displayName: 'Felix' };
13
+ const juan: Principal = { kind: 'user', email: 'juan@bevel.software', displayName: 'Juan V' };
14
+ const productTeam: Principal = { kind: 'role', role: 'Product Team' };
15
+
16
+ /** A realistic access.md with a prose body + comment — the thing parse→emit destroys. */
17
+ const ROOT_ACCESS_MD = `---
18
+ write:
19
+ - Admin
20
+ # download is independent of write
21
+ download:
22
+ - Juan <juan@bevel.software>
23
+ owner: []
24
+ ---
25
+
26
+ # Repository access
27
+
28
+ This file is the root of the access-control tree. It grants write access only
29
+ to the \`Admin\` role by default. Subfolders can broaden access.
30
+
31
+ See \`lib/access-control.js\` for the resolution semantics.
32
+ `;
33
+
34
+ describe('spliceGrant — body + comment preservation (keystone)', () => {
35
+ it('keeps the markdown body, comments, and key order byte-for-byte', () => {
36
+ const r = spliceGrant(ROOT_ACCESS_MD, 'write', felix);
37
+ expect(r.changed).toBe(true);
38
+ // The body prose must survive verbatim.
39
+ expect(r.text).toContain('# Repository access');
40
+ expect(r.text).toContain('See `lib/access-control.js` for the resolution semantics.');
41
+ // The comment must survive.
42
+ expect(r.text).toContain('# download is independent of write');
43
+ // Existing entries untouched, new one appended under write.
44
+ expect(r.text).toContain(' - Admin');
45
+ expect(r.text).toContain(' - Felix <felix.kissel@example.com>');
46
+ // owner stays empty-list.
47
+ expect(r.text).toContain('owner: []');
48
+ // Felix lands under write, immediately after Admin, before the comment.
49
+ const lines = r.text.split('\n');
50
+ const adminIdx = lines.indexOf(' - Admin');
51
+ const felixIdx = lines.indexOf(' - Felix <felix.kissel@example.com>');
52
+ const commentIdx = lines.indexOf('# download is independent of write');
53
+ expect(adminIdx).toBeGreaterThan(-1);
54
+ expect(felixIdx).toBe(adminIdx + 1);
55
+ expect(commentIdx).toBeGreaterThan(felixIdx);
56
+ });
57
+
58
+ it('grant then revoke returns to a body-preserving state', () => {
59
+ const granted = spliceGrant(ROOT_ACCESS_MD, 'write', felix).text;
60
+ const revoked = spliceRevoke(granted, 'write', felix).text;
61
+ expect(revoked).toContain('# Repository access');
62
+ expect(revoked).toContain(' - Admin');
63
+ expect(revoked).not.toContain('Felix');
64
+ });
65
+ });
66
+
67
+ describe('spliceGrant — idempotency + creation', () => {
68
+ it('is a no-op when the principal already has the grant', () => {
69
+ const once = spliceGrant(ROOT_ACCESS_MD, 'write', felix).text;
70
+ const twice = spliceGrant(once, 'write', felix);
71
+ expect(twice.changed).toBe(false);
72
+ expect(twice.text).toBe(once);
73
+ });
74
+
75
+ it('creates the verb key when absent', () => {
76
+ const r = spliceGrant(ROOT_ACCESS_MD, 'read', productTeam);
77
+ expect(r.changed).toBe(true);
78
+ expect(r.text).toContain('read:');
79
+ expect(r.text).toContain(' - Product Team');
80
+ expect(r.text).toContain('# Repository access'); // body still there
81
+ });
82
+
83
+ it('promotes an inline-empty `owner: []` to a block list', () => {
84
+ const r = spliceGrant(ROOT_ACCESS_MD, 'owner', juan);
85
+ expect(r.changed).toBe(true);
86
+ expect(r.text).toContain('owner:\n - Juan V <juan@bevel.software>');
87
+ expect(r.text).not.toContain('owner: []');
88
+ });
89
+
90
+ it('synthesises frontmatter for a fresh (frontmatter-less) file', () => {
91
+ const r = spliceGrant('', 'write', felix);
92
+ expect(r.changed).toBe(true);
93
+ expect(r.text.startsWith('---\n')).toBe(true);
94
+ expect(r.text).toContain('write:\n - Felix <felix.kissel@example.com>');
95
+ });
96
+ });
97
+
98
+ describe('spliceRevoke', () => {
99
+ it('removes one entry and collapses the verb to [] when it empties', () => {
100
+ const r = spliceRevoke(ROOT_ACCESS_MD, 'write', { kind: 'role', role: 'Admin' });
101
+ expect(r.changed).toBe(true);
102
+ expect(r.text).toContain('write: []');
103
+ expect(r.text).not.toMatch(/- Admin/);
104
+ expect(r.text).toContain('# Repository access');
105
+ });
106
+
107
+ it('is a no-op when the principal is not present', () => {
108
+ const r = spliceRevoke(ROOT_ACCESS_MD, 'write', felix);
109
+ expect(r.changed).toBe(false);
110
+ expect(r.text).toBe(ROOT_ACCESS_MD);
111
+ });
112
+ });
113
+
114
+ describe('spliceGrant — verbs are independent (a principal may hold several)', () => {
115
+ it('grants a second verb without removing the principal from the first', () => {
116
+ // Verbs do not nest: granting `download` on top of `write` leaves BOTH.
117
+ const granted = spliceGrant(ROOT_ACCESS_MD, 'write', felix).text;
118
+ const both = spliceGrant(granted, 'download', felix).text;
119
+ const writeBlock = both.slice(both.indexOf('write:'), both.indexOf('download:'));
120
+ expect(writeBlock).toContain('Felix'); // still under write
121
+ expect(both).toContain('download:'); // and now under download too
122
+ const downloadBlock = both.slice(both.indexOf('download:'));
123
+ expect(downloadBlock).toContain('Felix');
124
+ });
125
+
126
+ it('is idempotent for an already-granted verb', () => {
127
+ const granted = spliceGrant(ROOT_ACCESS_MD, 'owner', felix).text;
128
+ const again = spliceGrant(granted, 'owner', felix);
129
+ expect(again.changed).toBe(false);
130
+ });
131
+ });
132
+
133
+ describe('spliceGrant({ deny }) — the deny-here per-item override', () => {
134
+ it('adds a `deny <principal>` line to an existing block list', () => {
135
+ // `write:` already lists Admin; deny Felix there.
136
+ const r = spliceGrant(ROOT_ACCESS_MD, 'write', felix, { deny: true });
137
+ expect(r.changed).toBe(true);
138
+ const writeBlock = r.text.slice(r.text.indexOf('write:'), r.text.indexOf('download:'));
139
+ expect(writeBlock).toContain('- deny Felix <felix.kissel@example.com>');
140
+ expect(writeBlock).toContain('- Admin'); // existing grant untouched
141
+ // The denial parses back as a deny entry.
142
+ const entry = parseAccessEntry('deny Felix <felix.kissel@example.com>');
143
+ expect(entry.ok && entry.entry.deny).toBe(true);
144
+ });
145
+
146
+ it('promotes a node-frontmatter scalar to a block list with the deny entry', () => {
147
+ // A node whose own frontmatter names a sole owner via the scalar form.
148
+ const node = `---\nnodeType: process\nowner: Juan V <juan@bevel.software>\n---\n# Body\n`;
149
+ const r = spliceGrant(node, 'owner', felix, { deny: true, allowScalar: true });
150
+ expect(r.changed).toBe(true);
151
+ expect(r.text).toContain('owner:');
152
+ expect(r.text).toContain('- Juan V <juan@bevel.software>'); // old scalar preserved
153
+ expect(r.text).toContain('- deny Felix <felix.kissel@example.com>'); // deny added
154
+ expect(r.text).toContain('# Body'); // body survives
155
+ });
156
+
157
+ it('fills an inline-empty `owner: []` with the deny entry', () => {
158
+ const r = spliceGrant(ROOT_ACCESS_MD, 'owner', felix, { deny: true });
159
+ expect(r.changed).toBe(true);
160
+ const ownerBlock = r.text.slice(r.text.indexOf('owner:'));
161
+ expect(ownerBlock).toContain('- deny Felix <felix.kissel@example.com>');
162
+ expect(ownerBlock).not.toContain('owner: []'); // the [] collapsed to a real list
163
+ });
164
+
165
+ it('is a no-op when the same deny entry already exists', () => {
166
+ const once = spliceGrant(ROOT_ACCESS_MD, 'write', felix, { deny: true }).text;
167
+ const twice = spliceGrant(once, 'write', felix, { deny: true });
168
+ expect(twice.changed).toBe(false);
169
+ expect(twice.text).toBe(once);
170
+ });
171
+
172
+ it('a deny and a grant for the same principal are independent (deny does not no-op a grant)', () => {
173
+ // An existing grant must NOT make the deny idempotent, and vice-versa —
174
+ // the idempotency key includes deny-ness. (The resolver-level grant-beats-
175
+ // deny interaction is handled one layer up in AccessMutationService.)
176
+ const granted = spliceGrant(ROOT_ACCESS_MD, 'write', felix).text;
177
+ const denied = spliceGrant(granted, 'write', felix, { deny: true });
178
+ expect(denied.changed).toBe(true);
179
+ const writeBlock = denied.text.slice(denied.text.indexOf('write:'), denied.text.indexOf('download:'));
180
+ expect(writeBlock).toContain('- Felix <felix.kissel@example.com>');
181
+ expect(writeBlock).toContain('- deny Felix <felix.kissel@example.com>');
182
+ });
183
+ });
184
+
185
+ describe('validatePrincipal — injection guard', () => {
186
+ it('rejects a newline-bearing email (frontmatter injection)', () => {
187
+ expect(() =>
188
+ validatePrincipal({
189
+ kind: 'user',
190
+ email: 'x@y.com\nwrite:\n - everyone',
191
+ displayName: 'X',
192
+ }),
193
+ ).toThrow(AccessSpliceError);
194
+ });
195
+
196
+ it('rejects a display name containing angle brackets', () => {
197
+ expect(() =>
198
+ validatePrincipal({ kind: 'user', email: 'a@b.com', displayName: 'Bad <hack>' }),
199
+ ).toThrow(AccessSpliceError);
200
+ });
201
+
202
+ it('rejects a group name with a colon (would forge a key)', () => {
203
+ expect(() => validatePrincipal({ kind: 'role', role: 'write:\n - everyone' })).toThrow(
204
+ AccessSpliceError,
205
+ );
206
+ });
207
+
208
+ it('rejects the reserved group name deny', () => {
209
+ expect(() => validatePrincipal({ kind: 'role', role: 'deny' })).toThrow(AccessSpliceError);
210
+ });
211
+
212
+ it('accepts the built-in everyone role (grantable as public read; route gates the verb)', () => {
213
+ expect(validatePrincipal({ kind: 'role', role: 'everyone' })).toEqual({
214
+ kind: 'role',
215
+ role: 'everyone',
216
+ });
217
+ });
218
+
219
+ it('rejects `#` in a name (uncomment() would truncate it on read-back)', () => {
220
+ expect(() =>
221
+ validatePrincipal({ kind: 'user', email: 'a@b.com', displayName: 'Felix #1' }),
222
+ ).toThrow(AccessSpliceError);
223
+ expect(() => validatePrincipal({ kind: 'role', role: 'Team #1' })).toThrow(AccessSpliceError);
224
+ });
225
+
226
+ it('canonicalises a valid principal (lowercased email)', () => {
227
+ const p = validatePrincipal({ kind: 'user', email: 'Felix@Example.COM', displayName: 'Felix' });
228
+ expect(p.kind === 'user' && p.email).toBe('felix@example.com');
229
+ });
230
+ });
231
+
232
+ describe('round-trip — every granted entry parses back', () => {
233
+ it('a granted person renders to a line parseAccessEntry accepts', () => {
234
+ const r = spliceGrant(ROOT_ACCESS_MD, 'write', felix);
235
+ const line = r.text
236
+ .split('\n')
237
+ .find((l) => l.trim().startsWith('- Felix'))!
238
+ .trim()
239
+ .slice(2)
240
+ .trim();
241
+ const parsed = parseAccessEntry(line);
242
+ expect(parsed.ok).toBe(true);
243
+ expect(parsed.ok && parsed.entry.kind === 'user' && parsed.entry.email).toBe(
244
+ canonicalEmail(felix.kind === 'user' ? felix.email : ''),
245
+ );
246
+ });
247
+ });
@@ -0,0 +1,407 @@
1
+ import type { Server } from 'node:http';
2
+ import type { AddressInfo } from 'node:net';
3
+ import express from 'express';
4
+ import { describe, it, expect, afterEach, vi } from 'vitest';
5
+
6
+ import type { IAccessControl, GrantSources } from '../access-control.interface.js';
7
+ import type { WorkspaceService } from '../../workspace/workspace.service.js';
8
+ import type { AuthService } from '../../auth/auth.service.js';
9
+ import type { WorkflowService } from '../../workflow/workflow.service.js';
10
+ import type { WorkflowEventBus } from '../../workflow/event-bus.js';
11
+ import type { Database } from '../../database/connection.js';
12
+ import { createAccessRoutes } from '../access.routes.js';
13
+
14
+ /**
15
+ * HTTP-level contract tests for the revoke route's CLASSIFICATION + MODE logic —
16
+ * the branching the mutation-layer tests can't reach: the `409 { kind:'inherited' }`
17
+ * body, the 200-vs-409-vs-no-op fork, the ancestor-keyed 403 gate, and the
18
+ * stale-ancestor TOCTOU abort. The resolver/mutation are stubbed so each test
19
+ * pins exactly one route decision; the in-memory file store lets the real
20
+ * `AccessMutationService` splice run end-to-end under the route's lock wrapper.
21
+ */
22
+
23
+ const USER = { id: 'u-1', email: 'alice@bevel.software', name: 'Alice' };
24
+ const WS = 'alice/feature'; // a non-protected feature branch
25
+ const KB = 'knowledge-base';
26
+
27
+ interface Stubs {
28
+ files: Map<string, string>;
29
+ canWrite: ReturnType<typeof vi.fn>;
30
+ grantSources: ReturnType<typeof vi.fn>;
31
+ acquireLock: ReturnType<typeof vi.fn>;
32
+ releaseLock: ReturnType<typeof vi.fn>;
33
+ releaseLockNoCommit: ReturnType<typeof vi.fn>;
34
+ }
35
+
36
+ interface Harness {
37
+ server: Server;
38
+ baseUrl: string;
39
+ stubs: Stubs;
40
+ }
41
+
42
+ async function makeHarness(opts: {
43
+ files?: Record<string, string>;
44
+ /** canWrite(path) verdict — defaults to true (writable). */
45
+ canWrite?: (repoRelPath: string) => boolean;
46
+ /** grantSources verdict, by repo-relative target. */
47
+ grantSources?: (repoRelTarget: string) => GrantSources;
48
+ /**
49
+ * Per-email effective-access override for `denyHere`'s post-write assert
50
+ * (canRead/canWrite/canDownload/canOwner). Map an email → false to model a
51
+ * deny that actually removed that principal's access; emails absent from the
52
+ * map fall back to the defaults (read true, others false; canWrite via the
53
+ * `canWrite` opt).
54
+ */
55
+ effectiveEmails?: Record<string, boolean>;
56
+ /** Override the (otherwise-empty) eligibleWriters list so a principal shows as a row in resolvedView. */
57
+ eligibleWriters?: { roles: string[]; users: { name: string; email: string }[] };
58
+ }): Promise<Harness> {
59
+ const files = new Map<string, string>(Object.entries(opts.files ?? {}));
60
+
61
+ // canWrite is consumed by BOTH the route's pre-lock gate (actor email) and
62
+ // denyHere's post-write effectiveness assert (the denied principal's email).
63
+ // `effectiveEmails` lets a test say "after the deny, THIS email no longer has
64
+ // access" without disturbing the actor's gate verdict.
65
+ const canWrite = vi.fn(async (_w: string, email: string, p: string) => {
66
+ if (opts.effectiveEmails && email in opts.effectiveEmails) return opts.effectiveEmails[email];
67
+ return opts.canWrite ? opts.canWrite(p) : true;
68
+ });
69
+ const grantSources = vi.fn(async (_w: string, _k: string, target: string) =>
70
+ opts.grantSources ? opts.grantSources(target) : ({} as GrantSources),
71
+ );
72
+ const effEmail = (email: string, fallback: boolean) =>
73
+ opts.effectiveEmails && email in opts.effectiveEmails ? opts.effectiveEmails[email] : fallback;
74
+
75
+ const accessControl = {
76
+ canWrite,
77
+ grantSources,
78
+ invalidate: vi.fn(),
79
+ // resolvedView() fans out to these after a successful mutation; harmless stubs.
80
+ // canRead/canDownload/canOwner are ALSO consumed by denyHere's assert.
81
+ canRead: vi.fn(async (_w: string, email: string) => effEmail(email, true)),
82
+ canDownload: vi.fn(async (_w: string, email: string) => effEmail(email, false)),
83
+ canOwner: vi.fn(async (_w: string, email: string) => effEmail(email, false)),
84
+ eligibleWriters: vi.fn(async () => opts.eligibleWriters ?? { roles: [], users: [] }),
85
+ eligibleReaders: vi.fn(async () => ({ restricted: true, roles: [], users: [] })),
86
+ eligibleOwners: vi.fn(async () => ({ roles: [], users: [] })),
87
+ eligibleDownloaders: vi.fn(async () => ({ roles: [], users: [] })),
88
+ } as unknown as IAccessControl;
89
+
90
+ const workspaceService = {
91
+ getOrCreateForBranch: vi.fn(async () => ({ id: WS, name: WS, kbDirName: KB })),
92
+ readFile: vi.fn(async (_id: string, wsRel: string) => {
93
+ const v = files.get(wsRel);
94
+ if (v === undefined) {
95
+ const err = new Error(`ENOENT ${wsRel}`) as NodeJS.ErrnoException;
96
+ err.code = 'ENOENT';
97
+ throw err;
98
+ }
99
+ return v;
100
+ }),
101
+ writeFile: vi.fn(async (_id: string, wsRel: string, content: string) => {
102
+ files.set(wsRel, content);
103
+ }),
104
+ } as unknown as WorkspaceService;
105
+
106
+ const authService = { getUserById: vi.fn(async () => USER) } as unknown as AuthService;
107
+
108
+ const acquireLock = vi.fn(async () => ({ acquired: true, lock: {} }));
109
+ const releaseLock = vi.fn(async () => undefined);
110
+ const releaseLockNoCommit = vi.fn(async () => undefined);
111
+ const workflowService = {
112
+ getLock: vi.fn(async () => null),
113
+ acquireLock,
114
+ releaseLock,
115
+ releaseLockNoCommit,
116
+ } as unknown as WorkflowService;
117
+
118
+ const eventBus = { emit: vi.fn() } as unknown as WorkflowEventBus;
119
+ const db = {} as unknown as Database;
120
+
121
+ const app = express();
122
+ app.use(express.json());
123
+ app.use('/api', (req, _res, next) => {
124
+ (req as unknown as { userId: string }).userId = USER.id;
125
+ next();
126
+ });
127
+ app.use('/api', createAccessRoutes(accessControl, workspaceService, authService, workflowService, eventBus, db, KB));
128
+
129
+ const server = await new Promise<Server>((resolve) => {
130
+ const s = app.listen(0, () => resolve(s));
131
+ });
132
+ const addr = server.address() as AddressInfo;
133
+ return {
134
+ server,
135
+ baseUrl: `http://127.0.0.1:${addr.port}`,
136
+ stubs: { files, canWrite, grantSources, acquireLock, releaseLock, releaseLockNoCommit },
137
+ };
138
+ }
139
+
140
+ function close(s: Server): Promise<void> {
141
+ return new Promise((resolve, reject) => s.close((e) => (e ? reject(e) : resolve())));
142
+ }
143
+
144
+ const ALICE = { kind: 'user' as const, email: 'alice@bevel.software', displayName: 'Alice' };
145
+
146
+ describe('POST /access/revoke — route classification + modes', () => {
147
+ let h: Harness | null = null;
148
+ afterEach(async () => {
149
+ if (h) await close(h.server);
150
+ h = null;
151
+ });
152
+
153
+ const revoke = (body: unknown) =>
154
+ fetch(`${h!.baseUrl}/api/workspace/${encodeURIComponent(WS)}/access/revoke`, {
155
+ method: 'POST',
156
+ headers: { 'Content-Type': 'application/json' },
157
+ body: JSON.stringify(body),
158
+ });
159
+
160
+ describe('default mode — classify the revoke', () => {
161
+ it('DIRECT grant → 200 with the fresh view (target edited)', async () => {
162
+ h = await makeHarness({
163
+ files: { [`${KB}/Sales/access.md`]: '---\nwrite:\n - Alice <alice@bevel.software>\n---\n' },
164
+ grantSources: () => ({}), // after the splice removes her, no source remains
165
+ });
166
+ const res = await revoke({ path: `${KB}/Sales`, kind: 'folder', principal: ALICE });
167
+ expect(res.status).toBe(200);
168
+ // The entry was spliced out of the folder access.md.
169
+ expect(h.stubs.files.get(`${KB}/Sales/access.md`)).not.toContain('alice@bevel.software');
170
+ // The lock was acquired and released (committed), not released-no-commit.
171
+ expect(h.stubs.releaseLock).toHaveBeenCalledOnce();
172
+ expect(h.stubs.releaseLockNoCommit).not.toHaveBeenCalled();
173
+ });
174
+
175
+ it('DIRECT + still inherited → 200 strips the direct entry; the fresh view lists the principal with only the ancestor source', async () => {
176
+ // The file names Alice directly (splice changes it → changed:true), but she
177
+ // is ALSO inherited from Sales. After the direct entry is gone she still
178
+ // resolves, so the fresh view's per-row sources show only the ancestor —
179
+ // which is how the dialog chains into "Remove from parent?" (no special
180
+ // response field; the richer per-verb sources subsume the old stillInherited).
181
+ h = await makeHarness({
182
+ files: {
183
+ [`${KB}/Sales/Deal.md`]:
184
+ '---\nnodeType: process\nwrite:\n - Alice <alice@bevel.software>\n---\n# Deal\n',
185
+ },
186
+ // She remains an effective writer (via the ancestor) so she's still a row...
187
+ eligibleWriters: { roles: [], users: [{ name: 'Alice', email: 'alice@bevel.software' }] },
188
+ // ...and her per-row source is the remaining ancestor.
189
+ grantSources: () => ({ write: [{ kind: 'ancestor', path: 'Sales/access.md' }] }),
190
+ });
191
+ const res = await revoke({ path: `${KB}/Sales/Deal.md`, kind: 'file', principal: ALICE });
192
+ expect(res.status).toBe(200);
193
+ const body = await res.json();
194
+ // The direct entry was spliced out of the node frontmatter...
195
+ expect(h.stubs.files.get(`${KB}/Sales/Deal.md`)).not.toContain('alice@bevel.software');
196
+ // ...and the fresh view exposes her remaining ancestor source (drives the chain).
197
+ expect(body.sources['u:alice@bevel.software'].write).toEqual([
198
+ { kind: 'ancestor', path: 'Sales/access.md' },
199
+ ]);
200
+ });
201
+
202
+ it('DIRECT + NOT otherwise inherited → 200; the principal no longer appears with any source', async () => {
203
+ h = await makeHarness({
204
+ files: { [`${KB}/Sales/access.md`]: '---\nwrite:\n - Alice <alice@bevel.software>\n---\n' },
205
+ grantSources: () => ({}), // nothing left after the direct entry is removed
206
+ });
207
+ const res = await revoke({ path: `${KB}/Sales`, kind: 'folder', principal: ALICE });
208
+ expect(res.status).toBe(200);
209
+ // She's gone from the eligible lists (stubbed empty) so there's no row/source.
210
+ expect((await res.json()).sources['u:alice@bevel.software']).toBeUndefined();
211
+ });
212
+
213
+ it('INHERITED from a file ancestor → 409 { kind: "inherited", sources } and the target is untouched', async () => {
214
+ // The file target names no one; Alice resolves via Sales/access.md.
215
+ h = await makeHarness({
216
+ files: { [`${KB}/Sales/Deal.md`]: '---\nnodeType: process\n---\n# Deal\n' },
217
+ grantSources: () => ({ write: [{ kind: 'ancestor', path: 'Sales/access.md' }] }),
218
+ });
219
+ const res = await revoke({ path: `${KB}/Sales/Deal.md`, kind: 'file', principal: ALICE });
220
+ expect(res.status).toBe(409);
221
+ const body = await res.json();
222
+ expect(body.kind).toBe('inherited');
223
+ expect(body.sources.write).toEqual([{ kind: 'ancestor', path: 'Sales/access.md' }]);
224
+ // The target node frontmatter is unchanged (no deny written).
225
+ expect(h.stubs.files.get(`${KB}/Sales/Deal.md`)).toBe('---\nnodeType: process\n---\n# Deal\n');
226
+ });
227
+
228
+ it('VERB-SPECIFIC revoke of an INHERITED verb → 409 inherited (unchecking "Can edit" on a direct-download row)', async () => {
229
+ // The exact share-dialog case: the principal is direct on `download` (on the
230
+ // file) but inherits `write` from the parent. Unchecking "Can edit" sends a
231
+ // single-verb revoke of `write` — which finds nothing on the file to splice
232
+ // (changed:false) but still resolves via the ancestor → 409 inherited. The
233
+ // dialog converts this 409 into the "Remove from parent?" flow.
234
+ h = await makeHarness({
235
+ files: {
236
+ [`${KB}/Sales/Deal.md`]:
237
+ '---\nnodeType: process\ndownload:\n - Alice <alice@bevel.software>\n---\n# Deal\n',
238
+ },
239
+ grantSources: () => ({ write: [{ kind: 'ancestor', path: 'Sales/access.md' }] }),
240
+ });
241
+ const res = await revoke({
242
+ path: `${KB}/Sales/Deal.md`,
243
+ kind: 'file',
244
+ principal: ALICE,
245
+ verb: 'write', // ← the single inherited verb being unchecked
246
+ });
247
+ expect(res.status).toBe(409);
248
+ const body = await res.json();
249
+ expect(body.kind).toBe('inherited');
250
+ expect(body.sources.write).toEqual([{ kind: 'ancestor', path: 'Sales/access.md' }]);
251
+ // The direct download entry is untouched — only `write` was targeted.
252
+ expect(h.stubs.files.get(`${KB}/Sales/Deal.md`)).toContain('download:');
253
+ expect(h.stubs.files.get(`${KB}/Sales/Deal.md`)).toContain('alice@bevel.software');
254
+ });
255
+
256
+ it('NO removable source (resolves only via a group/everyone/rescue) → 200 no-op, NOT a 409', async () => {
257
+ // grantSources is empty (a group/everyone/rescue grant is not a per-user
258
+ // file source), and the target splice no-ops. The route must 200, never 409.
259
+ h = await makeHarness({
260
+ files: { [`${KB}/Sales/Deal.md`]: '---\nnodeType: process\n---\n# Deal\n' },
261
+ grantSources: () => ({}),
262
+ });
263
+ const res = await revoke({ path: `${KB}/Sales/Deal.md`, kind: 'file', principal: ALICE });
264
+ expect(res.status).toBe(200);
265
+ expect(h.stubs.files.get(`${KB}/Sales/Deal.md`)).toBe('---\nnodeType: process\n---\n# Deal\n');
266
+ });
267
+
268
+ it('NO effective access anywhere → 200 no-op', async () => {
269
+ h = await makeHarness({
270
+ files: { [`${KB}/Sales/access.md`]: '---\nwrite:\n - Admin\n---\n' },
271
+ grantSources: () => ({}),
272
+ });
273
+ const res = await revoke({ path: `${KB}/Sales`, kind: 'folder', principal: ALICE });
274
+ expect(res.status).toBe(200);
275
+ expect(h.stubs.files.get(`${KB}/Sales/access.md`)).toContain('Admin'); // untouched
276
+ });
277
+
278
+ it('refuses 403 when the caller cannot write the target access config', async () => {
279
+ h = await makeHarness({
280
+ files: { [`${KB}/Sales/access.md`]: '---\nwrite:\n - Alice <alice@bevel.software>\n---\n' },
281
+ canWrite: () => false,
282
+ });
283
+ const res = await revoke({ path: `${KB}/Sales`, kind: 'folder', principal: ALICE });
284
+ expect(res.status).toBe(403);
285
+ // No lock acquired — the gate is BEFORE the lock.
286
+ expect(h.stubs.acquireLock).not.toHaveBeenCalled();
287
+ });
288
+ });
289
+
290
+ describe('mode: remove-from-parent', () => {
291
+ it('gates 403 on the ANCESTOR, not the target (write-target-but-not-ancestor is refused)', async () => {
292
+ // Caller can write the target folder but NOT the ancestor access.md.
293
+ h = await makeHarness({
294
+ files: { [`${KB}/Sales/access.md`]: '---\nwrite:\n - Alice <alice@bevel.software>\n---\n' },
295
+ canWrite: (p) => p !== 'Sales/access.md', // ancestor gate fails
296
+ });
297
+ const res = await revoke({
298
+ path: `${KB}/Sales/Deal.md`,
299
+ kind: 'file',
300
+ principal: ALICE,
301
+ mode: 'remove-from-parent',
302
+ ancestor: 'Sales',
303
+ });
304
+ expect(res.status).toBe(403);
305
+ // The ancestor access.md was never edited.
306
+ expect(h.stubs.files.get(`${KB}/Sales/access.md`)).toContain('alice@bevel.software');
307
+ expect(h.stubs.acquireLock).not.toHaveBeenCalled();
308
+ });
309
+
310
+ it('TOCTOU: aborts 409 stale-ancestor when the principal is no longer inherited from the named ancestor', async () => {
311
+ // The chain changed since the 409 — grantSources no longer reports this
312
+ // ancestor. The route must abort under the lock with kind:'stale-ancestor'
313
+ // and leave the ancestor untouched.
314
+ h = await makeHarness({
315
+ files: { [`${KB}/Sales/access.md`]: '---\nwrite:\n - Admin\n---\n' },
316
+ grantSources: () => ({}), // not inherited from Sales anymore
317
+ });
318
+ const res = await revoke({
319
+ path: `${KB}/Sales/Deal.md`,
320
+ kind: 'file',
321
+ principal: ALICE,
322
+ mode: 'remove-from-parent',
323
+ ancestor: 'Sales',
324
+ });
325
+ expect(res.status).toBe(409);
326
+ expect((await res.json()).kind).toBe('stale-ancestor');
327
+ // Lock acquired (the re-check is under the lock) then released WITHOUT commit.
328
+ expect(h.stubs.acquireLock).toHaveBeenCalledOnce();
329
+ expect(h.stubs.releaseLockNoCommit).toHaveBeenCalledOnce();
330
+ expect(h.stubs.releaseLock).not.toHaveBeenCalled();
331
+ expect(h.stubs.files.get(`${KB}/Sales/access.md`)).toContain('Admin'); // untouched
332
+ });
333
+
334
+ it('still-inherited → revokes on the ancestor access.md and 200s', async () => {
335
+ h = await makeHarness({
336
+ files: { [`${KB}/Sales/access.md`]: '---\nwrite:\n - Alice <alice@bevel.software>\n---\n' },
337
+ grantSources: () => ({ write: [{ kind: 'ancestor', path: 'Sales/access.md' }] }),
338
+ });
339
+ const res = await revoke({
340
+ path: `${KB}/Sales/Deal.md`,
341
+ kind: 'file',
342
+ principal: ALICE,
343
+ mode: 'remove-from-parent',
344
+ ancestor: 'Sales',
345
+ });
346
+ expect(res.status).toBe(200);
347
+ // Alice was spliced out of the ANCESTOR access.md.
348
+ expect(h.stubs.files.get(`${KB}/Sales/access.md`)).not.toContain('alice@bevel.software');
349
+ expect(h.stubs.releaseLock).toHaveBeenCalledOnce();
350
+ });
351
+
352
+ it('requires an ancestor in the body', async () => {
353
+ h = await makeHarness({ files: {} });
354
+ const res = await revoke({
355
+ path: `${KB}/Sales/Deal.md`,
356
+ kind: 'file',
357
+ principal: ALICE,
358
+ mode: 'remove-from-parent',
359
+ });
360
+ expect(res.status).toBe(400);
361
+ });
362
+ });
363
+
364
+ describe('mode: deny-here', () => {
365
+ const BOB = { kind: 'user' as const, email: 'bob@bevel.software', displayName: 'Bob' };
366
+
367
+ it('writes a deny at the target and 200s when the deny removes effective access', async () => {
368
+ // Alice (the actor) can write the target; Bob inherits write from a folder.
369
+ // Deny Bob on the file. denyHere's post-write assert sees Bob no longer has
370
+ // effective access (effectiveEmails), so it commits — 200.
371
+ h = await makeHarness({
372
+ files: { [`${KB}/Sales/Deal.md`]: '---\nnodeType: process\n---\n# Deal\n' },
373
+ effectiveEmails: { 'bob@bevel.software': false },
374
+ });
375
+ const res = await revoke({
376
+ path: `${KB}/Sales/Deal.md`,
377
+ kind: 'file',
378
+ principal: BOB,
379
+ mode: 'deny-here',
380
+ });
381
+ expect(res.status).toBe(200);
382
+ // A deny entry was written into the node's own frontmatter.
383
+ expect(h.stubs.files.get(`${KB}/Sales/Deal.md`)).toContain('deny Bob <bob@bevel.software>');
384
+ expect(h.stubs.releaseLock).toHaveBeenCalledOnce();
385
+ });
386
+
387
+ it('rolls back + 409 deny-ineffective when the deny does NOT remove access (e.g. admin-rescue)', async () => {
388
+ // Bob still has effective access after the deny (effectiveEmails true) —
389
+ // denyHere must roll the file back and surface kind:'deny-ineffective'.
390
+ const original = '---\nnodeType: process\n---\n# Deal\n';
391
+ h = await makeHarness({
392
+ files: { [`${KB}/Sales/Deal.md`]: original },
393
+ effectiveEmails: { 'bob@bevel.software': true },
394
+ });
395
+ const res = await revoke({
396
+ path: `${KB}/Sales/Deal.md`,
397
+ kind: 'file',
398
+ principal: BOB,
399
+ mode: 'deny-here',
400
+ });
401
+ expect(res.status).toBe(409);
402
+ expect((await res.json()).kind).toBe('deny-ineffective');
403
+ // Rolled back to the original bytes (no lingering deny entry).
404
+ expect(h.stubs.files.get(`${KB}/Sales/Deal.md`)).toBe(original);
405
+ });
406
+ });
407
+ });