@bevel-software/platform-core-backend 0.22.0 → 0.24.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 (466) 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 +46 -13
  7. package/dist/core/create-core-server.js.map +1 -1
  8. package/dist/core/create-core-services.d.ts +12 -2
  9. package/dist/core/create-core-services.d.ts.map +1 -1
  10. package/dist/core/create-core-services.js +137 -24
  11. package/dist/core/create-core-services.js.map +1 -1
  12. package/dist/core/lifecycle.d.ts +46 -1
  13. package/dist/core/lifecycle.d.ts.map +1 -1
  14. package/dist/core/lifecycle.js +99 -13
  15. package/dist/core/lifecycle.js.map +1 -1
  16. package/dist/core-config.d.ts +16 -12
  17. package/dist/core-config.d.ts.map +1 -1
  18. package/dist/core-config.js +28 -13
  19. package/dist/core-config.js.map +1 -1
  20. package/dist/index.d.ts +6 -4
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +12 -3
  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 +17 -16
  32. package/dist/modules/access/access.routes.js.map +1 -1
  33. package/dist/modules/access/directory-sync-bot.d.ts.map +1 -1
  34. package/dist/modules/access/directory-sync-bot.js +7 -3
  35. package/dist/modules/access/directory-sync-bot.js.map +1 -1
  36. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +8 -1
  37. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
  38. package/dist/modules/agent-instructions/agent-instructions.routes.js +8 -2
  39. package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
  40. package/dist/modules/agent-instructions/compose.d.ts +38 -6
  41. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  42. package/dist/modules/agent-instructions/compose.js +39 -6
  43. package/dist/modules/agent-instructions/compose.js.map +1 -1
  44. package/dist/modules/agent-instructions/index.d.ts +2 -1
  45. package/dist/modules/agent-instructions/index.d.ts.map +1 -1
  46. package/dist/modules/agent-instructions/index.js +2 -1
  47. package/dist/modules/agent-instructions/index.js.map +1 -1
  48. package/dist/modules/agent-instructions/shared-file-rules.d.ts +115 -0
  49. package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -0
  50. package/dist/modules/agent-instructions/shared-file-rules.js +272 -0
  51. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -0
  52. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  53. package/dist/modules/audit/agent-audit.service.js +4 -2
  54. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  55. package/dist/modules/auth/account-admission.d.ts +58 -7
  56. package/dist/modules/auth/account-admission.d.ts.map +1 -1
  57. package/dist/modules/auth/account-admission.js +44 -1
  58. package/dist/modules/auth/account-admission.js.map +1 -1
  59. package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
  60. package/dist/modules/auth/account-erasure.service.js +57 -16
  61. package/dist/modules/auth/account-erasure.service.js.map +1 -1
  62. package/dist/modules/auth/account.routes.d.ts +1 -1
  63. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  64. package/dist/modules/auth/account.routes.js +61 -5
  65. package/dist/modules/auth/account.routes.js.map +1 -1
  66. package/dist/modules/auth/auth.middleware.d.ts +1 -1
  67. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  68. package/dist/modules/auth/auth.middleware.js +18 -7
  69. package/dist/modules/auth/auth.middleware.js.map +1 -1
  70. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  71. package/dist/modules/auth/auth.routes.js +8 -0
  72. package/dist/modules/auth/auth.routes.js.map +1 -1
  73. package/dist/modules/auth/auth.service.d.ts +70 -2
  74. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  75. package/dist/modules/auth/auth.service.js +197 -18
  76. package/dist/modules/auth/auth.service.js.map +1 -1
  77. package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
  78. package/dist/modules/auth/oidc-auth-provider.js +7 -2
  79. package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
  80. package/dist/modules/code-mode/code-mode.tool.d.ts +20 -2
  81. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  82. package/dist/modules/code-mode/code-mode.tool.js +66 -35
  83. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  84. package/dist/modules/database/connection.d.ts +16 -0
  85. package/dist/modules/database/connection.d.ts.map +1 -1
  86. package/dist/modules/database/connection.js +117 -0
  87. package/dist/modules/database/connection.js.map +1 -1
  88. package/dist/modules/database/core-schema.d.ts +337 -120
  89. package/dist/modules/database/core-schema.d.ts.map +1 -1
  90. package/dist/modules/database/core-schema.js +116 -58
  91. package/dist/modules/database/core-schema.js.map +1 -1
  92. package/dist/modules/database/migrate.d.ts +8 -0
  93. package/dist/modules/database/migrate.d.ts.map +1 -1
  94. package/dist/modules/database/migrate.js +271 -1
  95. package/dist/modules/database/migrate.js.map +1 -1
  96. package/dist/modules/kb-fs/branch-name.d.ts.map +1 -1
  97. package/dist/modules/kb-fs/branch-name.js +12 -2
  98. package/dist/modules/kb-fs/branch-name.js.map +1 -1
  99. package/dist/modules/kb-fs/repo-path.d.ts +11 -16
  100. package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
  101. package/dist/modules/kb-fs/repo-path.js +79 -0
  102. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  103. package/dist/modules/kb-sync/kb-sync.routes.d.ts +2 -2
  104. package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
  105. package/dist/modules/kb-sync/kb-sync.routes.js +14 -3
  106. package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
  107. package/dist/modules/kb-sync/sync-auth.d.ts +3 -1
  108. package/dist/modules/kb-sync/sync-auth.d.ts.map +1 -1
  109. package/dist/modules/kb-sync/sync-auth.js +1 -1
  110. package/dist/modules/kb-sync/sync-auth.js.map +1 -1
  111. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  112. package/dist/modules/mcp/mcp-auth.middleware.js +21 -6
  113. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  114. package/dist/modules/mcp/mcp.service.d.ts +8 -0
  115. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  116. package/dist/modules/mcp/mcp.service.js +38 -8
  117. package/dist/modules/mcp/mcp.service.js.map +1 -1
  118. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  119. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +3 -2
  120. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  121. package/dist/modules/plugins/join-request-records.store.d.ts.map +1 -1
  122. package/dist/modules/plugins/join-request-records.store.js +7 -4
  123. package/dist/modules/plugins/join-request-records.store.js.map +1 -1
  124. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  125. package/dist/modules/settings/deployment-settings.service.js +8 -3
  126. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  127. package/dist/modules/settings/setup.routes.d.ts +52 -1
  128. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  129. package/dist/modules/settings/setup.routes.js +207 -19
  130. package/dist/modules/settings/setup.routes.js.map +1 -1
  131. package/dist/modules/skills/skills.contract.d.ts +90 -18
  132. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  133. package/dist/modules/skills/skills.contract.js +4 -1
  134. package/dist/modules/skills/skills.contract.js.map +1 -1
  135. package/dist/modules/skills/skills.service.d.ts +98 -9
  136. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  137. package/dist/modules/skills/skills.service.js +239 -37
  138. package/dist/modules/skills/skills.service.js.map +1 -1
  139. package/dist/modules/skills/skills.tools.d.ts +7 -0
  140. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  141. package/dist/modules/skills/skills.tools.js +71 -7
  142. package/dist/modules/skills/skills.tools.js.map +1 -1
  143. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  144. package/dist/modules/tool-auth/external-api-key.service.js +12 -4
  145. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  146. package/dist/modules/tool-auth/internal-token.service.d.ts +3 -3
  147. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +16 -7
  148. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  149. package/dist/modules/tool-auth/tool-auth.middleware.js +34 -12
  150. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  151. package/dist/modules/tool-helpers/tool-handler.d.ts +2 -1
  152. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  153. package/dist/modules/tool-helpers/tool-handler.js +25 -4
  154. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  155. package/dist/modules/tool-helpers/tool.contract.d.ts +3 -2
  156. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  157. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  158. package/dist/modules/tool-helpers/validate-token.js +1 -1
  159. package/dist/modules/tool-helpers/validate-token.js.map +1 -1
  160. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  161. package/dist/modules/tool-manuals/tool-manuals.tools.js +42 -31
  162. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  163. package/dist/modules/tool-registry/description-length.d.ts +80 -0
  164. package/dist/modules/tool-registry/description-length.d.ts.map +1 -0
  165. package/dist/modules/tool-registry/description-length.js +108 -0
  166. package/dist/modules/tool-registry/description-length.js.map +1 -0
  167. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +98 -0
  168. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -0
  169. package/dist/modules/workflow/agent-tools/change-request-summary.js +81 -0
  170. package/dist/modules/workflow/agent-tools/change-request-summary.js.map +1 -0
  171. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  172. package/dist/modules/workflow/agent-tools/workflow.tools.js +75 -35
  173. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  174. package/dist/modules/workflow/file-lock.service.d.ts +24 -0
  175. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  176. package/dist/modules/workflow/file-lock.service.js +30 -0
  177. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  178. package/dist/modules/workflow/git/git.service.d.ts +94 -0
  179. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  180. package/dist/modules/workflow/git/git.service.js +193 -3
  181. package/dist/modules/workflow/git/git.service.js.map +1 -1
  182. package/dist/modules/workflow/pending-commits.service.d.ts +38 -0
  183. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  184. package/dist/modules/workflow/pending-commits.service.js +60 -1
  185. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  186. package/dist/modules/workflow/recovery-bot.d.ts.map +1 -1
  187. package/dist/modules/workflow/recovery-bot.js +7 -3
  188. package/dist/modules/workflow/recovery-bot.js.map +1 -1
  189. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  190. package/dist/modules/workflow/review-workflow/review-workflow.service.js +12 -3
  191. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  192. package/dist/modules/workflow/workflow-hooks.d.ts +54 -32
  193. package/dist/modules/workflow/workflow-hooks.d.ts.map +1 -1
  194. package/dist/modules/workflow/workflow-hooks.js +16 -1
  195. package/dist/modules/workflow/workflow-hooks.js.map +1 -1
  196. package/dist/modules/workflow/workflow.service.d.ts +60 -6
  197. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  198. package/dist/modules/workflow/workflow.service.js +115 -5
  199. package/dist/modules/workflow/workflow.service.js.map +1 -1
  200. package/dist/modules/workspace/agent-access.gate.d.ts +94 -0
  201. package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -0
  202. package/dist/modules/workspace/agent-access.gate.js +123 -0
  203. package/dist/modules/workspace/agent-access.gate.js.map +1 -0
  204. package/dist/modules/workspace/agent-upload.routes.d.ts +77 -0
  205. package/dist/modules/workspace/agent-upload.routes.d.ts.map +1 -0
  206. package/dist/modules/workspace/agent-upload.routes.js +210 -0
  207. package/dist/modules/workspace/agent-upload.routes.js.map +1 -0
  208. package/dist/modules/workspace/agent-upload.store.d.ts +284 -0
  209. package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -0
  210. package/dist/modules/workspace/agent-upload.store.js +553 -0
  211. package/dist/modules/workspace/agent-upload.store.js.map +1 -0
  212. package/dist/modules/workspace/routine-write-policy.d.ts +5 -6
  213. package/dist/modules/workspace/routine-write-policy.d.ts.map +1 -1
  214. package/dist/modules/workspace/routine-write-policy.js +5 -6
  215. package/dist/modules/workspace/routine-write-policy.js.map +1 -1
  216. package/dist/modules/workspace/session-sink.d.ts +5 -5
  217. package/dist/modules/workspace/set-aside-clone.d.ts +46 -0
  218. package/dist/modules/workspace/set-aside-clone.d.ts.map +1 -0
  219. package/dist/modules/workspace/set-aside-clone.js +92 -0
  220. package/dist/modules/workspace/set-aside-clone.js.map +1 -0
  221. package/dist/modules/workspace/startup/kb-startup-runner.d.ts +59 -9
  222. package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
  223. package/dist/modules/workspace/startup/kb-startup-runner.js +65 -24
  224. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
  225. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  226. package/dist/modules/workspace/startup/steps/seed-tree.js +3 -3
  227. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  228. package/dist/modules/workspace/startup/steps/template-source.d.ts +40 -0
  229. package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
  230. package/dist/modules/workspace/startup/steps/template-source.js +46 -4
  231. package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
  232. package/dist/modules/workspace/upload-limits.d.ts +13 -0
  233. package/dist/modules/workspace/upload-limits.d.ts.map +1 -0
  234. package/dist/modules/workspace/upload-limits.js +13 -0
  235. package/dist/modules/workspace/upload-limits.js.map +1 -0
  236. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  237. package/dist/modules/workspace/workspace.routes.js +93 -4
  238. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  239. package/dist/modules/workspace/workspace.service.d.ts +128 -5
  240. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  241. package/dist/modules/workspace/workspace.service.js +316 -58
  242. package/dist/modules/workspace/workspace.service.js.map +1 -1
  243. package/dist/modules/workspace/workspace.tools.d.ts +13 -11
  244. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  245. package/dist/modules/workspace/workspace.tools.js +791 -198
  246. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  247. package/dist/modules/workspace/write-denial.d.ts +0 -6
  248. package/dist/modules/workspace/write-denial.d.ts.map +1 -1
  249. package/dist/modules/workspace/write-denial.js +0 -6
  250. package/dist/modules/workspace/write-denial.js.map +1 -1
  251. package/dist/modules/workspace/zip-entry-rules.d.ts +114 -0
  252. package/dist/modules/workspace/zip-entry-rules.d.ts.map +1 -0
  253. package/dist/modules/workspace/zip-entry-rules.js +154 -0
  254. package/dist/modules/workspace/zip-entry-rules.js.map +1 -0
  255. package/dist/modules/write-access/write-access.d.ts +60 -0
  256. package/dist/modules/write-access/write-access.d.ts.map +1 -0
  257. package/dist/modules/write-access/write-access.js +129 -0
  258. package/dist/modules/write-access/write-access.js.map +1 -0
  259. package/dist/shared/column-crypto.d.ts +194 -0
  260. package/dist/shared/column-crypto.d.ts.map +1 -0
  261. package/dist/shared/column-crypto.js +144 -0
  262. package/dist/shared/column-crypto.js.map +1 -0
  263. package/dist/shared/domain-errors.d.ts +25 -0
  264. package/dist/shared/domain-errors.d.ts.map +1 -1
  265. package/dist/shared/domain-errors.js +28 -0
  266. package/dist/shared/domain-errors.js.map +1 -1
  267. package/dist/shared/git.contract.d.ts +20 -0
  268. package/dist/shared/git.contract.d.ts.map +1 -1
  269. package/dist/shared/git.contract.js +26 -0
  270. package/dist/shared/git.contract.js.map +1 -1
  271. package/dist/shared/token-crypto.d.ts.map +1 -1
  272. package/dist/shared/token-crypto.js +25 -1
  273. package/dist/shared/token-crypto.js.map +1 -1
  274. package/dist/tenancy/static-tenant-source.d.ts +0 -1
  275. package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
  276. package/dist/tenancy/static-tenant-source.js +1 -2
  277. package/dist/tenancy/static-tenant-source.js.map +1 -1
  278. package/dist/tenancy/tenant-secrets.d.ts +5 -1
  279. package/dist/tenancy/tenant-secrets.d.ts.map +1 -1
  280. package/dist/tenancy/tenant-secrets.js +4 -0
  281. package/dist/tenancy/tenant-secrets.js.map +1 -1
  282. package/kb-template/AGENTS.md +2 -0
  283. package/migrations/0014_change_request_closed_reason.sql +1 -0
  284. package/migrations/0015_account_deactivation.sql +3 -0
  285. package/migrations/0016_pii_encryption.sql +20 -0
  286. package/migrations/meta/0014_snapshot.json +2265 -0
  287. package/migrations/meta/0015_snapshot.json +2271 -0
  288. package/migrations/meta/0016_snapshot.json +2327 -0
  289. package/migrations/meta/_journal.json +21 -0
  290. package/package.json +3 -3
  291. package/src/__tests__/kb-layout-config.test.ts +0 -2
  292. package/src/__tests__/retired-settings.test.ts +96 -0
  293. package/src/core/__tests__/gated-boot.test.ts +132 -0
  294. package/src/core/__tests__/lifecycle.test.ts +241 -5
  295. package/src/core/__tests__/set-aside-root-is-one-place.test.ts +63 -0
  296. package/src/core/core-ports.ts +11 -1
  297. package/src/core/create-core-server.ts +52 -16
  298. package/src/core/create-core-services.ts +156 -24
  299. package/src/core/lifecycle.ts +117 -13
  300. package/src/core-config.ts +31 -14
  301. package/src/index.ts +30 -1
  302. package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +385 -0
  303. package/src/modules/access/__tests__/access-control.prospective.test.ts +94 -18
  304. package/src/modules/access/__tests__/access.routes.prospective.test.ts +6 -7
  305. package/src/modules/access/__tests__/users-db-double.ts +21 -12
  306. package/src/modules/access/access-control.interface.ts +43 -12
  307. package/src/modules/access/access-control.service.ts +234 -36
  308. package/src/modules/access/access.routes.ts +17 -16
  309. package/src/modules/access/directory-sync-bot.ts +7 -3
  310. package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +11 -5
  311. package/src/modules/agent-instructions/__tests__/compose.test.ts +19 -10
  312. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +238 -0
  313. package/src/modules/agent-instructions/agent-instructions.routes.ts +12 -2
  314. package/src/modules/agent-instructions/compose.ts +50 -7
  315. package/src/modules/agent-instructions/index.ts +12 -0
  316. package/src/modules/agent-instructions/shared-file-rules.ts +314 -0
  317. package/src/modules/audit/agent-audit.service.ts +4 -2
  318. package/src/modules/auth/__tests__/account-deactivation.test.ts +253 -0
  319. package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +6 -2
  320. package/src/modules/auth/__tests__/account.routes.test.ts +87 -6
  321. package/src/modules/auth/__tests__/auth.middleware.test.ts +45 -22
  322. package/src/modules/auth/__tests__/auth.service.test.ts +4 -2
  323. package/src/modules/auth/account-admission.ts +78 -9
  324. package/src/modules/auth/account-erasure.service.ts +69 -18
  325. package/src/modules/auth/account.routes.ts +63 -7
  326. package/src/modules/auth/auth.middleware.ts +19 -8
  327. package/src/modules/auth/auth.routes.ts +8 -0
  328. package/src/modules/auth/auth.service.ts +206 -23
  329. package/src/modules/auth/oidc-auth-provider.ts +7 -2
  330. package/src/modules/code-mode/__tests__/chain-runtime.e2e.test.ts +335 -0
  331. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +46 -2
  332. package/src/modules/code-mode/code-mode.tool.ts +80 -34
  333. package/src/modules/database/__tests__/connection.test.ts +12 -0
  334. package/src/modules/database/__tests__/pii-backfill.pg.test.ts +561 -0
  335. package/src/modules/database/connection.ts +117 -0
  336. package/src/modules/database/core-schema.ts +116 -58
  337. package/src/modules/database/migrate.ts +353 -1
  338. package/src/modules/kb-fs/__tests__/branch-name.test.ts +10 -0
  339. package/src/modules/kb-fs/__tests__/repo-path.test.ts +123 -0
  340. package/src/modules/kb-fs/branch-name.ts +14 -1
  341. package/src/modules/kb-fs/repo-path.ts +85 -0
  342. package/src/modules/kb-sync/__tests__/kb-sync.routes.test.ts +26 -1
  343. package/src/modules/kb-sync/kb-sync.routes.ts +14 -4
  344. package/src/modules/kb-sync/sync-auth.ts +2 -2
  345. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +60 -3
  346. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +4 -3
  347. package/src/modules/mcp/__tests__/mcp.service.test.ts +115 -13
  348. package/src/modules/mcp/mcp-auth.middleware.ts +20 -6
  349. package/src/modules/mcp/mcp.service.ts +46 -7
  350. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +3 -1
  351. package/src/modules/plugins/__tests__/plugins.tools.test.ts +2 -1
  352. package/src/modules/plugins/join-request-records.store.ts +7 -4
  353. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +16 -0
  354. package/src/modules/settings/__tests__/setup.routes.git-mode.test.ts +91 -58
  355. package/src/modules/settings/__tests__/setup.routes.github-app.test.ts +25 -8
  356. package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +14 -11
  357. package/src/modules/settings/__tests__/setup.routes.repository-change.test.ts +390 -0
  358. package/src/modules/settings/__tests__/setup.routes.test.ts +3 -0
  359. package/src/modules/settings/deployment-settings.service.ts +8 -3
  360. package/src/modules/settings/setup.routes.ts +263 -19
  361. package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +2 -1
  362. package/src/modules/skills/__tests__/branch-skills.tools.test.ts +218 -0
  363. package/src/modules/skills/__tests__/skills.service.test.ts +229 -5
  364. package/src/modules/skills/skills.contract.ts +91 -18
  365. package/src/modules/skills/skills.service.ts +278 -41
  366. package/src/modules/skills/skills.tools.ts +80 -8
  367. package/src/modules/tool-auth/__tests__/manual-auth.middleware.test.ts +14 -1
  368. package/src/modules/tool-auth/external-api-key.service.ts +12 -4
  369. package/src/modules/tool-auth/internal-token.service.ts +3 -3
  370. package/src/modules/tool-auth/tool-auth.middleware.ts +34 -11
  371. package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +5 -3
  372. package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +3 -3
  373. package/src/modules/tool-helpers/__tests__/validate-token.test.ts +11 -1
  374. package/src/modules/tool-helpers/tool-handler.ts +24 -4
  375. package/src/modules/tool-helpers/tool.contract.ts +3 -2
  376. package/src/modules/tool-helpers/validate-token.ts +1 -1
  377. package/src/modules/tool-manuals/tool-manuals.tools.ts +44 -31
  378. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +378 -0
  379. package/src/modules/tool-registry/description-length.ts +111 -0
  380. package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +111 -0
  381. package/src/modules/workflow/__tests__/workflow.routes.history-read-gate.test.ts +25 -0
  382. package/src/modules/workflow/__tests__/workflow.service.repository-replaced.test.ts +249 -0
  383. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +264 -6
  384. package/src/modules/workflow/agent-tools/change-request-summary.ts +182 -0
  385. package/src/modules/workflow/agent-tools/workflow.tools.ts +86 -35
  386. package/src/modules/workflow/file-lock.service.ts +31 -0
  387. package/src/modules/workflow/git/__tests__/git.service.fileBytesAtCommit.test.ts +260 -0
  388. package/src/modules/workflow/git/__tests__/git.service.prFetchFailure.test.ts +170 -0
  389. package/src/modules/workflow/git/git.service.ts +215 -1
  390. package/src/modules/workflow/pending-commits.service.ts +64 -2
  391. package/src/modules/workflow/recovery-bot.ts +7 -3
  392. package/src/modules/workflow/review-workflow/__tests__/carry-approvals-forward.test.ts +4 -0
  393. package/src/modules/workflow/review-workflow/__tests__/erase-approver.test.ts +25 -3
  394. package/src/modules/workflow/review-workflow/review-workflow.service.ts +12 -3
  395. package/src/modules/workflow/workflow-hooks.ts +64 -26
  396. package/src/modules/workflow/workflow.service.ts +125 -5
  397. package/src/modules/workspace/__tests__/agent-access.gate.test.ts +208 -0
  398. package/src/modules/workspace/__tests__/agent-uploads.test.ts +1604 -0
  399. package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +394 -0
  400. package/src/modules/workspace/__tests__/file-stat-access.test.ts +2 -1
  401. package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -1
  402. package/src/modules/workspace/__tests__/set-aside-clone.test.ts +52 -0
  403. package/src/modules/workspace/__tests__/workspace.routes.at-ref.test.ts +305 -0
  404. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +2 -0
  405. package/src/modules/workspace/__tests__/workspace.service.any-workspace-credentials.test.ts +156 -0
  406. package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +147 -0
  407. package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +402 -0
  408. package/src/modules/workspace/__tests__/workspace.service.test.ts +77 -9
  409. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +41 -39
  410. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +2 -3
  411. package/src/modules/workspace/__tests__/workspace.tools.test.ts +766 -56
  412. package/src/modules/workspace/agent-access.gate.ts +164 -0
  413. package/src/modules/workspace/agent-upload.routes.ts +214 -0
  414. package/src/modules/workspace/agent-upload.store.ts +668 -0
  415. package/src/modules/workspace/routine-write-policy.ts +5 -6
  416. package/src/modules/workspace/session-sink.ts +5 -5
  417. package/src/modules/workspace/set-aside-clone.ts +96 -0
  418. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +86 -0
  419. package/src/modules/workspace/startup/kb-startup-runner.ts +115 -34
  420. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +9 -9
  421. package/src/modules/workspace/startup/steps/seed-tree.ts +3 -6
  422. package/src/modules/workspace/startup/steps/template-source.ts +53 -5
  423. package/src/modules/workspace/upload-limits.ts +12 -0
  424. package/src/modules/workspace/workspace.routes.ts +96 -5
  425. package/src/modules/workspace/workspace.service.ts +319 -61
  426. package/src/modules/workspace/workspace.tools.ts +894 -214
  427. package/src/modules/workspace/write-denial.ts +0 -8
  428. package/src/modules/workspace/zip-entry-rules.ts +173 -0
  429. package/src/modules/write-access/__tests__/write-access.test.ts +248 -0
  430. package/src/modules/write-access/write-access.ts +153 -0
  431. package/src/shared/__tests__/column-crypto.test.ts +217 -0
  432. package/src/shared/column-crypto.ts +218 -0
  433. package/src/shared/domain-errors.ts +31 -0
  434. package/src/shared/git.contract.ts +27 -0
  435. package/src/shared/token-crypto.ts +28 -1
  436. package/src/tenancy/__tests__/static-tenant-source.test.ts +4 -1
  437. package/src/tenancy/static-tenant-source.ts +1 -3
  438. package/src/tenancy/tenant-secrets.ts +5 -1
  439. package/dist/modules/workflow/session-ontology.policy.d.ts +0 -52
  440. package/dist/modules/workflow/session-ontology.policy.d.ts.map +0 -1
  441. package/dist/modules/workflow/session-ontology.policy.js +0 -62
  442. package/dist/modules/workflow/session-ontology.policy.js.map +0 -1
  443. package/dist/modules/workflow/session-ontology.service.d.ts +0 -105
  444. package/dist/modules/workflow/session-ontology.service.d.ts.map +0 -1
  445. package/dist/modules/workflow/session-ontology.service.js +0 -147
  446. package/dist/modules/workflow/session-ontology.service.js.map +0 -1
  447. package/dist/modules/workspace/session-ontology.gate.d.ts +0 -114
  448. package/dist/modules/workspace/session-ontology.gate.d.ts.map +0 -1
  449. package/dist/modules/workspace/session-ontology.gate.js +0 -161
  450. package/dist/modules/workspace/session-ontology.gate.js.map +0 -1
  451. package/dist/shared/kb-layout.d.ts +0 -39
  452. package/dist/shared/kb-layout.d.ts.map +0 -1
  453. package/dist/shared/kb-layout.js +0 -103
  454. package/dist/shared/kb-layout.js.map +0 -1
  455. package/dist/shared/kb-layout.test.d.ts +0 -2
  456. package/dist/shared/kb-layout.test.d.ts.map +0 -1
  457. package/dist/shared/kb-layout.test.js +0 -75
  458. package/dist/shared/kb-layout.test.js.map +0 -1
  459. package/src/modules/workflow/__tests__/session-ontology.policy.test.ts +0 -62
  460. package/src/modules/workflow/__tests__/session-ontology.service.test.ts +0 -201
  461. package/src/modules/workflow/session-ontology.policy.ts +0 -70
  462. package/src/modules/workflow/session-ontology.service.ts +0 -183
  463. package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +0 -239
  464. package/src/modules/workspace/session-ontology.gate.ts +0 -191
  465. package/src/shared/kb-layout.test.ts +0 -98
  466. package/src/shared/kb-layout.ts +0 -102
@@ -0,0 +1,390 @@
1
+ import type { Server as HttpServer } from 'node:http';
2
+ import type { AddressInfo } from 'node:net';
3
+ import express from 'express';
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
+ import { testKbContext } from '../../../__tests__/kb-context.js';
6
+ import { createSetupRoutes, type RepositoryChangeRequests } from '../setup.routes.js';
7
+ import { DeploymentSettingsService } from '../deployment-settings.service.js';
8
+ import type { Database } from '../../database/connection.js';
9
+ import type { IAdminAccessService } from '../../admin/admin.interface.js';
10
+ import type { ConnectionCheck, RepositoryConnection } from '../connection-check.js';
11
+
12
+ /**
13
+ * Saving a DIFFERENT knowledge-base repository.
14
+ *
15
+ * Every working copy on this server fetches through the address stored in its
16
+ * own clone, so pointing the deployment somewhere else means deleting and
17
+ * re-cloning all of them — and losing anything committed here and never
18
+ * pushed. On 2026-09-28 that happened with no warning, no re-clone, and a
19
+ * boot that then crash-looped against the repository that was gone.
20
+ *
21
+ * So: the save is REFUSED until the admin confirms, the confirmation says how
22
+ * many change requests are open and what to do with them, and a confirmed
23
+ * save runs the KB startup phase — which is what replaces the working copies
24
+ * — even though the deployment was complete and healthy.
25
+ */
26
+
27
+ const ENC_KEY = 'kToAi8FXWDpDn3A6yQ/60O39bv05N7XzVOIu/0CJrFc=';
28
+ const KB_ENV = ['KB_REPO_URL', 'GIT_TOKEN', 'GIT_USERNAME', 'KB_DIR_NAME', 'GITHUB_TOKEN'] as const;
29
+ const REPO = 'https://example.com/acme/kb.git';
30
+
31
+ let server: HttpServer | null = null;
32
+ let savedEnv: Partial<Record<(typeof KB_ENV)[number], string | undefined>> = {};
33
+ /** Set by the one test about a close the database will not do. Reset per test. */
34
+ let failTheClose = false;
35
+
36
+ beforeEach(() => {
37
+ failTheClose = false;
38
+ savedEnv = {};
39
+ for (const k of KB_ENV) {
40
+ savedEnv[k] = process.env[k];
41
+ delete process.env[k];
42
+ }
43
+ });
44
+
45
+ afterEach(() => {
46
+ server?.close();
47
+ server = null;
48
+ for (const k of KB_ENV) {
49
+ const original = savedEnv[k];
50
+ if (original === undefined) delete process.env[k];
51
+ else process.env[k] = original;
52
+ }
53
+ });
54
+
55
+ /** The remote answers for whatever it is asked about. */
56
+ const connected = async (connection: RepositoryConnection): Promise<ConnectionCheck> => {
57
+ void connection;
58
+ return { outcome: 'connected', branches: ['main'], defaultBranch: 'main', empty: false };
59
+ };
60
+
61
+ function listen(openChangeRequests: number) {
62
+ const db = {
63
+ select: () => ({ from: () => Promise.resolve([]) }),
64
+ insert: () => ({ values: () => ({ onConflictDoUpdate: () => Promise.resolve() }) }),
65
+ delete: () => ({ where: () => Promise.resolve() }),
66
+ } as unknown as Database;
67
+ const settings = new DeploymentSettingsService(db, ENC_KEY);
68
+ /** Every run of the KB startup phase — the thing that replaces the clones. */
69
+ const phaseRuns = { count: 0 };
70
+ const closes = { count: 0 };
71
+ /** Set by the one test about a move whose startup phase fails. */
72
+ const phase = { fail: false };
73
+ /** What happened, in order: the commit worker held and let go, and the phase between. */
74
+ const events: string[] = [];
75
+ const changeRequests: RepositoryChangeRequests = {
76
+ async countOpen() {
77
+ return openChangeRequests;
78
+ },
79
+ async closeAsRepositoryReplaced() {
80
+ closes.count += 1;
81
+ if (failTheClose) throw new Error('change_requests is down');
82
+ return openChangeRequests;
83
+ },
84
+ async whileCommitsHeld(work) {
85
+ // What was stored at the moment the hold began: the hold must come first.
86
+ events.push(`commits held on ${settings.resolve('kbRepoUrl')}`);
87
+ try {
88
+ return await work();
89
+ } finally {
90
+ events.push('commits released');
91
+ }
92
+ },
93
+ };
94
+ const app = express();
95
+ app.use(express.json());
96
+ app.use((req, _res, next) => {
97
+ req.userEmail = 'root@example.com';
98
+ req.userId = 'user-1';
99
+ next();
100
+ });
101
+ app.use(
102
+ '/api',
103
+ createSetupRoutes(
104
+ settings,
105
+ { isAdmin: async () => true } as IAdminAccessService,
106
+ {
107
+ async runAll() {
108
+ phaseRuns.count += 1;
109
+ events.push('phase');
110
+ if (phase.fail) throw new Error('git fetch failed: repository not found');
111
+ },
112
+ },
113
+ testKbContext(),
114
+ undefined,
115
+ connected,
116
+ undefined,
117
+ undefined,
118
+ undefined,
119
+ undefined,
120
+ // No alternative way of being given a repository: this suite is about the
121
+ // one that has always been there, an address and a token.
122
+ undefined,
123
+ changeRequests,
124
+ ),
125
+ );
126
+ server = app.listen(0);
127
+ const { port } = server.address() as AddressInfo;
128
+ return { base: `http://127.0.0.1:${port}`, settings, phaseRuns, closes, events, phase };
129
+ }
130
+
131
+ const post = (base: string, body: unknown) =>
132
+ fetch(`${base}/api/setup/settings`, {
133
+ method: 'POST',
134
+ headers: { 'content-type': 'application/json' },
135
+ body: JSON.stringify(body),
136
+ });
137
+
138
+ /** A deployment that has been running against REPO for a while. */
139
+ async function configured(openChangeRequests = 0) {
140
+ const harness = listen(openChangeRequests);
141
+ await harness.settings.save({ kbRepoUrl: REPO, gitToken: 'ghp_old' }, null);
142
+ // The save above is not what this suite counts.
143
+ harness.phaseRuns.count = 0;
144
+ return harness;
145
+ }
146
+
147
+ describe('a save that changes the knowledge-base repository', () => {
148
+ it('is refused until the admin confirms, and stores nothing meanwhile', async () => {
149
+ const { base, settings, phaseRuns } = await configured(0);
150
+
151
+ const res = await post(base, {
152
+ settings: { kbRepoUrl: 'https://example.com/acme/replacement.git', gitToken: 'ghp_new' },
153
+ });
154
+
155
+ expect(res.status).toBe(409);
156
+ expect(await res.json()).toMatchObject({ repositoryChange: { openChangeRequests: 0 } });
157
+ // Nothing saved, nothing replaced: a refused save destroys nothing.
158
+ expect(settings.resolve('kbRepoUrl')).toBe(REPO);
159
+ expect(phaseRuns.count).toBe(0);
160
+ });
161
+
162
+ it('says how many change requests are open, so the choice is about a number', async () => {
163
+ const { base } = await configured(3);
164
+ const res = await post(base, {
165
+ settings: { kbRepoUrl: 'https://example.com/acme/replacement.git', gitToken: 'ghp_new' },
166
+ });
167
+ expect(res.status).toBe(409);
168
+ expect((await res.json()).repositoryChange.openChangeRequests).toBe(3);
169
+ });
170
+
171
+ it('confirmed with "keep": saves, replaces the working copies, and closes nothing', async () => {
172
+ const { base, settings, phaseRuns, closes } = await configured(3);
173
+ const replacement = 'https://example.com/acme/replacement.git';
174
+
175
+ const res = await post(base, {
176
+ settings: { kbRepoUrl: replacement, gitToken: 'ghp_new' },
177
+ confirmRepositoryChange: 'keep',
178
+ });
179
+
180
+ expect(res.status).toBe(200);
181
+ expect(await res.json()).toMatchObject({
182
+ repositoryChange: { choice: 'keep', closedChangeRequests: 0 },
183
+ });
184
+ expect(settings.resolve('kbRepoUrl')).toBe(replacement);
185
+ // The phase is what deletes the clones of the old repository and makes new
186
+ // ones — on the SAVE, not only at the next boot.
187
+ expect(phaseRuns.count).toBe(1);
188
+ expect(closes.count).toBe(0);
189
+ });
190
+
191
+ it('confirmed with "close": closes the open requests as repository replaced', async () => {
192
+ const { base, phaseRuns, closes } = await configured(3);
193
+
194
+ const res = await post(base, {
195
+ settings: { kbRepoUrl: 'https://example.com/acme/replacement.git', gitToken: 'ghp_new' },
196
+ confirmRepositoryChange: 'close',
197
+ });
198
+
199
+ expect(res.status).toBe(200);
200
+ expect(await res.json()).toMatchObject({
201
+ repositoryChange: { choice: 'close', closedChangeRequests: 3 },
202
+ });
203
+ expect(closes.count).toBe(1);
204
+ expect(phaseRuns.count).toBe(1);
205
+ });
206
+
207
+ /**
208
+ * A close that cannot be done is a PRECONDITION that failed, not a detail of
209
+ * an otherwise successful save. Stored anyway, the address would now match,
210
+ * so the next save would see no change, ask nothing, and never retry the
211
+ * close — leaving those requests open on branches the new repository does not
212
+ * have, their file locks refusing paths to everyone, with nothing the admin
213
+ * could press to fix it.
214
+ */
215
+ it('refuses the whole save, saying why, when the requests cannot be closed', async () => {
216
+ const { base, settings, phaseRuns } = await configured(3);
217
+ failTheClose = true;
218
+
219
+ const res = await post(base, {
220
+ settings: { kbRepoUrl: 'https://example.com/acme/replacement.git', gitToken: 'ghp_new' },
221
+ confirmRepositoryChange: 'close',
222
+ });
223
+
224
+ expect(res.status).toBe(500);
225
+ expect((await res.json()).error).toMatch(/could not be closed/);
226
+ // Nothing stored, no working copy touched: the admin can press Save again.
227
+ expect(settings.resolve('kbRepoUrl')).toBe(REPO);
228
+ expect(phaseRuns.count).toBe(0);
229
+ });
230
+ });
231
+
232
+ /**
233
+ * An answer stands for the count it was given about. The screen sends "keep"
234
+ * when there was nothing to choose; a request opened while the question
235
+ * stood must not be decided by that.
236
+ */
237
+ describe('an answer given about a count that has since changed', () => {
238
+ it('is asked again, with the count as it is now', async () => {
239
+ const { base, settings, phaseRuns } = await configured(1);
240
+ const res = await post(base, {
241
+ settings: { kbRepoUrl: 'https://example.com/acme/replacement.git', gitToken: 'ghp_new' },
242
+ confirmRepositoryChange: 'keep',
243
+ seenOpenChangeRequests: 0,
244
+ });
245
+ expect(res.status).toBe(409);
246
+ expect((await res.json()).repositoryChange.openChangeRequests).toBe(1);
247
+ expect(settings.resolve('kbRepoUrl')).toBe(REPO);
248
+ expect(phaseRuns.count).toBe(0);
249
+ });
250
+
251
+ it('goes through when the count is the one the answer was given about', async () => {
252
+ const { base } = await configured(1);
253
+ const res = await post(base, {
254
+ settings: { kbRepoUrl: 'https://example.com/acme/replacement.git', gitToken: 'ghp_new' },
255
+ confirmRepositoryChange: 'keep',
256
+ seenOpenChangeRequests: 1,
257
+ });
258
+ expect(res.status).toBe(200);
259
+ });
260
+ });
261
+
262
+ /**
263
+ * A move that fails part-way leaves the deployment gated with the failure
264
+ * standing. It has served all the same, and its open change requests are the
265
+ * proof: the save that tries again must still ask what becomes of them.
266
+ */
267
+ describe('a move tried again after one that failed part-way', () => {
268
+ it('still asks, because change requests are open', async () => {
269
+ const { base, phase } = await configured(2);
270
+ phase.fail = true;
271
+ const failed = await post(base, {
272
+ settings: { kbRepoUrl: 'https://example.com/acme/replacement.git', gitToken: 'ghp_new' },
273
+ confirmRepositoryChange: 'keep',
274
+ });
275
+ expect(failed.status).toBe(500);
276
+ expect(await failed.json()).toHaveProperty('kbInit');
277
+
278
+ phase.fail = false;
279
+ const again = await post(base, {
280
+ settings: { kbRepoUrl: 'https://example.com/acme/third.git', gitToken: 'ghp_new' },
281
+ });
282
+ expect(again.status).toBe(409);
283
+ expect((await again.json()).repositoryChange.openChangeRequests).toBe(2);
284
+ });
285
+ });
286
+
287
+ describe('a save that does NOT change the repository', () => {
288
+ it('goes straight through, and runs no startup phase', async () => {
289
+ const { base, phaseRuns, closes } = await configured(2);
290
+
291
+ // The same address, with something else edited beside it.
292
+ const res = await post(base, { settings: { kbRepoUrl: REPO, gitUsername: 'oauth2' } });
293
+
294
+ expect(res.status).toBe(200);
295
+ expect(await res.json()).not.toHaveProperty('repositoryChange');
296
+ // A complete, healthy deployment's clones are of the configured
297
+ // repository: re-cloning them would be maintenance over live work.
298
+ expect(phaseRuns.count).toBe(0);
299
+ expect(closes.count).toBe(0);
300
+ });
301
+
302
+ it('treats a trailing slash, a .git suffix and the host’s letter case as the same address', async () => {
303
+ for (const spelling of [
304
+ 'https://example.com/acme/kb',
305
+ 'https://example.com/acme/kb/',
306
+ 'https://EXAMPLE.com/acme/kb.git',
307
+ 'https://example.com/acme/kb.git/',
308
+ ]) {
309
+ const { base, settings, phaseRuns } = await configured(2);
310
+ const res = await post(base, { settings: { kbRepoUrl: spelling } });
311
+ expect({ spelling, status: res.status }).toEqual({ spelling, status: 200 });
312
+ // Stored as typed — it is the same repository, not a correction.
313
+ expect(settings.resolve('kbRepoUrl')).toBe(spelling);
314
+ expect({ spelling, runs: phaseRuns.count }).toEqual({ spelling, runs: 0 });
315
+ server?.close();
316
+ server = null;
317
+ }
318
+ });
319
+
320
+ it('never asks the token question over a spelling, since it is the same repository', async () => {
321
+ const { base } = await configured(0);
322
+ // No token supplied. A DIFFERENT repository would be refused here — the
323
+ // stored token only ever goes to the repository it was saved for.
324
+ const res = await post(base, { settings: { kbRepoUrl: 'https://example.com/acme/kb/' } });
325
+ expect(res.status).toBe(200);
326
+ });
327
+ });
328
+
329
+ describe('first-run setup', () => {
330
+ it('asks nothing: there is no configured address and no working copy to lose', async () => {
331
+ const { base, settings, phaseRuns } = listen(0);
332
+ const res = await post(base, { settings: { kbRepoUrl: REPO, gitToken: 'ghp_new' } });
333
+ expect(res.status).toBe(200);
334
+ expect(settings.resolve('kbRepoUrl')).toBe(REPO);
335
+ // The completion transition runs the phase, as it always has.
336
+ expect(phaseRuns.count).toBe(1);
337
+ });
338
+ });
339
+
340
+ /**
341
+ * The move runs while the deployment is serving. A commit worker left
342
+ * running would write into a working copy that is being renamed away, or
343
+ * into the fresh clone of a repository the commit was never meant for.
344
+ */
345
+ describe('the commit worker, while a deployment is moved', () => {
346
+ /**
347
+ * Held BEFORE the new address is stored, not only around the phase. The
348
+ * way chosen takes effect with the store, and a worker still running in
349
+ * between would push a queued commit from a copy of the repository being
350
+ * left with the credential of the one moved to.
351
+ */
352
+ it('is held from before the address is stored until the startup phase has run', async () => {
353
+ const { base, events } = await configured(0);
354
+ events.length = 0;
355
+ const res = await post(base, {
356
+ settings: { kbRepoUrl: 'https://example.com/acme/another.git', gitToken: 'ghp_new' },
357
+ confirmRepositoryChange: 'keep',
358
+ });
359
+ expect(res.status).toBe(200);
360
+ expect(events).toEqual([`commits held on ${REPO}`, 'phase', 'commits released']);
361
+ });
362
+
363
+ it('is let go when the confirmed move is refused after all', async () => {
364
+ const { base, events } = await configured(2);
365
+ events.length = 0;
366
+ failTheClose = true;
367
+ const res = await post(base, {
368
+ settings: { kbRepoUrl: 'https://example.com/acme/another.git', gitToken: 'ghp_new' },
369
+ confirmRepositoryChange: 'close',
370
+ });
371
+ expect(res.status).toBe(500);
372
+ expect(events).toEqual([`commits held on ${REPO}`, 'commits released']);
373
+ });
374
+
375
+ it('is not touched by a save that moves nothing', async () => {
376
+ const { base, events } = await configured(0);
377
+ events.length = 0;
378
+ const res = await post(base, { settings: { kbSyncSecret: 'a-secret-of-sixteen-or-more' } });
379
+ expect(res.status).toBe(200);
380
+ expect(events).toEqual([]);
381
+ });
382
+
383
+ it('is not held by a move that was only asked about', async () => {
384
+ const { base, events } = await configured(0);
385
+ events.length = 0;
386
+ const res = await post(base, { settings: { kbRepoUrl: 'https://example.com/acme/another.git', gitToken: 'ghp_new' } });
387
+ expect(res.status).toBe(409);
388
+ expect(events).toEqual([]);
389
+ });
390
+ });
@@ -780,8 +780,11 @@ describe('POST /setup/settings — the connection is checked before it is stored
780
780
  const { base, settings } = listen(true, undefined, remote.check);
781
781
  await settings.save({ kbRepoUrl: REPO, gitToken: 'ghp_old' }, null);
782
782
  const moved = 'https://example.com/acme/moved.git';
783
+ // A different repository replaces every working copy, so the save is
784
+ // refused until the admin confirms — the address is checked all the same.
783
785
  const res = await post(base, '/api/setup/settings', {
784
786
  settings: { kbRepoUrl: moved, gitToken: 'ghp_new' },
787
+ confirmRepositoryChange: 'keep',
785
788
  });
786
789
  expect(res.status).toBe(200);
787
790
  expect(remote.asked).toEqual([{ url: moved, token: 'ghp_new', username: 'x-access-token' }]);
@@ -14,7 +14,7 @@ import {
14
14
  } from '@bevel-software/platform-shared';
15
15
  import { createHmac } from 'node:crypto';
16
16
  import { TokenCrypto } from '../../shared/token-crypto.js';
17
- import { assertKbDirNameFree } from '../kb-fs/repo-path.js';
17
+ import { assertKbDirNameFree, hasControlCharacter } from '../kb-fs/repo-path.js';
18
18
  import { parseRetentionWindow } from '../audit/audit.contract.js';
19
19
  import { normalizeIssuerUrl } from './oidc-check.js';
20
20
  import { GIT_MODES, isGitMode } from './repository-source.js';
@@ -210,10 +210,15 @@ export const CORE_SETTINGS: SettingDef[] = [
210
210
  envVar: 'KB_DIR_NAME',
211
211
  section: 'knowledge-base',
212
212
  // Joined with workspace paths, so a separator or `..` would let it escape
213
- // the workspace directory.
213
+ // the workspace directory — and a control character would be PREFIXED onto
214
+ // every path, where a line break is a separator to git's `cat-file
215
+ // --batch` and would make the normaliser emit a path it refuses from a
216
+ // caller (see `hasControlCharacter`).
214
217
  validate: (v) =>
215
218
  v && v !== '.' && v !== '..' && !v.includes('/') && !v.includes('\\')
216
- ? null
219
+ ? hasControlCharacter(v)
220
+ ? 'Use a single folder name — no line breaks or other invisible characters.'
221
+ : null
217
222
  : 'Use a single folder name — no slashes.',
218
223
  // Copied into a dozen services when they are constructed. A running server
219
224
  // keeps the name it started with.