@bevel-software/platform-core-backend 0.15.1 → 0.20.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 (1385) hide show
  1. package/THIRD-PARTY-NOTICES.md +4 -4
  2. package/dist/core/catalog-cache-invalidation.d.ts +16 -0
  3. package/dist/core/catalog-cache-invalidation.d.ts.map +1 -1
  4. package/dist/core/catalog-cache-invalidation.js +6 -1
  5. package/dist/core/catalog-cache-invalidation.js.map +1 -1
  6. package/dist/core/catalog-revision.d.ts +39 -0
  7. package/dist/core/catalog-revision.d.ts.map +1 -0
  8. package/dist/core/catalog-revision.js +94 -0
  9. package/dist/core/catalog-revision.js.map +1 -0
  10. package/dist/core/create-core-server.d.ts +7 -0
  11. package/dist/core/create-core-server.d.ts.map +1 -1
  12. package/dist/core/create-core-server.js +221 -75
  13. package/dist/core/create-core-server.js.map +1 -1
  14. package/dist/core/create-core-services.d.ts +52 -2
  15. package/dist/core/create-core-services.d.ts.map +1 -1
  16. package/dist/core/create-core-services.js +222 -87
  17. package/dist/core/create-core-services.js.map +1 -1
  18. package/dist/core/lifecycle.d.ts +138 -0
  19. package/dist/core/lifecycle.d.ts.map +1 -0
  20. package/dist/core/lifecycle.js +218 -0
  21. package/dist/core/lifecycle.js.map +1 -0
  22. package/dist/core/public-config.d.ts +82 -0
  23. package/dist/core/public-config.d.ts.map +1 -0
  24. package/dist/core/public-config.js +78 -0
  25. package/dist/core/public-config.js.map +1 -0
  26. package/dist/core/readiness.d.ts +86 -0
  27. package/dist/core/readiness.d.ts.map +1 -0
  28. package/dist/core/readiness.js +56 -0
  29. package/dist/core/readiness.js.map +1 -0
  30. package/dist/core-config.d.ts +30 -0
  31. package/dist/core-config.d.ts.map +1 -1
  32. package/dist/core-config.js +64 -2
  33. package/dist/core-config.js.map +1 -1
  34. package/dist/index.d.ts +4 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +6 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/modules/access/access-control.interface.d.ts +215 -11
  39. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  40. package/dist/modules/access/access-control.service.d.ts +57 -11
  41. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  42. package/dist/modules/access/access-control.service.js +616 -155
  43. package/dist/modules/access/access-control.service.js.map +1 -1
  44. package/dist/modules/access/access-mutation.service.d.ts +71 -5
  45. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  46. package/dist/modules/access/access-mutation.service.js +161 -13
  47. package/dist/modules/access/access-mutation.service.js.map +1 -1
  48. package/dist/modules/access/access-view.d.ts +147 -0
  49. package/dist/modules/access/access-view.d.ts.map +1 -0
  50. package/dist/modules/access/access-view.js +211 -0
  51. package/dist/modules/access/access-view.js.map +1 -0
  52. package/dist/modules/access/access.routes.d.ts.map +1 -1
  53. package/dist/modules/access/access.routes.js +255 -79
  54. package/dist/modules/access/access.routes.js.map +1 -1
  55. package/dist/modules/access/admin-locked-commit.d.ts +2 -3
  56. package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
  57. package/dist/modules/access/admin-locked-commit.js +9 -8
  58. package/dist/modules/access/admin-locked-commit.js.map +1 -1
  59. package/dist/modules/access/admin-route-helpers.d.ts.map +1 -1
  60. package/dist/modules/access/admin-route-helpers.js +4 -4
  61. package/dist/modules/access/admin-route-helpers.js.map +1 -1
  62. package/dist/modules/access/change-read-gate.d.ts +53 -0
  63. package/dist/modules/access/change-read-gate.d.ts.map +1 -0
  64. package/dist/modules/access/change-read-gate.js +134 -0
  65. package/dist/modules/access/change-read-gate.js.map +1 -0
  66. package/dist/modules/access/creator-access.d.ts +33 -39
  67. package/dist/modules/access/creator-access.d.ts.map +1 -1
  68. package/dist/modules/access/creator-access.js +92 -119
  69. package/dist/modules/access/creator-access.js.map +1 -1
  70. package/dist/modules/access/directory-sync-bot.d.ts.map +1 -1
  71. package/dist/modules/access/directory-sync-bot.js +3 -1
  72. package/dist/modules/access/directory-sync-bot.js.map +1 -1
  73. package/dist/modules/access/groups-admin.service.d.ts +3 -3
  74. package/dist/modules/access/groups-admin.service.js +2 -2
  75. package/dist/modules/access/reference-scan.d.ts +1 -1
  76. package/dist/modules/access/reference-scan.d.ts.map +1 -1
  77. package/dist/modules/access/reference-scan.js +5 -4
  78. package/dist/modules/access/reference-scan.js.map +1 -1
  79. package/dist/modules/access/roles-admin.service.d.ts +72 -13
  80. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  81. package/dist/modules/access/roles-admin.service.js +71 -13
  82. package/dist/modules/access/roles-admin.service.js.map +1 -1
  83. package/dist/modules/access/synced-groups-committer.d.ts.map +1 -1
  84. package/dist/modules/access/synced-groups-committer.js +8 -6
  85. package/dist/modules/access/synced-groups-committer.js.map +1 -1
  86. package/dist/modules/access/synced-groups-writer.d.ts.map +1 -1
  87. package/dist/modules/access/synced-groups-writer.js +3 -8
  88. package/dist/modules/access/synced-groups-writer.js.map +1 -1
  89. package/dist/modules/access/user-access-removal.service.d.ts +209 -0
  90. package/dist/modules/access/user-access-removal.service.d.ts.map +1 -0
  91. package/dist/modules/access/user-access-removal.service.js +662 -0
  92. package/dist/modules/access/user-access-removal.service.js.map +1 -0
  93. package/dist/modules/access-model/access-errors.d.ts +32 -1
  94. package/dist/modules/access-model/access-errors.d.ts.map +1 -1
  95. package/dist/modules/access-model/access-errors.js +38 -11
  96. package/dist/modules/access-model/access-errors.js.map +1 -1
  97. package/dist/modules/access-model/access-grammar.d.ts +49 -24
  98. package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
  99. package/dist/modules/access-model/access-grammar.js +68 -48
  100. package/dist/modules/access-model/access-grammar.js.map +1 -1
  101. package/dist/modules/access-model/access-splice.d.ts.map +1 -1
  102. package/dist/modules/access-model/access-splice.js +18 -18
  103. package/dist/modules/access-model/access-splice.js.map +1 -1
  104. package/dist/modules/access-model/access-template.d.ts +22 -0
  105. package/dist/modules/access-model/access-template.d.ts.map +1 -0
  106. package/dist/modules/access-model/access-template.js +42 -0
  107. package/dist/modules/access-model/access-template.js.map +1 -0
  108. package/dist/modules/access-model/change-gate.d.ts +65 -0
  109. package/dist/modules/access-model/change-gate.d.ts.map +1 -0
  110. package/dist/modules/access-model/change-gate.js +25 -0
  111. package/dist/modules/access-model/change-gate.js.map +1 -0
  112. package/dist/modules/access-model/creator.d.ts +13 -16
  113. package/dist/modules/access-model/creator.d.ts.map +1 -1
  114. package/dist/modules/access-model/creator.js.map +1 -1
  115. package/dist/modules/access-model/frontmatter-lines.d.ts +27 -0
  116. package/dist/modules/access-model/frontmatter-lines.d.ts.map +1 -0
  117. package/dist/modules/access-model/frontmatter-lines.js +74 -0
  118. package/dist/modules/access-model/frontmatter-lines.js.map +1 -0
  119. package/dist/modules/access-model/plugin-principals.d.ts +5 -3
  120. package/dist/modules/access-model/plugin-principals.d.ts.map +1 -1
  121. package/dist/modules/access-model/plugin-principals.js +11 -17
  122. package/dist/modules/access-model/plugin-principals.js.map +1 -1
  123. package/dist/modules/access-model/roles-yaml-guard.d.ts +73 -2
  124. package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -1
  125. package/dist/modules/access-model/roles-yaml-guard.js +153 -3
  126. package/dist/modules/access-model/roles-yaml-guard.js.map +1 -1
  127. package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
  128. package/dist/modules/admin/admin-access.service.js +5 -2
  129. package/dist/modules/admin/admin-access.service.js.map +1 -1
  130. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +21 -0
  131. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -0
  132. package/dist/modules/agent-instructions/agent-instructions.routes.js +38 -0
  133. package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -0
  134. package/dist/modules/agent-instructions/compose.d.ts +71 -0
  135. package/dist/modules/agent-instructions/compose.d.ts.map +1 -0
  136. package/dist/modules/agent-instructions/compose.js +234 -0
  137. package/dist/modules/agent-instructions/compose.js.map +1 -0
  138. package/dist/modules/agent-instructions/index.d.ts +4 -0
  139. package/dist/modules/agent-instructions/index.d.ts.map +1 -0
  140. package/dist/modules/agent-instructions/index.js +4 -0
  141. package/dist/modules/agent-instructions/index.js.map +1 -0
  142. package/dist/modules/agent-instructions/read-preamble.d.ts +37 -0
  143. package/dist/modules/agent-instructions/read-preamble.d.ts.map +1 -0
  144. package/dist/modules/agent-instructions/read-preamble.js +81 -0
  145. package/dist/modules/agent-instructions/read-preamble.js.map +1 -0
  146. package/dist/modules/audit/agent-audit.service.d.ts +54 -0
  147. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -0
  148. package/dist/modules/audit/agent-audit.service.js +290 -0
  149. package/dist/modules/audit/agent-audit.service.js.map +1 -0
  150. package/dist/modules/audit/agent-rest-audit.middleware.d.ts +40 -0
  151. package/dist/modules/audit/agent-rest-audit.middleware.d.ts.map +1 -0
  152. package/dist/modules/audit/agent-rest-audit.middleware.js +75 -0
  153. package/dist/modules/audit/agent-rest-audit.middleware.js.map +1 -0
  154. package/dist/modules/audit/audit.contract.d.ts +145 -0
  155. package/dist/modules/audit/audit.contract.d.ts.map +1 -0
  156. package/dist/modules/audit/audit.contract.js +35 -0
  157. package/dist/modules/audit/audit.contract.js.map +1 -0
  158. package/dist/modules/audit/audit.routes.d.ts +21 -0
  159. package/dist/modules/audit/audit.routes.d.ts.map +1 -0
  160. package/dist/modules/audit/audit.routes.js +157 -0
  161. package/dist/modules/audit/audit.routes.js.map +1 -0
  162. package/dist/modules/audit/event-classifier.d.ts +52 -0
  163. package/dist/modules/audit/event-classifier.d.ts.map +1 -0
  164. package/dist/modules/audit/event-classifier.js +123 -0
  165. package/dist/modules/audit/event-classifier.js.map +1 -0
  166. package/dist/modules/audit/index.d.ts +7 -0
  167. package/dist/modules/audit/index.d.ts.map +1 -0
  168. package/dist/modules/audit/index.js +7 -0
  169. package/dist/modules/audit/index.js.map +1 -0
  170. package/dist/modules/audit/request-audit.d.ts +57 -0
  171. package/dist/modules/audit/request-audit.d.ts.map +1 -0
  172. package/dist/modules/audit/request-audit.js +117 -0
  173. package/dist/modules/audit/request-audit.js.map +1 -0
  174. package/dist/modules/auth/account-erasure.service.d.ts +30 -4
  175. package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
  176. package/dist/modules/auth/account-erasure.service.js +96 -10
  177. package/dist/modules/auth/account-erasure.service.js.map +1 -1
  178. package/dist/modules/auth/account.routes.d.ts +6 -3
  179. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  180. package/dist/modules/auth/account.routes.js +109 -10
  181. package/dist/modules/auth/account.routes.js.map +1 -1
  182. package/dist/modules/auth/auth.routes.d.ts +10 -3
  183. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  184. package/dist/modules/auth/auth.routes.js +9 -4
  185. package/dist/modules/auth/auth.routes.js.map +1 -1
  186. package/dist/modules/auth/auth.service.d.ts +89 -5
  187. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  188. package/dist/modules/auth/auth.service.js +160 -25
  189. package/dist/modules/auth/auth.service.js.map +1 -1
  190. package/dist/modules/auth/oidc-auth-provider.d.ts +46 -3
  191. package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
  192. package/dist/modules/auth/oidc-auth-provider.js +99 -21
  193. package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
  194. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  195. package/dist/modules/code-mode/code-mode.tool.js +14 -2
  196. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  197. package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
  198. package/dist/modules/connection-probe/connection-probe.service.js +56 -7
  199. package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
  200. package/dist/modules/database/advisory-lock.d.ts +103 -0
  201. package/dist/modules/database/advisory-lock.d.ts.map +1 -0
  202. package/dist/modules/database/advisory-lock.js +320 -0
  203. package/dist/modules/database/advisory-lock.js.map +1 -0
  204. package/dist/modules/database/connection.d.ts.map +1 -1
  205. package/dist/modules/database/connection.js +9 -1
  206. package/dist/modules/database/connection.js.map +1 -1
  207. package/dist/modules/database/core-schema.d.ts +859 -99
  208. package/dist/modules/database/core-schema.d.ts.map +1 -1
  209. package/dist/modules/database/core-schema.js +218 -0
  210. package/dist/modules/database/core-schema.js.map +1 -1
  211. package/dist/modules/database/migrate.d.ts.map +1 -1
  212. package/dist/modules/database/migrate.js +26 -6
  213. package/dist/modules/database/migrate.js.map +1 -1
  214. package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
  215. package/dist/modules/declared-variables/declared-variables.routes.js +4 -2
  216. package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
  217. package/dist/modules/diff/diff.routes.d.ts.map +1 -1
  218. package/dist/modules/diff/diff.routes.js +2 -4
  219. package/dist/modules/diff/diff.routes.js.map +1 -1
  220. package/dist/modules/diff/diff.service.d.ts +4 -1
  221. package/dist/modules/diff/diff.service.d.ts.map +1 -1
  222. package/dist/modules/diff/diff.service.js +84 -81
  223. package/dist/modules/diff/diff.service.js.map +1 -1
  224. package/dist/modules/{workspace → kb-fs}/bevel-ignore.d.ts +14 -9
  225. package/dist/modules/kb-fs/bevel-ignore.d.ts.map +1 -0
  226. package/dist/modules/{workspace → kb-fs}/bevel-ignore.js +24 -14
  227. package/dist/modules/kb-fs/bevel-ignore.js.map +1 -0
  228. package/dist/modules/kb-fs/file-change-notifier.d.ts.map +1 -1
  229. package/dist/modules/kb-fs/file-change-notifier.js +3 -1
  230. package/dist/modules/kb-fs/file-change-notifier.js.map +1 -1
  231. package/dist/modules/kb-fs/git-guarded-filesystem.d.ts +45 -0
  232. package/dist/modules/kb-fs/git-guarded-filesystem.d.ts.map +1 -0
  233. package/dist/modules/kb-fs/git-guarded-filesystem.js +117 -0
  234. package/dist/modules/kb-fs/git-guarded-filesystem.js.map +1 -0
  235. package/dist/modules/kb-fs/locking-filesystem.d.ts +113 -18
  236. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  237. package/dist/modules/kb-fs/locking-filesystem.js +353 -76
  238. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  239. package/dist/modules/kb-fs/node-fs.d.ts +23 -0
  240. package/dist/modules/kb-fs/node-fs.d.ts.map +1 -0
  241. package/dist/modules/kb-fs/node-fs.js +158 -0
  242. package/dist/modules/kb-fs/node-fs.js.map +1 -0
  243. package/dist/modules/kb-fs/read-only-filesystem.d.ts +10 -9
  244. package/dist/modules/kb-fs/read-only-filesystem.d.ts.map +1 -1
  245. package/dist/modules/kb-fs/read-only-filesystem.js +18 -9
  246. package/dist/modules/kb-fs/read-only-filesystem.js.map +1 -1
  247. package/dist/modules/kb-fs/repo-path.d.ts +100 -3
  248. package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
  249. package/dist/modules/kb-fs/repo-path.js +195 -3
  250. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  251. package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
  252. package/dist/modules/kb-sync/kb-sync.routes.js +4 -2
  253. package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
  254. package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
  255. package/dist/modules/kb-sync/kb-sync.service.js +7 -5
  256. package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
  257. package/dist/modules/marketplace/git-http.routes.d.ts.map +1 -1
  258. package/dist/modules/marketplace/git-http.routes.js +6 -4
  259. package/dist/modules/marketplace/git-http.routes.js.map +1 -1
  260. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts +12 -2
  261. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts.map +1 -1
  262. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js +32 -3
  263. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js.map +1 -1
  264. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts +27 -0
  265. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts.map +1 -1
  266. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js +45 -0
  267. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js.map +1 -1
  268. package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts.map +1 -1
  269. package/dist/modules/marketplace/github-facade/github-facade.routes.js +30 -6
  270. package/dist/modules/marketplace/github-facade/github-facade.routes.js.map +1 -1
  271. package/dist/modules/marketplace/github-facade/github-facade.service.d.ts +21 -9
  272. package/dist/modules/marketplace/github-facade/github-facade.service.d.ts.map +1 -1
  273. package/dist/modules/marketplace/github-facade/github-facade.service.js +60 -19
  274. package/dist/modules/marketplace/github-facade/github-facade.service.js.map +1 -1
  275. package/dist/modules/marketplace/github-facade/index.d.ts +1 -1
  276. package/dist/modules/marketplace/github-facade/index.d.ts.map +1 -1
  277. package/dist/modules/marketplace/github-facade/index.js +1 -1
  278. package/dist/modules/marketplace/github-facade/index.js.map +1 -1
  279. package/dist/modules/marketplace/marketplace-repo.service.d.ts +14 -1
  280. package/dist/modules/marketplace/marketplace-repo.service.d.ts.map +1 -1
  281. package/dist/modules/marketplace/marketplace-repo.service.js +26 -8
  282. package/dist/modules/marketplace/marketplace-repo.service.js.map +1 -1
  283. package/dist/modules/mcp/downstream-pool.d.ts +107 -0
  284. package/dist/modules/mcp/downstream-pool.d.ts.map +1 -0
  285. package/dist/modules/mcp/downstream-pool.js +221 -0
  286. package/dist/modules/mcp/downstream-pool.js.map +1 -0
  287. package/dist/modules/mcp/downstream-token-refresh.d.ts +77 -0
  288. package/dist/modules/mcp/downstream-token-refresh.d.ts.map +1 -0
  289. package/dist/modules/mcp/downstream-token-refresh.js +166 -0
  290. package/dist/modules/mcp/downstream-token-refresh.js.map +1 -0
  291. package/dist/modules/mcp/first-call-probe.cli-args.d.ts +78 -0
  292. package/dist/modules/mcp/first-call-probe.cli-args.d.ts.map +1 -0
  293. package/dist/modules/mcp/first-call-probe.cli-args.js +151 -0
  294. package/dist/modules/mcp/first-call-probe.cli-args.js.map +1 -0
  295. package/dist/modules/mcp/first-call-probe.cli.d.ts +2 -0
  296. package/dist/modules/mcp/first-call-probe.cli.d.ts.map +1 -0
  297. package/dist/modules/mcp/first-call-probe.cli.js +62 -0
  298. package/dist/modules/mcp/first-call-probe.cli.js.map +1 -0
  299. package/dist/modules/mcp/first-call-probe.d.ts +165 -0
  300. package/dist/modules/mcp/first-call-probe.d.ts.map +1 -0
  301. package/dist/modules/mcp/first-call-probe.js +298 -0
  302. package/dist/modules/mcp/first-call-probe.js.map +1 -0
  303. package/dist/modules/mcp/manual-failure-memo.d.ts +42 -17
  304. package/dist/modules/mcp/manual-failure-memo.d.ts.map +1 -1
  305. package/dist/modules/mcp/manual-failure-memo.js +79 -23
  306. package/dist/modules/mcp/manual-failure-memo.js.map +1 -1
  307. package/dist/modules/mcp/mcp-auth.middleware.d.ts +10 -5
  308. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  309. package/dist/modules/mcp/mcp-auth.middleware.js +56 -16
  310. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  311. package/dist/modules/mcp/mcp.routes.d.ts +4 -4
  312. package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
  313. package/dist/modules/mcp/mcp.routes.js +91 -180
  314. package/dist/modules/mcp/mcp.routes.js.map +1 -1
  315. package/dist/modules/mcp/mcp.service.d.ts +226 -61
  316. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  317. package/dist/modules/mcp/mcp.service.js +758 -161
  318. package/dist/modules/mcp/mcp.service.js.map +1 -1
  319. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +59 -0
  320. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  321. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +184 -10
  322. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  323. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
  324. package/dist/modules/mcp/oauth/oauth-consent.routes.js +3 -1
  325. package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
  326. package/dist/modules/mcp/surface-log-throttle.d.ts +59 -0
  327. package/dist/modules/mcp/surface-log-throttle.d.ts.map +1 -0
  328. package/dist/modules/mcp/surface-log-throttle.js +94 -0
  329. package/dist/modules/mcp/surface-log-throttle.js.map +1 -0
  330. package/dist/modules/plugins/compile/compile-marketplace.d.ts +13 -6
  331. package/dist/modules/plugins/compile/compile-marketplace.d.ts.map +1 -1
  332. package/dist/modules/plugins/compile/compile-marketplace.js +119 -36
  333. package/dist/modules/plugins/compile/compile-marketplace.js.map +1 -1
  334. package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts +8 -1
  335. package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts.map +1 -1
  336. package/dist/modules/plugins/compile/marketplace-compiler.service.js +9 -6
  337. package/dist/modules/plugins/compile/marketplace-compiler.service.js.map +1 -1
  338. package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts.map +1 -1
  339. package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js +32 -9
  340. package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js.map +1 -1
  341. package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts +26 -6
  342. package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts.map +1 -1
  343. package/dist/modules/plugins/discovery/bundle-dialect/registry.js +71 -14
  344. package/dist/modules/plugins/discovery/bundle-dialect/registry.js.map +1 -1
  345. package/dist/modules/plugins/discovery/kb-plugin-source.d.ts +4 -2
  346. package/dist/modules/plugins/discovery/kb-plugin-source.d.ts.map +1 -1
  347. package/dist/modules/plugins/discovery/kb-plugin-source.js +9 -6
  348. package/dist/modules/plugins/discovery/kb-plugin-source.js.map +1 -1
  349. package/dist/modules/plugins/discovery/native.source.d.ts +2 -1
  350. package/dist/modules/plugins/discovery/native.source.d.ts.map +1 -1
  351. package/dist/modules/plugins/discovery/native.source.js +22 -4
  352. package/dist/modules/plugins/discovery/native.source.js.map +1 -1
  353. package/dist/modules/plugins/discovery/plugin-source.d.ts +17 -3
  354. package/dist/modules/plugins/discovery/plugin-source.d.ts.map +1 -1
  355. package/dist/modules/plugins/index.d.ts +6 -3
  356. package/dist/modules/plugins/index.d.ts.map +1 -1
  357. package/dist/modules/plugins/index.js +6 -3
  358. package/dist/modules/plugins/index.js.map +1 -1
  359. package/dist/modules/plugins/join-request-jobs.service.d.ts +250 -0
  360. package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -0
  361. package/dist/modules/plugins/join-request-jobs.service.js +602 -0
  362. package/dist/modules/plugins/join-request-jobs.service.js.map +1 -0
  363. package/dist/modules/plugins/join-request-records.store.d.ts +161 -0
  364. package/dist/modules/plugins/join-request-records.store.d.ts.map +1 -0
  365. package/dist/modules/plugins/join-request-records.store.js +179 -0
  366. package/dist/modules/plugins/join-request-records.store.js.map +1 -0
  367. package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
  368. package/dist/modules/plugins/join-requests.service.js +4 -2
  369. package/dist/modules/plugins/join-requests.service.js.map +1 -1
  370. package/dist/modules/plugins/plugin-links.d.ts +4 -1
  371. package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
  372. package/dist/modules/plugins/plugin-links.js +6 -3
  373. package/dist/modules/plugins/plugin-links.js.map +1 -1
  374. package/dist/modules/plugins/plugin-links.service.d.ts +2 -5
  375. package/dist/modules/plugins/plugin-links.service.d.ts.map +1 -1
  376. package/dist/modules/plugins/plugin-links.service.js +4 -13
  377. package/dist/modules/plugins/plugin-links.service.js.map +1 -1
  378. package/dist/modules/plugins/plugin-provision.service.d.ts +91 -19
  379. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
  380. package/dist/modules/plugins/plugin-provision.service.js +183 -38
  381. package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
  382. package/dist/modules/plugins/plugin-rename.service.d.ts +3 -1
  383. package/dist/modules/plugins/plugin-rename.service.d.ts.map +1 -1
  384. package/dist/modules/plugins/plugin-rename.service.js +20 -11
  385. package/dist/modules/plugins/plugin-rename.service.js.map +1 -1
  386. package/dist/modules/plugins/plugins.contract.d.ts +52 -4
  387. package/dist/modules/plugins/plugins.contract.d.ts.map +1 -1
  388. package/dist/modules/plugins/plugins.routes.d.ts +38 -5
  389. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  390. package/dist/modules/plugins/plugins.routes.js +190 -116
  391. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  392. package/dist/modules/plugins/plugins.service.d.ts +38 -6
  393. package/dist/modules/plugins/plugins.service.d.ts.map +1 -1
  394. package/dist/modules/plugins/plugins.service.js +86 -17
  395. package/dist/modules/plugins/plugins.service.js.map +1 -1
  396. package/dist/modules/plugins/plugins.tools.d.ts +25 -0
  397. package/dist/modules/plugins/plugins.tools.d.ts.map +1 -0
  398. package/dist/modules/plugins/plugins.tools.js +82 -0
  399. package/dist/modules/plugins/plugins.tools.js.map +1 -0
  400. package/dist/modules/plugins/teams.routes.d.ts +42 -0
  401. package/dist/modules/plugins/teams.routes.d.ts.map +1 -0
  402. package/dist/modules/plugins/teams.routes.js +79 -0
  403. package/dist/modules/plugins/teams.routes.js.map +1 -0
  404. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +40 -1
  405. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
  406. package/dist/modules/secrets-vault/db-secrets-vault.service.js +202 -37
  407. package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
  408. package/dist/modules/secrets-vault/index.d.ts +1 -1
  409. package/dist/modules/secrets-vault/index.d.ts.map +1 -1
  410. package/dist/modules/secrets-vault/index.js.map +1 -1
  411. package/dist/modules/secrets-vault/mcp-oauth-discovery.service.d.ts.map +1 -1
  412. package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js +5 -3
  413. package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js.map +1 -1
  414. package/dist/modules/secrets-vault/secrets-variable-loader.d.ts.map +1 -1
  415. package/dist/modules/secrets-vault/secrets-variable-loader.js +3 -1
  416. package/dist/modules/secrets-vault/secrets-variable-loader.js.map +1 -1
  417. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +78 -0
  418. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
  419. package/dist/modules/secrets-vault/secrets-vault.contract.js +40 -0
  420. package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
  421. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
  422. package/dist/modules/secrets-vault/secrets-vault.routes.js +106 -52
  423. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  424. package/dist/modules/settings/connection-check.d.ts +118 -0
  425. package/dist/modules/settings/connection-check.d.ts.map +1 -0
  426. package/dist/modules/settings/connection-check.js +287 -0
  427. package/dist/modules/settings/connection-check.js.map +1 -0
  428. package/dist/modules/settings/deployment-settings.service.d.ts +162 -8
  429. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  430. package/dist/modules/settings/deployment-settings.service.js +375 -52
  431. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  432. package/dist/modules/settings/git-root-folders.d.ts +54 -0
  433. package/dist/modules/settings/git-root-folders.d.ts.map +1 -0
  434. package/dist/modules/settings/git-root-folders.js +130 -0
  435. package/dist/modules/settings/git-root-folders.js.map +1 -0
  436. package/dist/modules/settings/oidc-check.d.ts +85 -0
  437. package/dist/modules/settings/oidc-check.d.ts.map +1 -0
  438. package/dist/modules/settings/oidc-check.js +172 -0
  439. package/dist/modules/settings/oidc-check.js.map +1 -0
  440. package/dist/modules/settings/setup.routes.d.ts +31 -1
  441. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  442. package/dist/modules/settings/setup.routes.js +400 -108
  443. package/dist/modules/settings/setup.routes.js.map +1 -1
  444. package/dist/modules/skills/allowed-tools-check.d.ts +132 -0
  445. package/dist/modules/skills/allowed-tools-check.d.ts.map +1 -0
  446. package/dist/modules/skills/allowed-tools-check.js +260 -0
  447. package/dist/modules/skills/allowed-tools-check.js.map +1 -0
  448. package/dist/modules/skills/index.d.ts +3 -1
  449. package/dist/modules/skills/index.d.ts.map +1 -1
  450. package/dist/modules/skills/index.js +1 -0
  451. package/dist/modules/skills/index.js.map +1 -1
  452. package/dist/modules/skills/pending-skills.service.d.ts +6 -11
  453. package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
  454. package/dist/modules/skills/pending-skills.service.js +18 -70
  455. package/dist/modules/skills/pending-skills.service.js.map +1 -1
  456. package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -1
  457. package/dist/modules/skills/skill-access-requests.routes.js +9 -6
  458. package/dist/modules/skills/skill-access-requests.routes.js.map +1 -1
  459. package/dist/modules/skills/skills.contract.d.ts +44 -10
  460. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  461. package/dist/modules/skills/skills.routes.d.ts +8 -1
  462. package/dist/modules/skills/skills.routes.d.ts.map +1 -1
  463. package/dist/modules/skills/skills.routes.js +16 -2
  464. package/dist/modules/skills/skills.routes.js.map +1 -1
  465. package/dist/modules/skills/skills.service.d.ts +31 -7
  466. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  467. package/dist/modules/skills/skills.service.js +191 -81
  468. package/dist/modules/skills/skills.service.js.map +1 -1
  469. package/dist/modules/skills/skills.tools.d.ts +4 -1
  470. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  471. package/dist/modules/skills/skills.tools.js +41 -6
  472. package/dist/modules/skills/skills.tools.js.map +1 -1
  473. package/dist/modules/tool-auth/connection-key-rejection.d.ts +18 -0
  474. package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -0
  475. package/dist/modules/tool-auth/connection-key-rejection.js +20 -0
  476. package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -0
  477. package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts +14 -0
  478. package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts.map +1 -0
  479. package/dist/modules/tool-auth/connection-keys-admin.routes.js +67 -0
  480. package/dist/modules/tool-auth/connection-keys-admin.routes.js.map +1 -0
  481. package/dist/modules/tool-auth/external-api-key.interface.d.ts +72 -8
  482. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  483. package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
  484. package/dist/modules/tool-auth/external-api-key.service.d.ts +17 -6
  485. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  486. package/dist/modules/tool-auth/external-api-key.service.js +83 -27
  487. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  488. package/dist/modules/tool-auth/internal-token.service.d.ts +15 -2
  489. package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
  490. package/dist/modules/tool-auth/internal-token.service.js +6 -2
  491. package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
  492. package/dist/modules/tool-auth/key-or-session.middleware.d.ts +23 -0
  493. package/dist/modules/tool-auth/key-or-session.middleware.d.ts.map +1 -0
  494. package/dist/modules/tool-auth/key-or-session.middleware.js +39 -0
  495. package/dist/modules/tool-auth/key-or-session.middleware.js.map +1 -0
  496. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +15 -1
  497. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  498. package/dist/modules/tool-auth/tool-auth.middleware.js +17 -6
  499. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  500. package/dist/modules/tool-helpers/tool-context.d.ts +14 -0
  501. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
  502. package/dist/modules/tool-helpers/tool-context.js +28 -6
  503. package/dist/modules/tool-helpers/tool-context.js.map +1 -1
  504. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  505. package/dist/modules/tool-helpers/tool-handler.js +22 -5
  506. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  507. package/dist/modules/tool-helpers/tool.contract.d.ts +6 -1
  508. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  509. package/dist/modules/tool-helpers/tool.contract.js +7 -1
  510. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  511. package/dist/modules/tool-manuals/index.d.ts +2 -1
  512. package/dist/modules/tool-manuals/index.d.ts.map +1 -1
  513. package/dist/modules/tool-manuals/index.js +1 -0
  514. package/dist/modules/tool-manuals/index.js.map +1 -1
  515. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
  516. package/dist/modules/tool-manuals/mcp-json-discovery.js +8 -6
  517. package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
  518. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +10 -2
  519. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -1
  520. package/dist/modules/tool-manuals/mcp-server-edit.service.js +12 -15
  521. package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
  522. package/dist/modules/tool-manuals/pending-tools.service.d.ts +50 -0
  523. package/dist/modules/tool-manuals/pending-tools.service.d.ts.map +1 -0
  524. package/dist/modules/tool-manuals/pending-tools.service.js +149 -0
  525. package/dist/modules/tool-manuals/pending-tools.service.js.map +1 -0
  526. package/dist/modules/tool-manuals/tool-delete.service.d.ts +135 -0
  527. package/dist/modules/tool-manuals/tool-delete.service.d.ts.map +1 -0
  528. package/dist/modules/tool-manuals/tool-delete.service.js +303 -0
  529. package/dist/modules/tool-manuals/tool-delete.service.js.map +1 -0
  530. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +161 -0
  531. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  532. package/dist/modules/tool-manuals/tool-manuals.contract.js +9 -0
  533. package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -1
  534. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +19 -2
  535. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  536. package/dist/modules/tool-manuals/tool-manuals.routes.js +127 -57
  537. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  538. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +119 -5
  539. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  540. package/dist/modules/tool-manuals/tool-manuals.service.js +404 -74
  541. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  542. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  543. package/dist/modules/tool-manuals/tool-manuals.tools.js +78 -6
  544. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  545. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  546. package/dist/modules/workflow/agent-tools/workflow.tools.js +88 -54
  547. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  548. package/dist/modules/workflow/event-bus.d.ts +10 -4
  549. package/dist/modules/workflow/event-bus.d.ts.map +1 -1
  550. package/dist/modules/workflow/event-bus.js +14 -11
  551. package/dist/modules/workflow/event-bus.js.map +1 -1
  552. package/dist/modules/workflow/events.routes.d.ts +17 -2
  553. package/dist/modules/workflow/events.routes.d.ts.map +1 -1
  554. package/dist/modules/workflow/events.routes.js +31 -12
  555. package/dist/modules/workflow/events.routes.js.map +1 -1
  556. package/dist/modules/workflow/file-lock.service.d.ts +22 -4
  557. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  558. package/dist/modules/workflow/file-lock.service.js +27 -4
  559. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  560. package/dist/modules/workflow/git/change-request-link.d.ts +20 -0
  561. package/dist/modules/workflow/git/change-request-link.d.ts.map +1 -0
  562. package/dist/modules/workflow/git/change-request-link.js +34 -0
  563. package/dist/modules/workflow/git/change-request-link.js.map +1 -0
  564. package/dist/modules/workflow/git/git-version.d.ts.map +1 -1
  565. package/dist/modules/workflow/git/git-version.js +3 -1
  566. package/dist/modules/workflow/git/git-version.js.map +1 -1
  567. package/dist/modules/workflow/git/git.service.d.ts +263 -4
  568. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  569. package/dist/modules/workflow/git/git.service.js +528 -118
  570. package/dist/modules/workflow/git/git.service.js.map +1 -1
  571. package/dist/modules/workflow/git/node-git-runner.d.ts +36 -0
  572. package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -0
  573. package/dist/modules/workflow/git/node-git-runner.js +273 -0
  574. package/dist/modules/workflow/git/node-git-runner.js.map +1 -0
  575. package/dist/modules/workflow/git/pull-request.service.d.ts +51 -2
  576. package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
  577. package/dist/modules/workflow/git/pull-request.service.js +157 -25
  578. package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
  579. package/dist/modules/workflow/pending-commits.service.d.ts +11 -0
  580. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  581. package/dist/modules/workflow/pending-commits.service.js +24 -4
  582. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  583. package/dist/modules/workflow/pending-commits.worker.d.ts +15 -8
  584. package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
  585. package/dist/modules/workflow/pending-commits.worker.js +107 -58
  586. package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
  587. package/dist/modules/workflow/recovery-bot.d.ts.map +1 -1
  588. package/dist/modules/workflow/recovery-bot.js +3 -1
  589. package/dist/modules/workflow/recovery-bot.js.map +1 -1
  590. package/dist/modules/workflow/review-workflow/approval-lock.d.ts +60 -0
  591. package/dist/modules/workflow/review-workflow/approval-lock.d.ts.map +1 -0
  592. package/dist/modules/workflow/review-workflow/approval-lock.js +116 -0
  593. package/dist/modules/workflow/review-workflow/approval-lock.js.map +1 -0
  594. package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts +54 -7
  595. package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts.map +1 -1
  596. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +64 -0
  597. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  598. package/dist/modules/workflow/review-workflow/review-workflow.service.js +269 -90
  599. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  600. package/dist/modules/workflow/sanitize-error.d.ts +5 -2
  601. package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
  602. package/dist/modules/workflow/sanitize-error.js +8 -4
  603. package/dist/modules/workflow/sanitize-error.js.map +1 -1
  604. package/dist/modules/workflow/workflow.routes.d.ts +2 -0
  605. package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
  606. package/dist/modules/workflow/workflow.routes.js +259 -29
  607. package/dist/modules/workflow/workflow.routes.js.map +1 -1
  608. package/dist/modules/workflow/workflow.service.d.ts +166 -14
  609. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  610. package/dist/modules/workflow/workflow.service.js +906 -106
  611. package/dist/modules/workflow/workflow.service.js.map +1 -1
  612. package/dist/modules/workspace/empty-dirs.d.ts +15 -0
  613. package/dist/modules/workspace/empty-dirs.d.ts.map +1 -0
  614. package/dist/modules/workspace/empty-dirs.js +90 -0
  615. package/dist/modules/workspace/empty-dirs.js.map +1 -0
  616. package/dist/modules/workspace/file-readers/content-mode.d.ts +50 -0
  617. package/dist/modules/workspace/file-readers/content-mode.d.ts.map +1 -0
  618. package/dist/modules/workspace/file-readers/content-mode.js +55 -0
  619. package/dist/modules/workspace/file-readers/content-mode.js.map +1 -0
  620. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +4 -1
  621. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
  622. package/dist/modules/workspace/file-readers/doc-extract.service.js +4 -1
  623. package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
  624. package/dist/modules/workspace/file-readers/doc-extract.types.d.ts +5 -2
  625. package/dist/modules/workspace/file-readers/doc-extract.types.d.ts.map +1 -1
  626. package/dist/modules/workspace/file-readers/doc-extract.types.js +7 -4
  627. package/dist/modules/workspace/file-readers/doc-extract.types.js.map +1 -1
  628. package/dist/modules/workspace/file-readers/document-reader.d.ts +2 -0
  629. package/dist/modules/workspace/file-readers/document-reader.d.ts.map +1 -1
  630. package/dist/modules/workspace/file-readers/document-reader.js +5 -0
  631. package/dist/modules/workspace/file-readers/document-reader.js.map +1 -1
  632. package/dist/modules/workspace/file-readers/extract-docx.d.ts +6 -0
  633. package/dist/modules/workspace/file-readers/extract-docx.d.ts.map +1 -1
  634. package/dist/modules/workspace/file-readers/extract-docx.js +6 -0
  635. package/dist/modules/workspace/file-readers/extract-docx.js.map +1 -1
  636. package/dist/modules/workspace/file-readers/file-reader.d.ts +24 -5
  637. package/dist/modules/workspace/file-readers/file-reader.d.ts.map +1 -1
  638. package/dist/modules/workspace/file-readers/file-reader.js +11 -6
  639. package/dist/modules/workspace/file-readers/file-reader.js.map +1 -1
  640. package/dist/modules/workspace/file-readers/file-reader.registry.d.ts.map +1 -1
  641. package/dist/modules/workspace/file-readers/file-reader.registry.js +6 -1
  642. package/dist/modules/workspace/file-readers/file-reader.registry.js.map +1 -1
  643. package/dist/modules/workspace/file-readers/image-reader.d.ts +7 -5
  644. package/dist/modules/workspace/file-readers/image-reader.d.ts.map +1 -1
  645. package/dist/modules/workspace/file-readers/image-reader.js +9 -5
  646. package/dist/modules/workspace/file-readers/image-reader.js.map +1 -1
  647. package/dist/modules/workspace/file-readers/ooxml-text.d.ts +3 -0
  648. package/dist/modules/workspace/file-readers/ooxml-text.d.ts.map +1 -1
  649. package/dist/modules/workspace/file-readers/ooxml-text.js +39 -2
  650. package/dist/modules/workspace/file-readers/ooxml-text.js.map +1 -1
  651. package/dist/modules/workspace/file-readers/text-reader.d.ts +29 -1
  652. package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
  653. package/dist/modules/workspace/file-readers/text-reader.js +57 -3
  654. package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
  655. package/dist/modules/workspace/git-internals.middleware.d.ts +28 -0
  656. package/dist/modules/workspace/git-internals.middleware.d.ts.map +1 -0
  657. package/dist/modules/workspace/git-internals.middleware.js +93 -0
  658. package/dist/modules/workspace/git-internals.middleware.js.map +1 -0
  659. package/dist/modules/workspace/not-found.d.ts +80 -0
  660. package/dist/modules/workspace/not-found.d.ts.map +1 -0
  661. package/dist/modules/workspace/not-found.js +112 -0
  662. package/dist/modules/workspace/not-found.js.map +1 -0
  663. package/dist/modules/workspace/session-ontology.gate.d.ts +1 -1
  664. package/dist/modules/workspace/session-ontology.gate.d.ts.map +1 -1
  665. package/dist/modules/workspace/session-ontology.gate.js +1 -1
  666. package/dist/modules/workspace/session-ontology.gate.js.map +1 -1
  667. package/dist/modules/workspace/startup/beside-checkout.d.ts +42 -0
  668. package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -0
  669. package/dist/modules/workspace/startup/beside-checkout.js +110 -0
  670. package/dist/modules/workspace/startup/beside-checkout.js.map +1 -0
  671. package/dist/modules/workspace/startup/kb-git.d.ts +7 -12
  672. package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
  673. package/dist/modules/workspace/startup/kb-git.js +34 -35
  674. package/dist/modules/workspace/startup/kb-git.js.map +1 -1
  675. package/dist/modules/workspace/startup/kb-startup-runner.d.ts +95 -0
  676. package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
  677. package/dist/modules/workspace/startup/kb-startup-runner.js +257 -55
  678. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
  679. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts +45 -0
  680. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts.map +1 -1
  681. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +417 -385
  682. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -1
  683. package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts +48 -0
  684. package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts.map +1 -0
  685. package/dist/modules/workspace/startup/steps/personal-spaces.step.js +183 -0
  686. package/dist/modules/workspace/startup/steps/personal-spaces.step.js.map +1 -0
  687. package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts +51 -0
  688. package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts.map +1 -0
  689. package/dist/modules/workspace/startup/steps/plugin-display-names.step.js +158 -0
  690. package/dist/modules/workspace/startup/steps/plugin-display-names.step.js.map +1 -0
  691. package/dist/modules/workspace/startup/steps/plugin-layout.d.ts +64 -0
  692. package/dist/modules/workspace/startup/steps/plugin-layout.d.ts.map +1 -0
  693. package/dist/modules/workspace/startup/steps/plugin-layout.js +97 -0
  694. package/dist/modules/workspace/startup/steps/plugin-layout.js.map +1 -0
  695. package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts +7 -1
  696. package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts.map +1 -1
  697. package/dist/modules/workspace/startup/steps/plugin-manifests.step.js +45 -81
  698. package/dist/modules/workspace/startup/steps/plugin-manifests.step.js.map +1 -1
  699. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +3 -1
  700. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts.map +1 -1
  701. package/dist/modules/workspace/startup/steps/roles-yaml.step.js +4 -12
  702. package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -1
  703. package/dist/modules/workspace/startup/steps/seed-tree.d.ts +6 -1
  704. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  705. package/dist/modules/workspace/startup/steps/seed-tree.js +154 -78
  706. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  707. package/dist/modules/workspace/startup/steps/template-files.step.d.ts +96 -24
  708. package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
  709. package/dist/modules/workspace/startup/steps/template-files.step.js +446 -138
  710. package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
  711. package/dist/modules/workspace/startup/steps/template-source.d.ts +94 -0
  712. package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -0
  713. package/dist/modules/workspace/startup/steps/template-source.js +160 -0
  714. package/dist/modules/workspace/startup/steps/template-source.js.map +1 -0
  715. package/dist/modules/workspace/workspace.routes.d.ts +14 -2
  716. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  717. package/dist/modules/workspace/workspace.routes.js +649 -238
  718. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  719. package/dist/modules/workspace/workspace.service.d.ts +298 -12
  720. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  721. package/dist/modules/workspace/workspace.service.js +933 -269
  722. package/dist/modules/workspace/workspace.service.js.map +1 -1
  723. package/dist/modules/workspace/workspace.tools.d.ts +42 -1
  724. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  725. package/dist/modules/workspace/workspace.tools.js +1626 -146
  726. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  727. package/dist/modules/workspace/write-denial.d.ts +76 -0
  728. package/dist/modules/workspace/write-denial.d.ts.map +1 -0
  729. package/dist/modules/workspace/write-denial.js +138 -0
  730. package/dist/modules/workspace/write-denial.js.map +1 -0
  731. package/dist/shared/canonical-file-identity.d.ts +64 -0
  732. package/dist/shared/canonical-file-identity.d.ts.map +1 -0
  733. package/dist/shared/canonical-file-identity.js +92 -0
  734. package/dist/shared/canonical-file-identity.js.map +1 -0
  735. package/dist/shared/domain-errors.d.ts +108 -0
  736. package/dist/shared/domain-errors.d.ts.map +1 -1
  737. package/dist/shared/domain-errors.js +137 -0
  738. package/dist/shared/domain-errors.js.map +1 -1
  739. package/dist/shared/email-identity.d.ts +32 -0
  740. package/dist/shared/email-identity.d.ts.map +1 -0
  741. package/dist/shared/email-identity.js +37 -0
  742. package/dist/shared/email-identity.js.map +1 -0
  743. package/dist/shared/fs.contract.d.ts +206 -0
  744. package/dist/shared/fs.contract.d.ts.map +1 -0
  745. package/dist/shared/fs.contract.js +79 -0
  746. package/dist/shared/fs.contract.js.map +1 -0
  747. package/dist/shared/git-failure.d.ts +80 -0
  748. package/dist/shared/git-failure.d.ts.map +1 -0
  749. package/dist/shared/git-failure.js +139 -0
  750. package/dist/shared/git-failure.js.map +1 -0
  751. package/dist/shared/git-internals.d.ts +23 -0
  752. package/dist/shared/git-internals.d.ts.map +1 -0
  753. package/dist/shared/git-internals.js +181 -0
  754. package/dist/shared/git-internals.js.map +1 -0
  755. package/dist/shared/git.contract.d.ts +157 -0
  756. package/dist/shared/git.contract.d.ts.map +1 -0
  757. package/dist/shared/git.contract.js +103 -0
  758. package/dist/shared/git.contract.js.map +1 -0
  759. package/dist/shared/http-errors.d.ts +33 -0
  760. package/dist/shared/http-errors.d.ts.map +1 -0
  761. package/dist/shared/http-errors.js +35 -0
  762. package/dist/shared/http-errors.js.map +1 -0
  763. package/dist/shared/logger.contract.d.ts +63 -0
  764. package/dist/shared/logger.contract.d.ts.map +1 -0
  765. package/dist/shared/logger.contract.js +126 -0
  766. package/dist/shared/logger.contract.js.map +1 -0
  767. package/dist/shared/logging.d.ts +31 -0
  768. package/dist/shared/logging.d.ts.map +1 -0
  769. package/dist/shared/logging.js +92 -0
  770. package/dist/shared/logging.js.map +1 -0
  771. package/dist/shared/path-containment.d.ts +21 -0
  772. package/dist/shared/path-containment.d.ts.map +1 -0
  773. package/dist/shared/path-containment.js +34 -0
  774. package/dist/shared/path-containment.js.map +1 -0
  775. package/dist/shared/pending-proposals.d.ts +71 -0
  776. package/dist/shared/pending-proposals.d.ts.map +1 -0
  777. package/dist/shared/pending-proposals.js +98 -0
  778. package/dist/shared/pending-proposals.js.map +1 -0
  779. package/dist/shared/printable.d.ts +22 -0
  780. package/dist/shared/printable.d.ts.map +1 -0
  781. package/dist/shared/printable.js +65 -0
  782. package/dist/shared/printable.js.map +1 -0
  783. package/dist/shared/redact-secret.d.ts +30 -0
  784. package/dist/shared/redact-secret.d.ts.map +1 -0
  785. package/dist/shared/redact-secret.js +114 -0
  786. package/dist/shared/redact-secret.js.map +1 -0
  787. package/dist/shared/rename-no-replace.d.ts +119 -0
  788. package/dist/shared/rename-no-replace.d.ts.map +1 -0
  789. package/dist/shared/rename-no-replace.js +323 -0
  790. package/dist/shared/rename-no-replace.js.map +1 -0
  791. package/dist/shared/ssrf.d.ts.map +1 -1
  792. package/dist/shared/ssrf.js +49 -18
  793. package/dist/shared/ssrf.js.map +1 -1
  794. package/dist/version.d.ts +10 -6
  795. package/dist/version.d.ts.map +1 -1
  796. package/dist/version.js +10 -6
  797. package/dist/version.js.map +1 -1
  798. package/kb-template/.bevelignore +4 -3
  799. package/kb-template/AGENTS.md +281 -36
  800. package/kb-template/KnowledgeBase/How to get started.md +1 -1
  801. package/kb-template/access.md +5 -3
  802. package/kb-template/mcp-description.md +27 -0
  803. package/migrations/0006_api_tokens_revoked_by.sql +1 -0
  804. package/migrations/0007_github_facade_registered.sql +1 -0
  805. package/migrations/0008_change_request_apply_failure.sql +4 -0
  806. package/migrations/0009_change_request_apply_failure_kind.sql +13 -0
  807. package/migrations/0010_plugin_join_requests.sql +15 -0
  808. package/migrations/0011_plugin_join_request_claim.sql +11 -0
  809. package/migrations/0012_agent_audit.sql +41 -0
  810. package/migrations/0013_agent_identity_and_key_soft_delete.sql +29 -0
  811. package/migrations/meta/0006_snapshot.json +1723 -0
  812. package/migrations/meta/0007_snapshot.json +1729 -0
  813. package/migrations/meta/0008_snapshot.json +1753 -0
  814. package/migrations/meta/0009_snapshot.json +1759 -0
  815. package/migrations/meta/0010_snapshot.json +1872 -0
  816. package/migrations/meta/0011_snapshot.json +1884 -0
  817. package/migrations/meta/0012_snapshot.json +2247 -0
  818. package/migrations/meta/0013_snapshot.json +2259 -0
  819. package/migrations/meta/_journal.json +56 -0
  820. package/package.json +7 -6
  821. package/src/__tests__/core-config.domain.test.ts +67 -2
  822. package/src/__tests__/kb-layout-config.test.ts +186 -1
  823. package/src/__tests__/open-change-gate.ts +15 -0
  824. package/src/core/__tests__/catalog-cache-invalidation.test.ts +83 -3
  825. package/src/core/__tests__/catalog-revision.test.ts +190 -0
  826. package/src/core/__tests__/lifecycle.test.ts +369 -0
  827. package/src/core/__tests__/public-config.test.ts +30 -0
  828. package/src/core/__tests__/readiness.test.ts +109 -0
  829. package/src/core/catalog-cache-invalidation.ts +20 -1
  830. package/src/core/catalog-revision.ts +110 -0
  831. package/src/core/create-core-server.ts +286 -79
  832. package/src/core/create-core-services.ts +315 -81
  833. package/src/core/lifecycle.ts +312 -0
  834. package/src/core/public-config.ts +86 -0
  835. package/src/core/readiness.ts +123 -0
  836. package/src/core-config.ts +70 -4
  837. package/src/index.ts +15 -0
  838. package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +362 -0
  839. package/src/modules/access/__tests__/access-control.atref-batch.test.ts +2 -1
  840. package/src/modules/access/__tests__/access-control.atref-cache.test.ts +46 -22
  841. package/src/modules/access/__tests__/access-control.deny-dependents.test.ts +215 -0
  842. package/src/modules/access/__tests__/access-control.download-read.test.ts +268 -0
  843. package/src/modules/access/__tests__/access-control.everyone-read.test.ts +145 -0
  844. package/src/modules/access/__tests__/access-control.group-read.test.ts +163 -0
  845. package/src/modules/access/__tests__/access-control.invalidate-race.test.ts +2 -0
  846. package/src/modules/access/__tests__/access-control.platform-restore.test.ts +152 -0
  847. package/src/modules/access/__tests__/access-control.prospective.test.ts +189 -0
  848. package/src/modules/access/__tests__/access-control.read-batch.test.ts +2 -1
  849. package/src/modules/access/__tests__/access-control.service.test.ts +123 -78
  850. package/src/modules/access/__tests__/access-groups.test.ts +47 -14
  851. package/src/modules/access/__tests__/access-md-format.test.ts +2 -1
  852. package/src/modules/access/__tests__/access-mutation.service.test.ts +52 -4
  853. package/src/modules/access/__tests__/access-own-read-grant.test.ts +2 -0
  854. package/src/modules/access/__tests__/access-personal-plugin.test.ts +153 -0
  855. package/src/modules/access/__tests__/access-plugin-principals.test.ts +18 -1
  856. package/src/modules/access/__tests__/access-restrict-here.test.ts +450 -0
  857. package/src/modules/access/__tests__/access.routes.batch.test.ts +139 -0
  858. package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +394 -0
  859. package/src/modules/access/__tests__/access.routes.group-grant.test.ts +6 -2
  860. package/src/modules/access/__tests__/access.routes.overrides.test.ts +3 -2
  861. package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +234 -0
  862. package/src/modules/access/__tests__/access.routes.prospective.test.ts +216 -0
  863. package/src/modules/access/__tests__/access.routes.revoke.test.ts +7 -2
  864. package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +360 -0
  865. package/src/modules/access/__tests__/change-read-gate.test.ts +272 -0
  866. package/src/modules/access/__tests__/creator-access.test.ts +81 -83
  867. package/src/modules/access/__tests__/grant-sources.test.ts +3 -2
  868. package/src/modules/access/__tests__/groups-admin.service.test.ts +6 -5
  869. package/src/modules/access/__tests__/roles-admin.security.test.ts +196 -0
  870. package/src/modules/access/__tests__/roles-admin.service.test.ts +127 -2
  871. package/src/modules/access/__tests__/roles-capabilities.test.ts +5 -4
  872. package/src/modules/access/__tests__/roles.routes.test.ts +5 -2
  873. package/src/modules/access/__tests__/user-access-removal.service.test.ts +402 -0
  874. package/src/modules/access/__tests__/users-db-double.ts +63 -0
  875. package/src/modules/access/access-control.interface.ts +251 -11
  876. package/src/modules/access/access-control.service.ts +719 -164
  877. package/src/modules/access/access-mutation.service.ts +182 -13
  878. package/src/modules/access/access-view.ts +316 -0
  879. package/src/modules/access/access.routes.ts +293 -95
  880. package/src/modules/access/admin-locked-commit.ts +12 -14
  881. package/src/modules/access/admin-route-helpers.ts +4 -4
  882. package/src/modules/access/change-read-gate.ts +148 -0
  883. package/src/modules/access/creator-access.ts +89 -124
  884. package/src/modules/access/directory-sync-bot.ts +4 -1
  885. package/src/modules/access/groups-admin.service.ts +3 -3
  886. package/src/modules/access/reference-scan.ts +5 -5
  887. package/src/modules/access/roles-admin.service.ts +94 -15
  888. package/src/modules/access/synced-groups-committer.ts +10 -15
  889. package/src/modules/access/synced-groups-writer.ts +3 -12
  890. package/src/modules/access/user-access-removal.service.ts +711 -0
  891. package/src/modules/access-model/__tests__/access-splice.test.ts +63 -1
  892. package/src/modules/access-model/__tests__/frontmatter-fences.test.ts +71 -0
  893. package/src/modules/access-model/__tests__/roles-yaml-guard.test.ts +131 -0
  894. package/src/modules/access-model/access-errors.ts +56 -15
  895. package/src/modules/access-model/access-grammar.ts +85 -48
  896. package/src/modules/access-model/access-splice.ts +18 -18
  897. package/src/modules/access-model/access-template.ts +44 -0
  898. package/src/modules/access-model/change-gate.ts +80 -0
  899. package/src/modules/access-model/creator.ts +18 -21
  900. package/src/modules/access-model/frontmatter-lines.ts +98 -0
  901. package/src/modules/access-model/plugin-principals.ts +12 -19
  902. package/src/modules/access-model/roles-yaml-guard.ts +182 -4
  903. package/src/modules/admin/admin-access.service.ts +6 -2
  904. package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +100 -0
  905. package/src/modules/agent-instructions/__tests__/compose.test.ts +256 -0
  906. package/src/modules/agent-instructions/__tests__/read-preamble.test.ts +135 -0
  907. package/src/modules/agent-instructions/agent-instructions.routes.ts +41 -0
  908. package/src/modules/agent-instructions/compose.ts +271 -0
  909. package/src/modules/agent-instructions/index.ts +14 -0
  910. package/src/modules/agent-instructions/read-preamble.ts +88 -0
  911. package/src/modules/audit/__tests__/agent-audit.service.test.ts +370 -0
  912. package/src/modules/audit/__tests__/agent-rest-audit.middleware.test.ts +113 -0
  913. package/src/modules/audit/__tests__/audit.routes.test.ts +201 -0
  914. package/src/modules/audit/__tests__/event-classifier.test.ts +121 -0
  915. package/src/modules/audit/agent-audit.service.ts +316 -0
  916. package/src/modules/audit/agent-rest-audit.middleware.ts +83 -0
  917. package/src/modules/audit/audit.contract.ts +164 -0
  918. package/src/modules/audit/audit.routes.ts +168 -0
  919. package/src/modules/audit/event-classifier.ts +155 -0
  920. package/src/modules/audit/index.ts +14 -0
  921. package/src/modules/audit/request-audit.ts +141 -0
  922. package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +209 -0
  923. package/src/modules/auth/__tests__/account.routes.test.ts +178 -2
  924. package/src/modules/auth/__tests__/auth.middleware.test.ts +78 -0
  925. package/src/modules/auth/__tests__/auth.service.test.ts +338 -5
  926. package/src/modules/auth/__tests__/oidc-auth-provider.test.ts +352 -12
  927. package/src/modules/auth/__tests__/onboarding-done.route.test.ts +3 -1
  928. package/src/modules/auth/account-erasure.service.ts +117 -10
  929. package/src/modules/auth/account.routes.ts +115 -14
  930. package/src/modules/auth/auth.routes.ts +20 -7
  931. package/src/modules/auth/auth.service.ts +187 -28
  932. package/src/modules/auth/oidc-auth-provider.ts +120 -22
  933. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +109 -1
  934. package/src/modules/code-mode/code-mode.tool.ts +13 -2
  935. package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +78 -0
  936. package/src/modules/connection-probe/connection-probe.service.ts +59 -7
  937. package/src/modules/database/__tests__/advisory-lease.test.ts +152 -0
  938. package/src/modules/database/__tests__/advisory-lock.test.ts +147 -0
  939. package/src/modules/database/advisory-lock.ts +346 -0
  940. package/src/modules/database/connection.ts +9 -1
  941. package/src/modules/database/core-schema.ts +227 -0
  942. package/src/modules/database/migrate.ts +27 -6
  943. package/src/modules/declared-variables/declared-variables.routes.ts +6 -3
  944. package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +3 -0
  945. package/src/modules/diff/__tests__/diff.service.test.ts +3 -0
  946. package/src/modules/diff/diff.routes.ts +2 -4
  947. package/src/modules/diff/diff.service.ts +80 -79
  948. package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +472 -9
  949. package/src/modules/kb-fs/__tests__/node-fs.test.ts +409 -0
  950. package/src/modules/kb-fs/__tests__/repo-path.test.ts +210 -1
  951. package/src/modules/{workspace → kb-fs}/bevel-ignore.ts +22 -15
  952. package/src/modules/kb-fs/file-change-notifier.ts +4 -1
  953. package/src/modules/kb-fs/git-guarded-filesystem.ts +136 -0
  954. package/src/modules/kb-fs/locking-filesystem.ts +399 -109
  955. package/src/modules/kb-fs/node-fs.ts +165 -0
  956. package/src/modules/kb-fs/read-only-filesystem.ts +18 -9
  957. package/src/modules/kb-fs/repo-path.ts +213 -3
  958. package/src/modules/kb-sync/kb-sync.routes.ts +5 -2
  959. package/src/modules/kb-sync/kb-sync.service.ts +8 -5
  960. package/src/modules/marketplace/__tests__/github-facade.test.ts +134 -2
  961. package/src/modules/marketplace/__tests__/marketplace-git.test.ts +2 -1
  962. package/src/modules/marketplace/__tests__/marketplace-repo.head.test.ts +47 -0
  963. package/src/modules/marketplace/git-http.routes.ts +7 -4
  964. package/src/modules/marketplace/github-facade/github-facade-admin.routes.ts +35 -3
  965. package/src/modules/marketplace/github-facade/github-facade-credentials.service.ts +55 -0
  966. package/src/modules/marketplace/github-facade/github-facade.routes.ts +32 -6
  967. package/src/modules/marketplace/github-facade/github-facade.service.ts +80 -19
  968. package/src/modules/marketplace/github-facade/index.ts +1 -0
  969. package/src/modules/marketplace/marketplace-repo.service.ts +23 -8
  970. package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +183 -13
  971. package/src/modules/mcp/__tests__/downstream-pool.test.ts +278 -0
  972. package/src/modules/mcp/__tests__/downstream-token-refresh.test.ts +178 -0
  973. package/src/modules/mcp/__tests__/fake-downstream-mcp-server.ts +156 -0
  974. package/src/modules/mcp/__tests__/first-call-probe.cli-args.test.ts +178 -0
  975. package/src/modules/mcp/__tests__/first-call-probe.test.ts +414 -0
  976. package/src/modules/mcp/__tests__/manual-failure-memo.test.ts +98 -6
  977. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +68 -7
  978. package/src/modules/mcp/__tests__/mcp-routes-harness.ts +1 -3
  979. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +1055 -59
  980. package/src/modules/mcp/__tests__/mcp.routes.local-token.test.ts +24 -4
  981. package/src/modules/mcp/__tests__/mcp.routes.stateless.test.ts +182 -0
  982. package/src/modules/mcp/__tests__/mcp.service.test.ts +391 -6
  983. package/src/modules/mcp/__tests__/surface-log-throttle.test.ts +169 -0
  984. package/src/modules/mcp/downstream-pool.ts +256 -0
  985. package/src/modules/mcp/downstream-token-refresh.ts +170 -0
  986. package/src/modules/mcp/first-call-probe.cli-args.ts +159 -0
  987. package/src/modules/mcp/first-call-probe.cli.ts +60 -0
  988. package/src/modules/mcp/first-call-probe.ts +418 -0
  989. package/src/modules/mcp/manual-failure-memo.ts +81 -23
  990. package/src/modules/mcp/mcp-auth.middleware.ts +56 -16
  991. package/src/modules/mcp/mcp.routes.ts +386 -482
  992. package/src/modules/mcp/mcp.service.ts +905 -177
  993. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +189 -8
  994. package/src/modules/mcp/oauth/oauth-consent.routes.ts +4 -4
  995. package/src/modules/mcp/surface-log-throttle.ts +120 -0
  996. package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +10 -7
  997. package/src/modules/plugins/__tests__/bundle-dialect.test.ts +165 -0
  998. package/src/modules/plugins/__tests__/compile-marketplace.test.ts +139 -10
  999. package/src/modules/plugins/__tests__/fake-join-request-store.ts +189 -0
  1000. package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +608 -0
  1001. package/src/modules/plugins/__tests__/plugin-creation-display-name.test.ts +241 -0
  1002. package/src/modules/plugins/__tests__/plugin-index.service.test.ts +148 -7
  1003. package/src/modules/plugins/__tests__/plugin-links.service.test.ts +10 -13
  1004. package/src/modules/plugins/__tests__/plugin-names.test.ts +77 -0
  1005. package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +129 -19
  1006. package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +91 -29
  1007. package/src/modules/plugins/__tests__/plugins.routes.test.ts +370 -19
  1008. package/src/modules/plugins/__tests__/plugins.tools.test.ts +131 -0
  1009. package/src/modules/plugins/__tests__/teams.routes.test.ts +248 -0
  1010. package/src/modules/plugins/compile/compile-marketplace.ts +143 -46
  1011. package/src/modules/plugins/compile/marketplace-compiler.service.ts +8 -7
  1012. package/src/modules/plugins/discovery/__tests__/kb-plugin-source.test.ts +56 -20
  1013. package/src/modules/plugins/discovery/bundle-dialect/bundle.source.ts +30 -9
  1014. package/src/modules/plugins/discovery/bundle-dialect/registry.ts +93 -18
  1015. package/src/modules/plugins/discovery/kb-plugin-source.ts +10 -7
  1016. package/src/modules/plugins/discovery/native.source.ts +21 -3
  1017. package/src/modules/plugins/discovery/plugin-source.ts +17 -3
  1018. package/src/modules/plugins/index.ts +16 -3
  1019. package/src/modules/plugins/join-request-jobs.service.ts +675 -0
  1020. package/src/modules/plugins/join-request-records.store.ts +353 -0
  1021. package/src/modules/plugins/join-requests.service.ts +7 -4
  1022. package/src/modules/plugins/plugin-links.service.ts +4 -17
  1023. package/src/modules/plugins/plugin-links.ts +6 -3
  1024. package/src/modules/plugins/plugin-provision.service.ts +209 -37
  1025. package/src/modules/plugins/plugin-rename.service.ts +21 -9
  1026. package/src/modules/plugins/plugins.contract.ts +52 -4
  1027. package/src/modules/plugins/plugins.routes.ts +204 -124
  1028. package/src/modules/plugins/plugins.service.ts +92 -18
  1029. package/src/modules/plugins/plugins.tools.ts +88 -0
  1030. package/src/modules/plugins/teams.routes.ts +129 -0
  1031. package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +359 -15
  1032. package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +248 -0
  1033. package/src/modules/secrets-vault/__tests__/namespace-secrets.test.ts +202 -0
  1034. package/src/modules/secrets-vault/db-secrets-vault.service.ts +217 -35
  1035. package/src/modules/secrets-vault/index.ts +1 -0
  1036. package/src/modules/secrets-vault/mcp-oauth-discovery.service.ts +6 -3
  1037. package/src/modules/secrets-vault/secrets-variable-loader.ts +4 -4
  1038. package/src/modules/secrets-vault/secrets-vault.contract.ts +93 -0
  1039. package/src/modules/secrets-vault/secrets-vault.routes.ts +99 -38
  1040. package/src/modules/settings/__tests__/connection-check.test.ts +233 -0
  1041. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +223 -6
  1042. package/src/modules/settings/__tests__/git-root-folders.test.ts +247 -0
  1043. package/src/modules/settings/__tests__/oidc-check.test.ts +185 -0
  1044. package/src/modules/settings/__tests__/setup.routes.boot-failure.test.ts +119 -0
  1045. package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +170 -0
  1046. package/src/modules/settings/__tests__/setup.routes.oidc.test.ts +555 -0
  1047. package/src/modules/settings/__tests__/setup.routes.test.ts +667 -16
  1048. package/src/modules/settings/connection-check.ts +351 -0
  1049. package/src/modules/settings/deployment-settings.service.ts +444 -59
  1050. package/src/modules/settings/git-root-folders.ts +171 -0
  1051. package/src/modules/settings/oidc-check.ts +229 -0
  1052. package/src/modules/settings/setup.routes.ts +456 -110
  1053. package/src/modules/skills/__tests__/allowed-tools-check.test.ts +224 -0
  1054. package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +210 -0
  1055. package/src/modules/skills/__tests__/pending-skills.service.test.ts +3 -3
  1056. package/src/modules/skills/__tests__/skills.routes.warnings.test.ts +91 -0
  1057. package/src/modules/skills/__tests__/skills.service.test.ts +223 -2
  1058. package/src/modules/skills/allowed-tools-check.ts +352 -0
  1059. package/src/modules/skills/index.ts +4 -0
  1060. package/src/modules/skills/pending-skills.service.ts +21 -82
  1061. package/src/modules/skills/skill-access-requests.routes.ts +10 -6
  1062. package/src/modules/skills/skills.contract.ts +43 -11
  1063. package/src/modules/skills/skills.routes.ts +16 -1
  1064. package/src/modules/skills/skills.service.ts +214 -84
  1065. package/src/modules/skills/skills.tools.ts +43 -5
  1066. package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +192 -0
  1067. package/src/modules/tool-auth/__tests__/connection-keys-admin.routes.test.ts +116 -0
  1068. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +153 -8
  1069. package/src/modules/tool-auth/__tests__/internal-token.service.test.ts +6 -0
  1070. package/src/modules/tool-auth/connection-key-rejection.ts +25 -0
  1071. package/src/modules/tool-auth/connection-keys-admin.routes.ts +76 -0
  1072. package/src/modules/tool-auth/external-api-key.interface.ts +78 -8
  1073. package/src/modules/tool-auth/external-api-key.service.ts +98 -33
  1074. package/src/modules/tool-auth/internal-token.service.ts +16 -2
  1075. package/src/modules/tool-auth/key-or-session.middleware.ts +45 -0
  1076. package/src/modules/tool-auth/tool-auth.middleware.ts +33 -8
  1077. package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +228 -0
  1078. package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +33 -4
  1079. package/src/modules/tool-helpers/tool-context.ts +46 -7
  1080. package/src/modules/tool-helpers/tool-handler.ts +23 -5
  1081. package/src/modules/tool-helpers/tool.contract.ts +5 -0
  1082. package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +2 -1
  1083. package/src/modules/tool-manuals/__tests__/pending-tools.service.test.ts +411 -0
  1084. package/src/modules/tool-manuals/__tests__/tool-delete.route.test.ts +133 -0
  1085. package/src/modules/tool-manuals/__tests__/tool-delete.service.test.ts +469 -0
  1086. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +21 -2
  1087. package/src/modules/tool-manuals/__tests__/tool-manuals.cli.test.ts +9 -4
  1088. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +6 -1
  1089. package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.routes.test.ts +254 -0
  1090. package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.test.ts +577 -0
  1091. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +6 -1
  1092. package/src/modules/tool-manuals/__tests__/tool-manuals.pending.route.test.ts +102 -0
  1093. package/src/modules/tool-manuals/__tests__/tool-manuals.restart.test.ts +285 -0
  1094. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +178 -2
  1095. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +114 -2
  1096. package/src/modules/tool-manuals/index.ts +3 -0
  1097. package/src/modules/tool-manuals/mcp-json-discovery.ts +13 -10
  1098. package/src/modules/tool-manuals/mcp-server-edit.service.ts +11 -14
  1099. package/src/modules/tool-manuals/pending-tools.service.ts +179 -0
  1100. package/src/modules/tool-manuals/tool-delete.service.ts +401 -0
  1101. package/src/modules/tool-manuals/tool-manuals.contract.ts +165 -0
  1102. package/src/modules/tool-manuals/tool-manuals.routes.ts +138 -54
  1103. package/src/modules/tool-manuals/tool-manuals.service.ts +432 -87
  1104. package/src/modules/tool-manuals/tool-manuals.tools.ts +81 -6
  1105. package/src/modules/workflow/__tests__/apply-failure.test.ts +1038 -0
  1106. package/src/modules/workflow/__tests__/event-bus.test.ts +61 -9
  1107. package/src/modules/workflow/__tests__/fake-file-lock-db.ts +199 -0
  1108. package/src/modules/workflow/__tests__/file-lock.service.path-identity.test.ts +237 -0
  1109. package/src/modules/workflow/__tests__/format-affected-owners.test.ts +18 -0
  1110. package/src/modules/workflow/__tests__/merge-announces-tree.test.ts +220 -0
  1111. package/src/modules/workflow/__tests__/pending-commits.worker.concurrency.test.ts +187 -0
  1112. package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +2 -0
  1113. package/src/modules/workflow/__tests__/sanitize-error.test.ts +10 -0
  1114. package/src/modules/workflow/__tests__/workflow.routes.folder-proposals.test.ts +128 -0
  1115. package/src/modules/workflow/__tests__/workflow.routes.fork-point-file.test.ts +149 -0
  1116. package/src/modules/workflow/__tests__/workflow.routes.lock-path.test.ts +305 -0
  1117. package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +2 -0
  1118. package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +2 -0
  1119. package/src/modules/workflow/__tests__/workflow.service.deleted-branch-sweep.test.ts +2 -0
  1120. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +294 -25
  1121. package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +564 -0
  1122. package/src/modules/workflow/__tests__/workflow.service.platform-restore.test.ts +135 -0
  1123. package/src/modules/workflow/__tests__/workflow.service.read-gate.test.ts +172 -0
  1124. package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +14 -0
  1125. package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +5 -0
  1126. package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +281 -0
  1127. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +158 -7
  1128. package/src/modules/workflow/agent-tools/workflow.tools.ts +93 -64
  1129. package/src/modules/workflow/event-bus.ts +26 -28
  1130. package/src/modules/workflow/events.routes.ts +56 -21
  1131. package/src/modules/workflow/file-lock.service.ts +27 -4
  1132. package/src/modules/workflow/git/__tests__/change-request-link.test.ts +43 -0
  1133. package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +1 -0
  1134. package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +238 -1
  1135. package/src/modules/workflow/git/__tests__/git.service.forkPoint.test.ts +245 -0
  1136. package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +35 -0
  1137. package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +149 -0
  1138. package/src/modules/workflow/git/__tests__/git.service.revertPathsAndPush.test.ts +177 -0
  1139. package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +114 -0
  1140. package/src/modules/workflow/git/__tests__/pull-request.service.getPrDetail.test.ts +69 -2
  1141. package/src/modules/workflow/git/__tests__/pull-request.service.list-fetch.test.ts +121 -0
  1142. package/src/modules/workflow/git/__tests__/pull-request.service.placeholder.test.ts +79 -0
  1143. package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
  1144. package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +1 -1
  1145. package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-update.test.ts +111 -0
  1146. package/src/modules/workflow/git/change-request-link.ts +35 -0
  1147. package/src/modules/workflow/git/git-version.ts +5 -2
  1148. package/src/modules/workflow/git/git.service.ts +684 -144
  1149. package/src/modules/workflow/git/node-git-runner.ts +315 -0
  1150. package/src/modules/workflow/git/pull-request.service.ts +179 -27
  1151. package/src/modules/workflow/pending-commits.service.ts +26 -7
  1152. package/src/modules/workflow/pending-commits.worker.ts +114 -69
  1153. package/src/modules/workflow/recovery-bot.ts +4 -1
  1154. package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +180 -1
  1155. package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
  1156. package/src/modules/workflow/review-workflow/__tests__/carry-approvals-forward.test.ts +520 -0
  1157. package/src/modules/workflow/review-workflow/__tests__/erase-approver.test.ts +84 -0
  1158. package/src/modules/workflow/review-workflow/__tests__/merge-gate.test.ts +89 -77
  1159. package/src/modules/workflow/review-workflow/approval-lock.ts +122 -0
  1160. package/src/modules/workflow/review-workflow/review-workflow.interface.ts +59 -7
  1161. package/src/modules/workflow/review-workflow/review-workflow.service.ts +309 -105
  1162. package/src/modules/workflow/sanitize-error.ts +9 -4
  1163. package/src/modules/workflow/workflow.routes.ts +273 -29
  1164. package/src/modules/workflow/workflow.service.ts +1087 -171
  1165. package/src/modules/workspace/__tests__/empty-dirs.test.ts +44 -0
  1166. package/src/modules/workspace/__tests__/file-stat-access.test.ts +301 -0
  1167. package/src/modules/workspace/__tests__/git-internals.security.test.ts +883 -0
  1168. package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +464 -0
  1169. package/src/modules/workspace/__tests__/workspace.routes.branch-errors.test.ts +139 -0
  1170. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +189 -37
  1171. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +51 -11
  1172. package/src/modules/workspace/__tests__/workspace.routes.download-implies-read.test.ts +161 -0
  1173. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +109 -10
  1174. package/src/modules/workspace/__tests__/workspace.routes.file-writes.test.ts +624 -0
  1175. package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +409 -0
  1176. package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +327 -0
  1177. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +25 -6
  1178. package/src/modules/workspace/__tests__/workspace.service.ensure-remotes-fetched.test.ts +109 -0
  1179. package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +6 -5
  1180. package/src/modules/workspace/__tests__/workspace.service.move-never-overwrites.test.ts +218 -0
  1181. package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +56 -1
  1182. package/src/modules/workspace/__tests__/workspace.service.test.ts +811 -42
  1183. package/src/modules/workspace/__tests__/workspace.service.unknown-branch.test.ts +197 -0
  1184. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +128 -0
  1185. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +212 -0
  1186. package/src/modules/workspace/__tests__/workspace.tools.test.ts +2632 -110
  1187. package/src/modules/workspace/__tests__/write-denial.test.ts +75 -0
  1188. package/src/modules/workspace/empty-dirs.ts +79 -0
  1189. package/src/modules/workspace/file-readers/README.md +32 -0
  1190. package/src/modules/workspace/file-readers/__tests__/doc-extract.test.ts +59 -2
  1191. package/src/modules/workspace/file-readers/__tests__/file-reader.registry.test.ts +133 -4
  1192. package/src/modules/workspace/file-readers/__tests__/fixtures/soft-break.docx +0 -0
  1193. package/src/modules/workspace/file-readers/__tests__/fixtures/tab.docx +0 -0
  1194. package/src/modules/workspace/file-readers/content-mode.ts +87 -0
  1195. package/src/modules/workspace/file-readers/doc-extract.service.ts +4 -1
  1196. package/src/modules/workspace/file-readers/doc-extract.types.ts +8 -4
  1197. package/src/modules/workspace/file-readers/document-reader.ts +6 -0
  1198. package/src/modules/workspace/file-readers/extract-docx.ts +6 -0
  1199. package/src/modules/workspace/file-readers/file-reader.registry.ts +9 -1
  1200. package/src/modules/workspace/file-readers/file-reader.ts +27 -6
  1201. package/src/modules/workspace/file-readers/image-reader.ts +10 -5
  1202. package/src/modules/workspace/file-readers/ooxml-text.ts +38 -2
  1203. package/src/modules/workspace/file-readers/text-reader.ts +61 -4
  1204. package/src/modules/workspace/git-internals.middleware.ts +86 -0
  1205. package/src/modules/workspace/not-found.ts +119 -0
  1206. package/src/modules/workspace/session-ontology.gate.ts +1 -1
  1207. package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +181 -0
  1208. package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +361 -0
  1209. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +53 -16
  1210. package/src/modules/workspace/startup/beside-checkout.ts +112 -0
  1211. package/src/modules/workspace/startup/kb-git.ts +43 -36
  1212. package/src/modules/workspace/startup/kb-startup-runner.ts +303 -63
  1213. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +1215 -73
  1214. package/src/modules/workspace/startup/steps/groups-to-plugins.step.ts +460 -417
  1215. package/src/modules/workspace/startup/steps/personal-spaces.step.ts +191 -0
  1216. package/src/modules/workspace/startup/steps/plugin-display-names.step.ts +169 -0
  1217. package/src/modules/workspace/startup/steps/plugin-layout.ts +110 -0
  1218. package/src/modules/workspace/startup/steps/plugin-manifests.step.ts +46 -85
  1219. package/src/modules/workspace/startup/steps/roles-yaml.step.ts +6 -12
  1220. package/src/modules/workspace/startup/steps/seed-tree.ts +159 -78
  1221. package/src/modules/workspace/startup/steps/template-files.step.ts +492 -141
  1222. package/src/modules/workspace/startup/steps/template-source.ts +168 -0
  1223. package/src/modules/workspace/workspace.routes.ts +687 -257
  1224. package/src/modules/workspace/workspace.service.ts +1000 -303
  1225. package/src/modules/workspace/workspace.tools.ts +1823 -180
  1226. package/src/modules/workspace/write-denial.ts +189 -0
  1227. package/src/shared/__tests__/canonical-file-identity.test.ts +154 -0
  1228. package/src/shared/__tests__/email-identity.test.ts +35 -0
  1229. package/src/shared/__tests__/filename.test.ts +0 -0
  1230. package/src/shared/__tests__/frontmatter-id.test.ts +5 -0
  1231. package/src/shared/__tests__/git-failure.test.ts +198 -0
  1232. package/src/shared/__tests__/git-internals.test.ts +156 -0
  1233. package/src/shared/__tests__/http-errors.test.ts +42 -0
  1234. package/src/shared/__tests__/logging.test.ts +185 -0
  1235. package/src/shared/__tests__/path-containment.test.ts +60 -0
  1236. package/src/shared/__tests__/pending-proposals.readAt.test.ts +102 -0
  1237. package/src/shared/__tests__/printable.test.ts +64 -0
  1238. package/src/shared/__tests__/redact-secret.test.ts +122 -0
  1239. package/src/shared/__tests__/rename-no-replace.test.ts +466 -0
  1240. package/src/shared/__tests__/ssrf.test.ts +20 -0
  1241. package/src/shared/canonical-file-identity.ts +92 -0
  1242. package/src/shared/domain-errors.ts +149 -0
  1243. package/src/shared/email-identity.ts +38 -0
  1244. package/src/shared/fs.contract.ts +225 -0
  1245. package/src/shared/git-failure.ts +196 -0
  1246. package/src/shared/git-internals.ts +179 -0
  1247. package/src/shared/git.contract.ts +170 -0
  1248. package/src/shared/http-errors.ts +36 -0
  1249. package/src/shared/logger.contract.ts +141 -0
  1250. package/src/shared/logging.ts +96 -0
  1251. package/src/shared/path-containment.ts +34 -0
  1252. package/src/shared/pending-proposals.ts +161 -0
  1253. package/src/shared/printable.ts +68 -0
  1254. package/src/shared/redact-secret.ts +118 -0
  1255. package/src/shared/rename-no-replace.ts +386 -0
  1256. package/src/shared/ssrf.ts +44 -16
  1257. package/src/version.ts +10 -6
  1258. package/dist/modules/access/access-errors.d.ts +0 -34
  1259. package/dist/modules/access/access-errors.d.ts.map +0 -1
  1260. package/dist/modules/access/access-errors.js +0 -40
  1261. package/dist/modules/access/access-errors.js.map +0 -1
  1262. package/dist/modules/access/access-splice.d.ts +0 -140
  1263. package/dist/modules/access/access-splice.d.ts.map +0 -1
  1264. package/dist/modules/access/access-splice.js +0 -389
  1265. package/dist/modules/access/access-splice.js.map +0 -1
  1266. package/dist/modules/access/group-files.d.ts +0 -83
  1267. package/dist/modules/access/group-files.d.ts.map +0 -1
  1268. package/dist/modules/access/group-files.js +0 -167
  1269. package/dist/modules/access/group-files.js.map +0 -1
  1270. package/dist/modules/access/kb-read-filter.d.ts +0 -41
  1271. package/dist/modules/access/kb-read-filter.d.ts.map +0 -1
  1272. package/dist/modules/access/kb-read-filter.js +0 -60
  1273. package/dist/modules/access/kb-read-filter.js.map +0 -1
  1274. package/dist/modules/access/render-roles-yaml.d.ts +0 -22
  1275. package/dist/modules/access/render-roles-yaml.d.ts.map +0 -1
  1276. package/dist/modules/access/render-roles-yaml.js +0 -56
  1277. package/dist/modules/access/render-roles-yaml.js.map +0 -1
  1278. package/dist/modules/access/roles-yaml-guard.d.ts +0 -56
  1279. package/dist/modules/access/roles-yaml-guard.d.ts.map +0 -1
  1280. package/dist/modules/access/roles-yaml-guard.js +0 -79
  1281. package/dist/modules/access/roles-yaml-guard.js.map +0 -1
  1282. package/dist/modules/diff/diff-paths.d.ts +0 -2
  1283. package/dist/modules/diff/diff-paths.d.ts.map +0 -1
  1284. package/dist/modules/diff/diff-paths.js +0 -9
  1285. package/dist/modules/diff/diff-paths.js.map +0 -1
  1286. package/dist/modules/groups/group-provision.service.d.ts +0 -130
  1287. package/dist/modules/groups/group-provision.service.d.ts.map +0 -1
  1288. package/dist/modules/groups/group-provision.service.js +0 -288
  1289. package/dist/modules/groups/group-provision.service.js.map +0 -1
  1290. package/dist/modules/groups/groups.contract.d.ts +0 -106
  1291. package/dist/modules/groups/groups.contract.d.ts.map +0 -1
  1292. package/dist/modules/groups/groups.contract.js +0 -36
  1293. package/dist/modules/groups/groups.contract.js.map +0 -1
  1294. package/dist/modules/groups/groups.routes.d.ts +0 -42
  1295. package/dist/modules/groups/groups.routes.d.ts.map +0 -1
  1296. package/dist/modules/groups/groups.routes.js +0 -379
  1297. package/dist/modules/groups/groups.routes.js.map +0 -1
  1298. package/dist/modules/groups/groups.service.d.ts +0 -60
  1299. package/dist/modules/groups/groups.service.d.ts.map +0 -1
  1300. package/dist/modules/groups/groups.service.js +0 -172
  1301. package/dist/modules/groups/groups.service.js.map +0 -1
  1302. package/dist/modules/groups/index.d.ts +0 -7
  1303. package/dist/modules/groups/index.d.ts.map +0 -1
  1304. package/dist/modules/groups/index.js +0 -6
  1305. package/dist/modules/groups/index.js.map +0 -1
  1306. package/dist/modules/groups/join-proposals.d.ts +0 -53
  1307. package/dist/modules/groups/join-proposals.d.ts.map +0 -1
  1308. package/dist/modules/groups/join-proposals.js +0 -67
  1309. package/dist/modules/groups/join-proposals.js.map +0 -1
  1310. package/dist/modules/groups/join-requests.service.d.ts +0 -81
  1311. package/dist/modules/groups/join-requests.service.d.ts.map +0 -1
  1312. package/dist/modules/groups/join-requests.service.js +0 -135
  1313. package/dist/modules/groups/join-requests.service.js.map +0 -1
  1314. package/dist/modules/mcp/mcp-session-store.d.ts +0 -74
  1315. package/dist/modules/mcp/mcp-session-store.d.ts.map +0 -1
  1316. package/dist/modules/mcp/mcp-session-store.js +0 -131
  1317. package/dist/modules/mcp/mcp-session-store.js.map +0 -1
  1318. package/dist/modules/workflow/file-change-notifier.d.ts +0 -38
  1319. package/dist/modules/workflow/file-change-notifier.d.ts.map +0 -1
  1320. package/dist/modules/workflow/file-change-notifier.js +0 -22
  1321. package/dist/modules/workflow/file-change-notifier.js.map +0 -1
  1322. package/dist/modules/workflow/git/branch-name.d.ts +0 -10
  1323. package/dist/modules/workflow/git/branch-name.d.ts.map +0 -1
  1324. package/dist/modules/workflow/git/branch-name.js +0 -76
  1325. package/dist/modules/workflow/git/branch-name.js.map +0 -1
  1326. package/dist/modules/workflow/git/clone-config.d.ts +0 -61
  1327. package/dist/modules/workflow/git/clone-config.d.ts.map +0 -1
  1328. package/dist/modules/workflow/git/clone-config.js +0 -69
  1329. package/dist/modules/workflow/git/clone-config.js.map +0 -1
  1330. package/dist/modules/workflow/git/mutex.d.ts +0 -11
  1331. package/dist/modules/workflow/git/mutex.d.ts.map +0 -1
  1332. package/dist/modules/workflow/git/mutex.js +0 -23
  1333. package/dist/modules/workflow/git/mutex.js.map +0 -1
  1334. package/dist/modules/workflow/locking-filesystem.d.ts +0 -137
  1335. package/dist/modules/workflow/locking-filesystem.d.ts.map +0 -1
  1336. package/dist/modules/workflow/locking-filesystem.js +0 -553
  1337. package/dist/modules/workflow/locking-filesystem.js.map +0 -1
  1338. package/dist/modules/workflow/read-only-filesystem.d.ts +0 -24
  1339. package/dist/modules/workflow/read-only-filesystem.d.ts.map +0 -1
  1340. package/dist/modules/workflow/read-only-filesystem.js +0 -39
  1341. package/dist/modules/workflow/read-only-filesystem.js.map +0 -1
  1342. package/dist/modules/workflow/workflow.errors.d.ts +0 -197
  1343. package/dist/modules/workflow/workflow.errors.d.ts.map +0 -1
  1344. package/dist/modules/workflow/workflow.errors.js +0 -298
  1345. package/dist/modules/workflow/workflow.errors.js.map +0 -1
  1346. package/dist/modules/workspace/bevel-ignore.d.ts.map +0 -1
  1347. package/dist/modules/workspace/bevel-ignore.js.map +0 -1
  1348. package/dist/modules/workspace/kb-seed.interface.d.ts +0 -36
  1349. package/dist/modules/workspace/kb-seed.interface.d.ts.map +0 -1
  1350. package/dist/modules/workspace/kb-seed.interface.js +0 -2
  1351. package/dist/modules/workspace/kb-seed.interface.js.map +0 -1
  1352. package/dist/modules/workspace/kb-seed.service.d.ts +0 -125
  1353. package/dist/modules/workspace/kb-seed.service.d.ts.map +0 -1
  1354. package/dist/modules/workspace/kb-seed.service.js +0 -535
  1355. package/dist/modules/workspace/kb-seed.service.js.map +0 -1
  1356. package/dist/modules/workspace/plugins-migration.d.ts +0 -50
  1357. package/dist/modules/workspace/plugins-migration.d.ts.map +0 -1
  1358. package/dist/modules/workspace/plugins-migration.js +0 -379
  1359. package/dist/modules/workspace/plugins-migration.js.map +0 -1
  1360. package/dist/shared/fs-errors.d.ts +0 -8
  1361. package/dist/shared/fs-errors.d.ts.map +0 -1
  1362. package/dist/shared/fs-errors.js +0 -11
  1363. package/dist/shared/fs-errors.js.map +0 -1
  1364. package/dist/shared/fs-walk.d.ts +0 -23
  1365. package/dist/shared/fs-walk.d.ts.map +0 -1
  1366. package/dist/shared/fs-walk.js +0 -42
  1367. package/dist/shared/fs-walk.js.map +0 -1
  1368. package/dist/shared/hash-email.d.ts +0 -11
  1369. package/dist/shared/hash-email.d.ts.map +0 -1
  1370. package/dist/shared/hash-email.js +0 -14
  1371. package/dist/shared/hash-email.js.map +0 -1
  1372. package/dist/shared/kb-walk.d.ts +0 -52
  1373. package/dist/shared/kb-walk.d.ts.map +0 -1
  1374. package/dist/shared/kb-walk.js +0 -76
  1375. package/dist/shared/kb-walk.js.map +0 -1
  1376. package/src/modules/diff/diff-paths.ts +0 -9
  1377. package/src/modules/mcp/__tests__/mcp-session-store.test.ts +0 -151
  1378. package/src/modules/mcp/__tests__/mcp.routes.session.test.ts +0 -226
  1379. package/src/modules/mcp/mcp-session-store.ts +0 -162
  1380. package/src/shared/__tests__/fs-walk.test.ts +0 -63
  1381. package/src/shared/__tests__/kb-walk.test.ts +0 -107
  1382. package/src/shared/fs-errors.ts +0 -10
  1383. package/src/shared/fs-walk.ts +0 -45
  1384. package/src/shared/hash-email.ts +0 -14
  1385. package/src/shared/kb-walk.ts +0 -95
@@ -1,15 +1,45 @@
1
1
  import fs from 'node:fs/promises';
2
+ import { logger } from '../../shared/logging.js';
3
+ const log = logger('workspace.routes');
2
4
  import path from 'node:path';
3
- import { IGNORE_FILENAME } from './bevel-ignore.js';
5
+ import { IGNORE_FILENAME, isAbsence } from '../../shared/fs.contract.js';
6
+ import { printable } from '../../shared/printable.js';
4
7
  import express from 'express';
5
- import { DEFAULT_BRANCH, KNOWLEDGE_DIR, reservedRootDirNames } from '@bevel-software/platform-shared';
8
+ import { DEFAULT_BRANCH, KNOWLEDGE_DIR, canonicalRelativePath, folderPlaceholderPath, isPlatformFile, isPlatformRestoreShape, platformFileCreationRefusal, platformFileRefusal, reservedRootDirNames, } from '@bevel-software/platform-shared';
6
9
  import { FolderTooLargeError } from './workspace.service.js';
7
10
  import { branchForWorkspaceId } from '../../shared/workspace-id.js';
11
+ import { EntryExistsError } from './workspace.service.js';
8
12
  import { canReadWorkspacePath, resolveReadableMap, toKbRelative } from '../access-model/kb-read-filter.js';
9
13
  import { isRolesYamlPath, assertRolesYamlParsable } from '../access-model/roles-yaml-guard.js';
10
- import { WorkflowDomainError } from '../../shared/domain-errors.js';
14
+ import { PathTraversalError, WorkflowDomainError } from '../../shared/domain-errors.js';
15
+ import { domainErrorBody } from '../../shared/http-errors.js';
16
+ import { assertWithinDirectory } from '../../shared/path-containment.js';
17
+ import { normalizeWorkspacePath } from '../kb-fs/repo-path.js';
18
+ import { hasGitInternalsSegment } from '../../shared/git-internals.js';
19
+ import { createGitInternalsRouteGuard } from './git-internals.middleware.js';
20
+ import { removeEmptyDirs } from './empty-dirs.js';
11
21
  import '../auth/auth.middleware.js'; // Express Request augmentation
12
22
  const MAX_UPLOAD_BYTES = 50 * 1024 * 1024; // 50 MB
23
+ /**
24
+ * One file identity from one request field, or `null` when the caller sent
25
+ * nothing usable.
26
+ *
27
+ * TYPED, not cast: a repeated `?path=a&path=b` arrives as an array and a JSON
28
+ * body can hold anything, so `as string` would hand a non-string to the
29
+ * canonicaliser and throw a 500 on what is a client mistake.
30
+ *
31
+ * CANONICAL, because the mutating verbs on this surface coordinate on the
32
+ * path: `PUT` takes an in-process write turn on it, all of them take the
33
+ * workflow lock row keyed by it, and the bytes live at it. A save spelled
34
+ * `x/a.md` and a delete spelled `./x//a.md` would otherwise take two
35
+ * different lock rows for one file and interleave. See
36
+ * `canonicalRelativePath` for what it refuses to touch.
37
+ */
38
+ function requestPath(value) {
39
+ if (typeof value !== 'string' || value.length === 0)
40
+ return null;
41
+ return canonicalRelativePath(value);
42
+ }
13
43
  /**
14
44
  * Workspaces are per-branch (PLAN §3). Any authenticated user can access
15
45
  * any branch's workspace; coordination of concurrent edits lives in the
@@ -25,8 +55,61 @@ const MAX_UPLOAD_BYTES = 50 * 1024 * 1024; // 50 MB
25
55
  * exactly like the lock-aware filesystem the agent writes through. There
26
56
  * is no separate "share" step; save and share are fused.
27
57
  */
28
- export function createWorkspaceRoutes(workspaceService, authService, workflowService, eventBus, accessControl, kbDirName, creatorAccess, adminAccess) {
58
+ export function createWorkspaceRoutes(workspaceService, authService, workflowService, eventBus, accessControl, kbDirName, creatorAccess, adminAccess, disk,
59
+ /** Save-time skill check: `PUT /file` on a SKILL.md answers with `warnings` (advisory, never a refusal). */
60
+ skillSaveCheck,
61
+ /**
62
+ * Read-before-write, asked ahead of the lock where a route puts bytes on
63
+ * disk BEFORE it locks them (archive extraction). Every other route's writes
64
+ * meet the same gate inside `acquireLock`. Optional so route harnesses that
65
+ * exercise other behaviour need not wire it.
66
+ */
67
+ changeGate) {
29
68
  const router = express.Router();
69
+ const gitInternalsRouteGuard = createGitInternalsRouteGuard(workspaceService);
70
+ // The git folder is refused ahead of every route here by
71
+ // `createGitInternalsRouteGuard`, which the app mounts once for the whole
72
+ // `/workspace/:id` prefix (see `git-internals.middleware.ts`). Mounted here
73
+ // too so this router carries the rule wherever it is mounted on its own.
74
+ router.use("/workspace/:id", gitInternalsRouteGuard);
75
+ /**
76
+ * The REPOSITORY path a request names, or `null` once the refusal has been
77
+ * sent.
78
+ *
79
+ * Every handler on this surface calls this FIRST, before the access check and
80
+ * before the service, so one path is what is checked and what is written.
81
+ * That was the hole: the access helpers stripped `<kbDirName>/` when it was
82
+ * there and otherwise took the path as written, so an unprefixed
83
+ * `KnowledgeBase/Reports` was judged as a repository path and then created
84
+ * beside the repository — a folder, eight documents and a `Plugins/` tree on
85
+ * core-staging, none of it ever committed. The normaliser closes it by making
86
+ * the unprefixed spelling MEAN the repository path it was judged as.
87
+ */
88
+ function inRepo(res, wsPath) {
89
+ try {
90
+ return normalizeWorkspacePath(wsPath, kbDirName);
91
+ }
92
+ catch (err) {
93
+ sendError(res, err);
94
+ return null;
95
+ }
96
+ }
97
+ /**
98
+ * A repository path's absolute location, checked against the repository root.
99
+ *
100
+ * The ONE place this router resolves a workspace path — the delete fast-path,
101
+ * the platform-restore look and the folder-keep all used to call
102
+ * `path.resolve(workspaceDir, …)` for themselves, against the WORKSPACE dir,
103
+ * which is the containment an unprefixed path satisfied on its way to being
104
+ * written beside the checkout. A drift-guard test fails if a second one
105
+ * appears.
106
+ */
107
+ async function absoluteInRepo(workspaceId, repoRelative) {
108
+ const workspaceDir = await workspaceService.getWorkspacePath(workspaceId);
109
+ const absolute = path.resolve(workspaceDir, repoRelative);
110
+ assertWithinDirectory(absolute, path.join(workspaceDir, kbDirName));
111
+ return absolute;
112
+ }
30
113
  function authenticated(req, res) {
31
114
  if (!req.userId) {
32
115
  res.status(401).json({ error: 'Unauthenticated' });
@@ -59,11 +142,47 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
59
142
  return user;
60
143
  }
61
144
  catch (err) {
62
- console.error('[workspace.routes] requireUser failed:', err);
145
+ log.error('requireUser failed:', { err });
63
146
  res.status(500).json({ error: 'Internal server error' });
64
147
  return null;
65
148
  }
66
149
  }
150
+ /**
151
+ * Whether a failed op is one that provably touched no bytes, so its lock
152
+ * may be released with the disk and the commit queue left exactly as they
153
+ * are (`releaseLockUntouched`) rather than reset to HEAD.
154
+ *
155
+ * This is not tidiness. A release that discards resets the PATH, not "this
156
+ * request's changes" — git has no notion of the latter — so when the path
157
+ * holds a landed save whose commit is still queued (commits run out of band
158
+ * in the pending-commits worker), the discard destroys that save. Two moves
159
+ * racing onto the same free name are exactly that situation: the winner
160
+ * lands and enqueues, the loser then takes the same destination lock, is
161
+ * refused because the name is now taken, and its unwind would throw the
162
+ * winner's file away. One refusal, two lost files.
163
+ *
164
+ * A destination-taken refusal qualifies because nothing it can do writes:
165
+ * the preflight throws before the move is attempted, and the move's own
166
+ * no-clobber calls (`link`, `mkdir`, `open` with `O_EXCL` — see
167
+ * `shared/rename-no-replace.ts`) fail without creating anything, with the
168
+ * folder claim rolling itself back. `LockingFilesystem.withLock` draws the
169
+ * same line for its `CheckRefusal`, in the same words: a refusal that wrote
170
+ * nothing releases untouched.
171
+ *
172
+ * Deliberately a closed list of refusal TYPES rather than a guess at what
173
+ * an op did. An unrecognised failure keeps the discarding release, which is
174
+ * the fail-closed side: at worst it throws away bytes nobody promised to
175
+ * keep, where the other mistake throws away bytes someone was told were
176
+ * saved.
177
+ *
178
+ * One type, not two: the move's lower-level `DestinationTakenError` never
179
+ * reaches this layer — `moveEntry` converts it into `EntryExistsError`, the
180
+ * refusal this surface answers 409 with — so recognising it here as well
181
+ * would be a branch nothing can take.
182
+ */
183
+ function wroteNothing(err) {
184
+ return err instanceof EntryExistsError;
185
+ }
67
186
  /**
68
187
  * Acquire the workflow lock for `(workspaceId, branch, targetPath)`, run
69
188
  * `op`, then release. Release commits + pushes the file as a one-file
@@ -122,23 +241,31 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
122
241
  eventBus.emit({ kind: 'fs-tree-changed', workspaceId, branch });
123
242
  return result;
124
243
  }
125
- const acquired = await workflowService.acquireLock(workspaceId, branch, targetPath, user);
244
+ const acquired = await workflowService.acquireLock(workspaceId, branch, targetPath, user, {
245
+ platformRestore: options?.platformRestore,
246
+ });
126
247
  if (!acquired.acquired) {
127
248
  const holder = acquired.lock.holderName || 'another user';
128
249
  const err = new Error(`"${targetPath}" is being edited by ${holder}. Try again in a moment.`);
129
250
  err.status = 409;
130
251
  throw err;
131
252
  }
132
- // Two release modes, depending on whether the op succeeded:
253
+ // Three release modes, the same three `LockingFilesystem.withLock` uses,
254
+ // depending on what the op did:
255
+ //
256
+ // - op() FAILED having possibly WRITTEN → drop the lock WITHOUT
257
+ // enqueueing a commit. The op may have written partial bytes to disk
258
+ // before throwing (write that errored mid-stream, etc.). A normal
259
+ // `releaseLock` would enqueue a commit for whatever's on disk and the
260
+ // worker would silently persist that partial state as a real
261
+ // committed change. `releaseLockNoCommit` resets the path to HEAD, so
262
+ // partial bytes never become a committed change with the user's name
263
+ // on it.
133
264
  //
134
- // - op() FAILED → drop the lock WITHOUT enqueueing a commit. The
135
- // op may have written partial bytes to disk before throwing
136
- // (write that errored mid-stream, etc.). A normal `releaseLock`
137
- // would enqueue a commit for whatever's on disk and the worker
138
- // would silently persist that partial state as a real committed
139
- // change. `releaseLockNoCommit` drops the lock row only —
140
- // partial disk state stays where it is but never becomes a
141
- // committed change with the user's name on it.
265
+ // - op() FAILED having written NOTHING → `releaseLockUntouched`: drop
266
+ // the lock row and leave both the disk and the commit queue exactly
267
+ // as they are. See `wroteNothing` for which refusals qualify and why
268
+ // the distinction is load-bearing rather than tidy.
142
269
  //
143
270
  // - op() SUCCEEDED → release. The new releaseLock enqueues a
144
271
  // pending-commit row (the actual `commitFile + push` runs out
@@ -153,11 +280,17 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
153
280
  opSucceeded = true;
154
281
  }
155
282
  catch (err) {
283
+ const untouched = wroteNothing(err);
156
284
  try {
157
- await workflowService.releaseLockNoCommit(workspaceId, branch, targetPath, user);
285
+ if (untouched) {
286
+ await workflowService.releaseLockUntouched(workspaceId, branch, targetPath, user);
287
+ }
288
+ else {
289
+ await workflowService.releaseLockNoCommit(workspaceId, branch, targetPath, user);
290
+ }
158
291
  }
159
292
  catch (releaseErr) {
160
- console.warn(`[workspace.routes] releaseLockNoCommit failed after op error for "${targetPath}":`, releaseErr instanceof Error ? releaseErr.message : releaseErr);
293
+ log.warn(`${untouched ? 'releaseLockUntouched' : 'releaseLockNoCommit'} failed after op error for "${targetPath}":`, { err: releaseErr });
161
294
  }
162
295
  throw err;
163
296
  }
@@ -182,19 +315,57 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
182
315
  }
183
316
  return result;
184
317
  }
318
+ /**
319
+ * Keep the folder a removal just emptied. A folder exists until it is
320
+ * deleted explicitly, so when deleting a file (or a subfolder), or moving
321
+ * one out, leaves its parent empty, the parent gets the placeholder in its
322
+ * own lock cycle — committed like any other save, within the same request.
323
+ * Only folders inside the repository qualify, never the clone folder
324
+ * itself.
325
+ *
326
+ * It runs in the folder's turn, which an explicit folder delete also takes,
327
+ * and looks again inside it: a folder that is gone by then was deleted
328
+ * explicitly and stays gone, and one that gained an entry needs nothing.
329
+ * The placeholder is only ever written into a folder that exists, and never
330
+ * through a link (see `writeFolderPlaceholder`).
331
+ *
332
+ * A failure is the request's failure: the removal landed, but a request
333
+ * that answers success would leave a folder that vanishes on the next
334
+ * clone, which is exactly what this rule forbids. It keeps its own status
335
+ * (a contended placeholder lock is still a 409) and only gains the context.
336
+ */
337
+ async function keepFolderOf(workspaceId, user, removedPath) {
338
+ const trimmed = removedPath.replace(/\/+$/, '');
339
+ const dir = trimmed.includes('/') ? trimmed.slice(0, trimmed.lastIndexOf('/')) : '';
340
+ if (!dir.startsWith(`${kbDirName}/`))
341
+ return;
342
+ try {
343
+ const absolute = await absoluteInRepo(workspaceId, dir);
344
+ await workspaceService.withFolderTurn(workspaceId, dir, async () => {
345
+ if (!(await isEmptyFolder(absolute)))
346
+ return;
347
+ // One tree refresh per request: the removal already announced it.
348
+ await withLock(workspaceId, user, folderPlaceholderPath(dir), () => workspaceService.writeFolderPlaceholder(workspaceId, dir), { skipFsTreeEvent: true });
349
+ });
350
+ }
351
+ catch (err) {
352
+ const reason = err instanceof Error ? err.message : String(err);
353
+ log.error(`could not keep the folder ${printable(dir)} after removing ${printable(removedPath)}: ${printable(reason)}`);
354
+ throw withKeptFolderContext(err, `"${removedPath}" was removed, but its folder "${dir}" could not be kept: ${reason}`);
355
+ }
356
+ }
185
357
  /**
186
358
  * Map any thrown error to an HTTP response. Centralises the response
187
359
  * shape so each route handler stays focused on its own logic.
188
- * - `WorkflowDomainError` → its `.status` + `.payload`
189
- * - Path traversal → 403
190
- * - Invalid path / zip → 400 (Only .zip is also 400)
191
- * - Could not read zip → 422
360
+ * - `WorkflowDomainError` → its `.status` + `.payload` (a traversal is a
361
+ * 403, an invalid path a 400, an unreadable archive a 422 — each by
362
+ * its TYPE; this surface used to recognise all three by their message)
192
363
  * - Anything else with a `.status` → that status
193
364
  * - Default → 500
194
365
  */
195
366
  function sendError(res, err) {
196
367
  if (err instanceof WorkflowDomainError) {
197
- res.status(err.status).json({ error: err.message, ...(err.payload ?? {}) });
368
+ res.status(err.status).json(domainErrorBody(err));
198
369
  return;
199
370
  }
200
371
  const msg = err instanceof Error ? err.message : 'Unknown error';
@@ -203,18 +374,6 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
203
374
  res.status(status).json({ error: msg });
204
375
  return;
205
376
  }
206
- if (msg === 'Path traversal detected') {
207
- res.status(403).json({ error: msg });
208
- return;
209
- }
210
- if (msg.startsWith('Invalid path') || msg.startsWith('Only .zip')) {
211
- res.status(400).json({ error: msg });
212
- return;
213
- }
214
- if (msg.startsWith('Could not read zip file')) {
215
- res.status(422).json({ error: msg });
216
- return;
217
- }
218
377
  res.status(500).json({ error: msg });
219
378
  }
220
379
  // GET /api/workspace[?branch=<branch>]
@@ -254,7 +413,7 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
254
413
  // Log the full stack so the next 500 isn't a guessing game — the
255
414
  // bare `error.message` we returned before lost most diagnostic
256
415
  // signal (cause chain, stack frames, error class).
257
- console.error(`[workspace.routes] GET /workspace failed branch=${req.query.branch ?? '(default)'}:`, error instanceof Error ? error.stack ?? error.message : error);
416
+ log.error(`GET /workspace failed branch=${req.query.branch ?? '(default)'}:`, { err: error });
258
417
  const msg = error instanceof Error ? error.message : 'Unknown error';
259
418
  res.status(500).json({ error: msg });
260
419
  }
@@ -347,12 +506,11 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
347
506
  let allowed;
348
507
  try {
349
508
  // `download` is a per-path verb in access.md, resolved via the same
350
- // chain walk as `write`. Strip the kbDirName prefix so the resolver
351
- // receives a repo-relative path (mirrors the lock-acquire gate's
352
- // strip in workflow.service.ts's assertCanWriteAtPath).
353
- const repoRelative = relativePath.startsWith(`${kbDirName}/`)
354
- ? relativePath.slice(kbDirName.length + 1)
355
- : relativePath;
509
+ // chain walk as `write`. The caller has already normalised the path into
510
+ // the repository, so this only takes the prefix off — where it used to
511
+ // pass an unprefixed path through untouched and judge a stray as if it
512
+ // were inside.
513
+ const repoRelative = toKbRelative(relativePath, kbDirName) ?? relativePath;
356
514
  allowed = await accessControl.canDownload(workspaceId, user.email, repoRelative);
357
515
  }
358
516
  catch (err) {
@@ -427,7 +585,7 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
427
585
  // `.bevelignore` is ADMIN-ONLY. It is the file that decides what the file
428
586
  // tree and the agent view show at all, so it is deployment configuration
429
587
  // rather than knowledge — and it sits alone in being visible: its
430
- // siblings (`.gitignore`, `roles.yaml`, `access.md`, `AGENTS.md`) are
588
+ // siblings (`.gitignore`, `roles.yaml`, `access.md`, the agent guide) are
431
589
  // already hidden from every reader by the shipped ignore rules. Somebody
432
590
  // who cannot act on it gains a puzzle; somebody who can needs to reach it.
433
591
  //
@@ -440,10 +598,14 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
440
598
  // on the default branch precisely so that editing `roles.yaml` on your
441
599
  // own branch cannot promote you, and the same reasoning applies to
442
600
  // anything gated on being an admin.
601
+ //
602
+ // `'unlisted'`, not `false`: hiding it withholds no content, so a
603
+ // non-admin in an empty knowledge base (every one ships this file) is
604
+ // told it is empty, not that something is being kept from them.
443
605
  const ignoreFiles = wsRelPaths.filter((wp) => path.basename(wp) === IGNORE_FILENAME);
444
606
  if (ignoreFiles.length > 0 && !(await adminAccess.isAdmin(userEmail))) {
445
607
  for (const wp of ignoreFiles)
446
- verdict.set(wp, false);
608
+ verdict.set(wp, 'unlisted');
447
609
  }
448
610
  return verdict;
449
611
  };
@@ -457,39 +619,68 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
457
619
  * grant on the same new directory survives. Runs BEFORE the creation itself
458
620
  * so the explorer never shows-then-hides the new subtree; if the creation
459
621
  * subsequently fails, the leftover is an empty new folder readable only by
460
- * its creator. Best-effort by contract: a failure here logs and never
461
- * blocks the creation.
622
+ * its creator.
623
+ *
624
+ * A failure here FAILS the creation, and propagates as it is. The seed is
625
+ * planned only for a new folder at a root the creator cannot read — the one
626
+ * creation the read gate lets past an unreadable spot — so without the
627
+ * grant the folder would come into existence invisible to the person who
628
+ * made it, which is exactly what the gate exists to prevent. The seed's
629
+ * own lock passes that gate too: if the folder appeared under someone else
630
+ * between the plan and this write, the refusal is theirs to see, and
631
+ * nothing lands.
462
632
  */
463
633
  async function seedCreatorAccessMd(workspaceId, user, plan) {
464
- try {
465
- await withLock(workspaceId, user, plan.wsRelPath, async () => {
466
- let current = '';
467
- try {
468
- current = await workspaceService.readFile(workspaceId, plan.wsRelPath);
469
- }
470
- catch {
471
- // Not there yet — the normal case for a brand-new directory.
472
- }
473
- const next = plan.apply(current);
474
- if (next !== current) {
475
- await workspaceService.writeFile(workspaceId, plan.wsRelPath, next);
476
- }
477
- });
478
- creatorAccess.noteAccessFileWritten(workspaceId);
479
- }
480
- catch (err) {
481
- console.warn(`[workspace.routes] creator access.md seed failed for "${plan.wsRelPath}":`, err instanceof Error ? err.message : err);
482
- }
634
+ await withLock(workspaceId, user, plan.wsRelPath, async () => {
635
+ let current = '';
636
+ try {
637
+ current = await workspaceService.readFile(workspaceId, plan.wsRelPath);
638
+ }
639
+ catch {
640
+ // Not there yet — the normal case for a brand-new directory.
641
+ }
642
+ const next = plan.apply(current);
643
+ if (next !== current) {
644
+ await workspaceService.writeFile(workspaceId, plan.wsRelPath, next);
645
+ }
646
+ });
647
+ creatorAccess.noteAccessFileWritten(workspaceId);
483
648
  }
649
+ /**
650
+ * GET /workspace/:id/file/raw?path=<file>[&download=1][&v=<n>]
651
+ *
652
+ * The bytes of one workspace file: for the document renderers' fetches, the
653
+ * file tree's Download, and the `<img>` tags the markdown pipeline emits for
654
+ * `![alt](./assets/x.png)`. An `<img>` sends no Authorization header, so it
655
+ * is the auth middleware's `bevel_token` cookie fallback that lets a plain
656
+ * image tag through.
657
+ *
658
+ * request ──▶ auth: Bearer, else the cookie
659
+ * ──▶ read gate on the path 403 if the caller may not read it
660
+ * ──▶ download gate, if ?download=1 403 without the download: verb
661
+ * ──▶ readFileBinary 404 missing, 403 traversal
662
+ * ──▶ Content-Type from the extension, nosniff, a CSP sandbox for
663
+ * inline svg, attachment disposition for a download, and
664
+ * Cache-Control: private, no-cache
665
+ * ──▶ res.send(buffer): Express sets a weak ETag and answers 304 to
666
+ * a matching If-None-Match, so a revisited page costs a read
667
+ * and a hash per image rather than the bytes on the wire
668
+ *
669
+ * `?v=` is not read here. The frontend bumps it when it learns an image
670
+ * changed, so the browser asks for a URL it has not cached.
671
+ */
484
672
  router.get('/workspace/:id/file/raw', async (req, res) => {
485
673
  const id = authenticated(req, res);
486
674
  if (id === null)
487
675
  return;
488
- const filePath = req.query.path;
489
- if (!filePath) {
676
+ const requested = req.query.path;
677
+ if (typeof requested !== 'string' || requested.length === 0) {
490
678
  res.status(400).json({ error: 'path query parameter is required' });
491
679
  return;
492
680
  }
681
+ const filePath = inRepo(res, requested);
682
+ if (filePath === null)
683
+ return;
493
684
  // `?download=1` flips this from inline-serve (used by PdfRenderer and
494
685
  // the image renderers) to "save to disk" — and the save path is gated
495
686
  // on per-path `download:` rules in access.md. The inline path stays
@@ -546,12 +737,19 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
546
737
  const basename = (filePath.split('/').pop() || 'file').replace(/[\r\n]/g, '');
547
738
  res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodeURIComponent(basename)}`);
548
739
  }
740
+ // `private`: this is one person's authenticated file, and a shared cache
741
+ // (a CDN, a corporate proxy) must not hand it to the next person.
742
+ // `no-cache`: the browser may keep it but asks before reusing it, which
743
+ // the ETag answers with a 304; a picture replaced under the same name is
744
+ // therefore never older than one revalidation.
745
+ res.setHeader('Cache-Control', 'private, no-cache');
549
746
  res.send(buffer);
550
747
  }
551
748
  catch (error) {
552
- const msg = error instanceof Error ? error.message : 'Unknown error';
553
- if (msg === 'Path traversal detected') {
554
- res.status(403).json({ error: msg });
749
+ // A traversal is the caller's 403; every other read failure here is the
750
+ // route's honest 404 (the image either is not there or cannot be shown).
751
+ if (error instanceof PathTraversalError) {
752
+ sendError(res, error);
555
753
  return;
556
754
  }
557
755
  res.status(404).json({ error: 'File not found' });
@@ -586,11 +784,14 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
586
784
  // Trim whitespace and strip trailing slashes — `foo/` would otherwise
587
785
  // basename to `''` and produce a misleading `.zip` filename. Done
588
786
  // before the empty check so `' / '` is also caught.
589
- const folderPath = rawPath.trim().replace(/\/+$/, '');
590
- if (!folderPath) {
787
+ const trimmedPath = rawPath.trim().replace(/\/+$/, '');
788
+ if (!trimmedPath) {
591
789
  res.status(400).json({ error: 'path query parameter is required' });
592
790
  return;
593
791
  }
792
+ const folderPath = inRepo(res, trimmedPath);
793
+ if (folderPath === null)
794
+ return;
594
795
  if (req.query.download !== '1') {
595
796
  res.status(400).json({ error: 'download=1 is required for folder zip downloads' });
596
797
  return;
@@ -615,11 +816,11 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
615
816
  res.status(413).json({ error: error.message });
616
817
  return;
617
818
  }
618
- const msg = error instanceof Error ? error.message : 'Unknown error';
619
- if (msg === 'Path traversal detected') {
620
- res.status(403).json({ error: msg });
819
+ if (error instanceof PathTraversalError) {
820
+ sendError(res, error);
621
821
  return;
622
822
  }
823
+ const msg = error instanceof Error ? error.message : 'Unknown error';
623
824
  if (msg === 'Not a directory') {
624
825
  res.status(400).json({ error: msg });
625
826
  return;
@@ -632,11 +833,14 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
632
833
  const id = authenticated(req, res);
633
834
  if (id === null)
634
835
  return;
635
- const filePath = req.query.path;
636
- if (!filePath) {
836
+ const requested = requestPath(req.query.path);
837
+ if (requested === null) {
637
838
  res.status(400).json({ error: 'path query parameter is required' });
638
839
  return;
639
840
  }
841
+ const filePath = inRepo(res, requested);
842
+ if (filePath === null)
843
+ return;
640
844
  if (!(await requireReadPermission(req, res, id, filePath)))
641
845
  return;
642
846
  try {
@@ -648,23 +852,47 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
648
852
  res.json({ content });
649
853
  }
650
854
  catch (error) {
651
- const msg = error instanceof Error ? error.message : 'Unknown error';
652
- if (msg === 'Path traversal detected') {
653
- res.status(403).json({ error: msg });
855
+ // 404 means ONE thing here: there is no file at this path. That is
856
+ // ENOENT, a parent that is not a directory (ENOTDIR), and a directory
857
+ // asked for as a file (EISDIR). Every other read failure keeps its own
858
+ // status, because callers act on the difference: the inline
859
+ // agent-description editor opens EMPTY on a 404 (a knowledge base older
860
+ // than the template has no such file yet), so dressing an unreadable
861
+ // file up as a missing one would offer an empty editor over content the
862
+ // save then overwrites.
863
+ if (isAbsence(error) || error.code === 'EISDIR') {
864
+ res.status(404).json({ error: 'File not found' });
654
865
  return;
655
866
  }
656
- res.status(404).json({ error: 'File not found' });
867
+ // Traversal stays a 403 and a malformed workspace id its domain status:
868
+ // both carry messages written to be read by the caller. (A traversal IS
869
+ // a domain error now, so the one check covers both.)
870
+ if (error instanceof WorkflowDomainError) {
871
+ sendError(res, error);
872
+ return;
873
+ }
874
+ // Anything else is a real failure, and it says so WITHOUT quoting
875
+ // itself: an errno message carries absolute workspace paths and a
876
+ // bootstrap failure carries git's stderr, neither of which belongs in a
877
+ // response body. The distinction this route exists to make is still
878
+ // made — an unreadable file is not a missing one — it is just not
879
+ // narrated to the client.
880
+ log.warn(`GET /file failed for "${filePath}" in "${id}":`, { err: error });
881
+ res.status(500).json({ error: "Couldn't read the file." });
657
882
  }
658
883
  });
659
884
  router.delete('/workspace/:id/file', async (req, res) => {
660
885
  const id = authenticated(req, res);
661
886
  if (id === null)
662
887
  return;
663
- const filePath = req.query.path;
664
- if (!filePath) {
888
+ const requested = requestPath(req.query.path);
889
+ if (requested === null) {
665
890
  res.status(400).json({ error: 'path query parameter is required' });
666
891
  return;
667
892
  }
893
+ const filePath = inRepo(res, requested);
894
+ if (filePath === null)
895
+ return;
668
896
  const user = await requireUser(req, res);
669
897
  if (!user)
670
898
  return;
@@ -679,19 +907,12 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
679
907
  // emits are also coalesced into a single end-of-batch event so the
680
908
  // explorer doesn't refresh N times.
681
909
  const workspaceDir = await workspaceService.getWorkspacePath(id);
682
- const absolute = path.resolve(workspaceDir, filePath);
683
- // Boundary check before any `fs.*` call. The single-file path
684
- // bottoms out in `workspaceService.deleteFile` (which asserts the
685
- // boundary itself), but this fast-path resolves the absolute
686
- // ourselves and then `fs.stat` / `fs.rm` / `enumerateFilesUnder`
687
- // it. Without this guard, a `../escape` `filePath` would let the
688
- // dir-delete branch operate on directories outside the workspace.
689
- const workspaceRoot = path.resolve(workspaceDir);
690
- if (absolute !== workspaceRoot && !absolute.startsWith(workspaceRoot + path.sep)) {
691
- const err = new Error('Path traversal detected');
692
- err.status = 403;
693
- throw err;
694
- }
910
+ // Boundary check before any `fs.*` call — against the REPOSITORY root,
911
+ // which is where `absoluteInRepo` holds it. The single-file path bottoms
912
+ // out in `workspaceService.deleteFile` (which resolves inside the
913
+ // repository itself), but this fast-path needs the absolute for
914
+ // `fs.stat` / `fs.rm` / `enumerateFilesUnder`.
915
+ const absolute = await absoluteInRepo(id, filePath);
695
916
  let stat = null;
696
917
  try {
697
918
  stat = await fs.stat(absolute);
@@ -700,48 +921,73 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
700
921
  // Not on disk — let workspaceService.deleteFile return its own 404.
701
922
  }
702
923
  if (stat?.isDirectory()) {
703
- const filesInDir = await enumerateFilesUnder(absolute, workspaceDir);
704
924
  const branch = branchForWorkspaceId(id);
705
- for (const relFile of filesInDir) {
706
- await withLock(id, user, relFile, async () => {
707
- await workspaceService.deleteFile(id, relFile);
708
- }, { skipFsTreeEvent: true });
709
- }
710
- // No explicit push here — each per-file release enqueues a
711
- // pending-commits row, and the background worker drains them
712
- // (commit + push) on its own schedule. The N round-trips that
713
- // the old skipPush+pushBranch pattern collapsed into one happen
714
- // serially in the worker; user perception is unchanged because
715
- // the disk-side delete is what other sessions see via
716
- // `fs-tree-changed`.
717
- // Sweep the now-empty directory subtree off disk. Git doesn't track
718
- // empty folders, so there's nothing to commit; this is just disk
719
- // hygiene so the file tree stops showing the empty containers.
720
- //
721
- // This MUST recurse: a folder that held *subfolders* still has those
722
- // (now-empty) subdirectory shells on disk after the per-file deletes,
723
- // so a single non-recursive `rmdir(absolute)` would see a non-empty
724
- // dir and bail — leaving the folder visible in the tree and looking
725
- // undeletable (BEVA-132). `removeEmptyDirs` walks bottom-up and only
726
- // removes dirs that are *actually empty* at the moment it visits them,
727
- // so a concurrent writer's new file (and its parent chain) is
728
- // preserved — the same safety property the old non-recursive check had.
925
+ // In the folder's turn: keeping a folder under this one (a file delete
926
+ // racing this one) waits until the sweep is done, and then finds the
927
+ // folder gone instead of writing it back.
928
+ let filesInDir;
729
929
  try {
730
- await removeEmptyDirs(absolute);
930
+ filesInDir = await workspaceService.withFolderTurn(id, filePath, async () => {
931
+ const files = await enumerateFilesUnder(disk, absolute, workspaceDir);
932
+ for (const relFile of files) {
933
+ await withLock(id, user, relFile, async () => {
934
+ await workspaceService.deleteFile(id, relFile);
935
+ }, { skipFsTreeEvent: true });
936
+ }
937
+ // No explicit push here — each per-file release enqueues a
938
+ // pending-commits row, and the background worker drains them
939
+ // (commit + push) on its own schedule. The N round-trips that
940
+ // the old skipPush+pushBranch pattern collapsed into one happen
941
+ // serially in the worker; user perception is unchanged because
942
+ // the disk-side delete is what other sessions see via
943
+ // `fs-tree-changed`.
944
+ // This is the EXPLICIT folder delete — the one operation that removes
945
+ // a folder — so the walk above deleted the placeholders too, and the
946
+ // now-empty directory subtree is swept off disk. Git doesn't track
947
+ // empty folders, so there's nothing more to commit; this is disk
948
+ // hygiene so the file tree stops showing the deleted containers.
949
+ //
950
+ // This MUST recurse: a folder that held *subfolders* still has those
951
+ // (now-empty) subdirectory shells on disk after the per-file deletes,
952
+ // so a single non-recursive `rmdir(absolute)` would see a non-empty
953
+ // dir and bail — leaving the folder visible in the tree and looking
954
+ // undeletable (BEVA-132). `removeEmptyDirs` walks bottom-up and only
955
+ // removes dirs that are *actually empty* at the moment it visits them,
956
+ // so a concurrent writer's new file (and its parent chain) is
957
+ // preserved — the same safety property the old non-recursive check had.
958
+ // A folder that vanished or filled up under a concurrent writer
959
+ // is passed over inside the sweep; what reaches here is a real
960
+ // failure (permissions, I/O) that left the folder on disk, and
961
+ // the request fails — as the agent's delete does — rather than
962
+ // the response reporting gone a folder the tree still shows. The
963
+ // per-file deletes have landed either way, and the next delete
964
+ // of the now-empty folder either sweeps it or says why not. The
965
+ // cause is logged; the answer carries no path of the host's.
966
+ try {
967
+ await removeEmptyDirs(absolute);
968
+ }
969
+ catch (rmErr) {
970
+ log.warn(`dir cleanup failed for ${printable(filePath)}`, { err: rmErr });
971
+ throw new Error('The files were deleted, but the empty folder could not be removed. Delete it again.');
972
+ }
973
+ return files;
974
+ });
975
+ // The folder that HELD the deleted one was not asked to go.
976
+ await keepFolderOf(id, user, filePath);
731
977
  }
732
- catch (rmErr) {
733
- // Directory already gone (raced delete), or a concurrent writer
734
- // repopulated it. Either way, skip removal — the per-file deletes
735
- // are what's load-bearing.
736
- console.warn(`[workspace.routes] dir cleanup skipped for "${filePath}":`, rmErr instanceof Error ? rmErr.message : rmErr);
978
+ finally {
979
+ // Single tree-refresh signal for the whole batch (we suppressed
980
+ // the per-file ones via `skipFsTreeEvent`) — sent on failure too:
981
+ // a batch that stops part way, or a parent that could not be kept,
982
+ // has still changed the tree.
983
+ eventBus.emit({ kind: 'fs-tree-changed', workspaceId: id, branch });
737
984
  }
738
- // Single tree-refresh signal for the whole batch (we suppressed
739
- // the per-file ones via `skipFsTreeEvent`).
740
- eventBus.emit({ kind: 'fs-tree-changed', workspaceId: id, branch });
741
985
  res.json({ status: 'deleted', count: filesInDir.length });
742
986
  return;
743
987
  }
744
988
  await withLock(id, user, filePath, () => workspaceService.deleteFile(id, filePath));
989
+ // Deleting content is not deleting structure: an emptied folder stays.
990
+ await keepFolderOf(id, user, filePath);
745
991
  res.json({ status: 'deleted' });
746
992
  }
747
993
  catch (err) {
@@ -752,15 +998,72 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
752
998
  const id = authenticated(req, res);
753
999
  if (id === null)
754
1000
  return;
755
- const { oldPath, newPath } = req.body;
756
- if (!oldPath || !newPath) {
1001
+ const body = (req.body ?? {});
1002
+ const requestedOld = requestPath(body.oldPath);
1003
+ const requestedNew = requestPath(body.newPath);
1004
+ if (requestedOld === null || requestedNew === null) {
757
1005
  res.status(400).json({ error: 'oldPath and newPath are required in body' });
758
1006
  return;
759
1007
  }
1008
+ // Both ends inside the repository before anything is judged: the platform-file
1009
+ // rules, the locks and the rename all read the same two paths.
1010
+ const oldPath = inRepo(res, requestedOld);
1011
+ if (oldPath === null)
1012
+ return;
1013
+ const newPath = inRepo(res, requestedNew);
1014
+ if (newPath === null)
1015
+ return;
1016
+ // Two spellings of one file are now ONE path, so a move can arrive with
1017
+ // both ends equal. `withLock` below would survive it — the inner
1018
+ // acquisition sees the lock the outer just took, held by this same user,
1019
+ // and runs straight through — but the move itself is a rename onto
1020
+ // itself that commits a change and tells the diff service the path was
1021
+ // both deleted and rewritten. It is a client mistake, so it is a 400.
1022
+ if (oldPath === newPath) {
1023
+ res.status(400).json({ error: 'oldPath and newPath must differ' });
1024
+ return;
1025
+ }
760
1026
  const user = await requireUser(req, res);
761
1027
  if (!user)
762
1028
  return;
763
1029
  try {
1030
+ // A platform file stays in the folder the platform reads it from —
1031
+ // rename, move and drag all arrive here, and all three are refused.
1032
+ // Moving one out is not a choice to confirm: once the root has no
1033
+ // `access.md`, write on the root denies everyone and the move that
1034
+ // would undo it is the move the gate refuses.
1035
+ //
1036
+ // The single exception is that repair: an admin putting a misplaced
1037
+ // copy BACK. `isPlatformRestoreShape` says whether the MOVE is that
1038
+ // repair — it is judged on the source's name and the destination, not
1039
+ // on the source being a platform file where it currently sits, because
1040
+ // a stray `roles.yaml` in a folder is ordinary content there and is
1041
+ // still the copy the root is missing. `canRestorePlatformFile` then
1042
+ // decides who may make it, and whether the disk agrees.
1043
+ const oldRel = toKbRelative(oldPath, kbDirName);
1044
+ const newRel = toKbRelative(newPath, kbDirName);
1045
+ let platformRestore = false;
1046
+ if (oldRel !== null && newRel !== null && isPlatformRestoreShape(oldRel, newRel)) {
1047
+ platformRestore = await accessControl.canRestorePlatformFile(id, user.email, newRel);
1048
+ }
1049
+ // A platform file stays where the platform reads it …
1050
+ if (oldRel !== null && isPlatformFile(oldRel) && !platformRestore) {
1051
+ res.status(409).json({ error: platformFileRefusal(oldRel) });
1052
+ return;
1053
+ }
1054
+ // … and nothing else becomes one. `moveEntry` is a plain rename, so
1055
+ // without this a note renamed to `access.md` would come back as the
1056
+ // folder's rules, and a note dragged ONTO the root's `access.md` would
1057
+ // replace the rules that are there — neither of which the rule above,
1058
+ // which reads only the SOURCE, says anything about. The agent's move
1059
+ // tool has refused both since it shipped; this is the same sentence,
1060
+ // from the same place. Only the restore lands on a platform path, and
1061
+ // only where the file is missing (`canRestorePlatformFile` checked the
1062
+ // disk; the destination lock below checks it again, under the lock).
1063
+ if (newRel !== null && isPlatformFile(newRel) && !platformRestore) {
1064
+ res.status(409).json({ error: platformFileCreationRefusal(newRel) });
1065
+ return;
1066
+ }
764
1067
  // Move = rename on disk + commit on both sides. We lock-and-release
765
1068
  // the destination first (commits the new file's appearance), then
766
1069
  // lock-and-release the source path (commits its deletion). Two
@@ -781,7 +1084,38 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
781
1084
  // not a single merge-style rename commit, but git's log/blame
782
1085
  // rename detection still groups them visually after the fact.
783
1086
  const [firstLock, secondLock] = oldPath < newPath ? [oldPath, newPath] : [newPath, oldPath];
784
- await withLock(id, user, firstLock, () => withLock(id, user, secondLock, () => workspaceService.moveEntry(id, oldPath, newPath)));
1087
+ // Only the DESTINATION side carries the restore claim. The source is an
1088
+ // ordinary write the caller must already hold: the exception exists so a
1089
+ // file can land where the platform reads it, not so an admin can take
1090
+ // one out of a folder that denies them.
1091
+ const restoreAt = (p) => ({
1092
+ platformRestore: platformRestore && p === newPath ? { source: oldPath } : undefined,
1093
+ });
1094
+ // The restore was authorised against a destination that was missing when
1095
+ // the access module looked. Both locks are in hand by the time this runs,
1096
+ // so nothing else can take that path from under the rename — but between
1097
+ // the two a writer still could, and a restore that lands on a platform
1098
+ // file replaces the rules it came to bring back. So the last thing the
1099
+ // move does before renaming is look again, inside the window it holds.
1100
+ const move = async () => {
1101
+ if (platformRestore) {
1102
+ const absoluteNew = await absoluteInRepo(id, newPath);
1103
+ const taken = await fs.stat(absoluteNew).then(() => true,
1104
+ // Genuine absence is the only "free": anything else is not an
1105
+ // answer, and an unanswered question does not clear the way onto
1106
+ // a platform file.
1107
+ (err) => !isAbsence(err));
1108
+ if (taken) {
1109
+ const err = new Error(platformFileRefusal(newPath));
1110
+ err.status = 409;
1111
+ throw err;
1112
+ }
1113
+ }
1114
+ return workspaceService.moveEntry(id, oldPath, newPath);
1115
+ };
1116
+ await withLock(id, user, firstLock, () => withLock(id, user, secondLock, move, restoreAt(secondLock)), restoreAt(firstLock));
1117
+ // Moving the last entry out leaves its folder in place, like a delete.
1118
+ await keepFolderOf(id, user, oldPath);
785
1119
  res.json({ status: 'moved' });
786
1120
  }
787
1121
  catch (err) {
@@ -792,41 +1126,89 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
792
1126
  const id = authenticated(req, res);
793
1127
  if (id === null)
794
1128
  return;
795
- const filePath = req.query.path;
796
- if (!filePath) {
1129
+ // ONE spelling of the target from here down. `x/a.md`, `./x/a.md` and
1130
+ // `x//a.md` are the same file and all pass the path validator, and this
1131
+ // route coordinates on that path three times over: the in-process write
1132
+ // turn, the workflow lock row, and the bytes themselves. Two clients
1133
+ // spelling one file differently would otherwise take two different locks
1134
+ // and both pass their own precondition.
1135
+ const requested = requestPath(req.query.path);
1136
+ if (requested === null) {
797
1137
  res.status(400).json({ error: 'path query parameter is required' });
798
1138
  return;
799
1139
  }
800
- const { content, ifAbsent } = req.body;
1140
+ const filePath = inRepo(res, requested);
1141
+ if (filePath === null)
1142
+ return;
1143
+ const { content, ifAbsent, ifMatch } = req.body;
801
1144
  if (content === undefined) {
802
1145
  res.status(400).json({ error: 'content is required in body' });
803
1146
  return;
804
1147
  }
1148
+ if (ifMatch !== undefined && typeof ifMatch !== 'string') {
1149
+ res.status(400).json({ error: 'ifMatch, if provided, must be a string' });
1150
+ return;
1151
+ }
805
1152
  const user = await requireUser(req, res);
806
1153
  if (!user)
807
1154
  return;
1155
+ // A precondition ANSWERS A QUESTION ABOUT CONTENT, so it is gated on
1156
+ // READING, not writing. Write authorisation happens at `acquireLock`
1157
+ // inside `withLock` below, which is after the compare: without this gate
1158
+ // the 409-versus-403 difference is a content-equality oracle on a file the
1159
+ // caller may not read, and the confirming case is a write. Same gate and
1160
+ // same 403 as `GET /file`; a caller who may write but not read can still
1161
+ // save, just not ask questions about what is there.
1162
+ if (ifMatch !== undefined && !(await requireReadPermission(req, res, id, filePath)))
1163
+ return;
808
1164
  try {
809
1165
  // Refuse a hand-edit that would leave roles.yaml unparseable BEFORE any
810
1166
  // byte hits disk — a broken roles.yaml is an app-wide admin lockout
811
- // (loadModel hard-throws). The dedicated Roles & Members surface has its
1167
+ // (loadModel hard-throws). The dedicated App roles surface has its
812
1168
  // own validate gate; this covers the raw-text editor path.
813
1169
  if (isRolesYamlPath(filePath, kbDirName))
814
1170
  assertRolesYamlParsable(content);
815
- // Creator read grant: a brand-new file at a spot whose access chain
816
- // doesn't grant the creator `read` would vanish from their own explorer
817
- // (read is default-deny). Plan BEFORE the write: a new subtree gets its
818
- // access.md seeded first; a loose .md carries the grant in its own
819
- // frontmatter as part of this same single write.
820
- const plan = await creatorAccess.planForCreate(id, user, filePath, 'file');
821
- if (plan?.kind === 'seed-access-md')
822
- await seedCreatorAccessMd(id, user, plan);
823
- const toWrite = plan?.kind === 'frontmatter' ? plan.apply(content) : content;
824
- // `ifAbsent` = exclusive create: the service's `wx` write turns a
825
- // concurrent or stale create against an existing file into a 409
826
- // instead of a silent replace. `withLock`'s failure arm releases
827
- // without committing, so the refusal leaves no trace.
828
- await withLock(id, user, filePath, () => workspaceService.writeFile(id, filePath, toWrite, { failIfExists: ifAbsent === true }));
829
- res.json({ status: 'written' });
1171
+ // One turn for the whole sequence, because the DECISION spans three
1172
+ // steps that must agree about the same file: check the precondition,
1173
+ // plan a creator-access grant that COMMITS, then write. A save landing
1174
+ // in between would leave that grant behind for a write about to be
1175
+ // refused, so the check is made here, before the plan, and nothing else
1176
+ // in this process may touch the path until the write is done. The write
1177
+ // inside takes the same turn re-entrantly.
1178
+ //
1179
+ // Across instances the workflow lock below is still the coordinator;
1180
+ // this only orders what this server is doing.
1181
+ await workspaceService.withPathTurn(id, filePath, async () => {
1182
+ // A stale `ifMatch` is refused before anything commits.
1183
+ if (ifMatch !== undefined)
1184
+ await workspaceService.assertContentMatches(id, filePath, ifMatch);
1185
+ // Creator read grant: a file that starts a new folder at a root the
1186
+ // creator cannot read would vanish from their own explorer (read is
1187
+ // default-deny). Plan BEFORE the write: the new folder gets its
1188
+ // access.md seeded first. Anywhere else the lock's read gate has
1189
+ // already decided — a creation the creator could not see is refused,
1190
+ // and one they can see needs no grant.
1191
+ const plan = await creatorAccess.planForCreate(id, user, filePath, 'file');
1192
+ if (plan?.kind === 'seed-access-md')
1193
+ await seedCreatorAccessMd(id, user, plan);
1194
+ const toWrite = content;
1195
+ // `ifAbsent` = exclusive create: the service's `wx` write turns a
1196
+ // concurrent or stale create against an existing file into a 409
1197
+ // instead of a silent replace. `withLock`'s failure arm releases
1198
+ // without committing, so the refusal leaves no trace.
1199
+ //
1200
+ // `ifMatch` = conditional write, the same 409 for a stale UPDATE: the
1201
+ // file must still hold the text the caller last read, re-checked at
1202
+ // the write itself as the decision that counts.
1203
+ await withLock(id, user, filePath, () => workspaceService.writeFile(id, filePath, toWrite, {
1204
+ failIfExists: ifAbsent === true,
1205
+ expectedContent: ifMatch,
1206
+ }));
1207
+ });
1208
+ // After the write, and only ever advisory: the check reports what the
1209
+ // saved skill names, it has no say in whether it saved.
1210
+ const warnings = skillSaveCheck ? await skillSaveCheck.checkSave(user.email, filePath, content) : [];
1211
+ res.json({ status: 'written', ...(warnings.length > 0 ? { warnings } : {}) });
830
1212
  }
831
1213
  catch (err) {
832
1214
  sendError(res, err);
@@ -836,11 +1218,18 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
836
1218
  const id = authenticated(req, res);
837
1219
  if (id === null)
838
1220
  return;
839
- const { path: dirPath, defer } = req.body;
840
- if (!dirPath) {
1221
+ const { path: requested, defer } = req.body;
1222
+ const canonical = requestPath(requested);
1223
+ if (canonical === null) {
841
1224
  res.status(400).json({ error: 'path is required in body' });
842
1225
  return;
843
1226
  }
1227
+ // The folder this call creates, as a repository path. `KnowledgeBase/Reports`
1228
+ // is `<kbDirName>/KnowledgeBase/Reports` — the access plan below, the
1229
+ // `.gitkeep` lock and the mkdir all use this one spelling.
1230
+ const dirPath = inRepo(res, canonical);
1231
+ if (dirPath === null)
1232
+ return;
844
1233
  const user = await requireUser(req, res);
845
1234
  if (!user)
846
1235
  return;
@@ -880,20 +1269,49 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
880
1269
  if (id === null)
881
1270
  return;
882
1271
  const body = (req.body ?? {});
883
- const zipPath = body.path;
884
- const destination = body.destination;
885
- if (typeof zipPath !== 'string' || zipPath.length === 0) {
1272
+ const requestedZip = body.path;
1273
+ const requestedDest = body.destination;
1274
+ if (typeof requestedZip !== 'string' || requestedZip.length === 0) {
886
1275
  res.status(400).json({ error: 'path is required in body and must be a string' });
887
1276
  return;
888
1277
  }
889
- if (destination !== undefined && (typeof destination !== 'string' || destination.length === 0)) {
1278
+ if (requestedDest !== undefined && (typeof requestedDest !== 'string' || requestedDest.length === 0)) {
890
1279
  res.status(400).json({ error: 'destination, if provided, must be a non-empty string' });
891
1280
  return;
892
1281
  }
1282
+ const zipPath = inRepo(res, requestedZip);
1283
+ if (zipPath === null)
1284
+ return;
1285
+ let destination;
1286
+ if (requestedDest !== undefined) {
1287
+ const normalizedDest = inRepo(res, requestedDest);
1288
+ if (normalizedDest === null)
1289
+ return;
1290
+ destination = normalizedDest;
1291
+ }
893
1292
  const user = await requireUser(req, res);
894
1293
  if (!user)
895
1294
  return;
896
1295
  try {
1296
+ // The destination is asked about BEFORE anything is extracted: this is
1297
+ // the one route whose bytes land on disk ahead of the lock, so the
1298
+ // lock's read gate would find them already there. Same verdict, same
1299
+ // refusal, one step earlier. A destination inside a folder the caller
1300
+ // cannot read is refused whole; a folder they can read — or a new
1301
+ // folder directly under a root — extracts, and each file then meets
1302
+ // the lock as any other write does.
1303
+ //
1304
+ // A new root folder gets its creator's access.md seeded first, as
1305
+ // every other creation route does: extraction would otherwise bring
1306
+ // the folder into existence itself, and the first file's lock would
1307
+ // then find an existing folder the caller cannot read.
1308
+ // Both spellings are already repository paths, so the inferred parent is
1309
+ // one too — the same destination the service resolves.
1310
+ const destDir = destination ?? path.posix.dirname(zipPath);
1311
+ const plan = await creatorAccess.planForCreate(id, user, destDir, 'dir');
1312
+ if (plan?.kind === 'seed-access-md')
1313
+ await seedCreatorAccessMd(id, user, plan);
1314
+ await changeGate?.assertMayChange(id, user.email, destDir, 'dir');
897
1315
  // Extract first (all files land on disk), then sweep each extracted
898
1316
  // file through a lock+release so it commits + pushes as its own
899
1317
  // one-file change. Per-file commits mean the validator runs N times
@@ -901,26 +1319,18 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
901
1319
  // but correct for a 100-file zip. If a single file's release fails
902
1320
  // (e.g. validator 422), the loop stops there so the user sees the
903
1321
  // first concrete problem rather than a list of N similar failures.
904
- const result = await workspaceService.unzipFile(id, zipPath, destination);
1322
+ //
1323
+ // Each entry is asked about before it is written, through the same
1324
+ // gate: an archive can carry a path into a nested folder the caller
1325
+ // cannot read, which the destination check above cannot see. Such an
1326
+ // entry is skipped and reported, never written and then refused.
1327
+ const result = await workspaceService.unzipFile(id, zipPath, destination, async (entryPath) => {
1328
+ await changeGate?.assertMayChange(id, user.email, entryPath, 'file');
1329
+ });
905
1330
  for (const relFile of result.extracted) {
906
- await withLock(id, user, relFile, async () => {
907
- // The file is already on disk from the unzip; the release commits +
908
- // pushes it. If the extraction landed a markdown node the creator
909
- // can't read (default-deny chains), splice their read grant into
910
- // its frontmatter first so the committed change already carries it.
911
- // Best-effort: a failed grant write must not throw — withLock's
912
- // failure path releases WITHOUT committing and would discard the
913
- // extracted file from disk, turning a cosmetic grant failure into
914
- // data loss.
915
- try {
916
- const granted = await creatorAccess.grantInExtractedFile(id, user, relFile);
917
- if (granted !== null)
918
- await workspaceService.writeFile(id, relFile, granted);
919
- }
920
- catch (err) {
921
- console.warn(`[workspace.routes] creator grant on extracted "${relFile}" failed:`, err instanceof Error ? err.message : err);
922
- }
923
- });
1331
+ // The file is already on disk from the unzip; the release commits +
1332
+ // pushes it.
1333
+ await withLock(id, user, relFile, async () => undefined);
924
1334
  }
925
1335
  res.json(result);
926
1336
  }
@@ -932,11 +1342,17 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
932
1342
  const id = authenticated(req, res);
933
1343
  if (id === null)
934
1344
  return;
935
- const filePath = req.query.path;
936
- if (!filePath) {
1345
+ const requested = requestPath(req.query.path);
1346
+ if (requested === null) {
937
1347
  res.status(400).json({ error: 'path query parameter is required' });
938
1348
  return;
939
1349
  }
1350
+ // An upload with no folder lands at the repository ROOT inside the
1351
+ // checkout, not beside it — eight staging test documents sat beside it
1352
+ // because this route took the path as written.
1353
+ const filePath = inRepo(res, requested);
1354
+ if (filePath === null)
1355
+ return;
940
1356
  // `?defer=true` is the bulk-upload caller's signal to suppress the
941
1357
  // per-file `fs-tree-changed` SSE — the caller emits one combined
942
1358
  // refresh at end-of-burst via `POST /flush`. Pushing isn't deferred
@@ -964,17 +1380,14 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
964
1380
  chunks.push(buf);
965
1381
  }
966
1382
  const data = Buffer.concat(chunks);
967
- // Creator read grant, mirroring PUT /file: seed a new subtree's
968
- // access.md first, or fold the grant into an uploaded markdown file's
969
- // frontmatter. Binary uploads into a pre-existing unreadable folder
970
- // can't carry a per-file grant (no frontmatter) and proceed ungranted.
1383
+ // Creator read grant, mirroring PUT /file: an upload that starts a new
1384
+ // folder at a root seeds that folder's access.md first. An upload into
1385
+ // a folder the caller cannot read never gets this far — the lock's read
1386
+ // gate refuses it.
971
1387
  const plan = await creatorAccess.planForCreate(id, user, filePath, 'file');
972
1388
  if (plan?.kind === 'seed-access-md')
973
1389
  await seedCreatorAccessMd(id, user, plan);
974
- const toWrite = plan?.kind === 'frontmatter'
975
- ? Buffer.from(plan.apply(data.toString('utf8')), 'utf8')
976
- : data;
977
- await withLock(id, user, filePath, () => workspaceService.writeFileBinary(id, filePath, toWrite), defer ? { skipFsTreeEvent: true } : undefined);
1390
+ await withLock(id, user, filePath, () => workspaceService.writeFileBinary(id, filePath, data), defer ? { skipFsTreeEvent: true } : undefined);
978
1391
  res.json({ status: 'uploaded' });
979
1392
  }
980
1393
  catch (err) {
@@ -987,6 +1400,8 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
987
1400
  // happens out of band in the worker, so this route's only remaining
988
1401
  // responsibility is to emit the single combined `fs-tree-changed`
989
1402
  // event the bulk caller suppressed per file.
1403
+ // It takes NO path: there is nothing for the normaliser to place, which is
1404
+ // why this route is the one on the surface that does not call it.
990
1405
  router.post('/workspace/:id/flush', async (req, res) => {
991
1406
  const id = authenticated(req, res);
992
1407
  if (id === null)
@@ -1011,67 +1426,63 @@ export function createWorkspaceRoutes(workspaceService, authService, workflowSer
1011
1426
  * a recursive directory delete into per-file lock+release cycles so each
1012
1427
  * deletion lands as its own one-file change.
1013
1428
  *
1014
- * Skips `.git` to avoid trying to commit the internal git index when a
1015
- * caller targets it accidentally. Returns paths in stable lexical order
1016
- * for predictable commit sequencing.
1429
+ * Skips the git folder, in any spelling (`hasGitInternalsSegment`), so a
1430
+ * folder delete never enumerates — or deletes — the repository's git data. Returns paths in the walk's order — stable
1431
+ * and lexical — for predictable commit sequencing. A link counts as a file:
1432
+ * it is deleted as one, never followed. A folder that cannot be listed is
1433
+ * the delete's error: an enumeration with a hole in it would delete what it
1434
+ * saw and report success over what it did not.
1017
1435
  */
1018
- async function enumerateFilesUnder(absoluteDir, workspaceDir) {
1436
+ async function enumerateFilesUnder(disk, absoluteDir, workspaceDir) {
1019
1437
  const out = [];
1020
- async function walk(dir) {
1021
- let entries;
1022
- try {
1023
- entries = await fs.readdir(dir, { withFileTypes: true });
1024
- }
1025
- catch {
1026
- return;
1027
- }
1028
- entries.sort((a, b) => a.name.localeCompare(b.name));
1029
- for (const entry of entries) {
1030
- if (entry.name === '.git' && entry.isDirectory())
1031
- continue;
1032
- const child = path.join(dir, entry.name);
1033
- if (entry.isDirectory()) {
1034
- await walk(child);
1035
- }
1036
- else {
1037
- out.push(path.relative(workspaceDir, child).replace(/\\/g, '/'));
1038
- }
1039
- }
1438
+ const relOf = (dir, name) => path.relative(workspaceDir, path.join(absoluteDir, dir, name)).replace(/\\/g, '/');
1439
+ try {
1440
+ await disk.walk(absoluteDir, { skip: (e) => hasGitInternalsSegment(e.name), unreadable: 'throw' }, [
1441
+ {
1442
+ onFile: (dir, name) => void out.push(relOf(dir, name)),
1443
+ onOther: (dir, e) => void out.push(relOf(dir, e.name)),
1444
+ },
1445
+ ]);
1446
+ }
1447
+ catch (err) {
1448
+ // The operator's line carries the errno and the path; the caller's answer
1449
+ // names only the folder they asked about — an OS message would leak the
1450
+ // server's spelling of the workspace, and would not help them anyway.
1451
+ // Both halves of the log line are user- or disk-controlled text, so both
1452
+ // go through `printable`: a folder name or an OS message carrying a
1453
+ // control character must not steer the terminal or forge a log line.
1454
+ const folder = path.relative(workspaceDir, absoluteDir).replace(/\\/g, '/');
1455
+ const reason = err instanceof Error ? err.message : String(err);
1456
+ logger('workspace').error(`could not list every file under ${printable(folder)} for a delete: ${printable(reason)}`);
1457
+ throw Object.assign(new Error(`Could not list every file under "${folder}" — nothing was deleted. Try again, or ask an admin.`), {
1458
+ status: 500,
1459
+ });
1040
1460
  }
1041
- await walk(absoluteDir);
1042
1461
  return out;
1043
1462
  }
1044
1463
  /**
1045
- * Recursively remove empty directories under `absoluteDir`, bottom-up, then
1046
- * remove `absoluteDir` itself if it ends up empty. A directory is removed only
1047
- * if it contains nothing at the moment it's visited, so any file a concurrent
1048
- * writer dropped in mid-delete — and every parent directory on its path —
1049
- * survives. `.git` is left alone. Used after a recursive folder delete to
1050
- * sweep the leftover empty-folder shells off disk so the file tree (which
1051
- * lists on-disk directories, not just tracked files) stops showing the
1052
- * deleted container.
1464
+ * The error a failed folder keep answers: the original one — its type,
1465
+ * status and payload decide the response — with `message` saying what
1466
+ * landed and what did not. Anything that is not an Error becomes one.
1467
+ */
1468
+ function withKeptFolderContext(err, message) {
1469
+ if (!(err instanceof Error))
1470
+ return new Error(message);
1471
+ err.message = message;
1472
+ return err;
1473
+ }
1474
+ /**
1475
+ * Whether a folder exists and holds nothing. A folder that is gone answers
1476
+ * false: it was deleted explicitly, and keeping it would bring it back.
1053
1477
  */
1054
- async function removeEmptyDirs(absoluteDir) {
1055
- let entries;
1478
+ async function isEmptyFolder(absoluteDir) {
1056
1479
  try {
1057
- entries = await fs.readdir(absoluteDir, { withFileTypes: true });
1480
+ return (await fs.readdir(absoluteDir)).length === 0;
1058
1481
  }
1059
- catch {
1060
- // Already gone (raced delete) — nothing to do.
1061
- return;
1062
- }
1063
- for (const entry of entries) {
1064
- if (entry.name === '.git' && entry.isDirectory())
1065
- continue;
1066
- if (entry.isDirectory()) {
1067
- await removeEmptyDirs(path.join(absoluteDir, entry.name));
1068
- }
1069
- }
1070
- // Re-read after pruning children: a subdir we just emptied now lets this
1071
- // dir become removable too. Any surviving file (or `.git`) keeps it.
1072
- const remaining = await fs.readdir(absoluteDir);
1073
- if (remaining.length === 0) {
1074
- await fs.rmdir(absoluteDir);
1482
+ catch (err) {
1483
+ if (isAbsence(err))
1484
+ return false;
1485
+ throw err;
1075
1486
  }
1076
1487
  }
1077
1488
  //# sourceMappingURL=workspace.routes.js.map