@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,12 +1,19 @@
1
1
  import fs from 'node:fs/promises';
2
+ import { logger } from '../../shared/logging.js';
3
+ const log = logger('workspace');
2
4
  import path from 'node:path';
3
- import { execFile } from 'node:child_process';
4
- import { promisify } from 'node:util';
5
5
  import AdmZip from 'adm-zip';
6
- import { assertValidRelativePath, validateFilename, DEFAULT_BRANCH } from '@bevel-software/platform-shared';
7
- import { BevelIgnoreStack } from './bevel-ignore.js';
6
+ import { AsyncLocalStorage } from 'node:async_hooks';
7
+ import { validateRelativePath, validateFilename, DEFAULT_BRANCH, FOLDER_PLACEHOLDER, isFolderPlaceholder, entryExistsMessage, } from '@bevel-software/platform-shared';
8
+ import { isAbsence } from '../../shared/fs.contract.js';
9
+ import { DestinationTakenError, inspectDestination, renameNoReplace, } from '../../shared/rename-no-replace.js';
10
+ import { NodeGitRunner } from '../workflow/git/node-git-runner.js';
11
+ import { assertWithinDirectory } from '../../shared/path-containment.js';
12
+ import { assertRepoRootNameFree, normalizeWorkspacePath } from '../kb-fs/repo-path.js';
13
+ import { assertNoGitInternalsSegment, assertNotGitInternals, hasGitInternalsSegment } from '../../shared/git-internals.js';
14
+ import { GitInternalsError, PathNotFoundError, PathTraversalError, UnreadableArchiveError, WorkflowValidationError, } from '../../shared/domain-errors.js';
8
15
  import { workspaceIdForBranch, branchForWorkspaceId } from '../../shared/workspace-id.js';
9
- import { RemoteBranchGoneError, WorkflowDomainError, isMissingRemoteBranchFailure, } from '../../shared/domain-errors.js';
16
+ import { BranchNotFoundError, RemoteBranchGoneError, WorkflowDomainError, isMissingRemoteBranchFailure, } from '../../shared/domain-errors.js';
10
17
  import { assertValidBranchName } from '../kb-fs/branch-name.js';
11
18
  import { cloneCredentialArgs, cloneCredentialConfigArgs, cloneTrackingConfigArgs, SAFE_IMPLICIT_FETCH_ARGS, } from '../kb-fs/clone-config.js';
12
19
  // Zip-bomb guardrails. The upload cap is 50 MB compressed; even a generous
@@ -33,7 +40,39 @@ export class FolderTooLargeError extends Error {
33
40
  this.name = 'FolderTooLargeError';
34
41
  }
35
42
  }
36
- const execFileAsync = promisify(execFile);
43
+ /**
44
+ * A rename or move would land on a name something else already has. 409, like
45
+ * every other precondition the caller has to clear first: nothing was moved,
46
+ * and the entry already there is untouched.
47
+ *
48
+ * Lives here rather than in `shared/domain-errors.ts` because the sentence is
49
+ * built by `entryExistsMessage` and that file deliberately imports no VALUE
50
+ * from `@bevel-software/platform-shared`, only a type. It still extends
51
+ * `WorkflowDomainError`, so the routes' `sendError` maps it to 409 with
52
+ * `{ kind: 'entry-exists', … }` without knowing this class exists.
53
+ */
54
+ export class EntryExistsError extends WorkflowDomainError {
55
+ entryKind;
56
+ destination;
57
+ kind = 'entry-exists';
58
+ constructor(entryKind, destination) {
59
+ super(entryExistsMessage(entryKind, destination), 409, {
60
+ kind: 'entry-exists',
61
+ entryKind,
62
+ destination,
63
+ });
64
+ this.entryKind = entryKind;
65
+ this.destination = destination;
66
+ this.name = 'EntryExistsError';
67
+ }
68
+ }
69
+ /**
70
+ * A floor under the git deadline for a clone: a first clone of a large
71
+ * knowledge base over a slow link legitimately takes minutes, and the port's
72
+ * default is sized for a running deployment's operations. A configured
73
+ * ceiling above this applies as configured.
74
+ */
75
+ const CLONE_TIMEOUT_MS = 600_000;
37
76
  /**
38
77
  * Identity stamped on the per-branch clone's git config. Every workflow
39
78
  * commit overrides this with `--author=…` so the human shows up in
@@ -49,9 +88,61 @@ function redactError(err) {
49
88
  return token ? msg.replaceAll(token, '***') : msg;
50
89
  }
51
90
  const FETCH_CACHE_TTL_MS = 30_000;
91
+ /**
92
+ * The bytes a conditional write compares against. An absent file reads as the
93
+ * empty string, so `expectedContent: ''` means "expect nothing there yet".
94
+ *
95
+ * A directory at the path is a client error, not a 500 with a raw OS message:
96
+ * `GET /file` counts EISDIR as "no file at this path", so the editor that
97
+ * opened empty on that 404 would otherwise be told its save failed for an
98
+ * unexplained internal reason. Anything else (EACCES, EIO) is a real read
99
+ * failure and propagates: an unreadable file must not pass for an empty one.
100
+ */
101
+ async function readForConditionalWrite(absolutePath, relativePath) {
102
+ try {
103
+ return await fs.readFile(absolutePath, 'utf-8');
104
+ }
105
+ catch (err) {
106
+ if (isAbsence(err))
107
+ return '';
108
+ const code = err.code;
109
+ if (code === 'EISDIR') {
110
+ const notAFile = new Error(`"${relativePath}" is a directory, not a file.`);
111
+ notAFile.status = 400;
112
+ throw notAFile;
113
+ }
114
+ throw err;
115
+ }
116
+ }
117
+ /**
118
+ * The published library's path rule, refused as a TYPED 400.
119
+ *
120
+ * The library throws a bare `Error('Invalid path: …')` — it is shared with
121
+ * the frontend and cannot carry a backend domain type — and the routes used
122
+ * to recognise that by its message prefix. Converting it here, at the one
123
+ * layer that calls it, is what lets the route layer read a status off a
124
+ * class like it does for every other refusal.
125
+ */
126
+ function assertValidPath(relativePath) {
127
+ const reason = validateRelativePath(relativePath);
128
+ if (reason)
129
+ throw new WorkflowValidationError(`Invalid path: ${reason}`);
130
+ }
131
+ /** Throw the conditional write's 409 unless the file still holds `expectedContent`. */
132
+ async function assertConditionalWriteMatches(absolutePath, relativePath, expectedContent) {
133
+ const current = await readForConditionalWrite(absolutePath, relativePath);
134
+ if (current === expectedContent)
135
+ return;
136
+ const stale = new Error(`"${relativePath}" changed since you opened it. Reload it and apply your edit again, ` +
137
+ 'so the other change is not overwritten.');
138
+ stale.status = 409;
139
+ throw stale;
140
+ }
52
141
  export class WorkspaceService {
53
142
  workspacesRoot;
54
143
  kbDirName;
144
+ disk;
145
+ gitRunner;
55
146
  /** Maps branch → absolute directory path. Lazily populated. */
56
147
  branchDirs = new Map();
57
148
  /**
@@ -65,6 +156,32 @@ export class WorkspaceService {
65
156
  /** Whether the last fetch per repo SUCCEEDED — read by strict callers of `ensureRemotesFetched`. */
66
157
  lastFetchOk = new Map();
67
158
  inFlightFetches = new Map();
159
+ /**
160
+ * One mutation at a time per file, keyed by RESOLVED absolute path — so
161
+ * `x/a.md`, `./x/a.md` and `x//a.md` are one queue and not three. See
162
+ * {@link withPathTurn} for what takes a turn and why.
163
+ */
164
+ writeTurns = new Map();
165
+ /**
166
+ * The folder structure changes in flight, keyed by RESOLVED absolute folder
167
+ * path — see {@link withFolderTurn}.
168
+ */
169
+ folderTurns = new Map();
170
+ /**
171
+ * The turns the CURRENT async context already holds. Taking one it holds
172
+ * runs straight through instead of waiting for itself, which is what lets a
173
+ * caller wrap a whole read-decide-write sequence in a turn and still call
174
+ * the ordinary write inside it.
175
+ */
176
+ /**
177
+ * The turns the current async context holds, each with the tail of the
178
+ * nested mutations already launched inside it — so a re-entrant call never
179
+ * waits for the outer turn (that would hang) while two nested calls on the
180
+ * same path, launched side by side inside one turn, still run one at a
181
+ * time. The map is inherited by every child context; the entry object is
182
+ * shared, which is what makes siblings queue on one tail.
183
+ */
184
+ heldTurns = new AsyncLocalStorage();
68
185
  /**
69
186
  * Late-bound by the composition root — DiffService depends on
70
187
  * WorkspaceService for path resolution.
@@ -87,9 +204,18 @@ export class WorkspaceService {
87
204
  * dozen other services at construction, so making it live only here would
88
205
  * buy an inconsistency rather than a feature.
89
206
  */
90
- kbRepoUrl, kbDirName, gitUsername = 'x-access-token') {
207
+ kbRepoUrl, kbDirName, disk, gitUsername = 'x-access-token',
208
+ /**
209
+ * How git is run — see `shared/git.contract.ts`. The composition root
210
+ * passes the one runner carrying the deployment's deadline; the default
211
+ * is the same runner on its default deadline, for a directly constructed
212
+ * service.
213
+ */
214
+ gitRunner = new NodeGitRunner()) {
91
215
  this.workspacesRoot = workspacesRoot;
92
216
  this.kbDirName = kbDirName;
217
+ this.disk = disk;
218
+ this.gitRunner = gitRunner;
93
219
  this.kbRepoUrl = typeof kbRepoUrl === 'function' ? kbRepoUrl : () => kbRepoUrl;
94
220
  this.gitUsername = typeof gitUsername === 'function' ? gitUsername : () => gitUsername;
95
221
  }
@@ -101,6 +227,78 @@ export class WorkspaceService {
101
227
  setWorkspaceClonedListener(listener) {
102
228
  this.onWorkspaceCloned = listener;
103
229
  }
230
+ /**
231
+ * Every branch name this process has been shown to be real: named by a
232
+ * listing of origin's branches, or registered as a clone of ours. Together
233
+ * with the clones still on disk this is the whole of what the platform
234
+ * KNOWS about branch names — and the difference between "your link is to a
235
+ * branch that was deleted" (410) and "there is no branch by that name"
236
+ * (404).
237
+ *
238
+ * Names ACCUMULATE and are never removed, and the two halves of that are
239
+ * the same rule: the evidence we need is evidence about a branch that is
240
+ * no longer there. Replacing the set on each listing would forget a
241
+ * deleted branch at the moment its deletion becomes the thing we have to
242
+ * report; evicting an old name would silently turn a long-dead branch back
243
+ * into "never existed"; dropping a clone's name when its workspace is swept
244
+ * would do that to a branch we cloned ourselves. So nothing here is ever
245
+ * dropped. Growth is one short string per distinct branch name origin has
246
+ * ever shown this process — kilobytes for any real repository, and reset
247
+ * by a restart.
248
+ *
249
+ * In memory only, by the spec's decision — no new persistence. A restart
250
+ * forgets a deleted branch whose clone was already retired, and that name
251
+ * then reads as unknown, which is exactly what the platform then knows.
252
+ */
253
+ branchesEverHeardOf = new Set();
254
+ /**
255
+ * Record the branch names a listing returned. Called by the git layer after
256
+ * every `listBranches` (wired in the composition root), which is the one
257
+ * place the platform ever sees origin's set of branches.
258
+ */
259
+ noteBranchesListed(names) {
260
+ for (const name of names)
261
+ this.branchesEverHeardOf.add(name);
262
+ }
263
+ /**
264
+ * Register a branch's clone directory — the only writer of `branchDirs`, so
265
+ * that every clone this process opens (fresh, adopted off disk, or handed
266
+ * over by a concurrent bootstrap) also leaves its branch name in
267
+ * `branchesEverHeardOf`. `branchDirs` is a cache of live clones and loses the
268
+ * branch when its workspace is swept; the name left behind is the record
269
+ * that the branch was real, and outlives the clone.
270
+ */
271
+ registerBranchDir(branch, workspaceDir) {
272
+ this.branchDirs.set(branch, workspaceDir);
273
+ this.branchesEverHeardOf.add(branch);
274
+ }
275
+ /**
276
+ * Has the platform ever heard of this branch? A clone of it — registered in
277
+ * this process, remembered from one, or sitting on disk from a previous one
278
+ * — or a listing that named it. Only asked on the failure path, where
279
+ * origin has just answered that it has no such ref: a yes means the branch
280
+ * was deleted (410), a no means it never existed as far as the platform is
281
+ * concerned (404).
282
+ */
283
+ async hasHeardOfBranch(branch) {
284
+ if (this.branchDirs.has(branch))
285
+ return true;
286
+ if (this.branchesEverHeardOf.has(branch))
287
+ return true;
288
+ try {
289
+ await fs.access(path.join(this.workspacesRoot, workspaceIdForBranch(branch), this.kbDirName, '.git'));
290
+ return true;
291
+ }
292
+ catch (err) {
293
+ // Only "there is nothing there" answers the question. A permission or
294
+ // I/O fault means we could not look, and a storage failure of ours must
295
+ // not be reported to the caller as a branch that never existed: it
296
+ // stays the 500 the operator reads.
297
+ if (isAbsence(err))
298
+ return false;
299
+ throw err;
300
+ }
301
+ }
104
302
  /**
105
303
  * Build git clone args. Credentials go in as `clone --config`, which both
106
304
  * authenticates this clone and leaves the helper in the new repo's config for
@@ -218,8 +416,7 @@ export class WorkspaceService {
218
416
  entries = await fs.readdir(this.workspacesRoot, { withFileTypes: true });
219
417
  }
220
418
  catch (err) {
221
- const code = err?.code;
222
- if (code === 'ENOENT' || code === 'ENOTDIR')
419
+ if (isAbsence(err))
223
420
  return [];
224
421
  throw err;
225
422
  }
@@ -244,8 +441,7 @@ export class WorkspaceService {
244
441
  await fs.access(path.join(this.workspacesRoot, entry.name, this.kbDirName, '.git'));
245
442
  }
246
443
  catch (err) {
247
- const code = err?.code;
248
- if (code === 'ENOENT' || code === 'ENOTDIR')
444
+ if (isAbsence(err))
249
445
  continue;
250
446
  cloned.push({
251
447
  id: entry.name,
@@ -271,22 +467,24 @@ export class WorkspaceService {
271
467
  * block a new branch from bootstrapping.
272
468
  */
273
469
  async runClone(targetDir, branch, referenceRepo) {
470
+ // Long enough for a first clone, whatever the configured ceiling says.
471
+ const timeoutMs = Math.max(this.gitRunner.defaultTimeoutMs, CLONE_TIMEOUT_MS);
274
472
  if (referenceRepo) {
275
473
  try {
276
- await execFileAsync('git', this.gitCloneArgs(targetDir, branch, referenceRepo), {
277
- env: { ...process.env },
474
+ await this.gitRunner.run(path.dirname(targetDir), this.gitCloneArgs(targetDir, branch, referenceRepo), {
475
+ timeoutMs,
278
476
  });
279
477
  return;
280
478
  }
281
479
  catch (err) {
282
- console.warn(`[workspace] referenced clone for "${branch}" failed, retrying without reference:`, redactError(err));
480
+ log.warn(`referenced clone for "${branch}" failed, retrying without reference:`, {
481
+ detail: redactError(err),
482
+ });
283
483
  // Clear any partial output so the retry clones into a clean dir.
284
484
  await fs.rm(targetDir, { recursive: true, force: true }).catch(() => { });
285
485
  }
286
486
  }
287
- await execFileAsync('git', this.gitCloneArgs(targetDir, branch), {
288
- env: { ...process.env },
289
- });
487
+ await this.gitRunner.run(path.dirname(targetDir), this.gitCloneArgs(targetDir, branch), { timeoutMs });
290
488
  }
291
489
  /**
292
490
  * Get-or-create the per-branch workspace. Idempotent — repeat calls for
@@ -333,7 +531,7 @@ export class WorkspaceService {
333
531
  // pulls it. Once per branch per process — the cached paths above return
334
532
  // before reaching here.
335
533
  await this.normalizeCloneConfig(repoDir, branch);
336
- this.branchDirs.set(branch, workspaceDir);
534
+ this.registerBranchDir(branch, workspaceDir);
337
535
  return this.buildWorkspaceInfo(branch, workspaceDir);
338
536
  }
339
537
  catch {
@@ -351,7 +549,7 @@ export class WorkspaceService {
351
549
  const existingBootstrap = this.inFlightBootstraps.get(branch);
352
550
  if (existingBootstrap) {
353
551
  await existingBootstrap;
354
- this.branchDirs.set(branch, workspaceDir);
552
+ this.registerBranchDir(branch, workspaceDir);
355
553
  return this.buildWorkspaceInfo(branch, workspaceDir);
356
554
  }
357
555
  this.inFlightBootstraps.set(branch, bootstrap);
@@ -360,7 +558,7 @@ export class WorkspaceService {
360
558
  // naturally; seeding the remote is the KB startup phase's job, at boot.
361
559
  await fs.mkdir(workspaceDir, { recursive: true });
362
560
  await this.cloneProcessMapForBranch(workspaceDir, branch);
363
- this.branchDirs.set(branch, workspaceDir);
561
+ this.registerBranchDir(branch, workspaceDir);
364
562
  resolveBootstrap();
365
563
  }
366
564
  catch (err) {
@@ -442,13 +640,13 @@ export class WorkspaceService {
442
640
  async normalizeCloneConfig(repoDir, branch) {
443
641
  try {
444
642
  for (const args of cloneTrackingConfigArgs(branch)) {
445
- await execFileAsync('git', ['-C', repoDir, ...args]);
643
+ await this.gitRunner.run(repoDir, args);
446
644
  }
447
645
  }
448
646
  catch (err) {
449
647
  // One line per branch, not per key: every key writes to the same
450
648
  // `.git/config`, so what fails for one fails for all.
451
- console.warn(`[workspace] could not normalize the config of the "${branch}" clone:`, redactError(err));
649
+ log.warn(`could not normalize the config of the "${branch}" clone:`, { detail: redactError(err) });
452
650
  }
453
651
  await this.stampCredentialHelper(repoDir, branch);
454
652
  }
@@ -487,23 +685,25 @@ export class WorkspaceService {
487
685
  // re-clone that fails on the same validation — masking the real cause.
488
686
  // Contain it as a loud non-stamp instead; `normalizeCloneConfig`'s
489
687
  // never-throws contract stays true.
490
- console.warn(`[workspace] refusing to stamp the credential helper of the "${branch}" clone:`, redactError(err));
688
+ log.warn(`refusing to stamp the credential helper of the "${branch}" clone:`, { detail: redactError(err) });
491
689
  this.stampedCredentialFingerprint.delete(branch);
492
690
  return;
493
691
  }
494
692
  let stamped = true;
495
693
  for (const args of argLists) {
496
694
  try {
497
- await execFileAsync('git', ['-C', repoDir, ...args]);
695
+ await this.gitRunner.run(repoDir, args);
498
696
  }
499
697
  catch (err) {
500
698
  // `--unset-all` with no matching value (exit 5) is the expected no-op
501
699
  // for a clone that never carried an app helper; anything else is a
502
700
  // real failure.
503
- const code = err?.code;
701
+ const code = err?.exitCode;
504
702
  if (!(args.includes('--unset-all') && code === 5)) {
505
703
  stamped = false;
506
- console.warn(`[workspace] could not stamp the credential helper of the "${branch}" clone:`, redactError(err));
704
+ log.warn(`could not stamp the credential helper of the "${branch}" clone:`, {
705
+ detail: redactError(err),
706
+ });
507
707
  }
508
708
  }
509
709
  }
@@ -539,20 +739,23 @@ export class WorkspaceService {
539
739
  // propagate to the caller instead of getting silently swallowed by an
540
740
  // outer catch (the previous shape let an inner rethrow fall through
541
741
  // to a clone-into-non-empty-dir, producing a confusing 500).
542
- let targetExists = true;
543
- try {
544
- await fs.access(targetDir);
545
- }
546
- catch (err) {
547
- const code = err?.code;
548
- if (code === 'ENOENT' || code === 'ENOTDIR') {
549
- targetExists = false;
550
- }
551
- else {
552
- throw err;
553
- }
742
+ //
743
+ // A FILE squatting the clone's name is refused outright, never wiped:
744
+ // the recovery below exists for a crashed bootstrap's directory shell,
745
+ // and a regular file here is a state a human put the deployment in — so
746
+ // it is named and stopped, the same stance the startup phase takes on a
747
+ // squatted reserved root. (Links are followed: one clone mounted
748
+ // elsewhere is the operator's business, as it is at the workspace root.)
749
+ const target = await fs.stat(targetDir).catch((err) => {
750
+ if (isAbsence(err))
751
+ return null;
752
+ throw err;
753
+ });
754
+ if (target !== null && !target.isDirectory()) {
755
+ throw new Error(`The clone path "${this.kbDirName}" in this workspace exists but is not a directory. ` +
756
+ 'Remove or rename it — the platform requires this name to be the knowledge-base clone.');
554
757
  }
555
- if (targetExists) {
758
+ if (target !== null) {
556
759
  // The directory exists, but a previous bootstrap may have crashed
557
760
  // mid-clone — leaving a directory shell without `.git`. Treat
558
761
  // anything missing `.git` as not-cloned and re-clone. (Can't be
@@ -570,10 +773,10 @@ export class WorkspaceService {
570
773
  // re-clone". A transient EACCES / EIO / EBUSY must NOT delete
571
774
  // what might be a perfectly valid repo whose `.git` we couldn't
572
775
  // read this moment — surface the error so the caller can retry.
573
- const code = err?.code;
574
- if (code !== 'ENOENT' && code !== 'ENOTDIR') {
776
+ // (`targetDir` is known to be a directory here, so ENOTDIR can only
777
+ // mean `.git` itself vanished under us — an absence either way.)
778
+ if (!isAbsence(err))
575
779
  throw err;
576
- }
577
780
  }
578
781
  if (alreadyCloned) {
579
782
  // We don't auto-pull because that could clobber another user's
@@ -593,19 +796,18 @@ export class WorkspaceService {
593
796
  await this.runClone(targetDir, branch, reference);
594
797
  // Persist longpaths in the cloned repo's config so subsequent
595
798
  // checkouts also honor it.
596
- await execFileAsync('git', ['-C', targetDir, 'config', 'core.longpaths', 'true']);
799
+ await this.gitRunner.run(targetDir, ['config', 'core.longpaths', 'true']);
597
800
  // Generic bot identity for the clone — every workflow commit
598
801
  // overrides via `--author=…` so the real human shows up in
599
802
  // `git log`. This is purely the fallback committer.
600
- await execFileAsync('git', ['-C', targetDir, 'config', 'user.name', BOT_NAME]);
601
- await execFileAsync('git', ['-C', targetDir, 'config', 'user.email', BOT_EMAIL]);
803
+ await this.gitRunner.run(targetDir, ['config', 'user.name', BOT_NAME]);
804
+ await this.gitRunner.run(targetDir, ['config', 'user.email', BOT_EMAIL]);
602
805
  // Pin the clone to exactly one fetch refspec and one upstream ref for
603
806
  // this branch. `git clone -b` already produces that shape; stamping it
604
807
  // explicitly means the shape is asserted rather than assumed, and the
605
808
  // same call is what repairs an existing clone that drifted.
606
809
  await this.normalizeCloneConfig(targetDir, branch);
607
- console.log(`[workspace] Cloned ${this.kbDirName} for branch "${branch}"` +
608
- (reference ? ' (referenced a sibling clone)' : ''));
810
+ log.info(`Cloned ${this.kbDirName} for branch "${branch}"` + (reference ? ' (referenced a sibling clone)' : ''));
609
811
  // A fresh clone has already downloaded every ref — tell the git layer
610
812
  // so the first `listBranches` skips the redundant implicit `git fetch`.
611
813
  // Isolated from the clone-rollback `catch` below: a misbehaving listener
@@ -614,7 +816,9 @@ export class WorkspaceService {
614
816
  this.onWorkspaceCloned?.(workspaceIdForBranch(branch));
615
817
  }
616
818
  catch (listenerErr) {
617
- console.error(`[workspace] onWorkspaceCloned listener failed for branch "${branch}":`, redactError(listenerErr));
819
+ log.error(`onWorkspaceCloned listener failed for branch "${branch}":`, {
820
+ detail: redactError(listenerErr),
821
+ });
618
822
  }
619
823
  }
620
824
  catch (err) {
@@ -622,15 +826,33 @@ export class WorkspaceService {
622
826
  // Roll back partial state so the next bootstrap retries cleanly.
623
827
  await fs.rm(targetDir, { recursive: true, force: true }).catch(() => { });
624
828
  // A branch origin does not have is a fact about the branch, not a
625
- // failure of ours: the host deleted it (and the sync retired the
626
- // clone), or the link was to a branch that never existed. Typed, so the
627
- // routes answer 410 with the branch named and the browser can say "this
628
- // branch no longer exists" instead of "something went wrong".
829
+ // failure of ours — but WHICH fact depends on whether the platform ever
830
+ // knew the name. A branch it cloned or listed and origin no longer has
831
+ // was deleted (410, "this branch no longer exists"); a name it has
832
+ // never seen is simply not a branch (404), and saying "no longer exists
833
+ // on the remote" to a typo tells the reader it once did.
629
834
  if (isMissingRemoteBranchFailure(redacted)) {
630
- console.log(`[workspace] branch "${branch}" does not exist on origin — nothing to clone`);
631
- throw new RemoteBranchGoneError(branch);
835
+ let everHeardOf;
836
+ try {
837
+ everHeardOf = await this.hasHeardOfBranch(branch);
838
+ }
839
+ catch (probeErr) {
840
+ // We could not read our own storage, so we cannot say which of the
841
+ // two stories this is — and guessing either one states something
842
+ // about the branch that we do not know. Our failure, so: 500.
843
+ log.error(`Could not tell whether branch "${branch}" was ever known here:`, {
844
+ detail: redactError(probeErr),
845
+ });
846
+ throw new Error(`Failed to clone process map: ${redacted}`);
847
+ }
848
+ if (everHeardOf) {
849
+ log.info(`branch "${branch}" is gone from origin — nothing to clone`);
850
+ throw new RemoteBranchGoneError(branch);
851
+ }
852
+ log.info(`branch "${branch}" has never existed here — nothing to clone`);
853
+ throw new BranchNotFoundError(branch);
632
854
  }
633
- console.error(`[workspace] Failed to clone for branch "${branch}":`, redacted);
855
+ log.error(`Failed to clone for branch "${branch}":`, { detail: redacted });
634
856
  throw new Error(`Failed to clone process map: ${redacted}`);
635
857
  }
636
858
  }
@@ -656,20 +878,123 @@ export class WorkspaceService {
656
878
  }
657
879
  async listFiles(workspaceId, readFilter) {
658
880
  const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
659
- return this.buildFileTree(workspaceDir, workspaceDir, BevelIgnoreStack.empty(), readFilter);
881
+ return this.buildFileTree(workspaceDir, workspaceDir, readFilter);
882
+ }
883
+ /** The repository inside a workspace directory: `<workspaceDir>/<kbDirName>`. */
884
+ repoRoot(workspaceDir) {
885
+ return path.join(workspaceDir, this.kbDirName);
660
886
  }
661
- async readFile(workspaceId, relativePath) {
887
+ /**
888
+ * THE resolution — the only place in this service that turns a workspace
889
+ * path into a location on disk.
890
+ *
891
+ * Two steps, and neither is optional. `normalizeWorkspacePath` places the
892
+ * path inside the repository (`KnowledgeBase/Report.md` is the page of that
893
+ * name in the checkout, not a stray beside it) and refuses `.`/`..`,
894
+ * backslashes and absolute paths outright. Then the RESOLVED path is checked
895
+ * against `<workspaceDir>/<kbDirName>` — after normalisation, because that
896
+ * is the path the operation will use, and a spelling that normalises inside
897
+ * and resolves outside is exactly the miss this check exists to catch.
898
+ *
899
+ * The git rule reads the caller's RAW spelling before either step — the
900
+ * whole rule, links resolved, not just the spelling. Those two refuse
901
+ * `.`/`..`, backslashes and absolute paths with a message that quotes the
902
+ * path back and names a corrected one, and a git path spelled any of those
903
+ * ways was answered by THAT rather than by the one sanitized refusal
904
+ * (measured in production), as was a LINK into the folder spelled the same
905
+ * ways. Which rule speaks is not the caller's to choose.
906
+ *
907
+ * Callers use the returned `relativePath` for everything downstream — the
908
+ * git-internals check, the diff baseline, the lock-free path turns, their own
909
+ * error messages — so what is checked is what is written. Nine inline
910
+ * `path.resolve(workspaceDir, …)` calls used to do this, none of them
911
+ * checking the repository root; a drift-guard test now fails if one returns.
912
+ */
913
+ async resolveInsideRepo(workspaceId, wsPath) {
662
914
  const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
915
+ await assertNotGitInternals(workspaceDir, wsPath); // The RAW spelling, first — see above.
916
+ const relativePath = normalizeWorkspacePath(wsPath, this.kbDirName);
663
917
  const absolutePath = path.resolve(workspaceDir, relativePath);
664
- this.assertWithinWorkspace(absolutePath, workspaceDir);
918
+ assertWithinDirectory(absolutePath, this.repoRoot(workspaceDir));
919
+ return { workspaceDir, relativePath, absolutePath };
920
+ }
921
+ async readFile(workspaceId, wsPath) {
922
+ const { workspaceDir, relativePath, absolutePath } = await this.resolveInsideRepo(workspaceId, wsPath);
923
+ assertNoGitInternalsSegment(relativePath);
924
+ await assertNotGitInternals(workspaceDir, relativePath, absolutePath);
925
+ await this.assertNotThroughLink(absolutePath, workspaceDir);
665
926
  return fs.readFile(absolutePath, 'utf-8');
666
927
  }
667
- async readFileBinary(workspaceId, relativePath) {
668
- const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
669
- const absolutePath = path.resolve(workspaceDir, relativePath);
670
- this.assertWithinWorkspace(absolutePath, workspaceDir);
928
+ async readFileBinary(workspaceId, wsPath) {
929
+ const { workspaceDir, relativePath, absolutePath } = await this.resolveInsideRepo(workspaceId, wsPath);
930
+ assertNoGitInternalsSegment(relativePath);
931
+ await assertNotGitInternals(workspaceDir, relativePath, absolutePath);
932
+ await this.assertNotThroughLink(absolutePath, workspaceDir);
671
933
  return fs.readFile(absolutePath);
672
934
  }
935
+ /**
936
+ * Refuse a path that reaches its file through a symbolic link — as the
937
+ * final component, or as any directory between the workspace root and it.
938
+ * `resolveInsideRepo`'s containment is lexical: `knowledge-base/notes.md`
939
+ * passes it however the name resolves, so a link the repository carries (they only
940
+ * arrive by direct git push; the app's own writes never create one) would
941
+ * let a read hand out bytes from anywhere the server process can read, and
942
+ * a write replace whatever the link points at. The rule is the plugin
943
+ * archive's: the deepest ancestor that exists must resolve to exactly its
944
+ * own spelling under the workspace's resolved root — identity, not mere
945
+ * containment — and the entry itself, when it exists, must not be a link.
946
+ * The workspace root may sit behind a link of the operator's (a mounted
947
+ * volume): both sides are resolved, so that is allowed.
948
+ *
949
+ * The same refusal as the lexical check on purpose: this IS a traversal,
950
+ * reached through a link rather than through a spelling.
951
+ */
952
+ async assertNotThroughLink(absolutePath, workspaceDir) {
953
+ const traversal = () => new PathTraversalError();
954
+ const entry = await fs.lstat(absolutePath).catch((err) => {
955
+ if (isAbsence(err))
956
+ return null;
957
+ throw err;
958
+ });
959
+ if (entry?.isSymbolicLink())
960
+ throw traversal();
961
+ // The deepest existing ancestor: a write creates the rest, under it. Each
962
+ // ancestor is lstat-ed BEFORE its absence is taken as leave to climb: a
963
+ // dangling link resolves to nothing too, and climbing past it would let
964
+ // a target created in the meantime receive the write.
965
+ let ancestor = path.dirname(absolutePath);
966
+ let realAncestor = null;
967
+ while (realAncestor === null) {
968
+ const ancestorEntry = await fs.lstat(ancestor).catch((err) => {
969
+ if (isAbsence(err))
970
+ return null;
971
+ throw err;
972
+ });
973
+ // The workspace directory itself may be a link of the operator's (one
974
+ // workspace mounted elsewhere), like the root above it; the identity
975
+ // check below still holds everything beneath it to its own spelling.
976
+ if (ancestorEntry?.isSymbolicLink() && path.resolve(ancestor) !== path.resolve(workspaceDir))
977
+ throw traversal();
978
+ realAncestor = await fs.realpath(ancestor).catch((err) => {
979
+ if (isAbsence(err))
980
+ return null;
981
+ throw err;
982
+ });
983
+ if (realAncestor === null) {
984
+ const up = path.dirname(ancestor);
985
+ if (up === ancestor)
986
+ throw traversal();
987
+ ancestor = up;
988
+ }
989
+ }
990
+ const realRoot = await fs.realpath(workspaceDir);
991
+ const expected = path.join(realRoot, path.relative(path.resolve(workspaceDir), ancestor));
992
+ const same = process.platform === 'win32'
993
+ ? realAncestor.toLowerCase() === expected.toLowerCase()
994
+ : realAncestor === expected;
995
+ if (!same)
996
+ throw traversal();
997
+ }
673
998
  /**
674
999
  * Read every `.md` file under the KB repo in one disk pass, keyed by
675
1000
  * repo-root-relative POSIX path (e.g. `Product/NodeTypes/ServiceCommitment.md`)
@@ -684,26 +1009,16 @@ export class WorkspaceService {
684
1009
  const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
685
1010
  const repoRoot = path.join(workspaceDir, this.kbDirName);
686
1011
  const files = {};
687
- const walk = async (dir, ignoreStack) => {
688
- const nextStack = await ignoreStack.extendedWith(dir);
689
- const entries = await fs.readdir(dir, { withFileTypes: true });
690
- for (const entry of entries) {
691
- if (entry.name === '.git' && entry.isDirectory())
692
- continue;
693
- const childAbs = path.join(dir, entry.name);
694
- if (nextStack.isIgnored(childAbs, entry.isDirectory()))
695
- continue;
696
- if (entry.isDirectory()) {
697
- await walk(childAbs, nextStack);
698
- continue;
699
- }
700
- if (!entry.isFile() || !entry.name.endsWith('.md'))
701
- continue;
702
- const repoPath = path.relative(repoRoot, childAbs).replace(/\\/g, '/');
703
- files[repoPath] = await fs.readFile(childAbs, 'utf-8');
704
- }
705
- };
706
- await walk(repoRoot, BevelIgnoreStack.empty());
1012
+ await this.disk.walk(repoRoot, explorerWalk(), [
1013
+ {
1014
+ async onFile(dir, name) {
1015
+ if (!name.endsWith('.md'))
1016
+ return;
1017
+ const repoPath = dir ? `${dir}/${name}` : name;
1018
+ files[repoPath] = await fs.readFile(path.join(repoRoot, dir, name), 'utf-8');
1019
+ },
1020
+ },
1021
+ ]);
707
1022
  return files;
708
1023
  }
709
1024
  /**
@@ -719,10 +1034,10 @@ export class WorkspaceService {
719
1034
  * size crosses `ZIP_DOWNLOAD_MAX_BYTES`. Buffered in memory (adm-zip has
720
1035
  * no streaming API); the cap therefore doubles as a peak-heap bound.
721
1036
  */
722
- async createFolderZip(workspaceId, relativePath) {
723
- const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
724
- const absoluteRoot = path.resolve(workspaceDir, relativePath);
725
- this.assertWithinWorkspace(absoluteRoot, workspaceDir);
1037
+ async createFolderZip(workspaceId, wsPath) {
1038
+ const { workspaceDir, relativePath, absolutePath: absoluteRoot } = await this.resolveInsideRepo(workspaceId, wsPath);
1039
+ assertNoGitInternalsSegment(relativePath);
1040
+ await assertNotGitInternals(workspaceDir, relativePath, absoluteRoot);
726
1041
  const stat = await fs.stat(absoluteRoot);
727
1042
  if (!stat.isDirectory()) {
728
1043
  throw new Error('Not a directory');
@@ -730,48 +1045,31 @@ export class WorkspaceService {
730
1045
  const zipRoot = path.basename(absoluteRoot) || 'folder';
731
1046
  const zip = new AdmZip();
732
1047
  let totalBytes = 0;
733
- const walk = async (dir, ignoreStack) => {
734
- const nextStack = await ignoreStack.extendedWith(dir);
735
- const entries = await fs.readdir(dir, { withFileTypes: true });
736
- entries.sort((a, b) => a.name.localeCompare(b.name));
737
- for (const entry of entries) {
738
- if (entry.name === '.git' && entry.isDirectory())
739
- continue;
740
- if (entry.name === '.gitkeep' && entry.isFile())
741
- continue;
742
- const childAbs = path.join(dir, entry.name);
743
- if (nextStack.isIgnored(childAbs, entry.isDirectory()))
744
- continue;
745
- if (entry.isDirectory()) {
746
- await walk(childAbs, nextStack);
747
- continue;
748
- }
749
- if (!entry.isFile())
750
- continue; // skip sockets, symlinks, etc.
751
- // Check the size cap BEFORE reading the file into memory. Reading
752
- // first would let a single hostile 2 GB file allocate the whole
753
- // buffer before the throw — defeating the cap as a peak-heap
754
- // bound. `stat.size` is an upper bound that we re-verify after
755
- // the read in case the file grew between stat and readFile.
756
- const stat = await fs.stat(childAbs);
757
- if (totalBytes + stat.size > ZIP_DOWNLOAD_MAX_BYTES) {
758
- throw new FolderTooLargeError(ZIP_DOWNLOAD_MAX_BYTES);
759
- }
760
- const data = await fs.readFile(childAbs);
761
- if (totalBytes + data.byteLength > ZIP_DOWNLOAD_MAX_BYTES) {
762
- throw new FolderTooLargeError(ZIP_DOWNLOAD_MAX_BYTES);
763
- }
764
- totalBytes += data.byteLength;
765
- // Path inside the archive: <folderName>/<relPathUnderFolder>, POSIX
766
- // separators regardless of host OS so the zip extracts cleanly
767
- // on Windows/Mac/Linux alike.
768
- const relInside = path
769
- .relative(absoluteRoot, childAbs)
770
- .replace(/\\/g, '/');
771
- zip.addFile(`${zipRoot}/${relInside}`, data);
772
- }
773
- };
774
- await walk(absoluteRoot, BevelIgnoreStack.empty());
1048
+ await this.disk.walk(absoluteRoot, explorerWalk(), [
1049
+ {
1050
+ async onFile(dir, name) {
1051
+ const childAbs = path.join(absoluteRoot, dir, name);
1052
+ // Check the size cap BEFORE reading the file into memory. Reading
1053
+ // first would let a single hostile 2 GB file allocate the whole
1054
+ // buffer before the throw — defeating the cap as a peak-heap
1055
+ // bound. `stat.size` is an upper bound that we re-verify after
1056
+ // the read in case the file grew between stat and readFile.
1057
+ const stat = await fs.stat(childAbs);
1058
+ if (totalBytes + stat.size > ZIP_DOWNLOAD_MAX_BYTES) {
1059
+ throw new FolderTooLargeError(ZIP_DOWNLOAD_MAX_BYTES);
1060
+ }
1061
+ const data = await fs.readFile(childAbs);
1062
+ if (totalBytes + data.byteLength > ZIP_DOWNLOAD_MAX_BYTES) {
1063
+ throw new FolderTooLargeError(ZIP_DOWNLOAD_MAX_BYTES);
1064
+ }
1065
+ totalBytes += data.byteLength;
1066
+ // Path inside the archive: <folderName>/<relPathUnderFolder>, POSIX
1067
+ // separators regardless of host OS so the zip extracts cleanly
1068
+ // on Windows/Mac/Linux alike.
1069
+ zip.addFile(`${zipRoot}/${dir ? `${dir}/${name}` : name}`, data);
1070
+ },
1071
+ },
1072
+ ]);
775
1073
  return zip.toBuffer();
776
1074
  }
777
1075
  /**
@@ -782,6 +1080,7 @@ export class WorkspaceService {
782
1080
  */
783
1081
  async readFileAtRef(workspaceId, ref, relativePath) {
784
1082
  this.assertValidGitRef(ref);
1083
+ assertNoGitInternalsSegment(relativePath);
785
1084
  this.assertValidRepoRelativePath(relativePath);
786
1085
  const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
787
1086
  const repoDir = path.join(workspaceDir, this.kbDirName);
@@ -791,7 +1090,7 @@ export class WorkspaceService {
791
1090
  }
792
1091
  for (const candidate of candidates) {
793
1092
  try {
794
- const { stdout } = await execFileAsync('git', ['-C', repoDir, 'show', `${candidate}:${relativePath}`], { encoding: 'utf-8', maxBuffer: 16 * 1024 * 1024 });
1093
+ const { stdout } = await this.gitRunner.run(repoDir, ['show', `${candidate}:${relativePath}`]);
795
1094
  return stdout;
796
1095
  }
797
1096
  catch {
@@ -804,6 +1103,13 @@ export class WorkspaceService {
804
1103
  * Run `git fetch --prune origin` for this branch's clone. Results are
805
1104
  * cached for `FETCH_CACHE_TTL_MS`; concurrent callers share the same
806
1105
  * in-flight fetch to avoid fetch storms when e.g. the CR list poll fans out.
1106
+ *
1107
+ * `force` skips the TTL — for a caller that KNOWS the remote just moved
1108
+ * (an event-driven `?fresh=1` read, which bypasses its own cache for the
1109
+ * same reason) and would otherwise answer from refs older than the change
1110
+ * it is being asked about. It keeps the in-flight join, which is the half
1111
+ * that actually prevents storms: ten forced callers in the same tick still
1112
+ * share one fetch.
807
1113
  */
808
1114
  async ensureRemotesFetched(workspaceId, opts = {}) {
809
1115
  const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
@@ -812,7 +1118,7 @@ export class WorkspaceService {
812
1118
  // The TTL is stamped only by a SUCCESSFUL fetch, so a fresh window is
813
1119
  // itself the proof a strict caller wants.
814
1120
  const last = this.lastFetchAt.get(repoDir) ?? 0;
815
- if (now - last < FETCH_CACHE_TTL_MS)
1121
+ if (!opts.force && now - last < FETCH_CACHE_TTL_MS)
816
1122
  return;
817
1123
  const inFlight = this.inFlightFetches.get(repoDir);
818
1124
  if (inFlight) {
@@ -828,14 +1134,15 @@ export class WorkspaceService {
828
1134
  // This driver runs outside the git layer's per-workspace mutex, so it may
829
1135
  // only run the safe implicit-fetch shape — see `SAFE_IMPLICIT_FETCH_ARGS`
830
1136
  // in `kb-fs/clone-config.ts` for the full rationale.
831
- const promise = execFileAsync('git', ['-C', repoDir, ...SAFE_IMPLICIT_FETCH_ARGS], { env: { ...process.env } })
1137
+ const promise = this.gitRunner
1138
+ .run(repoDir, [...SAFE_IMPLICIT_FETCH_ARGS])
832
1139
  .then(() => {
833
1140
  this.lastFetchAt.set(repoDir, Date.now());
834
1141
  this.lastFetchOk.set(repoDir, true);
835
1142
  })
836
1143
  .catch((err) => {
837
1144
  this.lastFetchOk.set(repoDir, false);
838
- console.warn('[workspace] git fetch origin failed:', redactError(err));
1145
+ log.warn('git fetch origin failed:', { detail: redactError(err) });
839
1146
  })
840
1147
  .finally(() => {
841
1148
  if (this.inFlightFetches.get(repoDir) === promise) {
@@ -862,12 +1169,20 @@ export class WorkspaceService {
862
1169
  throw new Error('Invalid repo-relative path');
863
1170
  }
864
1171
  }
865
- async deleteFile(workspaceId, relativePath) {
866
- const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
867
- const absolutePath = path.resolve(workspaceDir, relativePath);
868
- this.assertWithinWorkspace(absolutePath, workspaceDir);
869
- await fs.rm(absolutePath, { recursive: true, force: true });
870
- await this.diffService?.markUserDeleted(workspaceId, relativePath);
1172
+ async deleteFile(workspaceId, wsPath) {
1173
+ const { workspaceDir, relativePath, absolutePath } = await this.resolveInsideRepo(workspaceId, wsPath);
1174
+ assertNoGitInternalsSegment(relativePath);
1175
+ // Under the path's turn like every other single-file mutation: a delete
1176
+ // landing between a conditional write's compare and its write would let
1177
+ // that write recreate the file the delete had just removed. The diff
1178
+ // baseline is updated inside the same turn, so two mutations of one path
1179
+ // update it in the order they landed on disk. The resolved git check runs
1180
+ // inside the turn too (see `withPathTurn`), so it cannot reorder callers.
1181
+ await this.withResolvedPathTurn(absolutePath, async () => {
1182
+ await assertNotGitInternals(workspaceDir, relativePath, absolutePath);
1183
+ await fs.rm(absolutePath, { recursive: true, force: true });
1184
+ await this.diffService?.markUserDeleted(workspaceId, relativePath);
1185
+ });
871
1186
  }
872
1187
  /**
873
1188
  * Remove the workspace's `tmp/` directory — transient run artifacts (code-mode
@@ -887,65 +1202,319 @@ export class WorkspaceService {
887
1202
  }
888
1203
  await fs.rm(tmpDir, { recursive: true, force: true });
889
1204
  }
890
- async moveEntry(workspaceId, oldRelativePath, newRelativePath) {
891
- assertValidRelativePath(newRelativePath);
892
- const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
893
- const oldAbsolute = path.resolve(workspaceDir, oldRelativePath);
894
- const newAbsolute = path.resolve(workspaceDir, newRelativePath);
895
- this.assertWithinWorkspace(oldAbsolute, workspaceDir);
896
- this.assertWithinWorkspace(newAbsolute, workspaceDir);
1205
+ /**
1206
+ * Rename or move one entry.
1207
+ *
1208
+ * Deliberately NOT under {@link withPathTurn}: a move mutates two paths, and
1209
+ * holding both turns would introduce the one shape that can deadlock here
1210
+ * (a move waiting on a target whose holder is waiting on the move's other
1211
+ * path). A rename onto a path being conditionally written is therefore
1212
+ * outside that guarantee; the workflow lock on the moved path is what
1213
+ * serializes it against the app's own editors.
1214
+ */
1215
+ async moveEntry(workspaceId, oldWsPath, newWsPath) {
1216
+ const { workspaceDir, relativePath: oldRelativePath, absolutePath: oldAbsolute } = await this.resolveInsideRepo(workspaceId, oldWsPath);
1217
+ const { relativePath: newRelativePath, absolutePath: newAbsolute } = await this.resolveInsideRepo(workspaceId, newWsPath);
1218
+ // A move and a rename are the same call, so this is where the checkout's
1219
+ // own name is reserved at the repository root for both.
1220
+ assertRepoRootNameFree(newRelativePath, this.kbDirName);
1221
+ assertNoGitInternalsSegment(oldRelativePath);
1222
+ assertNoGitInternalsSegment(newRelativePath);
1223
+ assertValidPath(newRelativePath);
1224
+ await assertNotGitInternals(workspaceDir, oldRelativePath, oldAbsolute);
1225
+ await assertNotGitInternals(workspaceDir, newRelativePath, newAbsolute);
1226
+ // Both ends: a link on the way to either end would carry the rename
1227
+ // outside. A link used AS the source is refused too — a committed link
1228
+ // is not something the app moves around, any more than reads it.
1229
+ await this.assertNotThroughLink(oldAbsolute, workspaceDir);
1230
+ await this.assertNotThroughLink(newAbsolute, workspaceDir);
1231
+ // Before `mkdir`, so a refused move leaves no empty folder behind — and
1232
+ // it is the look that produces the sentence naming what is in the way.
1233
+ await this.assertDestinationFree(oldAbsolute, newAbsolute, newRelativePath);
897
1234
  await fs.mkdir(path.dirname(newAbsolute), { recursive: true });
898
- await fs.rename(oldAbsolute, newAbsolute);
1235
+ // The move itself cannot replace anything either, so a destination that
1236
+ // appears between the look above and this line is refused rather than
1237
+ // eaten (`shared/rename-no-replace.ts`). This path takes no lock on
1238
+ // purpose — see the note above — so the atomicity has to come from the
1239
+ // filesystem call.
1240
+ try {
1241
+ await renameNoReplace(oldAbsolute, newAbsolute, newRelativePath);
1242
+ }
1243
+ catch (err) {
1244
+ if (err instanceof DestinationTakenError) {
1245
+ throw new EntryExistsError(err.entryKind, newRelativePath);
1246
+ }
1247
+ throw err;
1248
+ }
899
1249
  if (this.diffService) {
900
1250
  await this.diffService.markUserDeleted(workspaceId, oldRelativePath);
901
1251
  await this.diffService.syncFromDisk(workspaceId, newRelativePath);
902
1252
  }
903
1253
  }
904
- async writeFile(workspaceId, relativePath, content, options) {
905
- assertValidRelativePath(relativePath);
906
- const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
907
- const absolutePath = path.resolve(workspaceDir, relativePath);
908
- this.assertWithinWorkspace(absolutePath, workspaceDir);
909
- await fs.mkdir(path.dirname(absolutePath), { recursive: true });
1254
+ /**
1255
+ * Refuse a move onto a name that is taken. `fs.rename` REPLACES an existing
1256
+ * file on every platform — that is how renaming a `.docx` onto an existing
1257
+ * `.md` handed the markdown file the Word bytes — so the destination is
1258
+ * looked at first, and a move never overwrites a file or merges into a
1259
+ * folder.
1260
+ *
1261
+ * Judged by `inspectDestination`, which reads a case-only rename on a
1262
+ * case-insensitive filesystem — where `notes.md` → `Notes.md` finds the
1263
+ * SOURCE at the destination — as the rename that was asked for rather than a
1264
+ * clash, and everything else as a clash. It is the same reading the move
1265
+ * itself uses, so the sentence and the refusal cannot drift apart.
1266
+ */
1267
+ async assertDestinationFree(oldAbsolute, newAbsolute, newRelativePath) {
1268
+ const verdict = await inspectDestination(oldAbsolute, newAbsolute);
1269
+ if (verdict.state === 'taken')
1270
+ throw new EntryExistsError(verdict.kind, newRelativePath);
1271
+ }
1272
+ /**
1273
+ * Refuse a conditional write, with the same 409 {@link writeFile} throws,
1274
+ * without writing anything. The route calls this BEFORE the creator-access
1275
+ * plan: that plan can commit a seeded `access.md` under its own lock, and a
1276
+ * precondition failing after it would leave an authorization grant behind
1277
+ * for a save that never landed. The write's own compare, under its write
1278
+ * turn, stays the authoritative one; this only keeps the refusal free of
1279
+ * side effects.
1280
+ */
1281
+ async assertContentMatches(workspaceId, wsPath, expectedContent) {
1282
+ const { workspaceDir, relativePath, absolutePath } = await this.resolveInsideRepo(workspaceId, wsPath);
1283
+ assertNoGitInternalsSegment(relativePath);
1284
+ assertValidPath(relativePath);
1285
+ await assertNotGitInternals(workspaceDir, relativePath, absolutePath);
1286
+ await assertConditionalWriteMatches(absolutePath, relativePath, expectedContent);
1287
+ }
1288
+ /**
1289
+ * Run `op` as the only mutation in flight for `relativePath` in this
1290
+ * process, so a read-then-decide-then-write sequence inside it cannot have
1291
+ * another mutation land in the middle of it.
1292
+ *
1293
+ * The conditional write ({@link writeFile}'s `expectedContent`) needs that,
1294
+ * and so does a caller whose decision spans more than one call: `PUT /file`
1295
+ * checks the precondition, plans a creator-access grant that COMMITS, and
1296
+ * only then writes, and a save that lands in between would leave that grant
1297
+ * behind for a write about to be refused. Such a caller wraps the sequence
1298
+ * in a turn and the write inside it takes the same one, re-entrantly.
1299
+ *
1300
+ * The route's own per-path lock does not establish this: it deliberately
1301
+ * runs the op WITHOUT acquiring when the caller already holds the lock, so
1302
+ * two requests from that holder would interleave.
1303
+ *
1304
+ * The reach of the guarantee, exactly: within this process, one mutation at
1305
+ * a time per resolved path. ACROSS instances the workflow lock row is the
1306
+ * coordinator, and it agrees with this queue only because `PUT /file`
1307
+ * canonicalises the path before taking either (see `canonicalRelativePath`);
1308
+ * a caller that reaches the service directly with an odd spelling gets the
1309
+ * local queue and whatever lock it took itself. Case is not folded: on the
1310
+ * Linux deployment target `Foo.md` and `foo.md` are two files, and treating
1311
+ * them as one would be a worse bug than the race it would close.
1312
+ */
1313
+ async withPathTurn(workspaceId, wsPath, op) {
1314
+ const { workspaceDir, relativePath, absolutePath } = await this.resolveInsideRepo(workspaceId, wsPath);
1315
+ assertNoGitInternalsSegment(relativePath);
1316
+ assertValidPath(relativePath);
1317
+ // The resolved check reads the disk, and how long that takes depends on
1318
+ // the spelling. Run before the queue, it let a later call overtake an
1319
+ // earlier one for the same file; inside the turn, callers keep their order
1320
+ // and `op` still never runs on a path that resolves into the git folder.
1321
+ return this.withResolvedPathTurn(absolutePath, async () => {
1322
+ await assertNotGitInternals(workspaceDir, relativePath, absolutePath);
1323
+ return op();
1324
+ });
1325
+ }
1326
+ /**
1327
+ * One folder structure change at a time per SUBTREE: an explicit folder
1328
+ * delete and the placeholder that keeps an emptied folder. Two turns wait
1329
+ * for each other when one folder is, or sits inside, the other, so keeping
1330
+ * a folder can never run in the middle of deleting it (and write the
1331
+ * placeholder back into a folder whose delete already enumerated its
1332
+ * files), and a delete never starts while a folder under it is being kept.
1333
+ * Not re-entrant: take it once, and never around another folder's turn.
1334
+ *
1335
+ * The reach of the guarantee, like {@link withPathTurn}'s: within this
1336
+ * process, over this process's clones. Across instances the workflow lock
1337
+ * rows coordinate — the placeholder write takes its own path's lock, as the
1338
+ * folder delete takes each file's — and each clone's changes meet in git,
1339
+ * as any write into a folder another instance deletes already does.
1340
+ */
1341
+ async withFolderTurn(workspaceId, wsDir, op) {
1342
+ const { relativePath: relativeDir, absolutePath: absoluteDir } = await this.resolveInsideRepo(workspaceId, wsDir);
1343
+ assertValidPath(relativeDir);
1344
+ const overlaps = (held) => held === absoluteDir ||
1345
+ held.startsWith(absoluteDir + path.sep) ||
1346
+ absoluteDir.startsWith(held + path.sep);
1347
+ // Check-and-claim with no await in between, so two callers cannot both
1348
+ // find the subtree free.
1349
+ for (;;) {
1350
+ const waiting = [...this.folderTurns].filter(([held]) => overlaps(held)).map(([, turn]) => turn);
1351
+ if (waiting.length === 0)
1352
+ break;
1353
+ await Promise.all(waiting);
1354
+ }
1355
+ let release;
1356
+ this.folderTurns.set(absoluteDir, new Promise((resolve) => (release = resolve)));
910
1357
  try {
911
- // `wx` makes create-if-absent ATOMIC at the fs level — an exists-check
912
- // followed by a plain write would let two concurrent creators (or a
913
- // stale client whose file list predates the file) both pass the check
914
- // and silently replace each other's content.
915
- await fs.writeFile(absolutePath, content, {
916
- encoding: 'utf-8',
917
- flag: options?.failIfExists ? 'wx' : 'w',
918
- });
1358
+ return await op();
919
1359
  }
920
- catch (err) {
921
- if (err.code === 'EEXIST') {
922
- const conflict = new Error(`"${relativePath}" already exists.`);
923
- conflict.status = 409;
924
- throw conflict;
925
- }
926
- throw err;
1360
+ finally {
1361
+ this.folderTurns.delete(absoluteDir);
1362
+ release();
927
1363
  }
928
- await this.diffService?.syncFromDisk(workspaceId, relativePath);
929
1364
  }
930
- async createDirectory(workspaceId, relativePath) {
931
- assertValidRelativePath(relativePath);
932
- const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
933
- const absolutePath = path.resolve(workspaceDir, relativePath);
934
- this.assertWithinWorkspace(absolutePath, workspaceDir);
1365
+ /**
1366
+ * {@link withPathTurn} on an already-resolved path.
1367
+ *
1368
+ * A turn this context holds runs through: waiting for itself would hang.
1369
+ * What is stored is the SETTLED tail, never the caller's promise, so a
1370
+ * throwing mutation cannot wedge the queue behind it and no rejection is
1371
+ * left unhandled; the entry is dropped once it is still the tail, so the
1372
+ * map does not grow by an entry per file ever touched.
1373
+ *
1374
+ * Nesting a DIFFERENT file's turn inside one is safe here because the only
1375
+ * caller that does it is `PUT /file`, and the grant it seeds is always an
1376
+ * `access.md` at or above its target: two such sequences cannot each hold
1377
+ * what the other wants, since that would need each target to sit above the
1378
+ * other. Every other holder takes one turn and nests nothing.
1379
+ */
1380
+ async withResolvedPathTurn(absolutePath, op) {
1381
+ const held = this.heldTurns.getStore();
1382
+ const inner = held?.get(absolutePath);
1383
+ if (inner) {
1384
+ // Re-entrant: runs without waiting for the turn this context holds —
1385
+ // but behind any sibling launched inside that same turn, so a held
1386
+ // turn that fires two conditional writes at once cannot have both
1387
+ // compare the same old content and then overwrite each other.
1388
+ const turn = inner.tail.then(() => op());
1389
+ inner.tail = turn.then(() => undefined, () => undefined);
1390
+ return turn;
1391
+ }
1392
+ const run = () => this.heldTurns.run(new Map([...(held ?? []), [absolutePath, { tail: Promise.resolve() }]]), op);
1393
+ const previous = this.writeTurns.get(absolutePath);
1394
+ const turn = previous ? previous.then(run) : run();
1395
+ const tail = turn.then(() => undefined, () => undefined);
1396
+ this.writeTurns.set(absolutePath, tail);
1397
+ void tail.then(() => {
1398
+ if (this.writeTurns.get(absolutePath) === tail)
1399
+ this.writeTurns.delete(absolutePath);
1400
+ });
1401
+ return turn;
1402
+ }
1403
+ /**
1404
+ * Write one workspace file.
1405
+ *
1406
+ * - `failIfExists` is an exclusive create (see the `wx` note below).
1407
+ * - `expectedContent` is a conditional write: the file must still hold
1408
+ * exactly that text, or the write is refused with a 409. It is what lets
1409
+ * an editor that composed its save from a snapshot (the inline agent
1410
+ * description merges the private comments it read minutes earlier) refuse
1411
+ * rather than erase whatever landed in between. An absent file reads as
1412
+ * the empty string, so `expectedContent: ''` means "expect nothing there
1413
+ * yet" and creates it.
1414
+ *
1415
+ * The compare and the write run inside this path's write turn, so no other
1416
+ * write through this service can land between them, whatever locking the
1417
+ * caller did or skipped.
1418
+ */
1419
+ async writeFile(workspaceId, wsPath, content, options) {
1420
+ const { workspaceDir, relativePath, absolutePath } = await this.resolveInsideRepo(workspaceId, wsPath);
1421
+ // A write creates the folders on the way to it, so the reserved root name
1422
+ // is refused here too — otherwise `knowledge-base/knowledge-base/x.md`
1423
+ // would bring the unreachable folder into existence around the file.
1424
+ assertRepoRootNameFree(relativePath, this.kbDirName);
1425
+ assertNoGitInternalsSegment(relativePath);
1426
+ assertValidPath(relativePath);
1427
+ await this.withResolvedPathTurn(absolutePath, async () => {
1428
+ // Inside the turn, beside the link check (see `withPathTurn`).
1429
+ await assertNotGitInternals(workspaceDir, relativePath, absolutePath);
1430
+ await this.assertNotThroughLink(absolutePath, workspaceDir);
1431
+ // Compare BEFORE creating anything. The parent chain used to be made
1432
+ // first, so a refused save at `x/new-folder/note.md` left an empty
1433
+ // `x/new-folder/` behind in everyone's tree — for a write that never
1434
+ // happened. The compare reads the file, and an absent file reads as
1435
+ // empty whether or not its directory exists.
1436
+ if (options?.expectedContent !== undefined) {
1437
+ await assertConditionalWriteMatches(absolutePath, relativePath, options.expectedContent);
1438
+ }
1439
+ await fs.mkdir(path.dirname(absolutePath), { recursive: true });
1440
+ try {
1441
+ // `wx` makes create-if-absent ATOMIC at the fs level — an exists-check
1442
+ // followed by a plain write would let two concurrent creators (or a
1443
+ // stale client whose file list predates the file) both pass the check
1444
+ // and silently replace each other's content.
1445
+ await fs.writeFile(absolutePath, content, {
1446
+ encoding: 'utf-8',
1447
+ flag: options?.failIfExists ? 'wx' : 'w',
1448
+ });
1449
+ }
1450
+ catch (err) {
1451
+ if (err.code === 'EEXIST') {
1452
+ const conflict = new Error(`"${relativePath}" already exists.`);
1453
+ conflict.status = 409;
1454
+ throw conflict;
1455
+ }
1456
+ throw err;
1457
+ }
1458
+ // Inside the turn, so the baseline follows the order the writes landed in.
1459
+ await this.diffService?.syncFromDisk(workspaceId, relativePath);
1460
+ });
1461
+ }
1462
+ async createDirectory(workspaceId, wsPath) {
1463
+ const { workspaceDir, relativePath, absolutePath } = await this.resolveInsideRepo(workspaceId, wsPath);
1464
+ assertRepoRootNameFree(relativePath, this.kbDirName);
1465
+ assertNoGitInternalsSegment(relativePath);
1466
+ assertValidPath(relativePath);
1467
+ await assertNotGitInternals(workspaceDir, relativePath, absolutePath);
1468
+ await this.assertNotThroughLink(absolutePath, workspaceDir);
935
1469
  await fs.mkdir(absolutePath, { recursive: true });
936
1470
  const entries = await fs.readdir(absolutePath);
937
1471
  if (entries.length === 0) {
938
- await fs.writeFile(path.join(absolutePath, '.gitkeep'), '', 'utf-8');
1472
+ await fs.writeFile(path.join(absolutePath, FOLDER_PLACEHOLDER), '', 'utf-8');
939
1473
  }
940
1474
  }
941
- async writeFileBinary(workspaceId, relativePath, data) {
942
- assertValidRelativePath(relativePath);
943
- const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
944
- const absolutePath = path.resolve(workspaceDir, relativePath);
945
- this.assertWithinWorkspace(absolutePath, workspaceDir);
946
- await fs.mkdir(path.dirname(absolutePath), { recursive: true });
947
- await fs.writeFile(absolutePath, data);
948
- await this.diffService?.syncFromDisk(workspaceId, relativePath);
1475
+ /**
1476
+ * Write the empty-folder placeholder into `relativeDir` when that folder
1477
+ * exists and holds nothing; true when it wrote one. Unlike
1478
+ * {@link createDirectory} it never creates the folder: one that is gone was
1479
+ * deleted explicitly and stays gone. A folder reached through a link is
1480
+ * refused like any write through one, so the placeholder cannot land
1481
+ * outside the workspace.
1482
+ */
1483
+ async writeFolderPlaceholder(workspaceId, wsDir) {
1484
+ const { workspaceDir, relativePath: relativeDir, absolutePath: absoluteDir } = await this.resolveInsideRepo(workspaceId, wsDir);
1485
+ assertValidPath(relativeDir);
1486
+ const placeholder = path.join(absoluteDir, FOLDER_PLACEHOLDER);
1487
+ await this.assertNotThroughLink(placeholder, workspaceDir);
1488
+ let entries;
1489
+ try {
1490
+ entries = await fs.readdir(absoluteDir);
1491
+ }
1492
+ catch (err) {
1493
+ if (isAbsence(err))
1494
+ return false;
1495
+ throw err;
1496
+ }
1497
+ if (entries.length > 0)
1498
+ return false;
1499
+ await fs.writeFile(placeholder, '', { flag: 'wx' });
1500
+ return true;
1501
+ }
1502
+ async writeFileBinary(workspaceId, wsPath, data) {
1503
+ const { workspaceDir, relativePath, absolutePath } = await this.resolveInsideRepo(workspaceId, wsPath);
1504
+ assertRepoRootNameFree(relativePath, this.kbDirName);
1505
+ assertNoGitInternalsSegment(relativePath);
1506
+ assertValidPath(relativePath);
1507
+ // An upload over a path is a mutation of that path, so it takes the same
1508
+ // turn as a text write: see {@link withPathTurn}. The whole of it — the
1509
+ // parent chain too, so a folder delete serialized before this turn
1510
+ // cannot remove the parent between its creation and the write.
1511
+ await this.withResolvedPathTurn(absolutePath, async () => {
1512
+ await assertNotGitInternals(workspaceDir, relativePath, absolutePath);
1513
+ await this.assertNotThroughLink(absolutePath, workspaceDir);
1514
+ await fs.mkdir(path.dirname(absolutePath), { recursive: true });
1515
+ await fs.writeFile(absolutePath, data);
1516
+ await this.diffService?.syncFromDisk(workspaceId, relativePath);
1517
+ });
949
1518
  }
950
1519
  /**
951
1520
  * Extract a `.zip` file already present in the workspace into a destination
@@ -953,7 +1522,7 @@ export class WorkspaceService {
953
1522
  * Validation + zip-bomb guards are identical to the per-user version that
954
1523
  * preceded the workspace-per-branch migration.
955
1524
  */
956
- async unzipFile(workspaceId, zipRelativePath, destDirRelativePath,
1525
+ async unzipFile(workspaceId, zipWsPath, destDirWsPath,
957
1526
  /**
958
1527
  * Per-extracted-path write guard (ontology-session boundary). Called with
959
1528
  * each entry's workspace-relative target before it is written; if it throws,
@@ -961,28 +1530,55 @@ export class WorkspaceService {
961
1530
  * aborting the whole extraction. Omitted by non-agent / human callers.
962
1531
  */
963
1532
  guardWrite) {
964
- const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
965
- const zipAbsolute = path.resolve(workspaceDir, zipRelativePath);
966
- this.assertWithinWorkspace(zipAbsolute, workspaceDir);
1533
+ const { workspaceDir, relativePath: zipRelativePath, absolutePath: zipAbsolute } = await this.resolveInsideRepo(workspaceId, zipWsPath);
1534
+ assertNoGitInternalsSegment(zipRelativePath);
1535
+ await assertNotGitInternals(workspaceDir, zipRelativePath, zipAbsolute);
967
1536
  if (!zipRelativePath.toLowerCase().endsWith('.zip')) {
968
- throw new Error('Only .zip files can be extracted');
969
- }
970
- const inferredDest = (() => {
971
- const d = path.posix.dirname(zipRelativePath.replace(/\\/g, '/'));
972
- return d === '.' ? '' : d;
973
- })();
974
- const destRel = destDirRelativePath ?? inferredDest;
975
- if (destRel)
976
- assertValidRelativePath(destRel);
977
- const destAbsolute = destRel ? path.resolve(workspaceDir, destRel) : workspaceDir;
978
- this.assertWithinWorkspace(destAbsolute, workspaceDir);
1537
+ throw new WorkflowValidationError('Only .zip files can be extracted');
1538
+ }
1539
+ // Inferred from the NORMALISED archive path, so an archive named without
1540
+ // the prefix extracts beside itself inside the repository rather than at
1541
+ // the workspace root.
1542
+ const inferredDest = path.posix.dirname(zipRelativePath);
1543
+ const { relativePath: destRel, absolutePath: destAbsolute } = await this.resolveInsideRepo(workspaceId, destDirWsPath ?? inferredDest);
1544
+ assertValidPath(destRel);
1545
+ assertNoGitInternalsSegment(destRel);
1546
+ await assertNotGitInternals(workspaceDir, destRel, destAbsolute);
1547
+ // Neither the archive nor the destination may sit behind a link; each
1548
+ // entry's own target is checked again below, once it is known.
1549
+ await this.assertNotThroughLink(zipAbsolute, workspaceDir);
1550
+ if (destAbsolute !== this.repoRoot(workspaceDir))
1551
+ await this.assertNotThroughLink(destAbsolute, workspaceDir);
1552
+ // The archive is READ ONCE, here, and the reader is handed those bytes —
1553
+ // it is never given the path to open for itself. Two reasons, and the
1554
+ // first is why this is not a `stat` followed by `new AdmZip(path)`:
1555
+ //
1556
+ // - The zip reader cannot tell absence from corruption. On a path with
1557
+ // nothing at it `AdmZip` throws "ADM-ZIP: Invalid filename", which would
1558
+ // be dressed up as an unreadable archive (422) and blame the bytes for
1559
+ // a name that never existed. Only the read knows which it was.
1560
+ // - A separate probe answers about a DIFFERENT moment than the open. An
1561
+ // archive deleted in between passed the probe and then failed the open,
1562
+ // and the caller got the 422 the probe existed to prevent. One read is
1563
+ // one moment, so there is no in-between to lose.
1564
+ //
1565
+ // `AdmZip` from a buffer costs nothing extra: given a path it reads the
1566
+ // whole file in anyway.
1567
+ let zipBytes;
1568
+ try {
1569
+ zipBytes = await fs.readFile(zipAbsolute);
1570
+ }
1571
+ catch (err) {
1572
+ if (isAbsence(err))
1573
+ throw new PathNotFoundError(zipRelativePath);
1574
+ throw err;
1575
+ }
979
1576
  let zip;
980
1577
  try {
981
- zip = new AdmZip(zipAbsolute);
1578
+ zip = new AdmZip(zipBytes);
982
1579
  }
983
1580
  catch (err) {
984
- const msg = err instanceof Error ? err.message : String(err);
985
- throw new Error(`Could not read zip file: ${msg}`);
1581
+ throw new UnreadableArchiveError(err instanceof Error ? err.message : String(err));
986
1582
  }
987
1583
  // The destination directory is created on demand by the first allowed entry
988
1584
  // (each guarded path mkdirs its parents with `recursive: true`). Creating it
@@ -1012,6 +1608,11 @@ export class WorkspaceService {
1012
1608
  skipped.push({ path: rawName || '(empty)', reason: 'Invalid path' });
1013
1609
  continue;
1014
1610
  }
1611
+ // An archive never writes into the git folder, whatever the entry's spelling.
1612
+ if (hasGitInternalsSegment(rawName)) {
1613
+ skipped.push({ path: rawName, reason: new GitInternalsError().message });
1614
+ continue;
1615
+ }
1015
1616
  const trimmed = rawName.replace(/\/+$/, '');
1016
1617
  const segments = trimmed.split('/').filter((s) => s.length > 0);
1017
1618
  let invalidReason = null;
@@ -1051,6 +1652,38 @@ export class WorkspaceService {
1051
1652
  return false;
1052
1653
  }
1053
1654
  };
1655
+ // The symbolic-link rule, per entry: a link already on disk under the
1656
+ // destination must not redirect this entry's bytes. Reported like the
1657
+ // other per-entry refusals, so one such entry does not fail the rest.
1658
+ // The checkout's own name is reserved at the repository root here too: an
1659
+ // archive carrying it would otherwise create the one folder no workspace
1660
+ // path can name. Reported like the other per-entry refusals — one such
1661
+ // entry does not fail the rest — and asked on its own rather than inside
1662
+ // the catch below, which stays the closed list of types it was.
1663
+ const reservedName = (() => {
1664
+ try {
1665
+ assertRepoRootNameFree(relForReport, this.kbDirName);
1666
+ return null;
1667
+ }
1668
+ catch (err) {
1669
+ return err instanceof Error ? err.message : String(err);
1670
+ }
1671
+ })();
1672
+ if (reservedName !== null) {
1673
+ skipped.push({ path: rawName, reason: reservedName });
1674
+ continue;
1675
+ }
1676
+ try {
1677
+ await assertNotGitInternals(workspaceDir, trimmed, targetAbsolute);
1678
+ await this.assertNotThroughLink(targetAbsolute, workspaceDir);
1679
+ }
1680
+ catch (err) {
1681
+ if (err instanceof PathTraversalError || err instanceof GitInternalsError) {
1682
+ skipped.push({ path: rawName, reason: err.message });
1683
+ continue;
1684
+ }
1685
+ throw err;
1686
+ }
1054
1687
  if (entry.isDirectory) {
1055
1688
  if (!(await allowEntry()))
1056
1689
  continue;
@@ -1188,13 +1821,10 @@ export class WorkspaceService {
1188
1821
  }
1189
1822
  let stdout;
1190
1823
  try {
1191
- ({ stdout } = await execFileAsync('git', ['-C', repoDir, 'status', '--porcelain=v1', '-z'], {
1192
- encoding: 'utf-8',
1193
- maxBuffer: 16 * 1024 * 1024,
1194
- }));
1824
+ ({ stdout } = await this.gitRunner.run(repoDir, ['status', '--porcelain=v1', '-z']));
1195
1825
  }
1196
1826
  catch (err) {
1197
- console.warn(`[workspace] orphan scan via git status failed for ${workspaceId}:`, err instanceof Error ? err.message : err);
1827
+ log.warn(`orphan scan via git status failed for ${workspaceId}:`, { err });
1198
1828
  return [];
1199
1829
  }
1200
1830
  if (!stdout.trim())
@@ -1266,7 +1896,7 @@ export class WorkspaceService {
1266
1896
  removed.push(entry.name);
1267
1897
  }
1268
1898
  catch (err) {
1269
- console.warn(`[workspace] sweep failed for ${entry.name}:`, err instanceof Error ? err.message : err);
1899
+ log.warn(`sweep failed for ${entry.name}:`, { err });
1270
1900
  }
1271
1901
  }
1272
1902
  return { removed };
@@ -1331,7 +1961,7 @@ export class WorkspaceService {
1331
1961
  const workspaceDir = this.workspaceDirWithinRoot(workspaceId);
1332
1962
  try {
1333
1963
  await fs.access(path.join(workspaceDir, this.kbDirName, '.git'));
1334
- this.branchDirs.set(branch, workspaceDir);
1964
+ this.registerBranchDir(branch, workspaceDir);
1335
1965
  return workspaceDir;
1336
1966
  }
1337
1967
  catch {
@@ -1358,71 +1988,105 @@ export class WorkspaceService {
1358
1988
  throw new Error(`Invalid workspace ID "${workspaceId}": ${reason}`);
1359
1989
  }
1360
1990
  }
1361
- assertWithinWorkspace(absolutePath, workspaceDir) {
1362
- const resolved = path.resolve(absolutePath);
1363
- const root = path.resolve(workspaceDir);
1364
- if (!resolved.startsWith(root + path.sep) && resolved !== root) {
1365
- throw new Error('Path traversal detected');
1366
- }
1367
- }
1368
- async buildFileTree(dir, workspaceRoot, parentIgnore, readFilter) {
1369
- const name = path.basename(dir);
1370
- const relativePath = path.relative(workspaceRoot, dir).replace(/\\/g, '/');
1371
- const entries = await fs.readdir(dir, { withFileTypes: true });
1372
- const ignoreStack = await parentIgnore.extendedWith(dir);
1373
- // First pass: the entries surviving `.git`/`.gitkeep` + `.bevelignore`.
1374
- const candidates = [];
1375
- for (const entry of entries) {
1376
- // `.git/` is never user content and listing it would blow up the
1377
- // tree response. `.gitkeep` is the empty-folder placeholder — not
1378
- // worth surfacing in the file list.
1379
- if (entry.name === '.git' && entry.isDirectory())
1380
- continue;
1381
- if (entry.name === '.gitkeep' && entry.isFile())
1382
- continue;
1383
- const entryPath = path.join(dir, entry.name);
1384
- if (ignoreStack.isIgnored(entryPath, entry.isDirectory()))
1385
- continue;
1386
- candidates.push({
1387
- entry,
1388
- entryPath,
1389
- rel: path.relative(workspaceRoot, entryPath).replace(/\\/g, '/'),
1390
- });
1391
- }
1392
- // Read-permission filter: ONE batched check per directory. Drops files the
1393
- // caller can't read. A directory is kept when the caller can read it — a
1394
- // readable directory left empty after filtering stays visible, the folder
1395
- // itself is readable — OR when something readable survives beneath it: a
1396
- // grant below (a linked skill's folder opened to a plugin's readers, a
1397
- // sub-folder shared on its own) makes the folders above it the way there,
1398
- // shown as containers whose own contents stay filtered. So an unreadable
1399
- // directory is walked, not skipped, and dropped only when the walk finds
1400
- // nothing. Fail-closed: anything not explicitly readable is dropped. No
1401
- // filter → identical to the pre-feature tree (regression-safe).
1402
- const verdict = readFilter && candidates.length > 0 ? await readFilter(candidates.map((c) => c.rel)) : null;
1403
- const readable = (rel) => verdict === null || verdict.get(rel) === true;
1404
- const children = [];
1405
- for (const { entry, entryPath, rel } of candidates) {
1406
- if (entry.isDirectory()) {
1407
- const sub = await this.buildFileTree(entryPath, workspaceRoot, ignoreStack, readFilter);
1408
- if (readable(rel) || (sub.children?.length ?? 0) > 0)
1991
+ async buildFileTree(root, workspaceRoot, readFilter) {
1992
+ const relOf = (abs) => path.relative(workspaceRoot, abs).replace(/\\/g, '/');
1993
+ const top = { name: path.basename(root), relativePath: relOf(root) || '.', readable: true, unlisted: false, children: [], withheld: 0 };
1994
+ const nodes = new Map([['', top]]);
1995
+ await this.disk.walk(root, explorerWalk(), [
1996
+ {
1997
+ async onDir(rel, entries) {
1998
+ const node = nodes.get(rel);
1999
+ const abs = path.join(root, rel);
2000
+ const rels = entries.map((e) => relOf(path.join(abs, e.name)));
2001
+ // Read-permission filter: ONE batched check per directory. Drops files the
2002
+ // caller can't read. A directory is kept when the caller can read it — a
2003
+ // readable directory left empty after filtering stays visible, the folder
2004
+ // itself is readable — OR when something readable survives beneath it: a
2005
+ // grant below (a linked skill's folder opened to a plugin's readers, a
2006
+ // sub-folder shared on its own) makes the folders above it the way there,
2007
+ // shown as containers whose own contents stay filtered. So an unreadable
2008
+ // directory is walked, not skipped, and dropped only when the walk finds
2009
+ // nothing (see `finish`). Fail-closed: anything not explicitly readable is
2010
+ // dropped. No filter → identical to the pre-feature tree (regression-safe).
2011
+ const verdict = readFilter && entries.length > 0 ? await readFilter(rels) : null;
2012
+ const readable = (rel) => verdict === null || verdict.get(rel) === true;
2013
+ const unlisted = (rel) => verdict !== null && verdict.get(rel) === 'unlisted';
2014
+ entries.forEach((entry, i) => {
2015
+ const entryRel = rels[i];
2016
+ if (entry.isDirectory()) {
2017
+ const child = {
2018
+ name: entry.name,
2019
+ relativePath: entryRel,
2020
+ readable: readable(entryRel),
2021
+ unlisted: unlisted(entryRel),
2022
+ children: [],
2023
+ withheld: 0,
2024
+ };
2025
+ nodes.set(rel ? `${rel}/${entry.name}` : entry.name, child);
2026
+ node.children.push(child);
2027
+ }
2028
+ else if (readable(entryRel)) {
2029
+ node.children.push({ name: entry.name, relativePath: entryRel, type: 'file' });
2030
+ }
2031
+ else if (!unlisted(entryRel)) {
2032
+ node.withheld++;
2033
+ }
2034
+ });
2035
+ },
2036
+ },
2037
+ ]);
2038
+ /**
2039
+ * Bottom-up: drop a folder the caller may not read once nothing readable
2040
+ * turned up beneath it; sort each level. Each folder that stays carries
2041
+ * how many entries were withheld anywhere under it (a dropped folder
2042
+ * counts as one, its own subtree's count included), so a tree drawn from
2043
+ * ONE folder — the Library's, a delete's — can tell "empty" from "kept
2044
+ * from you" for that folder rather than for the listing. Only when
2045
+ * something was withheld, so an unfiltered — or all-readable — listing
2046
+ * stays exactly the tree it always was.
2047
+ */
2048
+ const finish = (node) => {
2049
+ const children = [];
2050
+ let withheld = node.withheld;
2051
+ for (const child of node.children) {
2052
+ if ('type' in child) {
2053
+ children.push(child);
2054
+ continue;
2055
+ }
2056
+ const sub = finish(child);
2057
+ withheld += sub.withheld ?? 0;
2058
+ if (child.readable || (sub.children?.length ?? 0) > 0)
1409
2059
  children.push(sub);
2060
+ else if (!child.unlisted)
2061
+ withheld++;
1410
2062
  }
1411
- else if (readable(rel)) {
1412
- children.push({ name: entry.name, relativePath: rel, type: 'file' });
1413
- }
1414
- }
1415
- children.sort((a, b) => {
1416
- if (a.type !== b.type)
1417
- return a.type === 'directory' ? -1 : 1;
1418
- return a.name.localeCompare(b.name);
1419
- });
1420
- return {
1421
- name,
1422
- relativePath: relativePath || '.',
1423
- type: 'directory',
1424
- children,
2063
+ children.sort((a, b) => {
2064
+ if (a.type !== b.type)
2065
+ return a.type === 'directory' ? -1 : 1;
2066
+ return a.name.localeCompare(b.name);
2067
+ });
2068
+ const entry = { name: node.name, relativePath: node.relativePath, type: 'directory', children };
2069
+ return withheld > 0 ? { ...entry, withheld } : entry;
1425
2070
  };
2071
+ return finish(top);
1426
2072
  }
1427
2073
  }
2074
+ /**
2075
+ * The explorer's walk of a workspace: no git internals (never user content,
2076
+ * and listing it would blow up the tree), no `.gitkeep` (the empty-folder
2077
+ * placeholder, not worth showing), `.bevelignore` honoured on the way down —
2078
+ * and a folder that cannot be listed is the request's error, not a tree with
2079
+ * a silent gap. A folder download and the whole-KB read walk the same way,
2080
+ * so they carry exactly what the explorer shows.
2081
+ */
2082
+ function explorerWalk() {
2083
+ return {
2084
+ // Any spelling of the git folder (`.GIT`, `.git.`) — the same rule the path
2085
+ // guards apply, so a download or listing never carries what they refuse —
2086
+ // and the empty-folder placeholder, which is never content.
2087
+ skip: (e) => hasGitInternalsSegment(e.name) || (isFolderPlaceholder(e.name) && e.isFile()),
2088
+ ignore: true,
2089
+ unreadable: 'throw',
2090
+ };
2091
+ }
1428
2092
  //# sourceMappingURL=workspace.service.js.map