@bevel-software/platform-core-backend 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/dist/core/core-ports.d.ts +11 -1
  2. package/dist/core/core-ports.d.ts.map +1 -1
  3. package/dist/core/core-ports.js.map +1 -1
  4. package/dist/core/create-core-server.d.ts +3 -3
  5. package/dist/core/create-core-server.d.ts.map +1 -1
  6. package/dist/core/create-core-server.js +30 -11
  7. package/dist/core/create-core-server.js.map +1 -1
  8. package/dist/core/create-core-services.d.ts +9 -2
  9. package/dist/core/create-core-services.d.ts.map +1 -1
  10. package/dist/core/create-core-services.js +112 -23
  11. package/dist/core/create-core-services.js.map +1 -1
  12. package/dist/core/lifecycle.d.ts +34 -1
  13. package/dist/core/lifecycle.d.ts.map +1 -1
  14. package/dist/core/lifecycle.js +89 -13
  15. package/dist/core/lifecycle.js.map +1 -1
  16. package/dist/core-config.d.ts +0 -12
  17. package/dist/core-config.d.ts.map +1 -1
  18. package/dist/core-config.js +11 -13
  19. package/dist/core-config.js.map +1 -1
  20. package/dist/index.d.ts +3 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/modules/access/access-control.interface.d.ts +42 -12
  25. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  26. package/dist/modules/access/access-control.service.d.ts +67 -10
  27. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  28. package/dist/modules/access/access-control.service.js +214 -35
  29. package/dist/modules/access/access-control.service.js.map +1 -1
  30. package/dist/modules/access/access.routes.d.ts.map +1 -1
  31. package/dist/modules/access/access.routes.js +13 -15
  32. package/dist/modules/access/access.routes.js.map +1 -1
  33. package/dist/modules/auth/account-admission.d.ts +58 -7
  34. package/dist/modules/auth/account-admission.d.ts.map +1 -1
  35. package/dist/modules/auth/account-admission.js +44 -1
  36. package/dist/modules/auth/account-admission.js.map +1 -1
  37. package/dist/modules/auth/account.routes.d.ts +1 -1
  38. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  39. package/dist/modules/auth/account.routes.js +61 -5
  40. package/dist/modules/auth/account.routes.js.map +1 -1
  41. package/dist/modules/auth/auth.middleware.d.ts +1 -1
  42. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  43. package/dist/modules/auth/auth.middleware.js +18 -7
  44. package/dist/modules/auth/auth.middleware.js.map +1 -1
  45. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  46. package/dist/modules/auth/auth.routes.js +8 -0
  47. package/dist/modules/auth/auth.routes.js.map +1 -1
  48. package/dist/modules/auth/auth.service.d.ts +70 -2
  49. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  50. package/dist/modules/auth/auth.service.js +176 -7
  51. package/dist/modules/auth/auth.service.js.map +1 -1
  52. package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
  53. package/dist/modules/auth/oidc-auth-provider.js +7 -2
  54. package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
  55. package/dist/modules/database/core-schema.d.ts +41 -24
  56. package/dist/modules/database/core-schema.d.ts.map +1 -1
  57. package/dist/modules/database/core-schema.js +35 -27
  58. package/dist/modules/database/core-schema.js.map +1 -1
  59. package/dist/modules/kb-fs/branch-name.d.ts.map +1 -1
  60. package/dist/modules/kb-fs/branch-name.js +12 -2
  61. package/dist/modules/kb-fs/branch-name.js.map +1 -1
  62. package/dist/modules/kb-fs/repo-path.d.ts +11 -16
  63. package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
  64. package/dist/modules/kb-fs/repo-path.js +79 -0
  65. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  66. package/dist/modules/kb-sync/kb-sync.routes.d.ts +2 -2
  67. package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
  68. package/dist/modules/kb-sync/kb-sync.routes.js +14 -3
  69. package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
  70. package/dist/modules/kb-sync/sync-auth.d.ts +3 -1
  71. package/dist/modules/kb-sync/sync-auth.d.ts.map +1 -1
  72. package/dist/modules/kb-sync/sync-auth.js +1 -1
  73. package/dist/modules/kb-sync/sync-auth.js.map +1 -1
  74. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  75. package/dist/modules/mcp/mcp-auth.middleware.js +21 -6
  76. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  77. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  78. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +3 -2
  79. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  80. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  81. package/dist/modules/settings/deployment-settings.service.js +8 -3
  82. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  83. package/dist/modules/settings/setup.routes.d.ts +52 -1
  84. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  85. package/dist/modules/settings/setup.routes.js +207 -19
  86. package/dist/modules/settings/setup.routes.js.map +1 -1
  87. package/dist/modules/skills/skills.contract.d.ts +90 -18
  88. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  89. package/dist/modules/skills/skills.contract.js +4 -1
  90. package/dist/modules/skills/skills.contract.js.map +1 -1
  91. package/dist/modules/skills/skills.service.d.ts +98 -9
  92. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  93. package/dist/modules/skills/skills.service.js +239 -37
  94. package/dist/modules/skills/skills.service.js.map +1 -1
  95. package/dist/modules/skills/skills.tools.d.ts +7 -0
  96. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  97. package/dist/modules/skills/skills.tools.js +71 -7
  98. package/dist/modules/skills/skills.tools.js.map +1 -1
  99. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  100. package/dist/modules/tool-auth/external-api-key.service.js +4 -2
  101. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  102. package/dist/modules/tool-auth/internal-token.service.d.ts +3 -3
  103. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +16 -7
  104. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  105. package/dist/modules/tool-auth/tool-auth.middleware.js +34 -12
  106. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  107. package/dist/modules/tool-helpers/tool-handler.d.ts +2 -1
  108. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  109. package/dist/modules/tool-helpers/tool-handler.js +25 -4
  110. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  111. package/dist/modules/tool-helpers/tool.contract.d.ts +3 -2
  112. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  113. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  114. package/dist/modules/tool-helpers/validate-token.js +1 -1
  115. package/dist/modules/tool-helpers/validate-token.js.map +1 -1
  116. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +98 -0
  117. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -0
  118. package/dist/modules/workflow/agent-tools/change-request-summary.js +81 -0
  119. package/dist/modules/workflow/agent-tools/change-request-summary.js.map +1 -0
  120. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  121. package/dist/modules/workflow/agent-tools/workflow.tools.js +75 -35
  122. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  123. package/dist/modules/workflow/file-lock.service.d.ts +24 -0
  124. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  125. package/dist/modules/workflow/file-lock.service.js +30 -0
  126. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  127. package/dist/modules/workflow/git/git.service.d.ts +69 -0
  128. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  129. package/dist/modules/workflow/git/git.service.js +153 -2
  130. package/dist/modules/workflow/git/git.service.js.map +1 -1
  131. package/dist/modules/workflow/pending-commits.service.d.ts +38 -0
  132. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  133. package/dist/modules/workflow/pending-commits.service.js +55 -0
  134. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  135. package/dist/modules/workflow/workflow-hooks.d.ts +54 -32
  136. package/dist/modules/workflow/workflow-hooks.d.ts.map +1 -1
  137. package/dist/modules/workflow/workflow-hooks.js +16 -1
  138. package/dist/modules/workflow/workflow-hooks.js.map +1 -1
  139. package/dist/modules/workflow/workflow.service.d.ts +60 -6
  140. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  141. package/dist/modules/workflow/workflow.service.js +111 -4
  142. package/dist/modules/workflow/workflow.service.js.map +1 -1
  143. package/dist/modules/workspace/agent-access.gate.d.ts +94 -0
  144. package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -0
  145. package/dist/modules/workspace/agent-access.gate.js +123 -0
  146. package/dist/modules/workspace/agent-access.gate.js.map +1 -0
  147. package/dist/modules/workspace/routine-write-policy.d.ts +5 -6
  148. package/dist/modules/workspace/routine-write-policy.d.ts.map +1 -1
  149. package/dist/modules/workspace/routine-write-policy.js +5 -6
  150. package/dist/modules/workspace/routine-write-policy.js.map +1 -1
  151. package/dist/modules/workspace/session-sink.d.ts +5 -5
  152. package/dist/modules/workspace/set-aside-clone.d.ts +46 -0
  153. package/dist/modules/workspace/set-aside-clone.d.ts.map +1 -0
  154. package/dist/modules/workspace/set-aside-clone.js +92 -0
  155. package/dist/modules/workspace/set-aside-clone.js.map +1 -0
  156. package/dist/modules/workspace/startup/kb-startup-runner.d.ts +59 -9
  157. package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
  158. package/dist/modules/workspace/startup/kb-startup-runner.js +65 -24
  159. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
  160. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  161. package/dist/modules/workspace/workspace.routes.js +92 -3
  162. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  163. package/dist/modules/workspace/workspace.service.d.ts +115 -5
  164. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  165. package/dist/modules/workspace/workspace.service.js +255 -25
  166. package/dist/modules/workspace/workspace.service.js.map +1 -1
  167. package/dist/modules/workspace/workspace.tools.d.ts +2 -2
  168. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  169. package/dist/modules/workspace/workspace.tools.js +309 -115
  170. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  171. package/dist/modules/write-access/write-access.d.ts +60 -0
  172. package/dist/modules/write-access/write-access.d.ts.map +1 -0
  173. package/dist/modules/write-access/write-access.js +129 -0
  174. package/dist/modules/write-access/write-access.js.map +1 -0
  175. package/dist/shared/domain-errors.d.ts +25 -0
  176. package/dist/shared/domain-errors.d.ts.map +1 -1
  177. package/dist/shared/domain-errors.js +28 -0
  178. package/dist/shared/domain-errors.js.map +1 -1
  179. package/dist/shared/git.contract.d.ts +20 -0
  180. package/dist/shared/git.contract.d.ts.map +1 -1
  181. package/dist/shared/git.contract.js +26 -0
  182. package/dist/shared/git.contract.js.map +1 -1
  183. package/dist/tenancy/static-tenant-source.d.ts +0 -1
  184. package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
  185. package/dist/tenancy/static-tenant-source.js +0 -2
  186. package/dist/tenancy/static-tenant-source.js.map +1 -1
  187. package/migrations/0014_change_request_closed_reason.sql +1 -0
  188. package/migrations/0015_account_deactivation.sql +3 -0
  189. package/migrations/meta/0014_snapshot.json +2265 -0
  190. package/migrations/meta/0015_snapshot.json +2271 -0
  191. package/migrations/meta/_journal.json +14 -0
  192. package/package.json +3 -3
  193. package/src/__tests__/kb-layout-config.test.ts +0 -2
  194. package/src/__tests__/retired-settings.test.ts +96 -0
  195. package/src/core/__tests__/gated-boot.test.ts +132 -0
  196. package/src/core/__tests__/lifecycle.test.ts +170 -1
  197. package/src/core/__tests__/set-aside-root-is-one-place.test.ts +63 -0
  198. package/src/core/core-ports.ts +11 -1
  199. package/src/core/create-core-server.ts +32 -14
  200. package/src/core/create-core-services.ts +129 -23
  201. package/src/core/lifecycle.ts +98 -13
  202. package/src/core-config.ts +13 -14
  203. package/src/index.ts +12 -1
  204. package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +385 -0
  205. package/src/modules/access/__tests__/access-control.prospective.test.ts +94 -18
  206. package/src/modules/access/__tests__/access.routes.prospective.test.ts +6 -7
  207. package/src/modules/access/access-control.interface.ts +43 -12
  208. package/src/modules/access/access-control.service.ts +234 -36
  209. package/src/modules/access/access.routes.ts +13 -15
  210. package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +3 -1
  211. package/src/modules/auth/__tests__/account-deactivation.test.ts +252 -0
  212. package/src/modules/auth/__tests__/account.routes.test.ts +81 -3
  213. package/src/modules/auth/__tests__/auth.middleware.test.ts +45 -22
  214. package/src/modules/auth/__tests__/auth.service.test.ts +1 -1
  215. package/src/modules/auth/account-admission.ts +78 -9
  216. package/src/modules/auth/account.routes.ts +63 -7
  217. package/src/modules/auth/auth.middleware.ts +19 -8
  218. package/src/modules/auth/auth.routes.ts +8 -0
  219. package/src/modules/auth/auth.service.ts +179 -6
  220. package/src/modules/auth/oidc-auth-provider.ts +7 -2
  221. package/src/modules/database/core-schema.ts +35 -27
  222. package/src/modules/kb-fs/__tests__/branch-name.test.ts +10 -0
  223. package/src/modules/kb-fs/__tests__/repo-path.test.ts +123 -0
  224. package/src/modules/kb-fs/branch-name.ts +14 -1
  225. package/src/modules/kb-fs/repo-path.ts +85 -0
  226. package/src/modules/kb-sync/__tests__/kb-sync.routes.test.ts +26 -1
  227. package/src/modules/kb-sync/kb-sync.routes.ts +14 -4
  228. package/src/modules/kb-sync/sync-auth.ts +2 -2
  229. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +60 -3
  230. package/src/modules/mcp/__tests__/mcp.service.test.ts +68 -6
  231. package/src/modules/mcp/mcp-auth.middleware.ts +20 -6
  232. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +3 -1
  233. package/src/modules/plugins/__tests__/plugins.tools.test.ts +2 -1
  234. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +16 -0
  235. package/src/modules/settings/__tests__/setup.routes.git-mode.test.ts +91 -58
  236. package/src/modules/settings/__tests__/setup.routes.github-app.test.ts +25 -8
  237. package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +14 -11
  238. package/src/modules/settings/__tests__/setup.routes.repository-change.test.ts +390 -0
  239. package/src/modules/settings/__tests__/setup.routes.test.ts +3 -0
  240. package/src/modules/settings/deployment-settings.service.ts +8 -3
  241. package/src/modules/settings/setup.routes.ts +263 -19
  242. package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +2 -1
  243. package/src/modules/skills/__tests__/branch-skills.tools.test.ts +218 -0
  244. package/src/modules/skills/__tests__/skills.service.test.ts +229 -5
  245. package/src/modules/skills/skills.contract.ts +91 -18
  246. package/src/modules/skills/skills.service.ts +278 -41
  247. package/src/modules/skills/skills.tools.ts +80 -8
  248. package/src/modules/tool-auth/__tests__/manual-auth.middleware.test.ts +14 -1
  249. package/src/modules/tool-auth/external-api-key.service.ts +4 -2
  250. package/src/modules/tool-auth/internal-token.service.ts +3 -3
  251. package/src/modules/tool-auth/tool-auth.middleware.ts +34 -11
  252. package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +5 -3
  253. package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +3 -3
  254. package/src/modules/tool-helpers/__tests__/validate-token.test.ts +11 -1
  255. package/src/modules/tool-helpers/tool-handler.ts +24 -4
  256. package/src/modules/tool-helpers/tool.contract.ts +3 -2
  257. package/src/modules/tool-helpers/validate-token.ts +1 -1
  258. package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +111 -0
  259. package/src/modules/workflow/__tests__/workflow.routes.history-read-gate.test.ts +25 -0
  260. package/src/modules/workflow/__tests__/workflow.service.repository-replaced.test.ts +249 -0
  261. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +264 -6
  262. package/src/modules/workflow/agent-tools/change-request-summary.ts +182 -0
  263. package/src/modules/workflow/agent-tools/workflow.tools.ts +86 -35
  264. package/src/modules/workflow/file-lock.service.ts +31 -0
  265. package/src/modules/workflow/git/__tests__/git.service.fileBytesAtCommit.test.ts +260 -0
  266. package/src/modules/workflow/git/git.service.ts +168 -0
  267. package/src/modules/workflow/pending-commits.service.ts +59 -1
  268. package/src/modules/workflow/workflow-hooks.ts +64 -26
  269. package/src/modules/workflow/workflow.service.ts +121 -4
  270. package/src/modules/workspace/__tests__/agent-access.gate.test.ts +208 -0
  271. package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +384 -0
  272. package/src/modules/workspace/__tests__/file-stat-access.test.ts +2 -1
  273. package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -1
  274. package/src/modules/workspace/__tests__/set-aside-clone.test.ts +52 -0
  275. package/src/modules/workspace/__tests__/workspace.routes.at-ref.test.ts +305 -0
  276. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +2 -0
  277. package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +147 -0
  278. package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +326 -0
  279. package/src/modules/workspace/__tests__/workspace.service.test.ts +20 -9
  280. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +2 -3
  281. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +2 -3
  282. package/src/modules/workspace/__tests__/workspace.tools.test.ts +685 -14
  283. package/src/modules/workspace/agent-access.gate.ts +164 -0
  284. package/src/modules/workspace/routine-write-policy.ts +5 -6
  285. package/src/modules/workspace/session-sink.ts +5 -5
  286. package/src/modules/workspace/set-aside-clone.ts +96 -0
  287. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +86 -0
  288. package/src/modules/workspace/startup/kb-startup-runner.ts +115 -34
  289. package/src/modules/workspace/workspace.routes.ts +95 -3
  290. package/src/modules/workspace/workspace.service.ts +256 -24
  291. package/src/modules/workspace/workspace.tools.ts +338 -119
  292. package/src/modules/write-access/__tests__/write-access.test.ts +248 -0
  293. package/src/modules/write-access/write-access.ts +153 -0
  294. package/src/shared/domain-errors.ts +31 -0
  295. package/src/shared/git.contract.ts +27 -0
  296. package/src/tenancy/__tests__/static-tenant-source.test.ts +0 -1
  297. package/src/tenancy/static-tenant-source.ts +0 -3
  298. package/dist/modules/workflow/session-ontology.policy.d.ts +0 -52
  299. package/dist/modules/workflow/session-ontology.policy.d.ts.map +0 -1
  300. package/dist/modules/workflow/session-ontology.policy.js +0 -62
  301. package/dist/modules/workflow/session-ontology.policy.js.map +0 -1
  302. package/dist/modules/workflow/session-ontology.service.d.ts +0 -105
  303. package/dist/modules/workflow/session-ontology.service.d.ts.map +0 -1
  304. package/dist/modules/workflow/session-ontology.service.js +0 -147
  305. package/dist/modules/workflow/session-ontology.service.js.map +0 -1
  306. package/dist/modules/workspace/session-ontology.gate.d.ts +0 -114
  307. package/dist/modules/workspace/session-ontology.gate.d.ts.map +0 -1
  308. package/dist/modules/workspace/session-ontology.gate.js +0 -161
  309. package/dist/modules/workspace/session-ontology.gate.js.map +0 -1
  310. package/dist/shared/kb-layout.d.ts +0 -39
  311. package/dist/shared/kb-layout.d.ts.map +0 -1
  312. package/dist/shared/kb-layout.js +0 -103
  313. package/dist/shared/kb-layout.js.map +0 -1
  314. package/dist/shared/kb-layout.test.d.ts +0 -2
  315. package/dist/shared/kb-layout.test.d.ts.map +0 -1
  316. package/dist/shared/kb-layout.test.js +0 -75
  317. package/dist/shared/kb-layout.test.js.map +0 -1
  318. package/src/modules/workflow/__tests__/session-ontology.policy.test.ts +0 -62
  319. package/src/modules/workflow/__tests__/session-ontology.service.test.ts +0 -201
  320. package/src/modules/workflow/session-ontology.policy.ts +0 -70
  321. package/src/modules/workflow/session-ontology.service.ts +0 -183
  322. package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +0 -239
  323. package/src/modules/workspace/session-ontology.gate.ts +0 -191
  324. package/src/shared/kb-layout.test.ts +0 -98
  325. package/src/shared/kb-layout.ts +0 -102
@@ -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);