@bevel-software/platform-core-backend 0.21.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 (434) 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 +62 -13
  7. package/dist/core/create-core-server.js.map +1 -1
  8. package/dist/core/create-core-services.d.ts +22 -3
  9. package/dist/core/create-core-services.d.ts.map +1 -1
  10. package/dist/core/create-core-services.js +158 -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-requests.contract.d.ts +75 -0
  31. package/dist/modules/access/access-requests.contract.d.ts.map +1 -0
  32. package/dist/modules/access/access-requests.contract.js +20 -0
  33. package/dist/modules/access/access-requests.contract.js.map +1 -0
  34. package/dist/modules/access/access-requests.routes.d.ts +35 -0
  35. package/dist/modules/access/access-requests.routes.d.ts.map +1 -0
  36. package/dist/modules/access/access-requests.routes.js +237 -0
  37. package/dist/modules/access/access-requests.routes.js.map +1 -0
  38. package/dist/modules/access/access-requests.service.d.ts +123 -0
  39. package/dist/modules/access/access-requests.service.d.ts.map +1 -0
  40. package/dist/modules/access/access-requests.service.js +337 -0
  41. package/dist/modules/access/access-requests.service.js.map +1 -0
  42. package/dist/modules/access/access.routes.d.ts.map +1 -1
  43. package/dist/modules/access/access.routes.js +13 -15
  44. package/dist/modules/access/access.routes.js.map +1 -1
  45. package/dist/modules/access-model/access-grammar.d.ts +12 -0
  46. package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
  47. package/dist/modules/access-model/access-grammar.js +24 -0
  48. package/dist/modules/access-model/access-grammar.js.map +1 -1
  49. package/dist/modules/auth/account-admission.d.ts +58 -7
  50. package/dist/modules/auth/account-admission.d.ts.map +1 -1
  51. package/dist/modules/auth/account-admission.js +44 -1
  52. package/dist/modules/auth/account-admission.js.map +1 -1
  53. package/dist/modules/auth/account.routes.d.ts +1 -1
  54. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  55. package/dist/modules/auth/account.routes.js +61 -5
  56. package/dist/modules/auth/account.routes.js.map +1 -1
  57. package/dist/modules/auth/auth.middleware.d.ts +1 -1
  58. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  59. package/dist/modules/auth/auth.middleware.js +18 -7
  60. package/dist/modules/auth/auth.middleware.js.map +1 -1
  61. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  62. package/dist/modules/auth/auth.routes.js +8 -0
  63. package/dist/modules/auth/auth.routes.js.map +1 -1
  64. package/dist/modules/auth/auth.service.d.ts +96 -3
  65. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  66. package/dist/modules/auth/auth.service.js +187 -9
  67. package/dist/modules/auth/auth.service.js.map +1 -1
  68. package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
  69. package/dist/modules/auth/oidc-auth-provider.js +7 -2
  70. package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
  71. package/dist/modules/database/core-schema.d.ts +41 -24
  72. package/dist/modules/database/core-schema.d.ts.map +1 -1
  73. package/dist/modules/database/core-schema.js +35 -27
  74. package/dist/modules/database/core-schema.js.map +1 -1
  75. package/dist/modules/github-app/github-app.client.d.ts +101 -0
  76. package/dist/modules/github-app/github-app.client.d.ts.map +1 -0
  77. package/dist/modules/github-app/github-app.client.js +222 -0
  78. package/dist/modules/github-app/github-app.client.js.map +1 -0
  79. package/dist/modules/github-app/github-app.connection.d.ts +102 -0
  80. package/dist/modules/github-app/github-app.connection.d.ts.map +1 -0
  81. package/dist/modules/github-app/github-app.connection.js +185 -0
  82. package/dist/modules/github-app/github-app.connection.js.map +1 -0
  83. package/dist/modules/github-app/github-app.routes.d.ts +59 -0
  84. package/dist/modules/github-app/github-app.routes.d.ts.map +1 -0
  85. package/dist/modules/github-app/github-app.routes.js +323 -0
  86. package/dist/modules/github-app/github-app.routes.js.map +1 -0
  87. package/dist/modules/github-app/index.d.ts +4 -0
  88. package/dist/modules/github-app/index.d.ts.map +1 -0
  89. package/dist/modules/github-app/index.js +4 -0
  90. package/dist/modules/github-app/index.js.map +1 -0
  91. package/dist/modules/kb-fs/branch-name.d.ts.map +1 -1
  92. package/dist/modules/kb-fs/branch-name.js +12 -2
  93. package/dist/modules/kb-fs/branch-name.js.map +1 -1
  94. package/dist/modules/kb-fs/remote-url.d.ts +22 -0
  95. package/dist/modules/kb-fs/remote-url.d.ts.map +1 -0
  96. package/dist/modules/kb-fs/remote-url.js +35 -0
  97. package/dist/modules/kb-fs/remote-url.js.map +1 -0
  98. package/dist/modules/kb-fs/repo-path.d.ts +11 -16
  99. package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
  100. package/dist/modules/kb-fs/repo-path.js +79 -0
  101. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  102. package/dist/modules/kb-sync/kb-sync.routes.d.ts +2 -2
  103. package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
  104. package/dist/modules/kb-sync/kb-sync.routes.js +14 -3
  105. package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
  106. package/dist/modules/kb-sync/sync-auth.d.ts +3 -1
  107. package/dist/modules/kb-sync/sync-auth.d.ts.map +1 -1
  108. package/dist/modules/kb-sync/sync-auth.js +1 -1
  109. package/dist/modules/kb-sync/sync-auth.js.map +1 -1
  110. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  111. package/dist/modules/mcp/mcp-auth.middleware.js +21 -6
  112. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  113. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  114. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +3 -2
  115. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  116. package/dist/modules/plugins/join-proposals.d.ts +17 -5
  117. package/dist/modules/plugins/join-proposals.d.ts.map +1 -1
  118. package/dist/modules/plugins/join-proposals.js +76 -22
  119. package/dist/modules/plugins/join-proposals.js.map +1 -1
  120. package/dist/modules/plugins/join-requests.service.d.ts +111 -18
  121. package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
  122. package/dist/modules/plugins/join-requests.service.js +173 -29
  123. package/dist/modules/plugins/join-requests.service.js.map +1 -1
  124. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  125. package/dist/modules/plugins/plugins.routes.js +3 -2
  126. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  127. package/dist/modules/settings/deployment-settings.service.d.ts +22 -1
  128. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  129. package/dist/modules/settings/deployment-settings.service.js +101 -7
  130. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  131. package/dist/modules/settings/managed-repository.d.ts +43 -0
  132. package/dist/modules/settings/managed-repository.d.ts.map +1 -0
  133. package/dist/modules/settings/managed-repository.js +60 -0
  134. package/dist/modules/settings/managed-repository.js.map +1 -0
  135. package/dist/modules/settings/repository-source.d.ts +128 -0
  136. package/dist/modules/settings/repository-source.d.ts.map +1 -0
  137. package/dist/modules/settings/repository-source.js +150 -0
  138. package/dist/modules/settings/repository-source.js.map +1 -0
  139. package/dist/modules/settings/setup.routes.d.ts +78 -4
  140. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  141. package/dist/modules/settings/setup.routes.js +379 -17
  142. package/dist/modules/settings/setup.routes.js.map +1 -1
  143. package/dist/modules/skills/skill-access-requests.routes.d.ts +6 -8
  144. package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -1
  145. package/dist/modules/skills/skill-access-requests.routes.js +24 -63
  146. package/dist/modules/skills/skill-access-requests.routes.js.map +1 -1
  147. package/dist/modules/skills/skills.contract.d.ts +90 -18
  148. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  149. package/dist/modules/skills/skills.contract.js +4 -1
  150. package/dist/modules/skills/skills.contract.js.map +1 -1
  151. package/dist/modules/skills/skills.service.d.ts +98 -9
  152. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  153. package/dist/modules/skills/skills.service.js +239 -37
  154. package/dist/modules/skills/skills.service.js.map +1 -1
  155. package/dist/modules/skills/skills.tools.d.ts +7 -0
  156. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  157. package/dist/modules/skills/skills.tools.js +71 -7
  158. package/dist/modules/skills/skills.tools.js.map +1 -1
  159. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  160. package/dist/modules/tool-auth/external-api-key.service.js +4 -2
  161. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  162. package/dist/modules/tool-auth/internal-token.service.d.ts +3 -3
  163. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +16 -7
  164. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  165. package/dist/modules/tool-auth/tool-auth.middleware.js +34 -12
  166. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  167. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
  168. package/dist/modules/tool-helpers/tool-context.js +15 -0
  169. package/dist/modules/tool-helpers/tool-context.js.map +1 -1
  170. package/dist/modules/tool-helpers/tool-handler.d.ts +2 -1
  171. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  172. package/dist/modules/tool-helpers/tool-handler.js +25 -4
  173. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  174. package/dist/modules/tool-helpers/tool.contract.d.ts +3 -2
  175. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  176. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  177. package/dist/modules/tool-helpers/validate-token.js +1 -1
  178. package/dist/modules/tool-helpers/validate-token.js.map +1 -1
  179. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +98 -0
  180. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -0
  181. package/dist/modules/workflow/agent-tools/change-request-summary.js +81 -0
  182. package/dist/modules/workflow/agent-tools/change-request-summary.js.map +1 -0
  183. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  184. package/dist/modules/workflow/agent-tools/workflow.tools.js +113 -37
  185. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  186. package/dist/modules/workflow/file-lock.service.d.ts +24 -0
  187. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  188. package/dist/modules/workflow/file-lock.service.js +30 -0
  189. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  190. package/dist/modules/workflow/git/git.service.d.ts +72 -1
  191. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  192. package/dist/modules/workflow/git/git.service.js +170 -6
  193. package/dist/modules/workflow/git/git.service.js.map +1 -1
  194. package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -1
  195. package/dist/modules/workflow/git/node-git-runner.js +5 -0
  196. package/dist/modules/workflow/git/node-git-runner.js.map +1 -1
  197. package/dist/modules/workflow/git/pull-request.service.d.ts +45 -0
  198. package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
  199. package/dist/modules/workflow/git/pull-request.service.js +99 -0
  200. package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
  201. package/dist/modules/workflow/pending-commits.service.d.ts +38 -0
  202. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  203. package/dist/modules/workflow/pending-commits.service.js +55 -0
  204. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  205. package/dist/modules/workflow/workflow-hooks.d.ts +54 -32
  206. package/dist/modules/workflow/workflow-hooks.d.ts.map +1 -1
  207. package/dist/modules/workflow/workflow-hooks.js +16 -1
  208. package/dist/modules/workflow/workflow-hooks.js.map +1 -1
  209. package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
  210. package/dist/modules/workflow/workflow.routes.js +3 -1
  211. package/dist/modules/workflow/workflow.routes.js.map +1 -1
  212. package/dist/modules/workflow/workflow.service.d.ts +94 -13
  213. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  214. package/dist/modules/workflow/workflow.service.js +258 -51
  215. package/dist/modules/workflow/workflow.service.js.map +1 -1
  216. package/dist/modules/workspace/agent-access.gate.d.ts +94 -0
  217. package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -0
  218. package/dist/modules/workspace/agent-access.gate.js +123 -0
  219. package/dist/modules/workspace/agent-access.gate.js.map +1 -0
  220. package/dist/modules/workspace/routine-write-policy.d.ts +5 -6
  221. package/dist/modules/workspace/routine-write-policy.d.ts.map +1 -1
  222. package/dist/modules/workspace/routine-write-policy.js +5 -6
  223. package/dist/modules/workspace/routine-write-policy.js.map +1 -1
  224. package/dist/modules/workspace/session-sink.d.ts +5 -5
  225. package/dist/modules/workspace/set-aside-clone.d.ts +46 -0
  226. package/dist/modules/workspace/set-aside-clone.d.ts.map +1 -0
  227. package/dist/modules/workspace/set-aside-clone.js +92 -0
  228. package/dist/modules/workspace/set-aside-clone.js.map +1 -0
  229. package/dist/modules/workspace/startup/kb-startup-runner.d.ts +117 -1
  230. package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
  231. package/dist/modules/workspace/startup/kb-startup-runner.js +190 -1
  232. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
  233. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  234. package/dist/modules/workspace/workspace.routes.js +92 -3
  235. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  236. package/dist/modules/workspace/workspace.service.d.ts +115 -5
  237. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  238. package/dist/modules/workspace/workspace.service.js +272 -26
  239. package/dist/modules/workspace/workspace.service.js.map +1 -1
  240. package/dist/modules/workspace/workspace.tools.d.ts +2 -2
  241. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  242. package/dist/modules/workspace/workspace.tools.js +337 -117
  243. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  244. package/dist/modules/write-access/write-access.d.ts +60 -0
  245. package/dist/modules/write-access/write-access.d.ts.map +1 -0
  246. package/dist/modules/write-access/write-access.js +129 -0
  247. package/dist/modules/write-access/write-access.js.map +1 -0
  248. package/dist/shared/domain-errors.d.ts +64 -0
  249. package/dist/shared/domain-errors.d.ts.map +1 -1
  250. package/dist/shared/domain-errors.js +83 -0
  251. package/dist/shared/domain-errors.js.map +1 -1
  252. package/dist/shared/git.contract.d.ts +30 -0
  253. package/dist/shared/git.contract.d.ts.map +1 -1
  254. package/dist/shared/git.contract.js +26 -0
  255. package/dist/shared/git.contract.js.map +1 -1
  256. package/dist/tenancy/static-tenant-source.d.ts +0 -1
  257. package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
  258. package/dist/tenancy/static-tenant-source.js +0 -2
  259. package/dist/tenancy/static-tenant-source.js.map +1 -1
  260. package/migrations/0014_change_request_closed_reason.sql +1 -0
  261. package/migrations/0015_account_deactivation.sql +3 -0
  262. package/migrations/meta/0014_snapshot.json +2265 -0
  263. package/migrations/meta/0015_snapshot.json +2271 -0
  264. package/migrations/meta/_journal.json +14 -0
  265. package/package.json +3 -3
  266. package/src/__tests__/kb-layout-config.test.ts +0 -2
  267. package/src/__tests__/retired-settings.test.ts +96 -0
  268. package/src/core/__tests__/gated-boot.test.ts +132 -0
  269. package/src/core/__tests__/lifecycle.test.ts +170 -1
  270. package/src/core/__tests__/set-aside-root-is-one-place.test.ts +63 -0
  271. package/src/core/core-ports.ts +11 -1
  272. package/src/core/create-core-server.ts +69 -15
  273. package/src/core/create-core-services.ts +190 -27
  274. package/src/core/lifecycle.ts +98 -13
  275. package/src/core-config.ts +13 -14
  276. package/src/index.ts +12 -1
  277. package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +385 -0
  278. package/src/modules/access/__tests__/access-control.prospective.test.ts +94 -18
  279. package/src/modules/access/__tests__/access-requests.recut.test.ts +134 -0
  280. package/src/modules/access/__tests__/access-requests.routes.test.ts +610 -0
  281. package/src/modules/access/__tests__/access.routes.prospective.test.ts +6 -7
  282. package/src/modules/access/access-control.interface.ts +43 -12
  283. package/src/modules/access/access-control.service.ts +234 -36
  284. package/src/modules/access/access-requests.contract.ts +93 -0
  285. package/src/modules/access/access-requests.routes.ts +286 -0
  286. package/src/modules/access/access-requests.service.ts +420 -0
  287. package/src/modules/access/access.routes.ts +13 -15
  288. package/src/modules/access-model/access-grammar.ts +22 -0
  289. package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +3 -1
  290. package/src/modules/auth/__tests__/account-deactivation.test.ts +252 -0
  291. package/src/modules/auth/__tests__/account.routes.test.ts +81 -3
  292. package/src/modules/auth/__tests__/auth.middleware.test.ts +45 -22
  293. package/src/modules/auth/__tests__/auth.service.test.ts +39 -1
  294. package/src/modules/auth/account-admission.ts +78 -9
  295. package/src/modules/auth/account.routes.ts +63 -7
  296. package/src/modules/auth/auth.middleware.ts +19 -8
  297. package/src/modules/auth/auth.routes.ts +8 -0
  298. package/src/modules/auth/auth.service.ts +207 -7
  299. package/src/modules/auth/oidc-auth-provider.ts +7 -2
  300. package/src/modules/database/core-schema.ts +35 -27
  301. package/src/modules/github-app/__tests__/github-app.test.ts +848 -0
  302. package/src/modules/github-app/github-app.client.ts +268 -0
  303. package/src/modules/github-app/github-app.connection.ts +204 -0
  304. package/src/modules/github-app/github-app.routes.ts +359 -0
  305. package/src/modules/github-app/index.ts +19 -0
  306. package/src/modules/kb-fs/__tests__/branch-name.test.ts +10 -0
  307. package/src/modules/kb-fs/__tests__/remote-url.test.ts +38 -0
  308. package/src/modules/kb-fs/__tests__/repo-path.test.ts +123 -0
  309. package/src/modules/kb-fs/branch-name.ts +14 -1
  310. package/src/modules/kb-fs/remote-url.ts +35 -0
  311. package/src/modules/kb-fs/repo-path.ts +85 -0
  312. package/src/modules/kb-sync/__tests__/kb-sync.routes.test.ts +26 -1
  313. package/src/modules/kb-sync/kb-sync.routes.ts +14 -4
  314. package/src/modules/kb-sync/sync-auth.ts +2 -2
  315. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +60 -3
  316. package/src/modules/mcp/__tests__/mcp.service.test.ts +68 -6
  317. package/src/modules/mcp/mcp-auth.middleware.ts +20 -6
  318. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +3 -1
  319. package/src/modules/plugins/__tests__/join-proposals.test.ts +100 -19
  320. package/src/modules/plugins/__tests__/join-requests.service.test.ts +27 -11
  321. package/src/modules/plugins/__tests__/join-requests.settlement.test.ts +371 -0
  322. package/src/modules/plugins/__tests__/plugins.routes.test.ts +1 -1
  323. package/src/modules/plugins/__tests__/plugins.tools.test.ts +2 -1
  324. package/src/modules/plugins/join-proposals.ts +87 -19
  325. package/src/modules/plugins/join-requests.service.ts +199 -39
  326. package/src/modules/plugins/plugins.routes.ts +3 -2
  327. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +16 -0
  328. package/src/modules/settings/__tests__/repository-source.test.ts +191 -0
  329. package/src/modules/settings/__tests__/setup.routes.git-mode.test.ts +386 -0
  330. package/src/modules/settings/__tests__/setup.routes.github-app.test.ts +299 -0
  331. package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +236 -0
  332. package/src/modules/settings/__tests__/setup.routes.repository-change.test.ts +390 -0
  333. package/src/modules/settings/__tests__/setup.routes.test.ts +3 -0
  334. package/src/modules/settings/deployment-settings.service.ts +117 -6
  335. package/src/modules/settings/managed-repository.ts +68 -0
  336. package/src/modules/settings/repository-source.ts +197 -0
  337. package/src/modules/settings/setup.routes.ts +463 -15
  338. package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +2 -1
  339. package/src/modules/skills/__tests__/branch-skills.tools.test.ts +218 -0
  340. package/src/modules/skills/__tests__/skill-access-requests.routes.test.ts +5 -1
  341. package/src/modules/skills/__tests__/skills.service.test.ts +229 -5
  342. package/src/modules/skills/skill-access-requests.routes.ts +25 -75
  343. package/src/modules/skills/skills.contract.ts +91 -18
  344. package/src/modules/skills/skills.service.ts +278 -41
  345. package/src/modules/skills/skills.tools.ts +80 -8
  346. package/src/modules/tool-auth/__tests__/manual-auth.middleware.test.ts +14 -1
  347. package/src/modules/tool-auth/external-api-key.service.ts +4 -2
  348. package/src/modules/tool-auth/internal-token.service.ts +3 -3
  349. package/src/modules/tool-auth/tool-auth.middleware.ts +34 -11
  350. package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +5 -3
  351. package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +3 -3
  352. package/src/modules/tool-helpers/__tests__/validate-token.test.ts +11 -1
  353. package/src/modules/tool-helpers/tool-context.ts +15 -0
  354. package/src/modules/tool-helpers/tool-handler.ts +24 -4
  355. package/src/modules/tool-helpers/tool.contract.ts +3 -2
  356. package/src/modules/tool-helpers/validate-token.ts +1 -1
  357. package/src/modules/workflow/__tests__/apply-failure.test.ts +9 -1
  358. package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +111 -0
  359. package/src/modules/workflow/__tests__/workflow.routes.history-read-gate.test.ts +25 -0
  360. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +159 -8
  361. package/src/modules/workflow/__tests__/workflow.service.repository-replaced.test.ts +249 -0
  362. package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +146 -40
  363. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +405 -7
  364. package/src/modules/workflow/agent-tools/change-request-summary.ts +182 -0
  365. package/src/modules/workflow/agent-tools/workflow.tools.ts +135 -38
  366. package/src/modules/workflow/file-lock.service.ts +31 -0
  367. package/src/modules/workflow/git/__tests__/git.service.fileBytesAtCommit.test.ts +260 -0
  368. package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +121 -0
  369. package/src/modules/workflow/git/__tests__/pull-request.service.getPrDetail.test.ts +145 -2
  370. package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-delete.test.ts +166 -0
  371. package/src/modules/workflow/git/git.service.ts +193 -4
  372. package/src/modules/workflow/git/node-git-runner.ts +6 -0
  373. package/src/modules/workflow/git/pull-request.service.ts +119 -0
  374. package/src/modules/workflow/pending-commits.service.ts +59 -1
  375. package/src/modules/workflow/workflow-hooks.ts +64 -26
  376. package/src/modules/workflow/workflow.routes.ts +3 -1
  377. package/src/modules/workflow/workflow.service.ts +290 -55
  378. package/src/modules/workspace/__tests__/agent-access.gate.test.ts +208 -0
  379. package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +384 -0
  380. package/src/modules/workspace/__tests__/file-stat-access.test.ts +2 -1
  381. package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -1
  382. package/src/modules/workspace/__tests__/set-aside-clone.test.ts +52 -0
  383. package/src/modules/workspace/__tests__/workspace.routes.at-ref.test.ts +305 -0
  384. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +2 -0
  385. package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +147 -0
  386. package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +326 -0
  387. package/src/modules/workspace/__tests__/workspace.service.test.ts +20 -9
  388. package/src/modules/workspace/__tests__/workspace.service.unknown-branch.test.ts +43 -0
  389. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +2 -3
  390. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +235 -10
  391. package/src/modules/workspace/__tests__/workspace.tools.test.ts +720 -17
  392. package/src/modules/workspace/agent-access.gate.ts +164 -0
  393. package/src/modules/workspace/routine-write-policy.ts +5 -6
  394. package/src/modules/workspace/session-sink.ts +5 -5
  395. package/src/modules/workspace/set-aside-clone.ts +96 -0
  396. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +297 -0
  397. package/src/modules/workspace/startup/kb-startup-runner.ts +242 -2
  398. package/src/modules/workspace/workspace.routes.ts +95 -3
  399. package/src/modules/workspace/workspace.service.ts +273 -25
  400. package/src/modules/workspace/workspace.tools.ts +373 -119
  401. package/src/modules/write-access/__tests__/write-access.test.ts +248 -0
  402. package/src/modules/write-access/write-access.ts +153 -0
  403. package/src/shared/domain-errors.ts +89 -0
  404. package/src/shared/git.contract.ts +37 -0
  405. package/src/tenancy/__tests__/static-tenant-source.test.ts +0 -1
  406. package/src/tenancy/static-tenant-source.ts +0 -3
  407. package/dist/modules/workflow/session-ontology.policy.d.ts +0 -52
  408. package/dist/modules/workflow/session-ontology.policy.d.ts.map +0 -1
  409. package/dist/modules/workflow/session-ontology.policy.js +0 -62
  410. package/dist/modules/workflow/session-ontology.policy.js.map +0 -1
  411. package/dist/modules/workflow/session-ontology.service.d.ts +0 -105
  412. package/dist/modules/workflow/session-ontology.service.d.ts.map +0 -1
  413. package/dist/modules/workflow/session-ontology.service.js +0 -147
  414. package/dist/modules/workflow/session-ontology.service.js.map +0 -1
  415. package/dist/modules/workspace/session-ontology.gate.d.ts +0 -114
  416. package/dist/modules/workspace/session-ontology.gate.d.ts.map +0 -1
  417. package/dist/modules/workspace/session-ontology.gate.js +0 -161
  418. package/dist/modules/workspace/session-ontology.gate.js.map +0 -1
  419. package/dist/shared/kb-layout.d.ts +0 -39
  420. package/dist/shared/kb-layout.d.ts.map +0 -1
  421. package/dist/shared/kb-layout.js +0 -103
  422. package/dist/shared/kb-layout.js.map +0 -1
  423. package/dist/shared/kb-layout.test.d.ts +0 -2
  424. package/dist/shared/kb-layout.test.d.ts.map +0 -1
  425. package/dist/shared/kb-layout.test.js +0 -75
  426. package/dist/shared/kb-layout.test.js.map +0 -1
  427. package/src/modules/workflow/__tests__/session-ontology.policy.test.ts +0 -62
  428. package/src/modules/workflow/__tests__/session-ontology.service.test.ts +0 -201
  429. package/src/modules/workflow/session-ontology.policy.ts +0 -70
  430. package/src/modules/workflow/session-ontology.service.ts +0 -183
  431. package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +0 -239
  432. package/src/modules/workspace/session-ontology.gate.ts +0 -191
  433. package/src/shared/kb-layout.test.ts +0 -98
  434. package/src/shared/kb-layout.ts +0 -102
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-access.gate.js","sourceRoot":"","sources":["../../../src/modules/workspace/agent-access.gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAMH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,sBAAsB,GACjC,4VAA4V,CAAC;AAE/V;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAe;IAC1C,IAAI,EAAE,QAAQ;IACd,WAAW,EAAE,sBAAsB;CACpC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,OAAO,oBAAoB;IACvB,SAAS,GAAG,EAAE,CAAC;IACf,SAAS,GAAG,EAAE,CAAC;IACN,SAAS,GAAG,IAAI,GAAG,EAAc,CAAC;IAEnD,4DAA4D;IAC5D,qBAAqB,CAAC,IAAY;QAChC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,QAAQ,EAAE,CAAC;IAClB,CAAC;IAED,iEAAiE;IACjE,qBAAqB,CAAC,IAAY;QAChC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,QAAQ,EAAE,CAAC;IAClB,CAAC;IAED,oEAAoE;IACpE,aAAa;QACX,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,8EAA8E;IAC9E,oBAAoB;QAClB,OAAO,sBAAsB,GAAG,IAAI,CAAC,SAAS,CAAC;IACjD,CAAC;IAED,mFAAmF;IACnF,QAAQ,CAAC,QAAoB;QAC3B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAEO,QAAQ;QACd,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS;YAAE,QAAQ,EAAE,CAAC;IACpD,CAAC;CACF;AAgBD,gFAAgF;AAChF,SAAS,aAAa,CAAC,GAAgB;IACrC,OAAO,GAAG,CAAC,MAAM,KAAK,UAAU,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACpF,CAAC;AAED;;;;GAIG;AACH,SAAS,gBAAgB,CACvB,IAAqB,EACrB,GAAgB,EAChB,MAAc,EACd,MAAe;IAEf,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACjC,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,gBAAgB;QAAE,OAAO,IAAI,CAAC;IACxE,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;AAC9E,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAqB,EACrB,GAAgB,EAChB,MAAc,EACd,MAAc;IAEd,MAAM,EAAE,GAAG,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACvD,IAAI,EAAE,KAAK,IAAI;QAAE,OAAO;IACxB,MAAM,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,IAAqB,EACrB,GAAgB,EAChB,MAAc,EACd,MAAe;IAEf,MAAM,EAAE,GAAG,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACvD,IAAI,EAAE,KAAK,IAAI;QAAE,OAAO;IACxB,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;AACnC,CAAC"}
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Per-run write restriction for background/routine agents.
3
3
  *
4
- * The ontology-session gate ({@link ../workflow/session-ontology.service}) bounds
5
- * WHICH ontology a run may write; this narrower gate bounds WHAT KIND of file a
6
- * run may write, by extension. It exists for the `watchlist_check` routine, which is
4
+ * The agent-access gate ({@link ./agent-access.gate}) lets a deployment decide
5
+ * WHETHER a run may write a path at all; this narrower gate bounds WHAT KIND of
6
+ * file a run may write, by extension. It exists for the `watchlist_check` routine, which is
7
7
  * allowed to refresh dashboard views (`.html`) but must never touch the
8
8
  * knowledge-graph nodes (`.md`) or any other file — a hard, code-level guarantee
9
9
  * rather than a prompt request (a prompt-only rule has already proven insufficient).
@@ -11,9 +11,8 @@
11
11
  * Keyed by the agent run's `sessionId` (the background agent passes its run id as
12
12
  * the session id, which the internal token then carries onto every loopback tool
13
13
  * call as `ctx.sessionId`). A restriction lives only for the duration of one
14
- * in-process run, so the store is a plain in-memory map — no durability needed,
15
- * unlike the ontology touched-set which must survive restarts and span the MCP
16
- * proxy. The executor sets the restriction before streaming and clears it after.
14
+ * in-process run, so the store is a plain in-memory map — no durability needed.
15
+ * The executor sets the restriction before streaming and clears it after.
17
16
  */
18
17
  export interface IRoutineWritePolicy {
19
18
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"routine-write-policy.d.ts","sourceRoot":"","sources":["../../../src/modules/workspace/routine-write-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,MAAM,WAAW,mBAAmB;IAClC;;;;;OAKG;IACH,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAC7E,wDAAwD;IACxD,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;OAIG;IACH,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACxE;;;;OAIG;IACH,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;CACzD;AAED,qBAAa,yBAA0B,YAAW,mBAAmB;IACnE,wEAAwE;IACxE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkC;IAE/D,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IAI5E,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAI9B,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAcvE,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;CAQxD"}
1
+ {"version":3,"file":"routine-write-policy.d.ts","sourceRoot":"","sources":["../../../src/modules/workspace/routine-write-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,MAAM,WAAW,mBAAmB;IAClC;;;;;OAKG;IACH,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAC7E,wDAAwD;IACxD,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;OAIG;IACH,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACxE;;;;OAIG;IACH,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;CACzD;AAED,qBAAa,yBAA0B,YAAW,mBAAmB;IACnE,wEAAwE;IACxE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkC;IAE/D,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IAI5E,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAI9B,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAcvE,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;CAQxD"}
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Per-run write restriction for background/routine agents.
3
3
  *
4
- * The ontology-session gate ({@link ../workflow/session-ontology.service}) bounds
5
- * WHICH ontology a run may write; this narrower gate bounds WHAT KIND of file a
6
- * run may write, by extension. It exists for the `watchlist_check` routine, which is
4
+ * The agent-access gate ({@link ./agent-access.gate}) lets a deployment decide
5
+ * WHETHER a run may write a path at all; this narrower gate bounds WHAT KIND of
6
+ * file a run may write, by extension. It exists for the `watchlist_check` routine, which is
7
7
  * allowed to refresh dashboard views (`.html`) but must never touch the
8
8
  * knowledge-graph nodes (`.md`) or any other file — a hard, code-level guarantee
9
9
  * rather than a prompt request (a prompt-only rule has already proven insufficient).
@@ -11,9 +11,8 @@
11
11
  * Keyed by the agent run's `sessionId` (the background agent passes its run id as
12
12
  * the session id, which the internal token then carries onto every loopback tool
13
13
  * call as `ctx.sessionId`). A restriction lives only for the duration of one
14
- * in-process run, so the store is a plain in-memory map — no durability needed,
15
- * unlike the ontology touched-set which must survive restarts and span the MCP
16
- * proxy. The executor sets the restriction before streaming and clears it after.
14
+ * in-process run, so the store is a plain in-memory map — no durability needed.
15
+ * The executor sets the restriction before streaming and clears it after.
17
16
  */
18
17
  import { ToolError } from '../tool-helpers/tool.contract.js';
19
18
  export class RoutineWritePolicyService {
@@ -1 +1 @@
1
- {"version":3,"file":"routine-write-policy.js","sourceRoot":"","sources":["../../../src/modules/workspace/routine-write-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AA0B7D,MAAM,OAAO,yBAAyB;IACpC,wEAAwE;IACvD,YAAY,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE/D,oBAAoB,CAAC,SAAiB,EAAE,UAA6B;QACnE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC;IACpF,CAAC;IAED,KAAK,CAAC,SAAiB;QACrB,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC;IAED,kBAAkB,CAAC,SAA6B,EAAE,MAAc;QAC9D,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACzE,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;QACnC,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAAE,OAAO;QAClC,CAAC;QACD,MAAM,IAAI,SAAS,CACjB,2BAA2B,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,8BAA8B,MAAM,gBAAgB;YACpG,uEAAuE,EACzE,GAAG,CACJ,CAAC;IACJ,CAAC;IAED,kBAAkB,CAAC,SAA6B;QAC9C,IAAI,SAAS,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YAClD,MAAM,IAAI,SAAS,CACjB,sHAAsH,EACtH,GAAG,CACJ,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"routine-write-policy.js","sourceRoot":"","sources":["../../../src/modules/workspace/routine-write-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AA0B7D,MAAM,OAAO,yBAAyB;IACpC,wEAAwE;IACvD,YAAY,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE/D,oBAAoB,CAAC,SAAiB,EAAE,UAA6B;QACnE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC;IACpF,CAAC;IAED,KAAK,CAAC,SAAiB;QACrB,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC;IAED,kBAAkB,CAAC,SAA6B,EAAE,MAAc;QAC9D,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACzE,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;QACnC,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAAE,OAAO;QAClC,CAAC;QACD,MAAM,IAAI,SAAS,CACjB,2BAA2B,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,8BAA8B,MAAM,gBAAgB;YACpG,uEAAuE,EACzE,GAAG,CACJ,CAAC;IACJ,CAAC;IAED,kBAAkB,CAAC,SAA6B;QAC9C,IAAI,SAAS,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YAClD,MAAM,IAAI,SAAS,CACjB,sHAAsH,EACtH,GAAG,CACJ,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Port behind `start_session` (workspace.tools.ts): mints the session id an
3
- * external agent scopes its ontology boundary under. The workspace module owns
4
- * the port; WHAT backs the id is the composition root's choice:
2
+ * Port behind `start_session` (workspace.tools.ts): mints the id an external
3
+ * agent names its conversation with. The workspace module owns the port; WHAT
4
+ * backs the id is the composition root's choice:
5
5
  *
6
- * - Core default ({@link UuidSessionSink}): a bare random id — sufficient for
7
- * the session-ontology gate, with no app-side session record.
6
+ * - Core default ({@link UuidSessionSink}): a bare random id — all the
7
+ * agent-access hooks need, with no app-side session record.
8
8
  * - The enterprise app substitutes a sink that mints a REAL chat thread, so the
9
9
  * same id also works end to end with `ask` (its sessionId IS a chat thread).
10
10
  * See the `start_session` comment in workspace.tools.ts for why that
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Moving a working copy of a repository that is no longer the configured one
3
+ * OUT OF THE WAY, rather than deleting it.
4
+ *
5
+ * Two places find such a clone: the KB startup phase, sweeping the whole
6
+ * workspaces root at boot and on the save that changes the address, and the
7
+ * workspace service, refusing to adopt one it finds on disk. Both must do the
8
+ * same thing with it, because what is in it is the same thing: commits that
9
+ * may exist nowhere else. A clone fetches and pushes through the address in
10
+ * its own `remote.origin.url`, and two repositories need not share a commit,
11
+ * so such a clone cannot be re-pointed — but that is a reason not to USE it,
12
+ * never a reason to destroy it.
13
+ */
14
+ /**
15
+ * Where working copies are set aside. NOT under the workspaces root: the
16
+ * workspace service's orphan sweep removes every folder there that is not a
17
+ * known branch's, and what is set aside has to outlive that sweep.
18
+ *
19
+ * PASS THE OVERRIDE, and pass BOTH CALLERS THE SAME ONE. The fallback is a
20
+ * sibling of the workspaces root, which is a lasting place only when that
21
+ * parent is itself a mounted volume — in the shipped compose file it is the
22
+ * container's own filesystem, so a copy left there is gone at the next
23
+ * recreate, having been kept on the promise that it would not be. The
24
+ * composition root therefore roots both the startup phase and the workspace
25
+ * service at one directory under the backups volume; the fallback is for a
26
+ * test, or a caller with no deployment layout to speak of.
27
+ */
28
+ export declare function setAsideRootFor(workspacesRoot: string, override?: string): string;
29
+ /**
30
+ * One folder per occasion, so what was set aside together stays together and
31
+ * two replacements never land in each other's folder.
32
+ */
33
+ export declare function setAsideStamp(at?: Date): string;
34
+ /**
35
+ * Move the clone at `repoDir` to `dest`. A rename when it can be one; a copy
36
+ * when it cannot (another volume, a handle held open on it), and only then is
37
+ * the original removed — so a copy that fails leaves the clone exactly where
38
+ * it was rather than half of it in each place.
39
+ *
40
+ * Throws with what to do about it. The caller decides how much that costs:
41
+ * the startup phase stops the boot (leaving the clone would fail a step later
42
+ * with git's words about a repository nobody configured), while a single
43
+ * branch open refuses that branch.
44
+ */
45
+ export declare function setAsideClone(repoDir: string, dest: string): Promise<void>;
46
+ //# sourceMappingURL=set-aside-clone.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"set-aside-clone.d.ts","sourceRoot":"","sources":["../../../src/modules/workspace/set-aside-clone.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AAEH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAAE,GAAE,IAAiB,GAAG,MAAM,CAE3D;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyChF"}
@@ -0,0 +1,92 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ /**
4
+ * Moving a working copy of a repository that is no longer the configured one
5
+ * OUT OF THE WAY, rather than deleting it.
6
+ *
7
+ * Two places find such a clone: the KB startup phase, sweeping the whole
8
+ * workspaces root at boot and on the save that changes the address, and the
9
+ * workspace service, refusing to adopt one it finds on disk. Both must do the
10
+ * same thing with it, because what is in it is the same thing: commits that
11
+ * may exist nowhere else. A clone fetches and pushes through the address in
12
+ * its own `remote.origin.url`, and two repositories need not share a commit,
13
+ * so such a clone cannot be re-pointed — but that is a reason not to USE it,
14
+ * never a reason to destroy it.
15
+ */
16
+ /**
17
+ * Where working copies are set aside. NOT under the workspaces root: the
18
+ * workspace service's orphan sweep removes every folder there that is not a
19
+ * known branch's, and what is set aside has to outlive that sweep.
20
+ *
21
+ * PASS THE OVERRIDE, and pass BOTH CALLERS THE SAME ONE. The fallback is a
22
+ * sibling of the workspaces root, which is a lasting place only when that
23
+ * parent is itself a mounted volume — in the shipped compose file it is the
24
+ * container's own filesystem, so a copy left there is gone at the next
25
+ * recreate, having been kept on the promise that it would not be. The
26
+ * composition root therefore roots both the startup phase and the workspace
27
+ * service at one directory under the backups volume; the fallback is for a
28
+ * test, or a caller with no deployment layout to speak of.
29
+ */
30
+ export function setAsideRootFor(workspacesRoot, override) {
31
+ return override ?? path.resolve(workspacesRoot, '..', 'replaced-working-copies');
32
+ }
33
+ /**
34
+ * One folder per occasion, so what was set aside together stays together and
35
+ * two replacements never land in each other's folder.
36
+ */
37
+ export function setAsideStamp(at = new Date()) {
38
+ return at.toISOString().replace(/[:.]/g, '-');
39
+ }
40
+ /**
41
+ * Move the clone at `repoDir` to `dest`. A rename when it can be one; a copy
42
+ * when it cannot (another volume, a handle held open on it), and only then is
43
+ * the original removed — so a copy that fails leaves the clone exactly where
44
+ * it was rather than half of it in each place.
45
+ *
46
+ * Throws with what to do about it. The caller decides how much that costs:
47
+ * the startup phase stops the boot (leaving the clone would fail a step later
48
+ * with git's words about a repository nobody configured), while a single
49
+ * branch open refuses that branch.
50
+ */
51
+ export async function setAsideClone(repoDir, dest) {
52
+ // A destination that is already there is somebody's set-aside work, and
53
+ // never this call's to touch: the copy below cleans up after itself by
54
+ // removing the destination, which must then be one this call made.
55
+ const taken = () => new Error(`Could not set aside the working copy at ${repoDir}: ${dest} already holds one. Nothing was deleted or ` +
56
+ 'moved. Try again; the folder is named for the moment it is made.');
57
+ if (await fs.access(dest).then(() => true, () => false))
58
+ throw taken();
59
+ await fs.mkdir(path.dirname(dest), { recursive: true });
60
+ try {
61
+ await fs.rename(repoDir, dest);
62
+ return;
63
+ }
64
+ catch {
65
+ // Another volume, or a handle held open on it: copy instead.
66
+ }
67
+ // The destination is MADE here, by the one call that will fill it. Two
68
+ // calls that both found it absent above cannot both make it: the second
69
+ // is refused by the filesystem, before the cleanup below could ever be
70
+ // about a folder it did not create.
71
+ try {
72
+ await fs.mkdir(dest);
73
+ }
74
+ catch (err) {
75
+ if (err.code === 'EEXIST')
76
+ throw taken();
77
+ throw err;
78
+ }
79
+ try {
80
+ // Into the empty folder made above, which is why an existing destination
81
+ // is not an error here; nothing is overwritten (`force: false`).
82
+ await fs.cp(repoDir, dest, { recursive: true, errorOnExist: false, force: false, verbatimSymlinks: true });
83
+ }
84
+ catch (err) {
85
+ await fs.rm(dest, { recursive: true, force: true }).catch(() => undefined);
86
+ throw new Error(`Could not set aside the working copy at ${repoDir}, a clone of a repository that is no longer the ` +
87
+ `configured one, into ${dest}: ${err instanceof Error ? err.message : String(err)}. Nothing was deleted. ` +
88
+ 'Make room there, or move that folder away by hand, and start again.', { cause: err });
89
+ }
90
+ await fs.rm(repoDir, { recursive: true, force: true });
91
+ }
92
+ //# sourceMappingURL=set-aside-clone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"set-aside-clone.js","sourceRoot":"","sources":["../../../src/modules/workspace/set-aside-clone.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;;;;;;;;;;GAYG;AAEH;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,eAAe,CAAC,cAAsB,EAAE,QAAiB;IACvE,OAAO,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,IAAI,EAAE,yBAAyB,CAAC,CAAC;AACnF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,KAAW,IAAI,IAAI,EAAE;IACjD,OAAO,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;AAChD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAe,EAAE,IAAY;IAC/D,wEAAwE;IACxE,uEAAuE;IACvE,mEAAmE;IACnE,MAAM,KAAK,GAAG,GAAU,EAAE,CACxB,IAAI,KAAK,CACP,2CAA2C,OAAO,KAAK,IAAI,6CAA6C;QACtG,kEAAkE,CACrE,CAAC;IACJ,IAAI,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC;QAAE,MAAM,KAAK,EAAE,CAAC;IACvE,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC/B,OAAO;IACT,CAAC;IAAC,MAAM,CAAC;QACP,6DAA6D;IAC/D,CAAC;IACD,uEAAuE;IACvE,wEAAwE;IACxE,uEAAuE;IACvE,oCAAoC;IACpC,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,KAAK,EAAE,CAAC;QACpE,MAAM,GAAG,CAAC;IACZ,CAAC;IACD,IAAI,CAAC;QACH,yEAAyE;QACzE,iEAAiE;QACjE,MAAM,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7G,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC3E,MAAM,IAAI,KAAK,CACb,2CAA2C,OAAO,kDAAkD;YAClG,wBAAwB,IAAI,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,yBAAyB;YAC1G,qEAAqE,EACvE,EAAE,KAAK,EAAE,GAAG,EAAE,CACf,CAAC;IACJ,CAAC;IACD,MAAM,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACzD,CAAC"}
@@ -10,7 +10,9 @@ import { ClassifiedFailure, type GitFailure } from '../../../shared/git-failure.
10
10
  *
11
11
  * Fully fail-closed: any failure this phase cannot DECLARE (an unhandled
12
12
  * step throw, an unreachable remote, a clone that will not come down, a
13
- * refused write) throws out of `runAll` and stops the boot. The container's
13
+ * refused write) throws out of `runAll`. The boot stops on it unless the
14
+ * host ANSWERED about the repository or the credentials, in which case it
15
+ * comes up gated instead — see `bootMaySurvive`. The container's
14
16
  * restart policy is the retry — each attempt at boot time on quiet trees —
15
17
  * so an environmental failure converges without a human the moment the
16
18
  * environment returns. The one carve-out: a push rejected because a
@@ -32,6 +34,15 @@ export interface KbStartupRunnerOptions {
32
34
  gitRunner: IGitRunner;
33
35
  kbRepoUrl: () => string;
34
36
  workspacesRoot: string;
37
+ /**
38
+ * Where a working copy of a repository that is no longer the configured one
39
+ * is moved to, under a folder named for the moment it happened — see
40
+ * `reconcileClonesWithConfiguredRepository`. THE SAME VALUE THE WORKSPACE
41
+ * SERVICE IS GIVEN.
42
+ *
43
+ * Why, in one place: `setAsideRootFor` in `set-aside-clone.ts`.
44
+ */
45
+ setAsideRoot?: string;
35
46
  kbDirName: string;
36
47
  templateDir: string;
37
48
  defaultBranch: () => string;
@@ -48,6 +59,47 @@ export interface KbStartupRunnerOptions {
48
59
  template `.gitignore` rule can never silently drop a required seed file
49
60
  from the commit. */
50
61
  buildSeedTree: (dir: string) => Promise<string[]>;
62
+ /**
63
+ * Called with the workspace id of each working copy the phase is ABOUT TO
64
+ * set aside for being a clone of another repository, and awaited first.
65
+ * Whatever is still queued to be committed into that copy was written
66
+ * against the repository that was left, so it must be held back before a
67
+ * fresh clone of another repository appears at the same path.
68
+ *
69
+ * A PRECONDITION, so a failure here STOPS THE PHASE with the copy still
70
+ * where it was. Carrying on would clone the replacement with the queue
71
+ * untouched, and the worker would then commit those bytes into a
72
+ * repository they were never meant for. Stopped before anything moved, the
73
+ * next run finds the same copy and asks again. Optional: a minimal graph
74
+ * has no queue.
75
+ */
76
+ beforeCloneSetAside?: (workspaceId: string) => Promise<void>;
77
+ /**
78
+ * Called with the workspace id of each working copy the phase SET ASIDE for
79
+ * being a clone of another repository. The workspace service keeps a
80
+ * branch→directory cache and adopts whatever is on disk, so on a RUNNING
81
+ * server (the save that moves the deployment to another repository) the
82
+ * copy would otherwise stay in that cache as a path to nothing. Optional:
83
+ * at boot nothing has been cached yet.
84
+ *
85
+ * Never throws into the phase: a listener that fails must not stop a boot.
86
+ */
87
+ onCloneDiscarded?: (workspaceId: string) => void | Promise<void>;
88
+ /**
89
+ * Called with the workspace id of each working copy the phase CLONED fresh.
90
+ * A clone has just downloaded every ref, which is a successful fetch by any
91
+ * measure — but the git layer keeps its own per-workspace record of the last
92
+ * fetch and whether it worked, and the one that drove the replacement is a
93
+ * FAILED fetch of the repository that is gone. Left standing, a strict
94
+ * branch listing inside that record's TTL refuses the new clone's refs as
95
+ * unproven, having never touched it.
96
+ *
97
+ * The ordinary path already says this (`WorkspaceService`'s cloned
98
+ * listener); the phase clones with git directly, so it has to say it itself.
99
+ *
100
+ * Never throws into the phase, for the same reason as above.
101
+ */
102
+ onCloneCreated?: (workspaceId: string) => void;
51
103
  }
52
104
  /**
53
105
  * The remote could not be reached or refused us: a host that is down, a DNS
@@ -71,6 +123,15 @@ export declare class KbRemoteUnreachableError extends ClassifiedFailure {
71
123
  cause?: unknown;
72
124
  });
73
125
  }
126
+ /**
127
+ * Whether the boot may come up GATED on this failure instead of stopping.
128
+ *
129
+ * {@link KbRemoteUnreachableError} says so by its type — it is raised where
130
+ * the remote is first contacted, before any step. A failure raised INSIDE the
131
+ * phase says so by its classification: the same fetch that stops a boot when
132
+ * it is the first contact must not stop one when a step happened to make it.
133
+ */
134
+ export declare function bootMaySurvive(err: unknown): boolean;
74
135
  export interface RetryOptions {
75
136
  /** First wait before trying again. Default 30s. */
76
137
  initialDelayMs?: number;
@@ -150,6 +211,61 @@ export declare class KbStartupRunner {
150
211
  */
151
212
  private ensureRemote;
152
213
  private buildContext;
214
+ /**
215
+ * Bring every working copy on disk into line with the configured
216
+ * repository, WITHOUT DELETING ANYTHING.
217
+ *
218
+ * A clone fetches and pushes through the address stored in its own
219
+ * `remote.origin.url`, which nothing updates when the configured address
220
+ * changes. After an operator replaced the repository (2026-09-28: the old
221
+ * one deleted, a new one saved on the setup screen) every surviving clone
222
+ * kept asking the old address: Save and Retry failed, and the restart
223
+ * stopped the boot on `repository … not found`, taking the setup screen
224
+ * with it.
225
+ *
226
+ * A clone whose address differs from the configured one is one of two
227
+ * things, and the histories say which:
228
+ *
229
+ * - THE SAME REPOSITORY AT A NEW ADDRESS — moved, renamed, mirrored. The
230
+ * one change the setup screen tells an admin to make ("only change this
231
+ * if the same repository was moved or renamed"). Its history is on the
232
+ * configured remote, so the clone is pointed at the new address and
233
+ * kept, unpushed commits included: they are pushed where they belonged
234
+ * all along.
235
+ * - ANOTHER REPOSITORY. Re-pointing it would push one repository's
236
+ * commits into the other, so it cannot stay where the workspace service
237
+ * would adopt it. It is MOVED to {@link KbStartupRunnerOptions.setAsideRoot}
238
+ * and the log says where. Never removed: when the old repository is gone
239
+ * — as it was that day — these clones are the last copy of the
240
+ * knowledge base that exists, and the unpushed work in them exists
241
+ * nowhere else at all.
242
+ *
243
+ * Every clone under the workspaces root, not only the branches this phase
244
+ * touches: the workspace service adopts whatever it finds there. A clone
245
+ * whose address cannot be read is left where it is — "could not tell" is
246
+ * no reason to move someone's work. One that cannot be set aside stops the
247
+ * boot with the reason: leaving it would fail the boot a step later with
248
+ * git's words about a repository nobody configured.
249
+ */
250
+ private reconcileClonesWithConfiguredRepository;
251
+ /**
252
+ * Whether the configured repository holds history this clone has too —
253
+ * asked of git, not guessed from the two addresses. The configured
254
+ * remote's branches are fetched under a namespace of their own and the
255
+ * clone's HEAD is tested for a common ancestor with any of them.
256
+ *
257
+ * "Could not tell" answers false: a fetch that fails, a clone with no
258
+ * commit to compare. False sets the clone aside, which loses nothing, where
259
+ * a wrong true would push a stranger's commits into the configured
260
+ * repository.
261
+ */
262
+ private sharesHistoryWithConfigured;
263
+ /**
264
+ * Move a working copy out of the workspaces root, whole. A rename where
265
+ * the two folders share a volume; a copy and then a removal where they do
266
+ * not (the backups root is a volume of its own in the shipped compose
267
+ * files), with the original removed only once the copy is complete.
268
+ */
153
269
  /**
154
270
  * The branch's working copy at the runtime layout
155
271
  * (`<workspacesRoot>/<id>/<kbDirName>`), so the workspace service finds it
@@ -1 +1 @@
1
- {"version":3,"file":"kb-startup-runner.d.ts","sourceRoot":"","sources":["../../../../src/modules/workspace/startup/kb-startup-runner.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAY,aAAa,EAAsB,MAAM,sBAAsB,CAAC;AAExF,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC/E,OAAO,EACL,iBAAiB,EAIjB,KAAK,UAAU,EAChB,MAAM,gCAAgC,CAAC;AAGxC;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,SAAS,EAAE,UAAU,CAAC;IACtB,SAAS,EAAE,MAAM,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,MAAM,CAAC;IAC5B,iBAAiB,EAAE,MAAM,SAAS,MAAM,EAAE,CAAC;IAC3C,8EAA8E;IAC9E,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,oFAAoF;IACpF,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;IAChC;;;;;;2BAMuB;IACvB,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CACnD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,wBAAyB,SAAQ,iBAAiB;gBACjD,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAQxD;AAED,MAAM,WAAW,YAAY;IAC3B,mDAAmD;IACnD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,sBAAsB;IAEzD,gFAAgF;IAChF,OAAO,CAAC,QAAQ,CAA8B;IAC9C,yEAAyE;IACzE,OAAO,CAAC,OAAO,CAAuB;IACtC,iEAAiE;IACjE,OAAO,CAAC,WAAW,CAA2B;IAC9C,+EAA+E;IAC/E,OAAO,CAAC,aAAa,CAA4C;IAEjE;;;;;;OAMG;IACH,OAAO,CAAC,MAAM;IAId;;;;;OAKG;IACH,iBAAiB,IAAI;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;IAIvD;;;;;OAKG;IACH,WAAW,IAAI,MAAM,GAAG,IAAI;IAI5B,iGAAiG;IACjG,eAAe,IAAI,UAAU,GAAG,IAAI;IAIpC;;;;;;;OAOG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBvB;;;;;;;;;;OAUG;IACH,oBAAoB,CAAC,IAAI,GAAE,YAAiB,GAAG;QAAE,IAAI,IAAI,IAAI,CAAA;KAAE;YAgEjD,UAAU;IA2HxB;;;;;OAKG;YACW,YAAY;IA8F1B,OAAO,CAAC,YAAY;IAuBpB;;;;;;;;OAQG;YACW,WAAW;IAyBzB,6EAA6E;YAC/D,QAAQ;CAkFvB"}
1
+ {"version":3,"file":"kb-startup-runner.d.ts","sourceRoot":"","sources":["../../../../src/modules/workspace/startup/kb-startup-runner.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAY,aAAa,EAAsB,MAAM,sBAAsB,CAAC;AAExF,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC/E,OAAO,EACL,iBAAiB,EAIjB,KAAK,UAAU,EAEhB,MAAM,gCAAgC,CAAC;AAKxC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,SAAS,EAAE,UAAU,CAAC;IACtB,SAAS,EAAE,MAAM,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,MAAM,CAAC;IAC5B,iBAAiB,EAAE,MAAM,SAAS,MAAM,EAAE,CAAC;IAC3C,8EAA8E;IAC9E,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,oFAAoF;IACpF,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;IAChC;;;;;;2BAMuB;IACvB,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAClD;;;;;;;;;;;;;OAaG;IACH,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D;;;;;;;;;OASG;IACH,gBAAgB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CAChD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,wBAAyB,SAAQ,iBAAiB;gBACjD,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAQxD;AAyBD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAGpD;AAED,MAAM,WAAW,YAAY;IAC3B,mDAAmD;IACnD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,sBAAsB;IAEzD,gFAAgF;IAChF,OAAO,CAAC,QAAQ,CAA8B;IAC9C,yEAAyE;IACzE,OAAO,CAAC,OAAO,CAAuB;IACtC,iEAAiE;IACjE,OAAO,CAAC,WAAW,CAA2B;IAC9C,+EAA+E;IAC/E,OAAO,CAAC,aAAa,CAA4C;IAEjE;;;;;;OAMG;IACH,OAAO,CAAC,MAAM;IAId;;;;;OAKG;IACH,iBAAiB,IAAI;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;IAIvD;;;;;OAKG;IACH,WAAW,IAAI,MAAM,GAAG,IAAI;IAI5B,iGAAiG;IACjG,eAAe,IAAI,UAAU,GAAG,IAAI;IAIpC;;;;;;;OAOG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBvB;;;;;;;;;;OAUG;IACH,oBAAoB,CAAC,IAAI,GAAE,YAAiB,GAAG;QAAE,IAAI,IAAI,IAAI,CAAA;KAAE;YAgEjD,UAAU;IA+HxB;;;;;OAKG;YACW,YAAY;IA8F1B,OAAO,CAAC,YAAY;IAuBpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;YACW,uCAAuC;IAwDrD;;;;;;;;;;OAUG;YACW,2BAA2B;IAyBzC;;;;;OAKG;IAEH;;;;;;;;OAQG;YACW,WAAW;IAmCzB,6EAA6E;YAC/D,QAAQ;CAkFvB"}
@@ -10,6 +10,8 @@ import { git, lsRemoteHeads, stampIdentity, withTempDir } from './kb-git.js';
10
10
  import { GitRunError } from '../../../shared/git.contract.js';
11
11
  import { ClassifiedFailure, classifyGitFailure, failureOf, gitFailure, } from '../../../shared/git-failure.js';
12
12
  import { redactSecret, urlQuerySecrets } from '../../../shared/redact-secret.js';
13
+ import { normalizeRepositoryAddress, sameRepository } from '../../kb-fs/remote-url.js';
14
+ import { setAsideClone, setAsideRootFor, setAsideStamp } from '../set-aside-clone.js';
13
15
  /**
14
16
  * The remote could not be reached or refused us: a host that is down, a DNS
15
17
  * name that does not resolve, a token that was rotated. Told apart from every
@@ -33,6 +35,41 @@ export class KbRemoteUnreachableError extends ClassifiedFailure {
33
35
  this.name = 'KbRemoteUnreachableError';
34
36
  }
35
37
  }
38
+ /**
39
+ * The failure kinds a boot may survive GATED rather than stop on: the remote
40
+ * ANSWERED (or could not be reached at all), and what it said is about the
41
+ * repository or the credentials — not about what this deployment would write.
42
+ *
43
+ * All four are fixed by an operator somewhere other than this process: the
44
+ * host comes back, the repository is created or the token is granted access
45
+ * to it, a rotated token is entered on the setup screen. Refusing to boot
46
+ * over any of them takes away the very screen the fix is entered on — which
47
+ * is what happened on 2026-09-28, when a replaced repository answered
48
+ * `not found` inside a step and the container crash-looped.
49
+ *
50
+ * Every OTHER failure still stops the boot: it means a step or the template
51
+ * would write something wrong, and booting over that is worse than not
52
+ * booting at all.
53
+ */
54
+ const GATED_BOOT_KINDS = new Set([
55
+ 'unreachable',
56
+ 'not-found',
57
+ 'credentials-rejected',
58
+ 'write-refused',
59
+ ]);
60
+ /**
61
+ * Whether the boot may come up GATED on this failure instead of stopping.
62
+ *
63
+ * {@link KbRemoteUnreachableError} says so by its type — it is raised where
64
+ * the remote is first contacted, before any step. A failure raised INSIDE the
65
+ * phase says so by its classification: the same fetch that stops a boot when
66
+ * it is the first contact must not stop one when a step happened to make it.
67
+ */
68
+ export function bootMaySurvive(err) {
69
+ if (err instanceof KbRemoteUnreachableError)
70
+ return true;
71
+ return err instanceof ClassifiedFailure && GATED_BOOT_KINDS.has(err.failure.kind);
72
+ }
36
73
  export class KbStartupRunner {
37
74
  opts;
38
75
  constructor(opts) {
@@ -161,7 +198,7 @@ export class KbStartupRunner {
161
198
  // Stopped by either: a failure that is no longer the remote at all
162
199
  // (the knowledge base itself is wrong — asking again will not change
163
200
  // it), or a remote answer that a retry cannot change (see above).
164
- if (!(err instanceof KbRemoteUnreachableError) || !worthRetrying())
201
+ if (!bootMaySurvive(err) || !worthRetrying())
165
202
  return stopOnStanding();
166
203
  delay = Math.min(delay * 2, max);
167
204
  log(`remote still unreachable; trying again in ${Math.round(delay / 1000)}s`);
@@ -215,6 +252,10 @@ export class KbStartupRunner {
215
252
  'screen or GITHUB_TOKEN instead.');
216
253
  }
217
254
  const heads = await this.ensureRemote();
255
+ // AFTER the remote answered: the configured address is known to be a
256
+ // repository we can reach, so a clone of a different one is stale, not
257
+ // a casualty of a typo in the address.
258
+ await this.reconcileClonesWithConfiguredRepository();
218
259
  const ctx = this.buildContext(heads, handles);
219
260
  for (const step of this.opts.steps) {
220
261
  const started = Date.now();
@@ -406,6 +447,143 @@ export class KbStartupRunner {
406
447
  allBranches: async () => [...heads].sort().map(handleFor),
407
448
  };
408
449
  }
450
+ /**
451
+ * Bring every working copy on disk into line with the configured
452
+ * repository, WITHOUT DELETING ANYTHING.
453
+ *
454
+ * A clone fetches and pushes through the address stored in its own
455
+ * `remote.origin.url`, which nothing updates when the configured address
456
+ * changes. After an operator replaced the repository (2026-09-28: the old
457
+ * one deleted, a new one saved on the setup screen) every surviving clone
458
+ * kept asking the old address: Save and Retry failed, and the restart
459
+ * stopped the boot on `repository … not found`, taking the setup screen
460
+ * with it.
461
+ *
462
+ * A clone whose address differs from the configured one is one of two
463
+ * things, and the histories say which:
464
+ *
465
+ * - THE SAME REPOSITORY AT A NEW ADDRESS — moved, renamed, mirrored. The
466
+ * one change the setup screen tells an admin to make ("only change this
467
+ * if the same repository was moved or renamed"). Its history is on the
468
+ * configured remote, so the clone is pointed at the new address and
469
+ * kept, unpushed commits included: they are pushed where they belonged
470
+ * all along.
471
+ * - ANOTHER REPOSITORY. Re-pointing it would push one repository's
472
+ * commits into the other, so it cannot stay where the workspace service
473
+ * would adopt it. It is MOVED to {@link KbStartupRunnerOptions.setAsideRoot}
474
+ * and the log says where. Never removed: when the old repository is gone
475
+ * — as it was that day — these clones are the last copy of the
476
+ * knowledge base that exists, and the unpushed work in them exists
477
+ * nowhere else at all.
478
+ *
479
+ * Every clone under the workspaces root, not only the branches this phase
480
+ * touches: the workspace service adopts whatever it finds there. A clone
481
+ * whose address cannot be read is left where it is — "could not tell" is
482
+ * no reason to move someone's work. One that cannot be set aside stops the
483
+ * boot with the reason: leaving it would fail the boot a step later with
484
+ * git's words about a repository nobody configured.
485
+ */
486
+ async reconcileClonesWithConfiguredRepository() {
487
+ const configured = this.opts.kbRepoUrl();
488
+ let entries;
489
+ try {
490
+ entries = await fs.readdir(this.opts.workspacesRoot, { withFileTypes: true });
491
+ }
492
+ catch {
493
+ return; // no workspaces yet
494
+ }
495
+ // One folder per run, so what was set aside together stays together.
496
+ const stamp = setAsideStamp();
497
+ const setAsideRoot = setAsideRootFor(this.opts.workspacesRoot, this.opts.setAsideRoot);
498
+ for (const entry of entries) {
499
+ if (!entry.isDirectory())
500
+ continue;
501
+ const repoDir = path.join(this.opts.workspacesRoot, entry.name, this.opts.kbDirName);
502
+ const hasGit = await fs.access(path.join(repoDir, '.git')).then(() => true, () => false);
503
+ if (!hasGit)
504
+ continue;
505
+ let origin;
506
+ try {
507
+ origin = (await git(this.opts.gitRunner, repoDir, ['config', '--get', 'remote.origin.url'])).trim();
508
+ }
509
+ catch {
510
+ continue;
511
+ }
512
+ if (origin === '' || sameRepository(origin, configured))
513
+ continue;
514
+ // The normalized form carries no userinfo, and the scrub covers the rest.
515
+ const was = this.redact(normalizeRepositoryAddress(origin));
516
+ if (await this.sharesHistoryWithConfigured(repoDir, configured)) {
517
+ await git(this.opts.gitRunner, repoDir, ['remote', 'set-url', 'origin', configured]);
518
+ startupLog.warn(`working copy "${entry.name}" was cloned from ${was}, whose history the configured repository holds: ` +
519
+ 'the same repository at a new address. Pointed at the configured address and kept, unpushed work included.');
520
+ continue;
521
+ }
522
+ const kept = path.join(setAsideRoot, stamp, entry.name);
523
+ // First, and allowed to stop the phase: see `beforeCloneSetAside`. The
524
+ // directory name IS the workspace id (`workspaceIdForBranch`).
525
+ await this.opts.beforeCloneSetAside?.(entry.name);
526
+ await setAsideClone(repoDir, kept);
527
+ startupLog.warn(`working copy "${entry.name}" is a clone of another repository (${was}). Set aside at ${kept}; nothing was ` +
528
+ 'deleted, and it will be cloned fresh from the configured one. Work that was never pushed is in that ' +
529
+ 'folder: `git log` there shows it, and `git push <address> <branch>` from there sends it to a repository.');
530
+ // Setting it aside already removed it from the workspaces root, so the
531
+ // cached handle the workspace service holds for it now points at
532
+ // nothing.
533
+ try {
534
+ await this.opts.onCloneDiscarded?.(entry.name);
535
+ }
536
+ catch (err) {
537
+ startupLog.warn(`could not finish setting the working copy "${entry.name}" aside:`, {
538
+ detail: this.redact(err instanceof Error ? err.message : String(err)),
539
+ });
540
+ }
541
+ }
542
+ }
543
+ /**
544
+ * Whether the configured repository holds history this clone has too —
545
+ * asked of git, not guessed from the two addresses. The configured
546
+ * remote's branches are fetched under a namespace of their own and the
547
+ * clone's HEAD is tested for a common ancestor with any of them.
548
+ *
549
+ * "Could not tell" answers false: a fetch that fails, a clone with no
550
+ * commit to compare. False sets the clone aside, which loses nothing, where
551
+ * a wrong true would push a stranger's commits into the configured
552
+ * repository.
553
+ */
554
+ async sharesHistoryWithConfigured(repoDir, configured) {
555
+ const probe = 'refs/hexis-probe';
556
+ let theirs = [];
557
+ try {
558
+ await git(this.opts.gitRunner, repoDir, ['fetch', '--no-tags', '--quiet', configured, `+refs/heads/*:${probe}/*`]);
559
+ theirs = (await git(this.opts.gitRunner, repoDir, ['for-each-ref', '--format=%(refname)', `${probe}/`]))
560
+ .split('\n')
561
+ .map((ref) => ref.trim())
562
+ .filter(Boolean);
563
+ if (theirs.length === 0)
564
+ return false;
565
+ // One question for all of them: a common ancestor of HEAD and ANY of
566
+ // their branches is a common ancestor of HEAD and their merge. Capped,
567
+ // so a remote with thousands of branches cannot outgrow a command line.
568
+ await git(this.opts.gitRunner, repoDir, ['merge-base', 'HEAD', ...theirs.slice(0, 200)]);
569
+ return true;
570
+ }
571
+ catch {
572
+ return false;
573
+ }
574
+ finally {
575
+ // The probe leaves nothing behind in a clone that stays in use.
576
+ for (const ref of theirs) {
577
+ await git(this.opts.gitRunner, repoDir, ['update-ref', '-d', ref]).catch(() => undefined);
578
+ }
579
+ }
580
+ }
581
+ /**
582
+ * Move a working copy out of the workspaces root, whole. A rename where
583
+ * the two folders share a volume; a copy and then a removal where they do
584
+ * not (the backups root is a volume of its own in the shipped compose
585
+ * files), with the original removed only once the copy is complete.
586
+ */
409
587
  /**
410
588
  * The branch's working copy at the runtime layout
411
589
  * (`<workspacesRoot>/<id>/<kbDirName>`), so the workspace service finds it
@@ -423,6 +601,17 @@ export class KbStartupRunner {
423
601
  await fs.mkdir(workspaceDir, { recursive: true });
424
602
  await fs.rm(repoDir, { recursive: true, force: true });
425
603
  await git(this.opts.gitRunner, workspaceDir, ['clone', '-b', branch, this.opts.kbRepoUrl(), repoDir]);
604
+ // Said as soon as the clone is there, before its configuration: if a
605
+ // command below fails, the clone stays on disk, and the retry finds it
606
+ // and never comes back through here.
607
+ try {
608
+ this.opts.onCloneCreated?.(workspaceIdForBranch(branch));
609
+ }
610
+ catch (err) {
611
+ startupLog.warn(`could not announce the fresh working copy for "${branch}":`, {
612
+ detail: this.redact(err instanceof Error ? err.message : String(err)),
613
+ });
614
+ }
426
615
  await git(this.opts.gitRunner, repoDir, ['config', 'core.longpaths', 'true']);
427
616
  await stampIdentity(this.opts.gitRunner, repoDir);
428
617
  return repoDir;