@bevel-software/platform-core-backend 0.22.0 → 0.23.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 (325) hide show
  1. package/dist/core/core-ports.d.ts +11 -1
  2. package/dist/core/core-ports.d.ts.map +1 -1
  3. package/dist/core/core-ports.js.map +1 -1
  4. package/dist/core/create-core-server.d.ts +3 -3
  5. package/dist/core/create-core-server.d.ts.map +1 -1
  6. package/dist/core/create-core-server.js +30 -11
  7. package/dist/core/create-core-server.js.map +1 -1
  8. package/dist/core/create-core-services.d.ts +9 -2
  9. package/dist/core/create-core-services.d.ts.map +1 -1
  10. package/dist/core/create-core-services.js +112 -23
  11. package/dist/core/create-core-services.js.map +1 -1
  12. package/dist/core/lifecycle.d.ts +34 -1
  13. package/dist/core/lifecycle.d.ts.map +1 -1
  14. package/dist/core/lifecycle.js +89 -13
  15. package/dist/core/lifecycle.js.map +1 -1
  16. package/dist/core-config.d.ts +0 -12
  17. package/dist/core-config.d.ts.map +1 -1
  18. package/dist/core-config.js +11 -13
  19. package/dist/core-config.js.map +1 -1
  20. package/dist/index.d.ts +3 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/modules/access/access-control.interface.d.ts +42 -12
  25. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  26. package/dist/modules/access/access-control.service.d.ts +67 -10
  27. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  28. package/dist/modules/access/access-control.service.js +214 -35
  29. package/dist/modules/access/access-control.service.js.map +1 -1
  30. package/dist/modules/access/access.routes.d.ts.map +1 -1
  31. package/dist/modules/access/access.routes.js +13 -15
  32. package/dist/modules/access/access.routes.js.map +1 -1
  33. package/dist/modules/auth/account-admission.d.ts +58 -7
  34. package/dist/modules/auth/account-admission.d.ts.map +1 -1
  35. package/dist/modules/auth/account-admission.js +44 -1
  36. package/dist/modules/auth/account-admission.js.map +1 -1
  37. package/dist/modules/auth/account.routes.d.ts +1 -1
  38. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  39. package/dist/modules/auth/account.routes.js +61 -5
  40. package/dist/modules/auth/account.routes.js.map +1 -1
  41. package/dist/modules/auth/auth.middleware.d.ts +1 -1
  42. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  43. package/dist/modules/auth/auth.middleware.js +18 -7
  44. package/dist/modules/auth/auth.middleware.js.map +1 -1
  45. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  46. package/dist/modules/auth/auth.routes.js +8 -0
  47. package/dist/modules/auth/auth.routes.js.map +1 -1
  48. package/dist/modules/auth/auth.service.d.ts +70 -2
  49. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  50. package/dist/modules/auth/auth.service.js +176 -7
  51. package/dist/modules/auth/auth.service.js.map +1 -1
  52. package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
  53. package/dist/modules/auth/oidc-auth-provider.js +7 -2
  54. package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
  55. package/dist/modules/database/core-schema.d.ts +41 -24
  56. package/dist/modules/database/core-schema.d.ts.map +1 -1
  57. package/dist/modules/database/core-schema.js +35 -27
  58. package/dist/modules/database/core-schema.js.map +1 -1
  59. package/dist/modules/kb-fs/branch-name.d.ts.map +1 -1
  60. package/dist/modules/kb-fs/branch-name.js +12 -2
  61. package/dist/modules/kb-fs/branch-name.js.map +1 -1
  62. package/dist/modules/kb-fs/repo-path.d.ts +11 -16
  63. package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
  64. package/dist/modules/kb-fs/repo-path.js +79 -0
  65. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  66. package/dist/modules/kb-sync/kb-sync.routes.d.ts +2 -2
  67. package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
  68. package/dist/modules/kb-sync/kb-sync.routes.js +14 -3
  69. package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
  70. package/dist/modules/kb-sync/sync-auth.d.ts +3 -1
  71. package/dist/modules/kb-sync/sync-auth.d.ts.map +1 -1
  72. package/dist/modules/kb-sync/sync-auth.js +1 -1
  73. package/dist/modules/kb-sync/sync-auth.js.map +1 -1
  74. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  75. package/dist/modules/mcp/mcp-auth.middleware.js +21 -6
  76. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  77. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  78. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +3 -2
  79. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  80. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  81. package/dist/modules/settings/deployment-settings.service.js +8 -3
  82. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  83. package/dist/modules/settings/setup.routes.d.ts +52 -1
  84. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  85. package/dist/modules/settings/setup.routes.js +207 -19
  86. package/dist/modules/settings/setup.routes.js.map +1 -1
  87. package/dist/modules/skills/skills.contract.d.ts +90 -18
  88. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  89. package/dist/modules/skills/skills.contract.js +4 -1
  90. package/dist/modules/skills/skills.contract.js.map +1 -1
  91. package/dist/modules/skills/skills.service.d.ts +98 -9
  92. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  93. package/dist/modules/skills/skills.service.js +239 -37
  94. package/dist/modules/skills/skills.service.js.map +1 -1
  95. package/dist/modules/skills/skills.tools.d.ts +7 -0
  96. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  97. package/dist/modules/skills/skills.tools.js +71 -7
  98. package/dist/modules/skills/skills.tools.js.map +1 -1
  99. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  100. package/dist/modules/tool-auth/external-api-key.service.js +4 -2
  101. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  102. package/dist/modules/tool-auth/internal-token.service.d.ts +3 -3
  103. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +16 -7
  104. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  105. package/dist/modules/tool-auth/tool-auth.middleware.js +34 -12
  106. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  107. package/dist/modules/tool-helpers/tool-handler.d.ts +2 -1
  108. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  109. package/dist/modules/tool-helpers/tool-handler.js +25 -4
  110. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  111. package/dist/modules/tool-helpers/tool.contract.d.ts +3 -2
  112. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  113. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  114. package/dist/modules/tool-helpers/validate-token.js +1 -1
  115. package/dist/modules/tool-helpers/validate-token.js.map +1 -1
  116. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +98 -0
  117. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -0
  118. package/dist/modules/workflow/agent-tools/change-request-summary.js +81 -0
  119. package/dist/modules/workflow/agent-tools/change-request-summary.js.map +1 -0
  120. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  121. package/dist/modules/workflow/agent-tools/workflow.tools.js +75 -35
  122. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  123. package/dist/modules/workflow/file-lock.service.d.ts +24 -0
  124. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  125. package/dist/modules/workflow/file-lock.service.js +30 -0
  126. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  127. package/dist/modules/workflow/git/git.service.d.ts +69 -0
  128. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  129. package/dist/modules/workflow/git/git.service.js +153 -2
  130. package/dist/modules/workflow/git/git.service.js.map +1 -1
  131. package/dist/modules/workflow/pending-commits.service.d.ts +38 -0
  132. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  133. package/dist/modules/workflow/pending-commits.service.js +55 -0
  134. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  135. package/dist/modules/workflow/workflow-hooks.d.ts +54 -32
  136. package/dist/modules/workflow/workflow-hooks.d.ts.map +1 -1
  137. package/dist/modules/workflow/workflow-hooks.js +16 -1
  138. package/dist/modules/workflow/workflow-hooks.js.map +1 -1
  139. package/dist/modules/workflow/workflow.service.d.ts +60 -6
  140. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  141. package/dist/modules/workflow/workflow.service.js +111 -4
  142. package/dist/modules/workflow/workflow.service.js.map +1 -1
  143. package/dist/modules/workspace/agent-access.gate.d.ts +94 -0
  144. package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -0
  145. package/dist/modules/workspace/agent-access.gate.js +123 -0
  146. package/dist/modules/workspace/agent-access.gate.js.map +1 -0
  147. package/dist/modules/workspace/routine-write-policy.d.ts +5 -6
  148. package/dist/modules/workspace/routine-write-policy.d.ts.map +1 -1
  149. package/dist/modules/workspace/routine-write-policy.js +5 -6
  150. package/dist/modules/workspace/routine-write-policy.js.map +1 -1
  151. package/dist/modules/workspace/session-sink.d.ts +5 -5
  152. package/dist/modules/workspace/set-aside-clone.d.ts +46 -0
  153. package/dist/modules/workspace/set-aside-clone.d.ts.map +1 -0
  154. package/dist/modules/workspace/set-aside-clone.js +92 -0
  155. package/dist/modules/workspace/set-aside-clone.js.map +1 -0
  156. package/dist/modules/workspace/startup/kb-startup-runner.d.ts +59 -9
  157. package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
  158. package/dist/modules/workspace/startup/kb-startup-runner.js +65 -24
  159. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
  160. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  161. package/dist/modules/workspace/workspace.routes.js +92 -3
  162. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  163. package/dist/modules/workspace/workspace.service.d.ts +115 -5
  164. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  165. package/dist/modules/workspace/workspace.service.js +255 -25
  166. package/dist/modules/workspace/workspace.service.js.map +1 -1
  167. package/dist/modules/workspace/workspace.tools.d.ts +2 -2
  168. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  169. package/dist/modules/workspace/workspace.tools.js +309 -115
  170. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  171. package/dist/modules/write-access/write-access.d.ts +60 -0
  172. package/dist/modules/write-access/write-access.d.ts.map +1 -0
  173. package/dist/modules/write-access/write-access.js +129 -0
  174. package/dist/modules/write-access/write-access.js.map +1 -0
  175. package/dist/shared/domain-errors.d.ts +25 -0
  176. package/dist/shared/domain-errors.d.ts.map +1 -1
  177. package/dist/shared/domain-errors.js +28 -0
  178. package/dist/shared/domain-errors.js.map +1 -1
  179. package/dist/shared/git.contract.d.ts +20 -0
  180. package/dist/shared/git.contract.d.ts.map +1 -1
  181. package/dist/shared/git.contract.js +26 -0
  182. package/dist/shared/git.contract.js.map +1 -1
  183. package/dist/tenancy/static-tenant-source.d.ts +0 -1
  184. package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
  185. package/dist/tenancy/static-tenant-source.js +0 -2
  186. package/dist/tenancy/static-tenant-source.js.map +1 -1
  187. package/migrations/0014_change_request_closed_reason.sql +1 -0
  188. package/migrations/0015_account_deactivation.sql +3 -0
  189. package/migrations/meta/0014_snapshot.json +2265 -0
  190. package/migrations/meta/0015_snapshot.json +2271 -0
  191. package/migrations/meta/_journal.json +14 -0
  192. package/package.json +3 -3
  193. package/src/__tests__/kb-layout-config.test.ts +0 -2
  194. package/src/__tests__/retired-settings.test.ts +96 -0
  195. package/src/core/__tests__/gated-boot.test.ts +132 -0
  196. package/src/core/__tests__/lifecycle.test.ts +170 -1
  197. package/src/core/__tests__/set-aside-root-is-one-place.test.ts +63 -0
  198. package/src/core/core-ports.ts +11 -1
  199. package/src/core/create-core-server.ts +32 -14
  200. package/src/core/create-core-services.ts +129 -23
  201. package/src/core/lifecycle.ts +98 -13
  202. package/src/core-config.ts +13 -14
  203. package/src/index.ts +12 -1
  204. package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +385 -0
  205. package/src/modules/access/__tests__/access-control.prospective.test.ts +94 -18
  206. package/src/modules/access/__tests__/access.routes.prospective.test.ts +6 -7
  207. package/src/modules/access/access-control.interface.ts +43 -12
  208. package/src/modules/access/access-control.service.ts +234 -36
  209. package/src/modules/access/access.routes.ts +13 -15
  210. package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +3 -1
  211. package/src/modules/auth/__tests__/account-deactivation.test.ts +252 -0
  212. package/src/modules/auth/__tests__/account.routes.test.ts +81 -3
  213. package/src/modules/auth/__tests__/auth.middleware.test.ts +45 -22
  214. package/src/modules/auth/__tests__/auth.service.test.ts +1 -1
  215. package/src/modules/auth/account-admission.ts +78 -9
  216. package/src/modules/auth/account.routes.ts +63 -7
  217. package/src/modules/auth/auth.middleware.ts +19 -8
  218. package/src/modules/auth/auth.routes.ts +8 -0
  219. package/src/modules/auth/auth.service.ts +179 -6
  220. package/src/modules/auth/oidc-auth-provider.ts +7 -2
  221. package/src/modules/database/core-schema.ts +35 -27
  222. package/src/modules/kb-fs/__tests__/branch-name.test.ts +10 -0
  223. package/src/modules/kb-fs/__tests__/repo-path.test.ts +123 -0
  224. package/src/modules/kb-fs/branch-name.ts +14 -1
  225. package/src/modules/kb-fs/repo-path.ts +85 -0
  226. package/src/modules/kb-sync/__tests__/kb-sync.routes.test.ts +26 -1
  227. package/src/modules/kb-sync/kb-sync.routes.ts +14 -4
  228. package/src/modules/kb-sync/sync-auth.ts +2 -2
  229. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +60 -3
  230. package/src/modules/mcp/__tests__/mcp.service.test.ts +68 -6
  231. package/src/modules/mcp/mcp-auth.middleware.ts +20 -6
  232. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +3 -1
  233. package/src/modules/plugins/__tests__/plugins.tools.test.ts +2 -1
  234. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +16 -0
  235. package/src/modules/settings/__tests__/setup.routes.git-mode.test.ts +91 -58
  236. package/src/modules/settings/__tests__/setup.routes.github-app.test.ts +25 -8
  237. package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +14 -11
  238. package/src/modules/settings/__tests__/setup.routes.repository-change.test.ts +390 -0
  239. package/src/modules/settings/__tests__/setup.routes.test.ts +3 -0
  240. package/src/modules/settings/deployment-settings.service.ts +8 -3
  241. package/src/modules/settings/setup.routes.ts +263 -19
  242. package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +2 -1
  243. package/src/modules/skills/__tests__/branch-skills.tools.test.ts +218 -0
  244. package/src/modules/skills/__tests__/skills.service.test.ts +229 -5
  245. package/src/modules/skills/skills.contract.ts +91 -18
  246. package/src/modules/skills/skills.service.ts +278 -41
  247. package/src/modules/skills/skills.tools.ts +80 -8
  248. package/src/modules/tool-auth/__tests__/manual-auth.middleware.test.ts +14 -1
  249. package/src/modules/tool-auth/external-api-key.service.ts +4 -2
  250. package/src/modules/tool-auth/internal-token.service.ts +3 -3
  251. package/src/modules/tool-auth/tool-auth.middleware.ts +34 -11
  252. package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +5 -3
  253. package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +3 -3
  254. package/src/modules/tool-helpers/__tests__/validate-token.test.ts +11 -1
  255. package/src/modules/tool-helpers/tool-handler.ts +24 -4
  256. package/src/modules/tool-helpers/tool.contract.ts +3 -2
  257. package/src/modules/tool-helpers/validate-token.ts +1 -1
  258. package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +111 -0
  259. package/src/modules/workflow/__tests__/workflow.routes.history-read-gate.test.ts +25 -0
  260. package/src/modules/workflow/__tests__/workflow.service.repository-replaced.test.ts +249 -0
  261. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +264 -6
  262. package/src/modules/workflow/agent-tools/change-request-summary.ts +182 -0
  263. package/src/modules/workflow/agent-tools/workflow.tools.ts +86 -35
  264. package/src/modules/workflow/file-lock.service.ts +31 -0
  265. package/src/modules/workflow/git/__tests__/git.service.fileBytesAtCommit.test.ts +260 -0
  266. package/src/modules/workflow/git/git.service.ts +168 -0
  267. package/src/modules/workflow/pending-commits.service.ts +59 -1
  268. package/src/modules/workflow/workflow-hooks.ts +64 -26
  269. package/src/modules/workflow/workflow.service.ts +121 -4
  270. package/src/modules/workspace/__tests__/agent-access.gate.test.ts +208 -0
  271. package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +384 -0
  272. package/src/modules/workspace/__tests__/file-stat-access.test.ts +2 -1
  273. package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -1
  274. package/src/modules/workspace/__tests__/set-aside-clone.test.ts +52 -0
  275. package/src/modules/workspace/__tests__/workspace.routes.at-ref.test.ts +305 -0
  276. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +2 -0
  277. package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +147 -0
  278. package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +326 -0
  279. package/src/modules/workspace/__tests__/workspace.service.test.ts +20 -9
  280. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +2 -3
  281. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +2 -3
  282. package/src/modules/workspace/__tests__/workspace.tools.test.ts +685 -14
  283. package/src/modules/workspace/agent-access.gate.ts +164 -0
  284. package/src/modules/workspace/routine-write-policy.ts +5 -6
  285. package/src/modules/workspace/session-sink.ts +5 -5
  286. package/src/modules/workspace/set-aside-clone.ts +96 -0
  287. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +86 -0
  288. package/src/modules/workspace/startup/kb-startup-runner.ts +115 -34
  289. package/src/modules/workspace/workspace.routes.ts +95 -3
  290. package/src/modules/workspace/workspace.service.ts +256 -24
  291. package/src/modules/workspace/workspace.tools.ts +338 -119
  292. package/src/modules/write-access/__tests__/write-access.test.ts +248 -0
  293. package/src/modules/write-access/write-access.ts +153 -0
  294. package/src/shared/domain-errors.ts +31 -0
  295. package/src/shared/git.contract.ts +27 -0
  296. package/src/tenancy/__tests__/static-tenant-source.test.ts +0 -1
  297. package/src/tenancy/static-tenant-source.ts +0 -3
  298. package/dist/modules/workflow/session-ontology.policy.d.ts +0 -52
  299. package/dist/modules/workflow/session-ontology.policy.d.ts.map +0 -1
  300. package/dist/modules/workflow/session-ontology.policy.js +0 -62
  301. package/dist/modules/workflow/session-ontology.policy.js.map +0 -1
  302. package/dist/modules/workflow/session-ontology.service.d.ts +0 -105
  303. package/dist/modules/workflow/session-ontology.service.d.ts.map +0 -1
  304. package/dist/modules/workflow/session-ontology.service.js +0 -147
  305. package/dist/modules/workflow/session-ontology.service.js.map +0 -1
  306. package/dist/modules/workspace/session-ontology.gate.d.ts +0 -114
  307. package/dist/modules/workspace/session-ontology.gate.d.ts.map +0 -1
  308. package/dist/modules/workspace/session-ontology.gate.js +0 -161
  309. package/dist/modules/workspace/session-ontology.gate.js.map +0 -1
  310. package/dist/shared/kb-layout.d.ts +0 -39
  311. package/dist/shared/kb-layout.d.ts.map +0 -1
  312. package/dist/shared/kb-layout.js +0 -103
  313. package/dist/shared/kb-layout.js.map +0 -1
  314. package/dist/shared/kb-layout.test.d.ts +0 -2
  315. package/dist/shared/kb-layout.test.d.ts.map +0 -1
  316. package/dist/shared/kb-layout.test.js +0 -75
  317. package/dist/shared/kb-layout.test.js.map +0 -1
  318. package/src/modules/workflow/__tests__/session-ontology.policy.test.ts +0 -62
  319. package/src/modules/workflow/__tests__/session-ontology.service.test.ts +0 -201
  320. package/src/modules/workflow/session-ontology.policy.ts +0 -70
  321. package/src/modules/workflow/session-ontology.service.ts +0 -183
  322. package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +0 -239
  323. package/src/modules/workspace/session-ontology.gate.ts +0 -191
  324. package/src/shared/kb-layout.test.ts +0 -98
  325. package/src/shared/kb-layout.ts +0 -102
@@ -87,6 +87,26 @@ export declare class WorkspaceService implements IWorkspaceService {
87
87
  * constructed service.
88
88
  */
89
89
  private readonly gitRunner;
90
+ /**
91
+ * Where a working copy of a repository that is no longer the configured
92
+ * one is kept. THE SAME VALUE THE KB STARTUP PHASE IS GIVEN — what the two
93
+ * of them move must end up in one place, and omitting it here falls back to
94
+ * a directory that does not survive a deployment's next recreate.
95
+ *
96
+ * Why, in one place: `setAsideRootFor` in `set-aside-clone.ts`.
97
+ */
98
+ private readonly setAsideRoot?;
99
+ /**
100
+ * What surrounds the setting aside of ONE working copy when a branch is
101
+ * opened and its clone turns out to be of another repository: given the
102
+ * workspace id and the move, it runs the move. The composition root
103
+ * passes what the startup phase does around its own set-asides: the
104
+ * commit worker held still, the copy's queued commits held back for a
105
+ * person, the locks on its branch dropped. If it throws, the copy stays
106
+ * where it was and the branch is refused with that reason. Absent, the
107
+ * move runs as it is.
108
+ */
109
+ private readonly aroundSetAside;
90
110
  /** Maps branch → absolute directory path. Lazily populated. */
91
111
  private readonly branchDirs;
92
112
  /**
@@ -158,7 +178,33 @@ export declare class WorkspaceService implements IWorkspaceService {
158
178
  * runner on its default deadline and no token, for a directly
159
179
  * constructed service.
160
180
  */
161
- gitRunner?: IGitRunner);
181
+ gitRunner?: IGitRunner,
182
+ /**
183
+ * Where a working copy of a repository that is no longer the configured
184
+ * one is kept. THE SAME VALUE THE KB STARTUP PHASE IS GIVEN — what the two
185
+ * of them move must end up in one place, and omitting it here falls back to
186
+ * a directory that does not survive a deployment's next recreate.
187
+ *
188
+ * Why, in one place: `setAsideRootFor` in `set-aside-clone.ts`.
189
+ */
190
+ setAsideRoot?: string | undefined,
191
+ /**
192
+ * What surrounds the setting aside of ONE working copy when a branch is
193
+ * opened and its clone turns out to be of another repository: given the
194
+ * workspace id and the move, it runs the move. The composition root
195
+ * passes what the startup phase does around its own set-asides: the
196
+ * commit worker held still, the copy's queued commits held back for a
197
+ * person, the locks on its branch dropped. If it throws, the copy stays
198
+ * where it was and the branch is refused with that reason. Absent, the
199
+ * move runs as it is.
200
+ */
201
+ aroundSetAside?: (workspaceId: string, move: () => Promise<void>) => Promise<void>);
202
+ /**
203
+ * The set-asides in flight, one per branch. Two callers opening the same
204
+ * branch both find the clone of another repository; the second waits for
205
+ * the first's move instead of trying to move a directory that has gone.
206
+ */
207
+ private readonly inFlightSetAsides;
162
208
  private get kbDirName();
163
209
  private readonly kbRepoUrl;
164
210
  setDiffService(diffService: IDiffService): void;
@@ -287,6 +333,23 @@ export declare class WorkspaceService implements IWorkspaceService {
287
333
  */
288
334
  getOrCreateForUser(_user: AuthUser, branch?: string): Promise<WorkspaceInfo>;
289
335
  private buildWorkspaceInfo;
336
+ /**
337
+ * Publish a finished bootstrap's working copy to the branch→directory cache
338
+ * — unless the copy was taken out from under it while it ran.
339
+ *
340
+ * The save that replaces the knowledge-base repository sets every working
341
+ * copy aside and clears this cache (`forgetClone`), and a clone takes
342
+ * seconds: a bootstrap already in flight when that happens finishes
343
+ * afterwards, and a plain `registerBranchDir` would put the path back. The
344
+ * directory is gone (set aside), so every reader from then on gets an
345
+ * ENOENT off the fast path and nothing ever re-clones it — a cache entry
346
+ * is exactly what stops that.
347
+ *
348
+ * Refused rather than retried here: the address this bootstrap cloned from
349
+ * is not the one configured now, so there is nothing to salvage. The caller
350
+ * is told, and opening the branch again clones it from the new address.
351
+ */
352
+ private adoptBootstrapped;
290
353
  /**
291
354
  * Await the in-flight bootstrap for a branch (if any). On reject —
292
355
  * meaning the clone rolled back — drop the branch→dir mapping so the
@@ -299,6 +362,53 @@ export declare class WorkspaceService implements IWorkspaceService {
299
362
  * branch exists again, so there is nothing stale to remove.
300
363
  */
301
364
  isBootstrapInFlight(branch: string): boolean;
365
+ /**
366
+ * Whether the clone at `repoDir` belongs to a repository OTHER than the
367
+ * configured one.
368
+ *
369
+ * Errs towards NO — "could not tell" must never delete someone's work:
370
+ * an address that cannot be read (no `origin`, a git that would not run)
371
+ * leaves the clone alone, and only two addresses that are read and differ
372
+ * in more than their spelling count as different repositories (see
373
+ * `kb-fs/remote-url.ts`). An UNCONFIGURED address answers no as well: a
374
+ * process that does not know where the repository is has no business
375
+ * declaring a clone stale.
376
+ */
377
+ private isCloneOfAnotherRepository;
378
+ /**
379
+ * How many times each working copy has been taken out from under this
380
+ * process — see {@link forgetClone}. Deleting from the caches is not enough
381
+ * on a RUNNING server: work already in flight against the copy that went
382
+ * away finishes afterwards and writes its result back into those very
383
+ * caches. So every such reader records the count it started with and checks
384
+ * it before publishing: a count that moved means what it learned is about a
385
+ * working copy that no longer exists, and is dropped.
386
+ *
387
+ * Keyed by workspace id, which is what the phase announces and what both
388
+ * readers can derive (`workspaceIdForBranch`).
389
+ */
390
+ private readonly discardCount;
391
+ /**
392
+ * Both readers and `forgetClone` go through this, so a caller's spelling of
393
+ * a workspace id can never make them disagree about which working copy is
394
+ * meant: the id is taken back to its branch and spelled afresh, which is the
395
+ * one form the phase announces.
396
+ */
397
+ private discardKey;
398
+ private discardsOf;
399
+ /**
400
+ * Forget everything this process remembers about a working copy that was
401
+ * taken away underneath it — the KB startup phase setting aside a clone of a
402
+ * repository that was replaced.
403
+ *
404
+ * At boot there is nothing to forget; this is for the save that changes the
405
+ * address on a RUNNING server, where `branchDirs` would otherwise keep
406
+ * answering with the path of a directory that is gone and no later caller
407
+ * would ever re-clone it. The branch NAME is deliberately left in
408
+ * `branchesEverHeardOf` — the branch was real, and that record outlives
409
+ * its clone.
410
+ */
411
+ forgetClone(workspaceId: string): void;
302
412
  /**
303
413
  * Bring an adopted clone's config back to what a fresh clone would carry.
304
414
  *
@@ -600,10 +710,10 @@ export declare class WorkspaceService implements IWorkspaceService {
600
710
  */
601
711
  unzipFile(workspaceId: string, zipWsPath: string, destDirWsPath?: string,
602
712
  /**
603
- * Per-extracted-path write guard (ontology-session boundary). Called with
604
- * each entry's workspace-relative target before it is written; if it throws,
605
- * the entry is skipped (with the thrown message as the reason) rather than
606
- * aborting the whole extraction. Omitted by non-agent / human callers.
713
+ * Per-extracted-path write guard. Called with each entry's
714
+ * workspace-relative target before it is written; if it throws, the entry
715
+ * is skipped (with the thrown message as the reason) rather than aborting
716
+ * the whole extraction. Omitted by non-agent / human callers.
607
717
  */
608
718
  guardWrite?: (wsRelativePath: string) => Promise<void>): Promise<UnzipResult>;
609
719
  getWorkspacePath(workspaceId: string): Promise<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.service.d.ts","sourceRoot":"","sources":["../../../src/modules/workspace/workspace.service.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAEjH,OAAO,EAML,KAAK,iBAAiB,EACvB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAa,KAAK,WAAW,EAAwB,MAAM,6BAA6B,CAAC;AAMhG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAa5D,OAAO,EAGL,mBAAmB,EAEpB,MAAM,+BAA+B,CAAC;AAQvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAO9D;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,WAAW;IAC1B,mEAAmE;IACnE,WAAW,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,4HAA4H;IAC5H,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC7C;AAeD;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;aAChB,UAAU,EAAE,MAAM;gBAAlB,UAAU,EAAE,MAAM;CAI/C;AAED;;;;;;;;;;GAUG;AACH,qBAAa,gBAAiB,SAAQ,mBAAmB;IAE3C,QAAQ,CAAC,SAAS,EAAE,iBAAiB;IAAE,QAAQ,CAAC,WAAW,EAAE,MAAM;IAD/E,QAAQ,CAAC,IAAI,EAAG,cAAc,CAAU;gBACnB,SAAS,EAAE,iBAAiB,EAAW,WAAW,EAAE,MAAM;CAQhF;AAoFD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC;AAElG,qBAAa,gBAAiB,YAAW,iBAAiB;IA0DtD,OAAO,CAAC,QAAQ,CAAC,cAAc;IAW/B,uFAAuF;IACvF,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS;IA9E5B,+DAA+D;IAC/D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;IAExD;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAoC;IAEvE,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,oGAAoG;IACpG,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA8B;IAC1D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoC;IACpE;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoC;IAC/D;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoC;IAChE;;;;;OAKG;IACH;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0D;IAEpF;;;OAGG;IACH,OAAO,CAAC,WAAW,CAA6B;IAEhD;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB,CAAgD;gBAGtD,cAAc,EAAE,MAAM;IACvC;;;;;;;;OAQG;IACH,SAAS,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC;IAClC,uFAAuF;IACtE,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,WAAW,GAAG,eAAe,CAAC,EAClD,IAAI,EAAE,WAAW;IAClC;;;;;;OAMG;IACc,SAAS,GAAE,UAAgC;IAK9D,OAAO,KAAK,SAAS,GAEpB;IAED,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IAEzC,cAAc,CAAC,WAAW,EAAE,YAAY,GAAG,IAAI;IAI/C,0BAA0B,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAIzE;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IAEzD;;;;OAIG;IACH,kBAAkB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IAIlD;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IAKzB;;;;;;;OAOG;YACW,gBAAgB;IAgB9B;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAsBpB;;;;;;;OAOG;YACW,eAAe;IAqB7B;;;;;;;;;OASG;IACG,kBAAkB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAmBlD;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,oBAAoB,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IA2CjG;;;;;OAKG;YACW,QAAQ;IAwBtB;;;;OAIG;IACG,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAuGlE;;;;;;OAMG;IACG,kBAAkB,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAIlF,OAAO,CAAC,kBAAkB;IAU1B;;;;OAIG;YACW,qBAAqB;IAWnC;;;;OAIG;IACH,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAI5C;;;;;;;;;;;;;;;;;;;OAmBG;YACW,oBAAoB;IAalC;;;;;;;OAOG;IACH,OAAO,CAAC,qBAAqB;IAK7B,kFAAkF;IAClF,OAAO,CAAC,QAAQ,CAAC,4BAA4B,CAA6B;IAE1E;;;;;;;OAOG;YACW,qBAAqB;IA6CnC;;;;;;;;OAQG;YACW,8BAA8B;YAK9B,wBAAwB;IA4HhC,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IASrE;;;;OAIG;IACG,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOnD,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAKzF,iFAAiF;IACjF,OAAO,CAAC,QAAQ;IAIhB;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;YACW,iBAAiB;IAYzB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQ9D,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQ1E;;;;;;;;;;;;;;;;OAgBG;YACW,oBAAoB;IAyClC;;;;;;;;;OASG;IACG,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAgB1E;;;;;;;;;;;;OAYG;IACG,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAyC3E;;;;;OAKG;IACG,aAAa,CACjB,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAuBzB;;;;;;;;;;;OAWG;IACG,oBAAoB,CACxB,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO,GAC/C,OAAO,CAAC,IAAI,CAAC;IA8ChB,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,2BAA2B;IAO7B,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBpE;;;;;;;OAOG;IACG,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWlD;;;;;;;;;OASG;IACG,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyCzF;;;;;;;;;;;;OAYG;YACW,qBAAqB;IASnC;;;;;;;;OAQG;IACG,oBAAoB,CACxB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC;IAQhB;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACG,YAAY,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAc5F;;;;;;;;;;;;;;OAcG;IACG,cAAc,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAwB7F;;;;;;;;;;;;;;OAcG;YACW,oBAAoB;IA8BlC;;;;;;;;;;;;;;;OAeG;IACG,SAAS,CACb,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,CAAA;KAAE,GAC7D,OAAO,CAAC,IAAI,CAAC;IA6CV,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAczE;;;;;;;OAOG;IACG,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAkB5E,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB3F;;;;;OAKG;IACG,SAAS,CACb,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,MAAM;IACtB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GACrD,OAAO,CAAC,WAAW,CAAC;IAyNjB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI5D;;;;;;;OAOG;IACG,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAuBrE;;;;;;;;;;;OAWG;IACH,eAAe,IAAI,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAOxD;;;;;;;;;;;;;;;OAeG;IACG,iBAAiB,CACrB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAwCnC;;;;;;;;;;;;;;OAcG;IACG,uBAAuB,CAC3B,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,GAC9B,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAkCjC;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;YAShB,mBAAmB;YA2CnB,aAAa;CA+F5B"}
1
+ {"version":3,"file":"workspace.service.d.ts","sourceRoot":"","sources":["../../../src/modules/workspace/workspace.service.ts"],"names":[],"mappings":"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"}
@@ -15,6 +15,9 @@ import { GitInternalsError, PathNotFoundError, PathTraversalError, UnreadableArc
15
15
  import { workspaceIdForBranch, branchForWorkspaceId } from '../../shared/workspace-id.js';
16
16
  import { BranchNotFoundError, RemoteBranchGoneError, WorkflowDomainError, isMissingRemoteBranchFailure, } from '../../shared/domain-errors.js';
17
17
  import { assertValidBranchName } from '../kb-fs/branch-name.js';
18
+ import { normalizeRepositoryAddress, sameRepository } from '../kb-fs/remote-url.js';
19
+ import { redactSecret, urlQuerySecrets } from '../../shared/redact-secret.js';
20
+ import { setAsideClone, setAsideRootFor, setAsideStamp } from './set-aside-clone.js';
18
21
  import { cloneCredentialArgs, cloneCredentialConfigArgs, cloneTrackingConfigArgs, SAFE_IMPLICIT_FETCH_ARGS, } from '../kb-fs/clone-config.js';
19
22
  // Zip-bomb guardrails. The upload cap is 50 MB compressed; even a generous
20
23
  // 100× expansion ratio still fits inside these limits, so a well-behaved
@@ -142,6 +145,8 @@ export class WorkspaceService {
142
145
  kb;
143
146
  disk;
144
147
  gitRunner;
148
+ setAsideRoot;
149
+ aroundSetAside;
145
150
  /** Maps branch → absolute directory path. Lazily populated. */
146
151
  branchDirs = new Map();
147
152
  /**
@@ -213,13 +218,41 @@ export class WorkspaceService {
213
218
  * runner on its default deadline and no token, for a directly
214
219
  * constructed service.
215
220
  */
216
- gitRunner = new NodeGitRunner()) {
221
+ gitRunner = new NodeGitRunner(),
222
+ /**
223
+ * Where a working copy of a repository that is no longer the configured
224
+ * one is kept. THE SAME VALUE THE KB STARTUP PHASE IS GIVEN — what the two
225
+ * of them move must end up in one place, and omitting it here falls back to
226
+ * a directory that does not survive a deployment's next recreate.
227
+ *
228
+ * Why, in one place: `setAsideRootFor` in `set-aside-clone.ts`.
229
+ */
230
+ setAsideRoot,
231
+ /**
232
+ * What surrounds the setting aside of ONE working copy when a branch is
233
+ * opened and its clone turns out to be of another repository: given the
234
+ * workspace id and the move, it runs the move. The composition root
235
+ * passes what the startup phase does around its own set-asides: the
236
+ * commit worker held still, the copy's queued commits held back for a
237
+ * person, the locks on its branch dropped. If it throws, the copy stays
238
+ * where it was and the branch is refused with that reason. Absent, the
239
+ * move runs as it is.
240
+ */
241
+ aroundSetAside = (_workspaceId, move) => move()) {
217
242
  this.workspacesRoot = workspacesRoot;
218
243
  this.kb = kb;
219
244
  this.disk = disk;
220
245
  this.gitRunner = gitRunner;
246
+ this.setAsideRoot = setAsideRoot;
247
+ this.aroundSetAside = aroundSetAside;
221
248
  this.kbRepoUrl = typeof kbRepoUrl === 'function' ? kbRepoUrl : () => kbRepoUrl;
222
249
  }
250
+ /**
251
+ * The set-asides in flight, one per branch. Two callers opening the same
252
+ * branch both find the clone of another repository; the second waits for
253
+ * the first's move instead of trying to move a directory that has gone.
254
+ */
255
+ inFlightSetAsides = new Map();
223
256
  get kbDirName() {
224
257
  return this.kb.kbDirName;
225
258
  }
@@ -526,22 +559,96 @@ export class WorkspaceService {
526
559
  }
527
560
  // Check disk — the clone may have been bootstrapped by a previous
528
561
  // process and survived a restart.
529
- try {
530
- await fs.access(path.join(repoDir, '.git'));
531
- // Migration for clones already on disk: re-stamp the tracking config the
532
- // first time this process opens them, so a clone whose config drifted
533
- // (duplicate fetch refspec / merge ref) is repaired before anything
534
- // pulls it. Once per branch per process — the cached paths above return
535
- // before reaching here.
536
- await this.normalizeCloneConfig(repoDir, branch);
537
- this.registerBranchDir(branch, workspaceDir);
538
- return this.buildWorkspaceInfo(branch, workspaceDir);
562
+ const onDisk = await fs.access(path.join(repoDir, '.git')).then(() => true, () => false);
563
+ // A clone fetches and pushes through the address stored in its OWN
564
+ // `remote.origin.url`, which nothing rewrites when the configured
565
+ // repository changes. Adopting one of the repository that was replaced is
566
+ // how every later fetch answers "repository not found" — so it is set
567
+ // aside here and cloned fresh below, the same rule, and the same
568
+ // destination, as the KB startup phase applies to the whole workspaces
569
+ // root.
570
+ //
571
+ // SET ASIDE, never deleted. The phase sweeps only AFTER it has reached the
572
+ // configured remote, so that a typo in the address cannot cost anyone
573
+ // their work. This path has reached nothing: it is a branch being opened,
574
+ // and it runs in exactly the states where the address is stored but
575
+ // unproven — a gated boot, a break-glass start, a save that changed the
576
+ // address before the phase got to it. Deleting on a string difference
577
+ // there would throw away commits that exist nowhere else because somebody
578
+ // mistyped a host name.
579
+ //
580
+ // OUTSIDE the adoption `try` below, deliberately: a clone that cannot be
581
+ // moved must fail this call with that reason. Swallowed into "bootstrap
582
+ // below", it would be found again by the clone step, which sees a
583
+ // directory with a `.git` in it, calls that already-cloned, and hands back
584
+ // the very working copy this refused to adopt.
585
+ // Counted before the disk is looked at: the startup phase may take this
586
+ // very copy away while the checks below are awaited (`forgetClone`), and a
587
+ // copy that went must not be registered as the branch's.
588
+ const discardsAtAdoption = this.discardsOf(id);
589
+ const setAsideInFlight = this.inFlightSetAsides.get(branch);
590
+ if (setAsideInFlight) {
591
+ // Someone else is moving it. Wait, then clone below; a move that failed
592
+ // refuses this caller with the same reason.
593
+ await setAsideInFlight;
594
+ }
595
+ else if (onDisk && (await this.isCloneOfAnotherRepository(repoDir))) {
596
+ // Asked again: another caller may have started the move while the
597
+ // address was being read.
598
+ const startedMeanwhile = this.inFlightSetAsides.get(branch);
599
+ if (startedMeanwhile) {
600
+ await startedMeanwhile;
601
+ }
602
+ else {
603
+ const kept = path.join(setAsideRootFor(this.workspacesRoot, this.setAsideRoot), setAsideStamp(), id);
604
+ // What surrounds the move may wait, and the startup phase may set
605
+ // this very copy aside meanwhile. Then there is nothing left to move:
606
+ // the phase did it, and said so.
607
+ let moved = false;
608
+ const moving = this.aroundSetAside(id, async () => {
609
+ if (this.discardsOf(id) !== discardsAtAdoption)
610
+ return;
611
+ await setAsideClone(repoDir, kept);
612
+ moved = true;
613
+ }).finally(() => {
614
+ this.inFlightSetAsides.delete(branch);
615
+ });
616
+ this.inFlightSetAsides.set(branch, moving);
617
+ await moving;
618
+ if (moved) {
619
+ log.warn(`the "${branch}" working copy was a clone of another repository. Set aside at ${kept}; nothing was ` +
620
+ 'deleted, and it is being cloned fresh from the configured one. Work that was never pushed is in ' +
621
+ 'that folder: `git log` there shows it.');
622
+ }
623
+ }
539
624
  }
540
- catch {
541
- // Not on disk — bootstrap below.
625
+ else if (onDisk) {
626
+ try {
627
+ // Migration for clones already on disk: re-stamp the tracking config
628
+ // the first time this process opens them, so a clone whose config
629
+ // drifted (duplicate fetch refspec / merge ref) is repaired before
630
+ // anything pulls it. Once per branch per process — the cached paths
631
+ // above return before reaching here.
632
+ await this.normalizeCloneConfig(repoDir, branch);
633
+ // Taken away meanwhile: nothing to adopt, clone below.
634
+ if (this.discardsOf(id) !== discardsAtAdoption)
635
+ throw new Error('the working copy was set aside');
636
+ this.registerBranchDir(branch, workspaceDir);
637
+ return this.buildWorkspaceInfo(branch, workspaceDir);
638
+ }
639
+ catch {
640
+ // No longer worth adopting — bootstrap below.
641
+ }
542
642
  }
543
643
  // Single-flight bootstrap. Concurrent callers for the same branch
544
644
  // share the first one's promise instead of stacking clones.
645
+ //
646
+ // Recorded before any of it: a clone takes seconds, and the save that
647
+ // replaces the repository can land in the middle of one. Whatever this
648
+ // bootstrap produces is about the repository configured NOW, so if the
649
+ // working copy is taken away while it runs (`forgetClone`) the result must
650
+ // not be published — see `adoptBootstrapped`.
651
+ const discardsAtBootstrap = this.discardsOf(workspaceIdForBranch(branch));
545
652
  let resolveBootstrap;
546
653
  let rejectBootstrap;
547
654
  const bootstrap = new Promise((res, rej) => {
@@ -552,7 +659,7 @@ export class WorkspaceService {
552
659
  const existingBootstrap = this.inFlightBootstraps.get(branch);
553
660
  if (existingBootstrap) {
554
661
  await existingBootstrap;
555
- this.registerBranchDir(branch, workspaceDir);
662
+ this.adoptBootstrapped(branch, workspaceDir, discardsAtBootstrap);
556
663
  return this.buildWorkspaceInfo(branch, workspaceDir);
557
664
  }
558
665
  this.inFlightBootstraps.set(branch, bootstrap);
@@ -566,7 +673,7 @@ export class WorkspaceService {
566
673
  // naturally; seeding the remote is the KB startup phase's job, at boot.
567
674
  createdWorkspaceDir = await fs.mkdir(workspaceDir, { recursive: true });
568
675
  await this.cloneProcessMapForBranch(workspaceDir, branch);
569
- this.registerBranchDir(branch, workspaceDir);
676
+ this.adoptBootstrapped(branch, workspaceDir, discardsAtBootstrap);
570
677
  resolveBootstrap();
571
678
  }
572
679
  catch (err) {
@@ -611,6 +718,30 @@ export class WorkspaceService {
611
718
  kbDirName: this.kbDirName,
612
719
  };
613
720
  }
721
+ /**
722
+ * Publish a finished bootstrap's working copy to the branch→directory cache
723
+ * — unless the copy was taken out from under it while it ran.
724
+ *
725
+ * The save that replaces the knowledge-base repository sets every working
726
+ * copy aside and clears this cache (`forgetClone`), and a clone takes
727
+ * seconds: a bootstrap already in flight when that happens finishes
728
+ * afterwards, and a plain `registerBranchDir` would put the path back. The
729
+ * directory is gone (set aside), so every reader from then on gets an
730
+ * ENOENT off the fast path and nothing ever re-clones it — a cache entry
731
+ * is exactly what stops that.
732
+ *
733
+ * Refused rather than retried here: the address this bootstrap cloned from
734
+ * is not the one configured now, so there is nothing to salvage. The caller
735
+ * is told, and opening the branch again clones it from the new address.
736
+ */
737
+ adoptBootstrapped(branch, workspaceDir, discardsAtStart) {
738
+ if (this.discardsOf(workspaceIdForBranch(branch)) !== discardsAtStart) {
739
+ this.branchDirs.delete(branch);
740
+ throw new Error(`The knowledge-base repository was replaced while the "${branch}" working copy was being created, so ` +
741
+ 'that copy was set aside instead of being used. Open it again to get one of the new repository.');
742
+ }
743
+ this.registerBranchDir(branch, workspaceDir);
744
+ }
614
745
  /**
615
746
  * Await the in-flight bootstrap for a branch (if any). On reject —
616
747
  * meaning the clone rolled back — drop the branch→dir mapping so the
@@ -636,6 +767,94 @@ export class WorkspaceService {
636
767
  isBootstrapInFlight(branch) {
637
768
  return this.inFlightBootstraps.has(branch);
638
769
  }
770
+ /**
771
+ * Whether the clone at `repoDir` belongs to a repository OTHER than the
772
+ * configured one.
773
+ *
774
+ * Errs towards NO — "could not tell" must never delete someone's work:
775
+ * an address that cannot be read (no `origin`, a git that would not run)
776
+ * leaves the clone alone, and only two addresses that are read and differ
777
+ * in more than their spelling count as different repositories (see
778
+ * `kb-fs/remote-url.ts`). An UNCONFIGURED address answers no as well: a
779
+ * process that does not know where the repository is has no business
780
+ * declaring a clone stale.
781
+ */
782
+ async isCloneOfAnotherRepository(repoDir) {
783
+ const configured = this.kbRepoUrl().trim();
784
+ if (configured === '')
785
+ return false;
786
+ let origin;
787
+ try {
788
+ const { stdout } = await this.gitRunner.run(repoDir, ['config', '--get', 'remote.origin.url']);
789
+ origin = stdout.trim();
790
+ }
791
+ catch {
792
+ return false;
793
+ }
794
+ if (origin === '' || sameRepository(origin, configured))
795
+ return false;
796
+ // Normalizing strips the userinfo but KEEPS the query string, and the
797
+ // knowledge-base address is one that may carry its credential there (a
798
+ // signed URL — see `urlQuerySecrets`). So the address is scrubbed on the
799
+ // way into the log, exactly as the startup phase scrubs its own.
800
+ log.warn(`the "${path.basename(path.dirname(repoDir))}" working copy is a clone of another repository ` +
801
+ `(${redactSecret(normalizeRepositoryAddress(origin), urlQuerySecrets(origin))}).`);
802
+ return true;
803
+ }
804
+ /**
805
+ * How many times each working copy has been taken out from under this
806
+ * process — see {@link forgetClone}. Deleting from the caches is not enough
807
+ * on a RUNNING server: work already in flight against the copy that went
808
+ * away finishes afterwards and writes its result back into those very
809
+ * caches. So every such reader records the count it started with and checks
810
+ * it before publishing: a count that moved means what it learned is about a
811
+ * working copy that no longer exists, and is dropped.
812
+ *
813
+ * Keyed by workspace id, which is what the phase announces and what both
814
+ * readers can derive (`workspaceIdForBranch`).
815
+ */
816
+ discardCount = new Map();
817
+ /**
818
+ * Both readers and `forgetClone` go through this, so a caller's spelling of
819
+ * a workspace id can never make them disagree about which working copy is
820
+ * meant: the id is taken back to its branch and spelled afresh, which is the
821
+ * one form the phase announces.
822
+ */
823
+ discardKey(workspaceId) {
824
+ return workspaceIdForBranch(branchForWorkspaceId(workspaceId));
825
+ }
826
+ discardsOf(workspaceId) {
827
+ return this.discardCount.get(this.discardKey(workspaceId)) ?? 0;
828
+ }
829
+ /**
830
+ * Forget everything this process remembers about a working copy that was
831
+ * taken away underneath it — the KB startup phase setting aside a clone of a
832
+ * repository that was replaced.
833
+ *
834
+ * At boot there is nothing to forget; this is for the save that changes the
835
+ * address on a RUNNING server, where `branchDirs` would otherwise keep
836
+ * answering with the path of a directory that is gone and no later caller
837
+ * would ever re-clone it. The branch NAME is deliberately left in
838
+ * `branchesEverHeardOf` — the branch was real, and that record outlives
839
+ * its clone.
840
+ */
841
+ forgetClone(workspaceId) {
842
+ const branch = branchForWorkspaceId(workspaceId);
843
+ const workspaceDir = path.join(this.workspacesRoot, workspaceId);
844
+ const repoDir = path.join(workspaceDir, this.kbDirName);
845
+ this.discardCount.set(this.discardKey(workspaceId), this.discardsOf(workspaceId) + 1);
846
+ this.branchDirs.delete(branch);
847
+ this.lastFetchAt.delete(repoDir);
848
+ this.lastFetchOk.delete(repoDir);
849
+ // Dropped as well as un-stamped: the promise still running belongs to the
850
+ // working copy that went away, so a caller that joined it would be told a
851
+ // fetch of a directory that is gone refreshed the one that replaced it.
852
+ // The count above is what stops that promise re-stamping the TTL when it
853
+ // finally settles.
854
+ this.inFlightFetches.delete(repoDir);
855
+ // Keyed by branch, not by directory — see `stampCredentialHelper`.
856
+ this.stampedCredentialFingerprint.delete(branch);
857
+ }
639
858
  /**
640
859
  * Bring an adopted clone's config back to what a fresh clone would carry.
641
860
  *
@@ -1154,14 +1373,25 @@ export class WorkspaceService {
1154
1373
  // This driver runs outside the git layer's per-workspace mutex, so it may
1155
1374
  // only run the safe implicit-fetch shape — see `SAFE_IMPLICIT_FETCH_ARGS`
1156
1375
  // in `kb-fs/clone-config.ts` for the full rationale.
1376
+ // What this fetch is ABOUT: the working copy in that directory as it is
1377
+ // now. Should the repository be replaced while the fetch runs, the copy is
1378
+ // set aside and a fresh one is cloned to the SAME path — so stamping the
1379
+ // result afterwards would hand the new clone a freshness it never earned
1380
+ // (or a failure that was the old one's), and the next reader would skip
1381
+ // refreshing it for the whole TTL.
1382
+ const discardsAtFetch = this.discardsOf(workspaceId);
1383
+ const stillTheSameClone = () => this.discardsOf(workspaceId) === discardsAtFetch;
1157
1384
  const promise = this.gitRunner
1158
1385
  .run(repoDir, [...SAFE_IMPLICIT_FETCH_ARGS])
1159
1386
  .then(() => {
1387
+ if (!stillTheSameClone())
1388
+ return;
1160
1389
  this.lastFetchAt.set(repoDir, Date.now());
1161
1390
  this.lastFetchOk.set(repoDir, true);
1162
1391
  })
1163
1392
  .catch((err) => {
1164
- this.lastFetchOk.set(repoDir, false);
1393
+ if (stillTheSameClone())
1394
+ this.lastFetchOk.set(repoDir, false);
1165
1395
  log.warn('git fetch origin failed:', { detail: redactError(err, this.gitRunner.credentials.token()) });
1166
1396
  })
1167
1397
  .finally(() => {
@@ -1544,10 +1774,10 @@ export class WorkspaceService {
1544
1774
  */
1545
1775
  async unzipFile(workspaceId, zipWsPath, destDirWsPath,
1546
1776
  /**
1547
- * Per-extracted-path write guard (ontology-session boundary). Called with
1548
- * each entry's workspace-relative target before it is written; if it throws,
1549
- * the entry is skipped (with the thrown message as the reason) rather than
1550
- * aborting the whole extraction. Omitted by non-agent / human callers.
1777
+ * Per-extracted-path write guard. Called with each entry's
1778
+ * workspace-relative target before it is written; if it throws, the entry
1779
+ * is skipped (with the thrown message as the reason) rather than aborting
1780
+ * the whole extraction. Omitted by non-agent / human callers.
1551
1781
  */
1552
1782
  guardWrite) {
1553
1783
  const { workspaceDir, relativePath: zipRelativePath, absolutePath: zipAbsolute } = await this.resolveInsideRepo(workspaceId, zipWsPath);
@@ -1656,10 +1886,10 @@ export class WorkspaceService {
1656
1886
  const relForReport = path
1657
1887
  .relative(workspaceDir, targetAbsolute)
1658
1888
  .replace(/\\/g, '/');
1659
- // Ontology-session boundary: a write-blocked (or cross-ontology) entry is
1660
- // skipped, not extracted, so an archive can't be a write path around it.
1661
- // Runs before any filesystem side effect — including directory creation —
1662
- // so a blocked entry leaves nothing behind on disk.
1889
+ // An entry the write guard refuses is skipped, not extracted, so an
1890
+ // archive can't be a way around whatever the guard enforces. Runs before
1891
+ // any filesystem side effect — including directory creation — so a
1892
+ // refused entry leaves nothing behind on disk.
1663
1893
  const allowEntry = async () => {
1664
1894
  if (!guardWrite)
1665
1895
  return true;
@@ -1668,7 +1898,7 @@ export class WorkspaceService {
1668
1898
  return true;
1669
1899
  }
1670
1900
  catch (err) {
1671
- skipped.push({ path: rawName, reason: err instanceof Error ? err.message : 'Blocked by the ontology-session boundary' });
1901
+ skipped.push({ path: rawName, reason: err instanceof Error ? err.message : 'Refused before it was written' });
1672
1902
  return false;
1673
1903
  }
1674
1904
  };