@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
@@ -27,7 +27,7 @@ import {
27
27
  } from '@utcp/sdk';
28
28
  import { CodeModeUtcpClient } from '@utcp/code-mode';
29
29
  import {
30
- CODE_MODE_META_TOOLS,
30
+ codeModeMetaTools,
31
31
  META_TOOL_NAMES,
32
32
  dispatchMetaTool,
33
33
  dispatchToolCall,
@@ -43,6 +43,7 @@ import {
43
43
  type SkillSummary,
44
44
  type LoadedSkill,
45
45
  } from '@bevel-software/platform-mcp-core';
46
+ import type { KbLayout } from '@bevel-software/platform-shared';
46
47
  import { bevelSecretsLoaderConfig } from '../secrets-vault/index.js';
47
48
  import {
48
49
  scopesCovered,
@@ -65,6 +66,7 @@ import {
65
66
  composeAgentInstructions,
66
67
  prefixToolDescription,
67
68
  PREFIXED_TOOLS,
69
+ sharedRulesPointer,
68
70
  type AgentPreambleReader,
69
71
  type ComposedAgentInstructions,
70
72
  } from '../agent-instructions/index.js';
@@ -91,6 +93,13 @@ export interface McpProxyOptions {
91
93
  * carries the platform header alone.
92
94
  */
93
95
  readAgentPreamble?: AgentPreambleReader;
96
+ /**
97
+ * The layout in effect, read per request: the shared file rules name the
98
+ * managed guide, and a deployment may rename it after boot (the setup save
99
+ * applies a name without a restart). A GETTER, so nothing snapshots the
100
+ * pre-setup default. Absent, the rules name `AGENTS.md`.
101
+ */
102
+ kbLayout?: () => KbLayout;
94
103
  /** Bounds of the downstream (`mcp.json`) connection pool; defaults are 4h idle / 5000 entries. */
95
104
  downstreamPool?: Pick<DownstreamPoolOptions<unknown>, 'idleTtlMs' | 'maxEntries' | 'now'>;
96
105
  /**
@@ -434,12 +443,26 @@ export class McpService {
434
443
  const seen = new Set(META_TOOL_NAMES);
435
444
  const dropped: string[] = [];
436
445
  const direct: McpTool[] = [];
446
+ const examplePool: ProxiedTool[] = [];
437
447
  for (const t of listed) {
438
448
  const entry = toListedTool(t); // logs its own reason on a name/schema drop
439
449
  if (!entry) {
440
450
  dropped.push(t.mcpName);
441
451
  continue;
442
452
  }
453
+ // Kept for the worked example in the meta-tool descriptions: it must
454
+ // be derived from the tools THIS caller actually gets, not from the
455
+ // whole catalog, or a connection-key caller is shown an example naming
456
+ // a credential-gated tool the filter above just removed from its
457
+ // listing — a copied call that cannot work.
458
+ //
459
+ // BEFORE the duplicate drop below, on purpose. A tool dropped from the
460
+ // LISTING for sharing its name with another is still in the catalog a
461
+ // chain dispatches to, so the chain sees two tools under one name and
462
+ // refuses the call as ambiguous. The example has to know about both to
463
+ // steer clear of either (`chainExample` skips a name two tools share);
464
+ // shown only the survivor, it took that name for a safe one.
465
+ examplePool.push(t);
443
466
  if (seen.has(entry.name)) {
444
467
  dropped.push(`${entry.name} (duplicate)`);
445
468
  continue;
@@ -456,18 +479,33 @@ export class McpService {
456
479
  : entry,
457
480
  );
458
481
  }
482
+ // The meta-tools' examples name the namespace THIS endpoint registers the
483
+ // knowledge-base tools under, and a tool this caller is really served, so
484
+ // a chain copied out of the description runs. Built here rather than held
485
+ // as a constant: the local MCP server registers the same tools under a
486
+ // different name, and one fixed example is necessarily wrong on one of the
487
+ // two surfaces.
488
+ //
489
+ // The chain's description ends with the same pointer every file tool ends
490
+ // with: what a chain does with a failure, a large result or an image is
491
+ // stated once, in the shared rules, and the clients that drop
492
+ // `instructions` have the description and the guide to go on. Composed
493
+ // here because the guide's name is this deployment's setting.
494
+ const metaTools = codeModeMetaTools(EXTERNAL_KB_MANUAL_NAME, examplePool, {
495
+ sharedRulesPointer: sharedRulesPointer(this.opts.kbLayout?.()),
496
+ });
459
497
  // Log only when a tool was dropped (name/schema/duplicate) — that's the
460
498
  // anomaly worth surfacing, since a downstream client would otherwise hide
461
499
  // it by rejecting the whole response.
462
500
  if (dropped.length) {
463
501
  log.warn(
464
- `tools/list: serving ${CODE_MODE_META_TOOLS.length + direct.length} tool(s); ` +
502
+ `tools/list: serving ${metaTools.length + direct.length} tool(s); ` +
465
503
  `dropped ${dropped.length} non-listable: ${dropped.join(', ')}`,
466
504
  );
467
505
  }
468
506
  return {
469
507
  // Code-mode meta-tools first, then every validated direct tool.
470
- tools: [...CODE_MODE_META_TOOLS, ...direct],
508
+ tools: [...metaTools, ...direct],
471
509
  };
472
510
  });
473
511
 
@@ -613,13 +651,14 @@ export class McpService {
613
651
  * fails over its preamble.
614
652
  */
615
653
  private async composeAgentInstructions(): Promise<ComposedAgentInstructions> {
654
+ const layout = this.opts.kbLayout?.();
616
655
  const read = this.opts.readAgentPreamble;
617
- if (!read) return composeAgentInstructions(null);
656
+ if (!read) return composeAgentInstructions(null, layout);
618
657
  try {
619
- return composeAgentInstructions(await read());
658
+ return composeAgentInstructions(await read(), layout);
620
659
  } catch (err) {
621
- log.warn('could not read mcp-description.md; this request gets the platform header alone:', { err });
622
- return composeAgentInstructions(null);
660
+ log.warn('could not read mcp-description.md; this request gets the platform text alone:', { err });
661
+ return composeAgentInstructions(null, layout);
623
662
  }
624
663
  }
625
664
 
@@ -309,7 +309,8 @@ export class BevelOAuthProvider implements OAuthServerProvider {
309
309
  // the revoke (a refresh that raced it) is dead on arrival rather than
310
310
  // live until its own expiry. A token from before connections existed
311
311
  // (null `connection_id`) has no connection to be revoked and verifies on
312
- // its own row alone.
312
+ // its own row alone. A switched-off account's tokens fail here too, and
313
+ // come back with it.
313
314
  const [row] = await this.deps.db
314
315
  .select({
315
316
  id: oauthTokens.id,
@@ -330,6 +331,7 @@ export class BevelOAuthProvider implements OAuthServerProvider {
330
331
  isNull(oauthTokens.revokedAt),
331
332
  gt(oauthTokens.expiresAt, now),
332
333
  or(isNull(oauthTokens.connectionId), isNull(agentConnections.revokedAt)),
334
+ isNull(users.deactivatedAt),
333
335
  ),
334
336
  )
335
337
  .limit(1);
@@ -53,7 +53,8 @@ describe('the creation endpoints behind the key-or-session gate', () => {
53
53
  } as never;
54
54
  const authService = {
55
55
  getUserById: async (id: string) => (id === ALICE.id ? ALICE : null),
56
- verifyToken: (t: string) => {
56
+ // A session is accepted only for an account that is on; Alice's is.
57
+ resolveSession: async (t: string) => {
57
58
  if (t !== 'session-alice') throw new Error('bad token');
58
59
  return { userId: ALICE.id, email: ALICE.email };
59
60
  },
@@ -143,11 +143,14 @@ export class DbJoinRequestStore implements JoinRequestStore {
143
143
  pluginKey: string;
144
144
  }): Promise<JoinRequestRecord> {
145
145
  const requesterEmail = input.requesterEmail.toLowerCase();
146
+ // Keyed by the blind index throughout: the email column is randomized
147
+ // ciphertext, so the uniqueness and every lookup go through its index,
148
+ // which is written and compared with the address.
146
149
  const [inserted] = await this.db
147
150
  .insert(pluginJoinRequests)
148
- .values({ requesterEmail, requesterName: input.requesterName, pluginKey: input.pluginKey })
151
+ .values({ requesterEmail, requesterEmailBidx: requesterEmail, requesterName: input.requesterName, pluginKey: input.pluginKey })
149
152
  .onConflictDoNothing({
150
- target: [pluginJoinRequests.requesterEmail, pluginJoinRequests.pluginKey],
153
+ target: [pluginJoinRequests.requesterEmailBidx, pluginJoinRequests.pluginKey],
151
154
  })
152
155
  .returning();
153
156
  if (inserted) return toRecord(inserted);
@@ -170,7 +173,7 @@ export class DbJoinRequestStore implements JoinRequestStore {
170
173
  })
171
174
  .where(
172
175
  and(
173
- eq(pluginJoinRequests.requesterEmail, requesterEmail),
176
+ eq(pluginJoinRequests.requesterEmailBidx, requesterEmail),
174
177
  eq(pluginJoinRequests.pluginKey, input.pluginKey),
175
178
  ),
176
179
  )
@@ -191,7 +194,7 @@ export class DbJoinRequestStore implements JoinRequestStore {
191
194
  const rows = await this.db
192
195
  .select()
193
196
  .from(pluginJoinRequests)
194
- .where(eq(pluginJoinRequests.requesterEmail, requesterEmail.toLowerCase()));
197
+ .where(eq(pluginJoinRequests.requesterEmailBidx, requesterEmail));
195
198
  return rows.map(toRecord);
196
199
  }
197
200
 
@@ -413,6 +413,22 @@ describe('DeploymentSettingsService — validation', () => {
413
413
  );
414
414
  });
415
415
 
416
+ it('rejects a directory name carrying a line break, which every path is prefixed with', async () => {
417
+ // The name is joined onto every workspace-relative path, and a line break
418
+ // in a path is a separator to git's `cat-file --batch` — so a name
419
+ // carrying one would make the normaliser emit a path it refuses from a
420
+ // caller. Refused where the name is SET, which is here and at boot.
421
+ const { db } = makeDb();
422
+ const settings = new DeploymentSettingsService(db, ENC_KEY);
423
+ for (const bad of ['kb\nname', 'kb\rname', 'kb\u0000name', 'kb\u007Fname']) {
424
+ await expect(settings.save({ kbDirName: bad }, null), bad).rejects.toThrow(
425
+ /Invalid settings/,
426
+ );
427
+ }
428
+ // The ordinary name beside them, so the rule is not simply refusing both.
429
+ await expect(settings.save({ kbDirName: 'company-brain' }, null)).resolves.toBeTruthy();
430
+ });
431
+
416
432
  it('rejects a username that would break out of the credential-helper snippet', async () => {
417
433
  const { db } = makeDb();
418
434
  const settings = new DeploymentSettingsService(db, ENC_KEY);
@@ -112,11 +112,12 @@ function listen(
112
112
  server = app.listen(0);
113
113
  const { port } = server.address() as AddressInfo;
114
114
  const base = `http://127.0.0.1:${port}`;
115
- const save = (entries: Record<string, string>) =>
115
+ /** `confirm` is the admin's answer to the question a move is asked, when they have given one. */
116
+ const save = (entries: Record<string, string>, confirm?: 'keep' | 'close') =>
116
117
  fetch(`${base}/api/setup/settings`, {
117
118
  method: 'POST',
118
119
  headers: { 'Content-Type': 'application/json' },
119
- body: JSON.stringify({ settings: entries }),
120
+ body: JSON.stringify({ settings: entries, ...(confirm ? { confirmRepositoryChange: confirm } : {}) }),
120
121
  });
121
122
  const status = async () => (await fetch(`${base}/api/setup/status`)).json() as Promise<Record<string, unknown>>;
122
123
  return { save, status, settings, source, managed, ensured, probed, kb, phase: { runs: () => phaseRuns, saw: phaseSaw } };
@@ -199,63 +200,97 @@ describe('a deployment that is serving moves to another repository', () => {
199
200
  return mounted;
200
201
  }
201
202
 
202
- it('owes a restart, and says both where it is and where it is going', async () => {
203
- const { save, status, phase, ensured } = await serving();
203
+ /**
204
+ * A move is asked about before anything is stored: the save is refused
205
+ * until the answer comes back, naming the way left and the way moved to.
206
+ * Refused, it has changed nothing — the deployment is where it was.
207
+ */
208
+ it('is asked first, and until it answers nothing has moved', async () => {
209
+ const { save, status, phase, source, settings } = await serving();
204
210
  const runsBefore = phase.runs();
205
211
  const res = await save({ gitMode: 'managed' });
206
- const body = (await res.json()) as Record<string, unknown>;
207
- expect(body).toMatchObject({ ok: true, complete: true, restartRequired: true, repository: { mode: 'token', chosen: 'managed' } });
208
- expect(await status()).toMatchObject({ complete: true, repository: { mode: 'token', chosen: 'managed' } });
209
- expect(ensured).toHaveLength(1);
210
- // Sessions may be live: the phase that moves working copies waits for the restart.
212
+ expect(res.status).toBe(409);
213
+ expect(await res.json()).toMatchObject({ repositoryChange: { openChangeRequests: 0, from: 'token', to: 'managed' } });
214
+ expect(settings.resolve('gitMode')).toBe('');
215
+ expect(source.mode()).toBe('token');
216
+ expect(source.url()).toBe(HOSTED.kbRepoUrl);
211
217
  expect(phase.runs()).toBe(runsBefore);
218
+ expect(await status()).toMatchObject({ complete: true, repository: { mode: 'token', chosen: 'token' } });
212
219
  });
213
220
 
214
221
  /**
215
- * The move is a fact about the NEXT start. Until then every working copy
216
- * is a clone of the repository the deployment had, so that is the
217
- * repository git is handed the address of, and its token is the one
218
- * presented. Reading the mode live moved the process at the save: pushes
219
- * went out with no credential, and a branch opened in between was cloned
220
- * from a repository nothing had prepared.
222
+ * Confirmed, the move takes effect ON THE SAVE: the way chosen is the way
223
+ * in effect, the startup phase runs against the repository moved to, and
224
+ * no restart is owed. It once waited for the next start, which an admin of
225
+ * a hosted workspace has no way to give.
221
226
  */
222
- it('goes on working against the repository it has until it is started again', async () => {
223
- const { save, source } = await serving();
224
- await save({ gitMode: 'managed' });
225
- expect(source.mode()).toBe('token');
226
- expect(source.url()).toBe(HOSTED.kbRepoUrl);
227
- expect(source.credentials.token()).toBe('the-token');
228
- expect(source.credentials.username()).toBe('x-access-token');
229
- // A token rotated meanwhile is still the one the next push carries.
230
- await save({ gitToken: 'a-rotated-token' });
231
- expect(source.credentials.token()).toBe('a-rotated-token');
227
+ it('moves on the save once confirmed, and owes no restart', async () => {
228
+ const { save, status, phase, ensured, managed, source } = await serving();
229
+ const runsBefore = phase.runs();
230
+ const res = await save({ gitMode: 'managed' }, 'keep');
231
+ const body = (await res.json()) as Record<string, unknown>;
232
+ expect(res.status, JSON.stringify(body)).toBe(200);
233
+ expect(body).toMatchObject({
234
+ ok: true,
235
+ complete: true,
236
+ restartRequired: false,
237
+ repository: { mode: 'managed', chosen: 'managed' },
238
+ repositoryChange: { choice: 'keep' },
239
+ });
240
+ expect(ensured).toHaveLength(1);
241
+ // The phase that sets the old working copies aside ran, against the repository moved to.
242
+ expect(phase.runs()).toBe(runsBefore + 1);
243
+ expect(phase.saw.at(-1)).toBe(managed.path);
244
+ expect(source.url()).toBe(managed.path);
245
+ expect(await status()).toMatchObject({ complete: true, repository: { mode: 'managed', chosen: 'managed' } });
232
246
  });
233
247
 
234
- it('is on the repository it chose once it is started again, and presents it nothing of the old one', async () => {
235
- const { save, source, managed } = await serving();
236
- await save({ gitMode: 'managed' });
237
- // What a restart does: the source is built on the mode chosen.
238
- source.takeEffect();
239
- expect(source.mode()).toBe('managed');
240
- expect(source.url()).toBe(managed.path);
248
+ /** One way's credential is never presented to another way's repository. */
249
+ it('presents the repository moved to nothing of the one that was left', async () => {
250
+ const { save, source } = await serving();
251
+ expect(source.credentials.token()).toBe('the-token');
252
+ await save({ gitMode: 'managed' }, 'keep');
241
253
  expect(source.credentials.token()).toBeNull();
242
254
  });
243
255
 
244
- it('goes on owing the restart through saves about other things', async () => {
256
+ it('owes nothing through later saves about other things, and is not asked again', async () => {
245
257
  const { save } = await serving();
246
- await save({ gitMode: 'managed' });
247
- const body = (await (await save({ kbSyncSecret: 'a-secret-of-sixteen-or-more' })).json()) as Record<string, unknown>;
248
- expect(body).toMatchObject({ ok: true, restartRequired: true, repository: { mode: 'token', chosen: 'managed' } });
258
+ await save({ gitMode: 'managed' }, 'keep');
259
+ const res = await save({ kbSyncSecret: 'a-secret-of-sixteen-or-more' });
260
+ expect(res.status).toBe(200);
261
+ expect(await res.json()).toMatchObject({ ok: true, restartRequired: false, repository: { mode: 'managed', chosen: 'managed' } });
249
262
  });
250
263
 
251
- it('owes nothing once the move is taken back', async () => {
252
- const { save, probed } = await serving();
253
- await save({ gitMode: 'managed' });
254
- const asked = probed.length;
255
- const body = (await (await save({ gitMode: 'token' })).json()) as Record<string, unknown>;
256
- expect(body).toMatchObject({ ok: true, restartRequired: false, repository: { mode: 'token', chosen: 'token' } });
257
- // Nothing about the connection changed, so the host is not asked again.
258
- expect(probed).toHaveLength(asked);
264
+ it('is asked again to move back: that is a move too', async () => {
265
+ const { save, source } = await serving();
266
+ await save({ gitMode: 'managed' }, 'keep');
267
+ const back = await save({ gitMode: 'token' });
268
+ expect(back.status).toBe(409);
269
+ expect(await back.json()).toMatchObject({ repositoryChange: { from: 'managed', to: 'token' } });
270
+ const done = await save({ gitMode: 'token' }, 'keep');
271
+ expect(await done.json()).toMatchObject({ ok: true, restartRequired: false, repository: { mode: 'token', chosen: 'token' } });
272
+ expect(source.url()).toBe(HOSTED.kbRepoUrl);
273
+ });
274
+
275
+ /** Another address under the same way is the same question, with the same answer. */
276
+ it('asks the same of another address under the way it is on', async () => {
277
+ const { save, source, phase } = await serving();
278
+ const moved = { kbRepoUrl: 'https://git.example.com/acme/another.git', gitToken: 'another-token' };
279
+ const asked = await save(moved);
280
+ expect(asked.status).toBe(409);
281
+ expect(await asked.json()).toMatchObject({ repositoryChange: { from: 'token', to: 'token' } });
282
+ const runsBefore = phase.runs();
283
+ const done = await save(moved, 'close');
284
+ expect(await done.json()).toMatchObject({ ok: true, restartRequired: false, repositoryChange: { choice: 'close' } });
285
+ expect(phase.runs()).toBe(runsBefore + 1);
286
+ expect(source.url()).toBe(moved.kbRepoUrl);
287
+ });
288
+
289
+ it('asks nothing of a save that only spells the same address another way', async () => {
290
+ const { save } = await serving();
291
+ const res = await save({ kbRepoUrl: 'https://GIT.example.com/acme/kb/' });
292
+ expect(res.status).toBe(200);
293
+ expect(await res.json()).not.toHaveProperty('repositoryChange');
259
294
  });
260
295
 
261
296
  it('owes none for naming the way it was already on', async () => {
@@ -268,10 +303,11 @@ describe('a deployment that is serving moves to another repository', () => {
268
303
  });
269
304
 
270
305
  /**
271
- * The pin protects what is running on the mode in effect. Behind a shut
272
- * gate nothing is, however completely the settings were answered, and a
273
- * deployment pinned there could not be got out of a repository that does
274
- * not work by choosing one that does.
306
+ * Behind a shut gate nothing is running on the way in effect, however
307
+ * completely the settings were answered, so a deployment there is got out of
308
+ * a repository that does not work by choosing one that does. One that never
309
+ * served is not even asked: there is no repository anyone worked on to
310
+ * leave. One that served before its boot failed is asked, as any move is.
275
311
  */
276
312
  describe('a deployment that answered everything and serves nobody', () => {
277
313
  const isHosted = (url: string) => url === HOSTED.kbRepoUrl;
@@ -300,19 +336,16 @@ describe('a deployment that answered everything and serves nobody', () => {
300
336
  const { save, status, phase, managed } = listen({ kb: testKbContext(), bootFailed: true, unreachable: isHosted });
301
337
  expect(await status()).toMatchObject({ complete: false, repository: { mode: 'token' } });
302
338
 
303
- const body = (await (await save({ gitMode: 'managed' })).json()) as Record<string, unknown>;
339
+ // It served before this boot, so there may be work on the repository it
340
+ // leaves: it is asked, and nothing moves until it answers.
341
+ const asked = await save({ gitMode: 'managed' });
342
+ expect(asked.status).toBe(409);
343
+ expect(phase.saw).toEqual([]);
344
+
345
+ const body = (await (await save({ gitMode: 'managed' }, 'keep')).json()) as Record<string, unknown>;
304
346
  expect(phase.saw).toEqual([managed.path]);
305
347
  expect(body).toMatchObject({ complete: true, restartRequired: false, repository: { mode: 'managed', chosen: 'managed' } });
306
348
  });
307
-
308
- it('is still asked for a restart once it IS serving: the same save, a different deployment', async () => {
309
- const { save, phase } = listen({ kb: testKbContext() });
310
- await save({ ...HOSTED, ...BRANCHES });
311
- const runs = phase.runs();
312
- const body = (await (await save({ gitMode: 'managed' })).json()) as Record<string, unknown>;
313
- expect(body).toMatchObject({ complete: true, restartRequired: true, repository: { mode: 'token', chosen: 'managed' } });
314
- expect(phase.runs()).toBe(runs);
315
- });
316
349
  });
317
350
 
318
351
  describe('a deployment whose way is chosen by the environment', () => {
@@ -108,11 +108,12 @@ function listen(
108
108
  );
109
109
  server = app.listen(0);
110
110
  const base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
111
- const save = async (entries: Record<string, string>) => {
111
+ /** `confirm` is the admin's answer to the question a move is asked. */
112
+ const save = async (entries: Record<string, string>, confirm?: 'keep' | 'close') => {
112
113
  const res = await fetch(`${base}/api/setup/settings`, {
113
114
  method: 'POST',
114
115
  headers: { 'Content-Type': 'application/json' },
115
- body: JSON.stringify({ settings: entries }),
116
+ body: JSON.stringify({ settings: entries, ...(confirm ? { confirmRepositoryChange: confirm } : {}) }),
116
117
  });
117
118
  return { status: res.status, body: (await res.json()) as Record<string, unknown> & { problems?: Record<string, string> } };
118
119
  };
@@ -271,12 +272,28 @@ describe('a deployment on GitHub that is serving', () => {
271
272
  expect(probed).toHaveLength(asked);
272
273
  });
273
274
 
274
- it('is asked again when it moves to another repository', async () => {
275
- const { save, probed } = await serving();
276
- const asked = probed.length;
277
- const { status } = await save({ githubRepository: 'acme/another' });
278
- expect(status).toBe(200);
279
- expect(probed).toHaveLength(asked + 1);
275
+ /**
276
+ * Another repository on GitHub is a move like any other: GitHub is asked
277
+ * about the new one, the admin is asked about the move, and confirmed it
278
+ * happens on the save.
279
+ */
280
+ it('proves the new repository, asks about the move, and moves on the save once confirmed', async () => {
281
+ const { save, probed, source, phaseRuns } = await serving();
282
+ const before = probed.length;
283
+ const asked = await save({ githubRepository: 'acme/another' });
284
+ expect(asked.status).toBe(409);
285
+ expect(asked.body).toMatchObject({ repositoryChange: { from: 'github-app', to: 'github-app' } });
286
+ // The connection is proven before the question is put: nobody agrees to
287
+ // a move only to be told the repository cannot be reached.
288
+ expect(probed).toHaveLength(before + 1);
280
289
  expect(probed.at(-1)!.url).toBe('https://github.com/acme/another.git');
290
+ expect(source.url()).toBe('https://github.com/acme/kb.git');
291
+
292
+ const runs = phaseRuns();
293
+ const moved = await save({ githubRepository: 'acme/another' }, 'keep');
294
+ expect(moved.status, JSON.stringify(moved.body)).toBe(200);
295
+ expect(moved.body).toMatchObject({ ok: true, restartRequired: false, repositoryChange: { choice: 'keep' } });
296
+ expect(source.url()).toBe('https://github.com/acme/another.git');
297
+ expect(phaseRuns()).toBe(runs + 1);
281
298
  });
282
299
  });
@@ -131,11 +131,12 @@ function boot(kb: KbContext) {
131
131
  );
132
132
  server = app.listen(0);
133
133
  const base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
134
- const save = async (entries: Record<string, string>) => {
134
+ /** `confirm` is the admin's answer to the question a move is asked. */
135
+ const save = async (entries: Record<string, string>, confirm?: 'keep' | 'close') => {
135
136
  const res = await fetch(`${base}/api/setup/settings`, {
136
137
  method: 'POST',
137
138
  headers: { 'content-type': 'application/json' },
138
- body: JSON.stringify({ settings: entries }),
139
+ body: JSON.stringify({ settings: entries, ...(confirm ? { confirmRepositoryChange: confirm } : {}) }),
139
140
  });
140
141
  return { status: res.status, body: (await res.json()) as Record<string, unknown> };
141
142
  };
@@ -175,7 +176,7 @@ describe('the save that chooses a repository the deployment keeps', () => {
175
176
 
176
177
  describe('a deployment that moves to a repository it keeps', () => {
177
178
  it('leaves the repository it had untouched and sets its working copies aside, unpushed work included', async () => {
178
- const { save, managed, hosted, runner, source, workingCopy, setAsideRoot } = boot(
179
+ const { save, managed, hosted, source, workingCopy, setAsideRoot } = boot(
179
180
  testKbContext({ branchModel: { defaultBranch: 'main', protectedBranches: ['main'] } }),
180
181
  );
181
182
  // A deployment on a repository of its own, set up and serving. The
@@ -203,17 +204,19 @@ describe('a deployment that moves to a repository it keeps', () => {
203
204
  await git(copy, ['commit', '-m', 'never pushed']);
204
205
  const hostedBefore = await git(hosted, ['for-each-ref']);
205
206
 
206
- const moved = await save({ gitMode: 'managed' });
207
- expect(moved.body).toMatchObject({ ok: true, restartRequired: true });
208
- // Until the restart the working copy is where it was, and so is the
209
- // deployment: the repository it has is the one it goes on talking to.
207
+ // Asked first: until the admin answers, the working copy is where it
208
+ // was and so is the deployment.
209
+ const asked = await save({ gitMode: 'managed' });
210
+ expect(asked.status).toBe(409);
210
211
  expect(await git(copy, ['config', '--get', 'remote.origin.url'])).toBe(hosted);
211
212
  expect(source.url()).toBe(hosted);
212
213
 
213
- // The restart: the deployment is built on the mode chosen, and the
214
- // phase brings the working copies into line.
215
- source.takeEffect();
216
- await runner.runAll();
214
+ // Confirmed, the move happens on the save, with no restart: the way
215
+ // chosen takes effect and the phase brings the working copies into line.
216
+ const moved = await save({ gitMode: 'managed' }, 'keep');
217
+ expect(moved.status, JSON.stringify(moved.body)).toBe(200);
218
+ expect(moved.body).toMatchObject({ ok: true, complete: true, restartRequired: false });
219
+ expect(source.url()).toBe(managed.path);
217
220
 
218
221
  expect(await git(hosted, ['for-each-ref'])).toBe(hostedBefore);
219
222
  expect(await git(copy, ['config', '--get', 'remote.origin.url'])).toBe(managed.path);