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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (466) hide show
  1. package/dist/core/core-ports.d.ts +11 -1
  2. package/dist/core/core-ports.d.ts.map +1 -1
  3. package/dist/core/core-ports.js.map +1 -1
  4. package/dist/core/create-core-server.d.ts +3 -3
  5. package/dist/core/create-core-server.d.ts.map +1 -1
  6. package/dist/core/create-core-server.js +46 -13
  7. package/dist/core/create-core-server.js.map +1 -1
  8. package/dist/core/create-core-services.d.ts +12 -2
  9. package/dist/core/create-core-services.d.ts.map +1 -1
  10. package/dist/core/create-core-services.js +137 -24
  11. package/dist/core/create-core-services.js.map +1 -1
  12. package/dist/core/lifecycle.d.ts +46 -1
  13. package/dist/core/lifecycle.d.ts.map +1 -1
  14. package/dist/core/lifecycle.js +99 -13
  15. package/dist/core/lifecycle.js.map +1 -1
  16. package/dist/core-config.d.ts +16 -12
  17. package/dist/core-config.d.ts.map +1 -1
  18. package/dist/core-config.js +28 -13
  19. package/dist/core-config.js.map +1 -1
  20. package/dist/index.d.ts +6 -4
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +12 -3
  23. package/dist/index.js.map +1 -1
  24. package/dist/modules/access/access-control.interface.d.ts +42 -12
  25. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  26. package/dist/modules/access/access-control.service.d.ts +67 -10
  27. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  28. package/dist/modules/access/access-control.service.js +214 -35
  29. package/dist/modules/access/access-control.service.js.map +1 -1
  30. package/dist/modules/access/access.routes.d.ts.map +1 -1
  31. package/dist/modules/access/access.routes.js +17 -16
  32. package/dist/modules/access/access.routes.js.map +1 -1
  33. package/dist/modules/access/directory-sync-bot.d.ts.map +1 -1
  34. package/dist/modules/access/directory-sync-bot.js +7 -3
  35. package/dist/modules/access/directory-sync-bot.js.map +1 -1
  36. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +8 -1
  37. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
  38. package/dist/modules/agent-instructions/agent-instructions.routes.js +8 -2
  39. package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
  40. package/dist/modules/agent-instructions/compose.d.ts +38 -6
  41. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  42. package/dist/modules/agent-instructions/compose.js +39 -6
  43. package/dist/modules/agent-instructions/compose.js.map +1 -1
  44. package/dist/modules/agent-instructions/index.d.ts +2 -1
  45. package/dist/modules/agent-instructions/index.d.ts.map +1 -1
  46. package/dist/modules/agent-instructions/index.js +2 -1
  47. package/dist/modules/agent-instructions/index.js.map +1 -1
  48. package/dist/modules/agent-instructions/shared-file-rules.d.ts +115 -0
  49. package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -0
  50. package/dist/modules/agent-instructions/shared-file-rules.js +272 -0
  51. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -0
  52. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  53. package/dist/modules/audit/agent-audit.service.js +4 -2
  54. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  55. package/dist/modules/auth/account-admission.d.ts +58 -7
  56. package/dist/modules/auth/account-admission.d.ts.map +1 -1
  57. package/dist/modules/auth/account-admission.js +44 -1
  58. package/dist/modules/auth/account-admission.js.map +1 -1
  59. package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
  60. package/dist/modules/auth/account-erasure.service.js +57 -16
  61. package/dist/modules/auth/account-erasure.service.js.map +1 -1
  62. package/dist/modules/auth/account.routes.d.ts +1 -1
  63. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  64. package/dist/modules/auth/account.routes.js +61 -5
  65. package/dist/modules/auth/account.routes.js.map +1 -1
  66. package/dist/modules/auth/auth.middleware.d.ts +1 -1
  67. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  68. package/dist/modules/auth/auth.middleware.js +18 -7
  69. package/dist/modules/auth/auth.middleware.js.map +1 -1
  70. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  71. package/dist/modules/auth/auth.routes.js +8 -0
  72. package/dist/modules/auth/auth.routes.js.map +1 -1
  73. package/dist/modules/auth/auth.service.d.ts +70 -2
  74. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  75. package/dist/modules/auth/auth.service.js +197 -18
  76. package/dist/modules/auth/auth.service.js.map +1 -1
  77. package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
  78. package/dist/modules/auth/oidc-auth-provider.js +7 -2
  79. package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
  80. package/dist/modules/code-mode/code-mode.tool.d.ts +20 -2
  81. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  82. package/dist/modules/code-mode/code-mode.tool.js +66 -35
  83. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  84. package/dist/modules/database/connection.d.ts +16 -0
  85. package/dist/modules/database/connection.d.ts.map +1 -1
  86. package/dist/modules/database/connection.js +117 -0
  87. package/dist/modules/database/connection.js.map +1 -1
  88. package/dist/modules/database/core-schema.d.ts +337 -120
  89. package/dist/modules/database/core-schema.d.ts.map +1 -1
  90. package/dist/modules/database/core-schema.js +116 -58
  91. package/dist/modules/database/core-schema.js.map +1 -1
  92. package/dist/modules/database/migrate.d.ts +8 -0
  93. package/dist/modules/database/migrate.d.ts.map +1 -1
  94. package/dist/modules/database/migrate.js +271 -1
  95. package/dist/modules/database/migrate.js.map +1 -1
  96. package/dist/modules/kb-fs/branch-name.d.ts.map +1 -1
  97. package/dist/modules/kb-fs/branch-name.js +12 -2
  98. package/dist/modules/kb-fs/branch-name.js.map +1 -1
  99. package/dist/modules/kb-fs/repo-path.d.ts +11 -16
  100. package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
  101. package/dist/modules/kb-fs/repo-path.js +79 -0
  102. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  103. package/dist/modules/kb-sync/kb-sync.routes.d.ts +2 -2
  104. package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
  105. package/dist/modules/kb-sync/kb-sync.routes.js +14 -3
  106. package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
  107. package/dist/modules/kb-sync/sync-auth.d.ts +3 -1
  108. package/dist/modules/kb-sync/sync-auth.d.ts.map +1 -1
  109. package/dist/modules/kb-sync/sync-auth.js +1 -1
  110. package/dist/modules/kb-sync/sync-auth.js.map +1 -1
  111. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  112. package/dist/modules/mcp/mcp-auth.middleware.js +21 -6
  113. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  114. package/dist/modules/mcp/mcp.service.d.ts +8 -0
  115. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  116. package/dist/modules/mcp/mcp.service.js +38 -8
  117. package/dist/modules/mcp/mcp.service.js.map +1 -1
  118. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  119. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +3 -2
  120. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  121. package/dist/modules/plugins/join-request-records.store.d.ts.map +1 -1
  122. package/dist/modules/plugins/join-request-records.store.js +7 -4
  123. package/dist/modules/plugins/join-request-records.store.js.map +1 -1
  124. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  125. package/dist/modules/settings/deployment-settings.service.js +8 -3
  126. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  127. package/dist/modules/settings/setup.routes.d.ts +52 -1
  128. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  129. package/dist/modules/settings/setup.routes.js +207 -19
  130. package/dist/modules/settings/setup.routes.js.map +1 -1
  131. package/dist/modules/skills/skills.contract.d.ts +90 -18
  132. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  133. package/dist/modules/skills/skills.contract.js +4 -1
  134. package/dist/modules/skills/skills.contract.js.map +1 -1
  135. package/dist/modules/skills/skills.service.d.ts +98 -9
  136. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  137. package/dist/modules/skills/skills.service.js +239 -37
  138. package/dist/modules/skills/skills.service.js.map +1 -1
  139. package/dist/modules/skills/skills.tools.d.ts +7 -0
  140. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  141. package/dist/modules/skills/skills.tools.js +71 -7
  142. package/dist/modules/skills/skills.tools.js.map +1 -1
  143. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  144. package/dist/modules/tool-auth/external-api-key.service.js +12 -4
  145. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  146. package/dist/modules/tool-auth/internal-token.service.d.ts +3 -3
  147. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +16 -7
  148. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  149. package/dist/modules/tool-auth/tool-auth.middleware.js +34 -12
  150. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  151. package/dist/modules/tool-helpers/tool-handler.d.ts +2 -1
  152. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  153. package/dist/modules/tool-helpers/tool-handler.js +25 -4
  154. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  155. package/dist/modules/tool-helpers/tool.contract.d.ts +3 -2
  156. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  157. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  158. package/dist/modules/tool-helpers/validate-token.js +1 -1
  159. package/dist/modules/tool-helpers/validate-token.js.map +1 -1
  160. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  161. package/dist/modules/tool-manuals/tool-manuals.tools.js +42 -31
  162. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  163. package/dist/modules/tool-registry/description-length.d.ts +80 -0
  164. package/dist/modules/tool-registry/description-length.d.ts.map +1 -0
  165. package/dist/modules/tool-registry/description-length.js +108 -0
  166. package/dist/modules/tool-registry/description-length.js.map +1 -0
  167. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +98 -0
  168. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -0
  169. package/dist/modules/workflow/agent-tools/change-request-summary.js +81 -0
  170. package/dist/modules/workflow/agent-tools/change-request-summary.js.map +1 -0
  171. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  172. package/dist/modules/workflow/agent-tools/workflow.tools.js +75 -35
  173. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  174. package/dist/modules/workflow/file-lock.service.d.ts +24 -0
  175. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  176. package/dist/modules/workflow/file-lock.service.js +30 -0
  177. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  178. package/dist/modules/workflow/git/git.service.d.ts +94 -0
  179. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  180. package/dist/modules/workflow/git/git.service.js +193 -3
  181. package/dist/modules/workflow/git/git.service.js.map +1 -1
  182. package/dist/modules/workflow/pending-commits.service.d.ts +38 -0
  183. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  184. package/dist/modules/workflow/pending-commits.service.js +60 -1
  185. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  186. package/dist/modules/workflow/recovery-bot.d.ts.map +1 -1
  187. package/dist/modules/workflow/recovery-bot.js +7 -3
  188. package/dist/modules/workflow/recovery-bot.js.map +1 -1
  189. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  190. package/dist/modules/workflow/review-workflow/review-workflow.service.js +12 -3
  191. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  192. package/dist/modules/workflow/workflow-hooks.d.ts +54 -32
  193. package/dist/modules/workflow/workflow-hooks.d.ts.map +1 -1
  194. package/dist/modules/workflow/workflow-hooks.js +16 -1
  195. package/dist/modules/workflow/workflow-hooks.js.map +1 -1
  196. package/dist/modules/workflow/workflow.service.d.ts +60 -6
  197. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  198. package/dist/modules/workflow/workflow.service.js +115 -5
  199. package/dist/modules/workflow/workflow.service.js.map +1 -1
  200. package/dist/modules/workspace/agent-access.gate.d.ts +94 -0
  201. package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -0
  202. package/dist/modules/workspace/agent-access.gate.js +123 -0
  203. package/dist/modules/workspace/agent-access.gate.js.map +1 -0
  204. package/dist/modules/workspace/agent-upload.routes.d.ts +77 -0
  205. package/dist/modules/workspace/agent-upload.routes.d.ts.map +1 -0
  206. package/dist/modules/workspace/agent-upload.routes.js +210 -0
  207. package/dist/modules/workspace/agent-upload.routes.js.map +1 -0
  208. package/dist/modules/workspace/agent-upload.store.d.ts +284 -0
  209. package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -0
  210. package/dist/modules/workspace/agent-upload.store.js +553 -0
  211. package/dist/modules/workspace/agent-upload.store.js.map +1 -0
  212. package/dist/modules/workspace/routine-write-policy.d.ts +5 -6
  213. package/dist/modules/workspace/routine-write-policy.d.ts.map +1 -1
  214. package/dist/modules/workspace/routine-write-policy.js +5 -6
  215. package/dist/modules/workspace/routine-write-policy.js.map +1 -1
  216. package/dist/modules/workspace/session-sink.d.ts +5 -5
  217. package/dist/modules/workspace/set-aside-clone.d.ts +46 -0
  218. package/dist/modules/workspace/set-aside-clone.d.ts.map +1 -0
  219. package/dist/modules/workspace/set-aside-clone.js +92 -0
  220. package/dist/modules/workspace/set-aside-clone.js.map +1 -0
  221. package/dist/modules/workspace/startup/kb-startup-runner.d.ts +59 -9
  222. package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
  223. package/dist/modules/workspace/startup/kb-startup-runner.js +65 -24
  224. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
  225. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  226. package/dist/modules/workspace/startup/steps/seed-tree.js +3 -3
  227. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  228. package/dist/modules/workspace/startup/steps/template-source.d.ts +40 -0
  229. package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
  230. package/dist/modules/workspace/startup/steps/template-source.js +46 -4
  231. package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
  232. package/dist/modules/workspace/upload-limits.d.ts +13 -0
  233. package/dist/modules/workspace/upload-limits.d.ts.map +1 -0
  234. package/dist/modules/workspace/upload-limits.js +13 -0
  235. package/dist/modules/workspace/upload-limits.js.map +1 -0
  236. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  237. package/dist/modules/workspace/workspace.routes.js +93 -4
  238. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  239. package/dist/modules/workspace/workspace.service.d.ts +128 -5
  240. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  241. package/dist/modules/workspace/workspace.service.js +316 -58
  242. package/dist/modules/workspace/workspace.service.js.map +1 -1
  243. package/dist/modules/workspace/workspace.tools.d.ts +13 -11
  244. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  245. package/dist/modules/workspace/workspace.tools.js +791 -198
  246. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  247. package/dist/modules/workspace/write-denial.d.ts +0 -6
  248. package/dist/modules/workspace/write-denial.d.ts.map +1 -1
  249. package/dist/modules/workspace/write-denial.js +0 -6
  250. package/dist/modules/workspace/write-denial.js.map +1 -1
  251. package/dist/modules/workspace/zip-entry-rules.d.ts +114 -0
  252. package/dist/modules/workspace/zip-entry-rules.d.ts.map +1 -0
  253. package/dist/modules/workspace/zip-entry-rules.js +154 -0
  254. package/dist/modules/workspace/zip-entry-rules.js.map +1 -0
  255. package/dist/modules/write-access/write-access.d.ts +60 -0
  256. package/dist/modules/write-access/write-access.d.ts.map +1 -0
  257. package/dist/modules/write-access/write-access.js +129 -0
  258. package/dist/modules/write-access/write-access.js.map +1 -0
  259. package/dist/shared/column-crypto.d.ts +194 -0
  260. package/dist/shared/column-crypto.d.ts.map +1 -0
  261. package/dist/shared/column-crypto.js +144 -0
  262. package/dist/shared/column-crypto.js.map +1 -0
  263. package/dist/shared/domain-errors.d.ts +25 -0
  264. package/dist/shared/domain-errors.d.ts.map +1 -1
  265. package/dist/shared/domain-errors.js +28 -0
  266. package/dist/shared/domain-errors.js.map +1 -1
  267. package/dist/shared/git.contract.d.ts +20 -0
  268. package/dist/shared/git.contract.d.ts.map +1 -1
  269. package/dist/shared/git.contract.js +26 -0
  270. package/dist/shared/git.contract.js.map +1 -1
  271. package/dist/shared/token-crypto.d.ts.map +1 -1
  272. package/dist/shared/token-crypto.js +25 -1
  273. package/dist/shared/token-crypto.js.map +1 -1
  274. package/dist/tenancy/static-tenant-source.d.ts +0 -1
  275. package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
  276. package/dist/tenancy/static-tenant-source.js +1 -2
  277. package/dist/tenancy/static-tenant-source.js.map +1 -1
  278. package/dist/tenancy/tenant-secrets.d.ts +5 -1
  279. package/dist/tenancy/tenant-secrets.d.ts.map +1 -1
  280. package/dist/tenancy/tenant-secrets.js +4 -0
  281. package/dist/tenancy/tenant-secrets.js.map +1 -1
  282. package/kb-template/AGENTS.md +2 -0
  283. package/migrations/0014_change_request_closed_reason.sql +1 -0
  284. package/migrations/0015_account_deactivation.sql +3 -0
  285. package/migrations/0016_pii_encryption.sql +20 -0
  286. package/migrations/meta/0014_snapshot.json +2265 -0
  287. package/migrations/meta/0015_snapshot.json +2271 -0
  288. package/migrations/meta/0016_snapshot.json +2327 -0
  289. package/migrations/meta/_journal.json +21 -0
  290. package/package.json +3 -3
  291. package/src/__tests__/kb-layout-config.test.ts +0 -2
  292. package/src/__tests__/retired-settings.test.ts +96 -0
  293. package/src/core/__tests__/gated-boot.test.ts +132 -0
  294. package/src/core/__tests__/lifecycle.test.ts +241 -5
  295. package/src/core/__tests__/set-aside-root-is-one-place.test.ts +63 -0
  296. package/src/core/core-ports.ts +11 -1
  297. package/src/core/create-core-server.ts +52 -16
  298. package/src/core/create-core-services.ts +156 -24
  299. package/src/core/lifecycle.ts +117 -13
  300. package/src/core-config.ts +31 -14
  301. package/src/index.ts +30 -1
  302. package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +385 -0
  303. package/src/modules/access/__tests__/access-control.prospective.test.ts +94 -18
  304. package/src/modules/access/__tests__/access.routes.prospective.test.ts +6 -7
  305. package/src/modules/access/__tests__/users-db-double.ts +21 -12
  306. package/src/modules/access/access-control.interface.ts +43 -12
  307. package/src/modules/access/access-control.service.ts +234 -36
  308. package/src/modules/access/access.routes.ts +17 -16
  309. package/src/modules/access/directory-sync-bot.ts +7 -3
  310. package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +11 -5
  311. package/src/modules/agent-instructions/__tests__/compose.test.ts +19 -10
  312. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +238 -0
  313. package/src/modules/agent-instructions/agent-instructions.routes.ts +12 -2
  314. package/src/modules/agent-instructions/compose.ts +50 -7
  315. package/src/modules/agent-instructions/index.ts +12 -0
  316. package/src/modules/agent-instructions/shared-file-rules.ts +314 -0
  317. package/src/modules/audit/agent-audit.service.ts +4 -2
  318. package/src/modules/auth/__tests__/account-deactivation.test.ts +253 -0
  319. package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +6 -2
  320. package/src/modules/auth/__tests__/account.routes.test.ts +87 -6
  321. package/src/modules/auth/__tests__/auth.middleware.test.ts +45 -22
  322. package/src/modules/auth/__tests__/auth.service.test.ts +4 -2
  323. package/src/modules/auth/account-admission.ts +78 -9
  324. package/src/modules/auth/account-erasure.service.ts +69 -18
  325. package/src/modules/auth/account.routes.ts +63 -7
  326. package/src/modules/auth/auth.middleware.ts +19 -8
  327. package/src/modules/auth/auth.routes.ts +8 -0
  328. package/src/modules/auth/auth.service.ts +206 -23
  329. package/src/modules/auth/oidc-auth-provider.ts +7 -2
  330. package/src/modules/code-mode/__tests__/chain-runtime.e2e.test.ts +335 -0
  331. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +46 -2
  332. package/src/modules/code-mode/code-mode.tool.ts +80 -34
  333. package/src/modules/database/__tests__/connection.test.ts +12 -0
  334. package/src/modules/database/__tests__/pii-backfill.pg.test.ts +561 -0
  335. package/src/modules/database/connection.ts +117 -0
  336. package/src/modules/database/core-schema.ts +116 -58
  337. package/src/modules/database/migrate.ts +353 -1
  338. package/src/modules/kb-fs/__tests__/branch-name.test.ts +10 -0
  339. package/src/modules/kb-fs/__tests__/repo-path.test.ts +123 -0
  340. package/src/modules/kb-fs/branch-name.ts +14 -1
  341. package/src/modules/kb-fs/repo-path.ts +85 -0
  342. package/src/modules/kb-sync/__tests__/kb-sync.routes.test.ts +26 -1
  343. package/src/modules/kb-sync/kb-sync.routes.ts +14 -4
  344. package/src/modules/kb-sync/sync-auth.ts +2 -2
  345. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +60 -3
  346. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +4 -3
  347. package/src/modules/mcp/__tests__/mcp.service.test.ts +115 -13
  348. package/src/modules/mcp/mcp-auth.middleware.ts +20 -6
  349. package/src/modules/mcp/mcp.service.ts +46 -7
  350. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +3 -1
  351. package/src/modules/plugins/__tests__/plugins.tools.test.ts +2 -1
  352. package/src/modules/plugins/join-request-records.store.ts +7 -4
  353. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +16 -0
  354. package/src/modules/settings/__tests__/setup.routes.git-mode.test.ts +91 -58
  355. package/src/modules/settings/__tests__/setup.routes.github-app.test.ts +25 -8
  356. package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +14 -11
  357. package/src/modules/settings/__tests__/setup.routes.repository-change.test.ts +390 -0
  358. package/src/modules/settings/__tests__/setup.routes.test.ts +3 -0
  359. package/src/modules/settings/deployment-settings.service.ts +8 -3
  360. package/src/modules/settings/setup.routes.ts +263 -19
  361. package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +2 -1
  362. package/src/modules/skills/__tests__/branch-skills.tools.test.ts +218 -0
  363. package/src/modules/skills/__tests__/skills.service.test.ts +229 -5
  364. package/src/modules/skills/skills.contract.ts +91 -18
  365. package/src/modules/skills/skills.service.ts +278 -41
  366. package/src/modules/skills/skills.tools.ts +80 -8
  367. package/src/modules/tool-auth/__tests__/manual-auth.middleware.test.ts +14 -1
  368. package/src/modules/tool-auth/external-api-key.service.ts +12 -4
  369. package/src/modules/tool-auth/internal-token.service.ts +3 -3
  370. package/src/modules/tool-auth/tool-auth.middleware.ts +34 -11
  371. package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +5 -3
  372. package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +3 -3
  373. package/src/modules/tool-helpers/__tests__/validate-token.test.ts +11 -1
  374. package/src/modules/tool-helpers/tool-handler.ts +24 -4
  375. package/src/modules/tool-helpers/tool.contract.ts +3 -2
  376. package/src/modules/tool-helpers/validate-token.ts +1 -1
  377. package/src/modules/tool-manuals/tool-manuals.tools.ts +44 -31
  378. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +378 -0
  379. package/src/modules/tool-registry/description-length.ts +111 -0
  380. package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +111 -0
  381. package/src/modules/workflow/__tests__/workflow.routes.history-read-gate.test.ts +25 -0
  382. package/src/modules/workflow/__tests__/workflow.service.repository-replaced.test.ts +249 -0
  383. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +264 -6
  384. package/src/modules/workflow/agent-tools/change-request-summary.ts +182 -0
  385. package/src/modules/workflow/agent-tools/workflow.tools.ts +86 -35
  386. package/src/modules/workflow/file-lock.service.ts +31 -0
  387. package/src/modules/workflow/git/__tests__/git.service.fileBytesAtCommit.test.ts +260 -0
  388. package/src/modules/workflow/git/__tests__/git.service.prFetchFailure.test.ts +170 -0
  389. package/src/modules/workflow/git/git.service.ts +215 -1
  390. package/src/modules/workflow/pending-commits.service.ts +64 -2
  391. package/src/modules/workflow/recovery-bot.ts +7 -3
  392. package/src/modules/workflow/review-workflow/__tests__/carry-approvals-forward.test.ts +4 -0
  393. package/src/modules/workflow/review-workflow/__tests__/erase-approver.test.ts +25 -3
  394. package/src/modules/workflow/review-workflow/review-workflow.service.ts +12 -3
  395. package/src/modules/workflow/workflow-hooks.ts +64 -26
  396. package/src/modules/workflow/workflow.service.ts +125 -5
  397. package/src/modules/workspace/__tests__/agent-access.gate.test.ts +208 -0
  398. package/src/modules/workspace/__tests__/agent-uploads.test.ts +1604 -0
  399. package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +394 -0
  400. package/src/modules/workspace/__tests__/file-stat-access.test.ts +2 -1
  401. package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -1
  402. package/src/modules/workspace/__tests__/set-aside-clone.test.ts +52 -0
  403. package/src/modules/workspace/__tests__/workspace.routes.at-ref.test.ts +305 -0
  404. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +2 -0
  405. package/src/modules/workspace/__tests__/workspace.service.any-workspace-credentials.test.ts +156 -0
  406. package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +147 -0
  407. package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +402 -0
  408. package/src/modules/workspace/__tests__/workspace.service.test.ts +77 -9
  409. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +41 -39
  410. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +2 -3
  411. package/src/modules/workspace/__tests__/workspace.tools.test.ts +766 -56
  412. package/src/modules/workspace/agent-access.gate.ts +164 -0
  413. package/src/modules/workspace/agent-upload.routes.ts +214 -0
  414. package/src/modules/workspace/agent-upload.store.ts +668 -0
  415. package/src/modules/workspace/routine-write-policy.ts +5 -6
  416. package/src/modules/workspace/session-sink.ts +5 -5
  417. package/src/modules/workspace/set-aside-clone.ts +96 -0
  418. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +86 -0
  419. package/src/modules/workspace/startup/kb-startup-runner.ts +115 -34
  420. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +9 -9
  421. package/src/modules/workspace/startup/steps/seed-tree.ts +3 -6
  422. package/src/modules/workspace/startup/steps/template-source.ts +53 -5
  423. package/src/modules/workspace/upload-limits.ts +12 -0
  424. package/src/modules/workspace/workspace.routes.ts +96 -5
  425. package/src/modules/workspace/workspace.service.ts +319 -61
  426. package/src/modules/workspace/workspace.tools.ts +894 -214
  427. package/src/modules/workspace/write-denial.ts +0 -8
  428. package/src/modules/workspace/zip-entry-rules.ts +173 -0
  429. package/src/modules/write-access/__tests__/write-access.test.ts +248 -0
  430. package/src/modules/write-access/write-access.ts +153 -0
  431. package/src/shared/__tests__/column-crypto.test.ts +217 -0
  432. package/src/shared/column-crypto.ts +218 -0
  433. package/src/shared/domain-errors.ts +31 -0
  434. package/src/shared/git.contract.ts +27 -0
  435. package/src/shared/token-crypto.ts +28 -1
  436. package/src/tenancy/__tests__/static-tenant-source.test.ts +4 -1
  437. package/src/tenancy/static-tenant-source.ts +1 -3
  438. package/src/tenancy/tenant-secrets.ts +5 -1
  439. package/dist/modules/workflow/session-ontology.policy.d.ts +0 -52
  440. package/dist/modules/workflow/session-ontology.policy.d.ts.map +0 -1
  441. package/dist/modules/workflow/session-ontology.policy.js +0 -62
  442. package/dist/modules/workflow/session-ontology.policy.js.map +0 -1
  443. package/dist/modules/workflow/session-ontology.service.d.ts +0 -105
  444. package/dist/modules/workflow/session-ontology.service.d.ts.map +0 -1
  445. package/dist/modules/workflow/session-ontology.service.js +0 -147
  446. package/dist/modules/workflow/session-ontology.service.js.map +0 -1
  447. package/dist/modules/workspace/session-ontology.gate.d.ts +0 -114
  448. package/dist/modules/workspace/session-ontology.gate.d.ts.map +0 -1
  449. package/dist/modules/workspace/session-ontology.gate.js +0 -161
  450. package/dist/modules/workspace/session-ontology.gate.js.map +0 -1
  451. package/dist/shared/kb-layout.d.ts +0 -39
  452. package/dist/shared/kb-layout.d.ts.map +0 -1
  453. package/dist/shared/kb-layout.js +0 -103
  454. package/dist/shared/kb-layout.js.map +0 -1
  455. package/dist/shared/kb-layout.test.d.ts +0 -2
  456. package/dist/shared/kb-layout.test.d.ts.map +0 -1
  457. package/dist/shared/kb-layout.test.js +0 -75
  458. package/dist/shared/kb-layout.test.js.map +0 -1
  459. package/src/modules/workflow/__tests__/session-ontology.policy.test.ts +0 -62
  460. package/src/modules/workflow/__tests__/session-ontology.service.test.ts +0 -201
  461. package/src/modules/workflow/session-ontology.policy.ts +0 -70
  462. package/src/modules/workflow/session-ontology.service.ts +0 -183
  463. package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +0 -239
  464. package/src/modules/workspace/session-ontology.gate.ts +0 -191
  465. package/src/shared/kb-layout.test.ts +0 -98
  466. package/src/shared/kb-layout.ts +0 -102
@@ -0,0 +1,63 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { setAsideRootFor } from '../../modules/workspace/set-aside-clone.js';
6
+
7
+ /**
8
+ * A working copy of a repository that is no longer the configured one is SET
9
+ * ASIDE rather than deleted, and two places do it: the KB startup phase,
10
+ * sweeping the whole workspaces root, and the workspace service, refusing to
11
+ * adopt one on a branch open. They must set aside into ONE directory.
12
+ *
13
+ * They did not. The composition root gave the phase a root under the backups
14
+ * volume and the workspace service nothing at all, so that one fell back to a
15
+ * sibling of the workspaces root — which in the shipped compose file is the
16
+ * container's own filesystem, not a volume. A copy the workspace service kept
17
+ * was therefore gone at the next ordinary recreate, after the setup screen had
18
+ * told the admin it was being kept and where to find it.
19
+ *
20
+ * Read off the source, like `migrations-unqualified.test.ts` reads the
21
+ * migrations: the wiring is a single call graph that cannot be built in a unit
22
+ * test (it wants a database, a git host and a disk), and the invariant is
23
+ * about the TEXT — that the folder is named once and that one name reaches
24
+ * both. A second literal is exactly how the two came apart.
25
+ */
26
+
27
+ const compositionRoot = path.resolve(
28
+ path.dirname(fileURLToPath(import.meta.url)),
29
+ '../create-core-services.ts',
30
+ );
31
+
32
+ describe('the set-aside root the composition root wires', () => {
33
+ it('is named once, and handed to both the startup phase and the workspace service', async () => {
34
+ const source = await fs.readFile(compositionRoot, 'utf8');
35
+
36
+ // Named once. Two occurrences means two expressions, which is how a
37
+ // change to one of them silently stops applying to the other.
38
+ const literals = source.match(/'replaced-working-copies'/g) ?? [];
39
+ expect(literals).toHaveLength(1);
40
+
41
+ // And that one name is rooted at the backups volume, which the deployment
42
+ // mounts — not at the workspaces root's parent, which it does not.
43
+ expect(source).toMatch(
44
+ /const replacedWorkingCopiesRoot = path\.join\(config\.backupsRoot, 'replaced-working-copies'\)/,
45
+ );
46
+
47
+ // Both consumers read the one variable.
48
+ expect(source).toMatch(/new WorkspaceService\([\s\S]*?replacedWorkingCopiesRoot,[\s\S]*?\);/);
49
+ expect(source).toMatch(/setAsideRoot: replacedWorkingCopiesRoot,/);
50
+ });
51
+ });
52
+
53
+ describe('setAsideRootFor', () => {
54
+ it('uses the root it is given, whatever the workspaces root is', () => {
55
+ expect(setAsideRootFor('/app/apps/server/workspaces', '/app/apps/server/backups/replaced-working-copies')).toBe(
56
+ '/app/apps/server/backups/replaced-working-copies',
57
+ );
58
+ });
59
+
60
+ it('falls back beside the workspaces root — a place a container deployment must not rely on', () => {
61
+ expect(setAsideRootFor('/app/apps/server/workspaces')).toBe('/app/apps/server/replaced-working-copies');
62
+ });
63
+ });
@@ -7,6 +7,7 @@ import type { ILlmUsageMeter } from '../modules/tool-auth/llm-usage-meter.js';
7
7
  import type { AuthProviderPlugin } from '../modules/auth/auth.routes.js';
8
8
  import type { IErasureParticipant } from '../modules/auth/account-erasure.service.js';
9
9
  import type { IAccountAdmission } from '../modules/auth/account-admission.js';
10
+ import type { IWriteAccess } from '../modules/write-access/write-access.js';
10
11
  import type { OnServerStart } from '../modules/workspace/startup/on-server-start.js';
11
12
 
12
13
  /**
@@ -34,7 +35,7 @@ import type { OnServerStart } from '../modules/workspace/startup/on-server-start
34
35
  * enterprise root overwrites the `CoreServices` fields (or pushes into the
35
36
  * `authProviders` array) after construction, before the server is built.
36
37
  *
37
- * (Commit-time KB validation and the ontology write block are NOT ports:
38
+ * (Commit-time KB validation and the agent read/write hooks are NOT ports:
38
39
  * they are workflow lifecycle HOOKS the enterprise root registers on
39
40
  * `workflowService.hooks` after construction — see
40
41
  * `modules/workflow/workflow-hooks.ts`.)
@@ -117,6 +118,15 @@ export interface CorePorts {
117
118
  * admin who asked. See `modules/auth/account-admission.ts`.
118
119
  */
119
120
  accountAdmission?: IAccountAdmission;
121
+ /**
122
+ * Whether the deployment may be changed right now. Asked before every
123
+ * mutating route (bar the ones that sign people in, manage accounts or
124
+ * configure the deployment) and every write tool. Core default: always.
125
+ * A host that sells seats answers no while more accounts are on than its
126
+ * plan allows, so the workspace is read-only until an admin fixes it. See
127
+ * `modules/write-access/write-access.ts`.
128
+ */
129
+ writeAccess?: IWriteAccess;
120
130
  }
121
131
 
122
132
  /**
@@ -4,6 +4,10 @@ import path from 'node:path';
4
4
  import type { Router, RequestHandler } from 'express';
5
5
  import { createAuthRoutes } from '../modules/auth/auth.routes.js';
6
6
  import { createWorkspaceRoutes } from '../modules/workspace/workspace.routes.js';
7
+ import {
8
+ createAgentUploadRoutes,
9
+ isAgentUploadRawBodyPath,
10
+ } from '../modules/workspace/agent-upload.routes.js';
7
11
  import { createGitInternalsRouteGuard } from '../modules/workspace/git-internals.middleware.js';
8
12
  import { startCore } from './lifecycle.js';
9
13
  import { createDiffRoutes } from '../modules/diff/diff.routes.js';
@@ -37,7 +41,7 @@ import {
37
41
  registerPluginsTools,
38
42
  } from '../modules/plugins/index.js';
39
43
  import { keyOrSessionAuth } from '../modules/tool-auth/key-or-session.middleware.js';
40
- import type { SessionOntologyGate } from '../modules/workspace/session-ontology.gate.js';
44
+ import { ToolDescriptionNotes, type AgentAccessGate } from '../modules/workspace/agent-access.gate.js';
41
45
  import {
42
46
  createSecretsVaultRoutes,
43
47
  createSecretsVaultPublicRoutes,
@@ -72,6 +76,7 @@ import { publicConfig } from './public-config.js';
72
76
  import { createReadiness } from './readiness.js';
73
77
  import { createAgentInstructionsRoutes } from '../modules/agent-instructions/index.js';
74
78
  import type { CoreServices } from './create-core-services.js';
79
+ import { createWriteAccessRoutes, createWriteGateMiddleware } from '../modules/write-access/write-access.js';
75
80
 
76
81
  type ExpressApp = ReturnType<typeof express>;
77
82
 
@@ -85,8 +90,8 @@ export interface ToolSurfaceCtx {
85
90
  router: Router;
86
91
  toolAuth: RequestHandler;
87
92
  toolHandler: CoreServices['toolHandlerFactory'];
88
- /** Shared ontology-session boundary gate config (file tools + graph tools). */
89
- sessionOntologyGate: SessionOntologyGate;
93
+ /** Shared agent-access gate config (file tools + graph tools). */
94
+ agentAccessGate: AgentAccessGate;
90
95
  core: CoreServices;
91
96
  }
92
97
 
@@ -200,8 +205,23 @@ export async function createCoreServer(
200
205
  // is mounted again below, once the body is parsed and once the caller is
201
206
  // known (see the two mounts under `/api/workspace/:id`).
202
207
  app.use('/api/workspace/:id', createGitInternalsRouteGuard(core.workspaceService));
208
+ // The read-only gate, ahead of every route — core's, the tool surface's
209
+ // and an overlay's alike — so a read-only deployment refuses a change
210
+ // wherever it would enter. It lets through what signs people in, manages
211
+ // accounts and configures the deployment (see `ALWAYS_WRITABLE`); write
212
+ // tools are judged by the tool layer. A no-op unless a host fills
213
+ // `ports.writeAccess`. Ahead of the body parser too: a refused write is
214
+ // refused with its own 403, never parsed first (and answered 400 or 413).
215
+ app.use(createWriteGateMiddleware(core.writeAccess));
203
216
  app.use((req, res, next) => {
204
- if (jsonExemptPaths.has(req.path) || isSyncRawBodyPath(req.path)) return next();
217
+ // The agent upload route is exempt for the same reason `/api/sync` is:
218
+ // whoever needs the EXACT bytes has to see them before any parser can
219
+ // drain the stream. `curl --data-binary @file.zip` with a JSON
220
+ // content-type is a request an agent can make, and the parser would
221
+ // otherwise leave the handler nothing to store.
222
+ if (jsonExemptPaths.has(req.path) || isSyncRawBodyPath(req.path) || isAgentUploadRawBodyPath(req.path)) {
223
+ return next();
224
+ }
205
225
  return globalJson(req, res, next);
206
226
  });
207
227
 
@@ -420,24 +440,30 @@ export async function createCoreServer(
420
440
  },
421
441
  }),
422
442
  );
423
- // Shared ontology-session boundary gate config, consumed by every tool
424
- // surface that touches the KB (file tools + graph tools). The gate's
425
- // blocking decision runs through the workflow hooks: core registers none
426
- // (tracking only); the enterprise root registers the ontology block on
427
- // `workflowService.hooks` before this server is built.
428
- const sessionOntologyGate = {
429
- service: core.sessionOntologyService,
430
- enabled: core.config.ontologySessionBlock,
431
- kb: core.kb,
443
+ // Shared agent-access gate config, consumed by every tool surface that
444
+ // touches the KB (file tools + graph tools). The gate decides nothing
445
+ // itself: it calls the read and write hooks a deployment registered on
446
+ // `workflowService.hooks`, and carries the notes that deployment wants
447
+ // agents to read about the gated tools. Core registers neither, so a
448
+ // core-only deployment refuses nothing and says nothing extra.
449
+ const agentAccessGate: AgentAccessGate = {
432
450
  recoveryBotEmail: RECOVERY_BOT_EMAIL,
433
451
  hooks: core.workflowService.hooks,
452
+ notes: new ToolDescriptionNotes(),
434
453
  };
435
454
  // A skill's `allowed-tools`, checked against what the caller can see — on
436
455
  // every save surface (agent write tools, the app's PUT /file) and on
437
456
  // `get_skill`. Warnings only; it never refuses a save.
438
457
  const allowedToolsChecker = new AllowedToolsChecker(core.toolRegistry, core.toolManualService, core.kb);
439
458
  registerWorkflowTools(core.toolRegistry, toolsRouter, ta, th, core.kb);
440
- registerWorkspaceTools(core.toolRegistry, toolsRouter, ta, th, core.spillStore, core.docExtractService, core.accessControl, core.kb, sessionOntologyGate, core.routineWritePolicy, core.sessionSink, allowedToolsChecker, core.changeGate);
459
+ registerWorkspaceTools(core.toolRegistry, toolsRouter, ta, th, core.spillStore, core.docExtractService, core.accessControl, core.kb, agentAccessGate, core.routineWritePolicy, core.sessionSink, allowedToolsChecker, core.changeGate, core.agentUploadStore);
460
+ // The agent upload route, on the same router as the tool endpoints so it
461
+ // mounts ahead of the JWT `/api` mounts below — but WITHOUT `toolAuth`: its
462
+ // whole credential is the single-use token in its path, which is the point
463
+ // (an agent's `curl` carries no session and no connection key). It resolves
464
+ // no workspace and writes into none; every access, platform-file and branch
465
+ // rule is applied later by `apply_file_upload`.
466
+ toolsRouter.use(createAgentUploadRoutes({ uploads: core.agentUploadStore }));
441
467
  registerSkillsTools(core.toolRegistry, toolsRouter, ta, th, core.skillService, allowedToolsChecker);
442
468
  // Definitions only: the endpoints they describe are the app's own plugin
443
469
  // creation routes, mounted below behind the key-or-session gate.
@@ -455,7 +481,7 @@ export async function createCoreServer(
455
481
  router: toolsRouter,
456
482
  toolAuth: ta,
457
483
  toolHandler: th,
458
- sessionOntologyGate,
484
+ agentAccessGate,
459
485
  core,
460
486
  });
461
487
  toolsRouter.use(createManualRoutes(
@@ -481,7 +507,7 @@ export async function createCoreServer(
481
507
  // What every connected agent is told at session start, as the hosted proxy
482
508
  // composes it: read by the local `hexis-mcp` bridge at startup and by the
483
509
  // External agent access card. Same `manualAuth`, same router, as `all-tools`.
484
- toolsRouter.use(createAgentInstructionsRoutes(core.manualAuthMiddleware, core.readAgentPreamble));
510
+ toolsRouter.use(createAgentInstructionsRoutes(core.manualAuthMiddleware, core.readAgentPreamble, () => core.kb.layout));
485
511
  // The fingerprint of the caller's released catalog. The local `hexis-mcp`
486
512
  // server polls it to learn that a manual or a skill changed under a
487
513
  // connection it cannot be pushed to; nothing else consults it. Same
@@ -546,6 +572,7 @@ export async function createCoreServer(
546
572
  // request also gets the resolved form judged — a link in the repository that
547
573
  // points into the git folder — before any read gate or lock.
548
574
  app.use('/api/workspace/:id', core.authMiddleware, createGitInternalsRouteGuard(core.workspaceService));
575
+ app.use('/api', core.authMiddleware, createWriteAccessRoutes(core.writeAccess));
549
576
  app.use('/api', core.authMiddleware, createWorkspaceRoutes(
550
577
  core.workspaceService,
551
578
  core.authService,
@@ -722,6 +749,15 @@ export async function createCoreServer(
722
749
  ensureManaged: (branch) => core.managedRepository.ensure(core.gitRunner, branch),
723
750
  githubApp: core.githubApp,
724
751
  },
752
+ // What a move to another repository has to decide about, and what it
753
+ // runs under. Bound here rather than handed the whole workflow service:
754
+ // the setup routes ask two questions, hold the commit worker for the
755
+ // move, and know nothing else about a request or a commit.
756
+ {
757
+ countOpen: () => core.workflowService.countOpenChangeRequests(),
758
+ closeAsRepositoryReplaced: () => core.workflowService.closeOpenChangeRequestsAsRepositoryReplaced(),
759
+ whileCommitsHeld: (work) => core.whileCommitsHeld(work),
760
+ },
725
761
  ),
726
762
  );
727
763
  // Connecting a repository on GitHub through a GitHub App: the round trips
@@ -12,6 +12,7 @@ import { sql } from 'drizzle-orm';
12
12
  import { DEFAULT_DB_SCHEMA, assertSearchPath } from '../modules/database/connection.js';
13
13
  import { DEFAULT_SECRETS_SCOPE } from '../modules/secrets-vault/secrets-variable-loader.js';
14
14
  import { KbContext } from '../shared/kb-context.js';
15
+ import { branchForWorkspaceId } from '../shared/workspace-id.js';
15
16
  import { getDb, type Database } from '../modules/database/connection.js';
16
17
  import { runCoreMigrations } from '../modules/database/migrate.js';
17
18
  import { coreMigrationsDir } from '../assets.js';
@@ -35,7 +36,7 @@ import { ManagedRepository, MANAGED_DEFAULT_BRANCH } from '../modules/settings/m
35
36
  import { RepositorySource } from '../modules/settings/repository-source.js';
36
37
  import { GitHubAppConnection } from '../modules/github-app/index.js';
37
38
  import { AdvisoryLease, AdvisoryLock } from '../modules/database/advisory-lock.js';
38
- import { holdCommitWorkerLease, withStartupTask, type LeaseLoopHandle } from './lifecycle.js';
39
+ import { holdCommitWorkerLease, holdable, withStartupTask, type LeaseLoopHandle } from './lifecycle.js';
39
40
 
40
41
  /** The hosted MCP endpoint at a deployment address, with any userinfo stripped. */
41
42
  function mcpEndpointUrl(publicBackendUrl: string): string {
@@ -53,6 +54,7 @@ function parseDomainList(raw: string): string[] {
53
54
  .filter((d) => d.length > 0);
54
55
  }
55
56
  import { SpillStore } from '../modules/workspace/spill-store.js';
57
+ import { AgentUploadStore, assertUploadsRootOutsideWorkspaces } from '../modules/workspace/agent-upload.store.js';
56
58
  import { DocExtractService } from '../modules/workspace/file-readers/doc-extract.service.js';
57
59
  import { UuidSessionSink, type ISessionSink } from '../modules/workspace/session-sink.js';
58
60
  import { AuthService } from '../modules/auth/auth.service.js';
@@ -110,7 +112,6 @@ import { WorkflowEventBus } from '../modules/workflow/event-bus.js';
110
112
  import { FileChangeNotifier } from '../modules/kb-fs/file-change-notifier.js';
111
113
  import { WorkflowService } from '../modules/workflow/workflow.service.js';
112
114
  import { WorkflowHooks } from '../modules/workflow/workflow-hooks.js';
113
- import { SessionOntologyService } from '../modules/workflow/session-ontology.service.js';
114
115
  import { PendingCommitsService } from '../modules/workflow/pending-commits.service.js';
115
116
  import {
116
117
  PendingCommitsWorker,
@@ -142,6 +143,7 @@ import { BevelOAuthProvider } from '../modules/mcp/oauth/bevel-oauth-provider.js
142
143
  import { getOAuthProtectedResourceMetadataUrl } from '@modelcontextprotocol/sdk/server/auth/router.js';
143
144
  import { ToolRegistry } from '../modules/tool-registry/tool-registry.js';
144
145
  import { createToolContextResolver } from '../modules/tool-helpers/tool-context.js';
146
+ import { alwaysWritable, type IWriteAccess } from '../modules/write-access/write-access.js';
145
147
  import { createToolHandlerFactory } from '../modules/tool-helpers/tool-handler.js';
146
148
  import { TokenCrypto } from '../shared/token-crypto.js';
147
149
  import { UpdateCheckService } from '../modules/update-check/update-check.service.js';
@@ -182,6 +184,12 @@ export interface CoreServices {
182
184
  * lease to the replacement.
183
185
  */
184
186
  commitWorker: LeaseLoopHandle;
187
+ /**
188
+ * Run something with no queued commit being written: the commit worker is
189
+ * stopped for its duration and started again afterwards, the lease kept.
190
+ * What a move to another repository runs the startup phase under.
191
+ */
192
+ whileCommitsHeld<T>(work: () => Promise<T>): Promise<T>;
185
193
  workspaceService: WorkspaceService;
186
194
  /**
187
195
  * The KB startup phase (see `startup/on-server-start.ts`): run at the
@@ -213,12 +221,13 @@ export interface CoreServices {
213
221
  /** The scope this graph's vault is registered under with the UTCP variable loader. */
214
222
  secretsScope: string;
215
223
  spillStore: SpillStore;
224
+ /** The bytes an agent uploaded, held until `apply_file_upload` lands them or their token expires. */
225
+ agentUploadStore: AgentUploadStore;
216
226
  docExtractService: DocExtractService;
217
227
  accessControl: AccessControlService;
218
228
  creatorAccess: CreatorAccessService;
219
229
  /** Read-before-write, for the surfaces that ask ahead of the lock (see `access-model/change-gate.ts`). */
220
230
  changeGate: ChangeReadGate;
221
- sessionOntologyService: SessionOntologyService;
222
231
  routineWritePolicy: RoutineWritePolicyService;
223
232
  skillService: SkillService;
224
233
  pendingSkillsService: PendingSkillsService;
@@ -327,6 +336,8 @@ export interface CoreServices {
327
336
  toolAuthMiddleware: ReturnType<typeof createToolAuthMiddleware>;
328
337
  manualAuthMiddleware: ReturnType<typeof createManualAuthMiddleware>;
329
338
  toolHandlerFactory: ReturnType<typeof createToolHandlerFactory>;
339
+ /** Whether the deployment may be changed right now — `ports.writeAccess`, or always. */
340
+ writeAccess: IWriteAccess;
330
341
  // ── Server-time seams (enterprise overwrites after construction) ────────
331
342
  /** `start_session` backing — core default {@link UuidSessionSink}. */
332
343
  sessionSink: ISessionSink;
@@ -349,7 +360,11 @@ export async function createCoreServices(
349
360
  // the default schema, so a single-tenant deployment keeps the lock ids and
350
361
  // the ledger it always had.
351
362
  const tenantKey = config.dbSchema === DEFAULT_DB_SCHEMA ? '' : config.dbSchema;
352
- const db = getDb(config.databaseUrl, { schema: config.dbSchema });
363
+ // The handle holds this knowledge base's key for its personal-data columns:
364
+ // the same `SECRETS_ENC_KEY` that seals its stored credentials — its own
365
+ // derived one, for a tenant — so everything in the schema opens with one
366
+ // key and nothing in it opens with another tenant's.
367
+ const db = getDb(config.databaseUrl, { schema: config.dbSchema, piiKey: config.secretsEncKey });
353
368
  // A schema of its own is created on first use, so a tenant's first
354
369
  // activation needs nothing done by hand; `public` always exists. Then the
355
370
  // server is asked whether the connections really search that schema: a
@@ -369,6 +384,10 @@ export async function createCoreServices(
369
384
  // package runs its own squashed idempotent CORE history from the packaged
370
385
  // `migrations/` folder, tracked in `__drizzle_migrations_core`. An
371
386
  // enterprise overlay runs its own history AFTER this (see migrate.ts).
387
+ // `runCoreMigrations` also seals any pre-encryption plaintext PII rows and
388
+ // swaps the unique constraints onto the blind-index columns (see
389
+ // migrate.ts), under the same lock, before any service reads or writes a
390
+ // PII column.
372
391
  await runCoreMigrations(db, coreMigrationsDir());
373
392
 
374
393
  // Deployment settings come next, before ANY service is built: the KB remote
@@ -486,12 +505,83 @@ export async function createCoreServices(
486
505
  if (repositorySource.mode() === 'github-app') {
487
506
  await githubApp.prepare().catch(() => undefined);
488
507
  }
508
+ /**
509
+ * Where a working copy of a replaced repository is kept, for BOTH the places
510
+ * that find one: the KB startup phase sweeping the workspaces root, and the
511
+ * workspace service refusing to adopt one on a branch open. One variable,
512
+ * passed to both, because they move the same clones for the same reason and
513
+ * an admin looking for their work must have one folder to look in — the two
514
+ * drifted apart once already, and the copy the workspace service set aside
515
+ * landed in the image's own filesystem and was gone at the next recreate.
516
+ *
517
+ * Under the backups root: a persistent volume of its own, and one nothing
518
+ * sweeps (see the runner's `reconcileClonesWithConfiguredRepository`).
519
+ */
520
+ const replacedWorkingCopiesRoot = path.join(config.backupsRoot, 'replaced-working-copies');
521
+ /**
522
+ * What must leave with a working copy that is set aside, whoever sets it
523
+ * aside: the startup phase, or the workspace service on a branch open.
524
+ *
525
+ * Its queue: whatever still waits to be committed into that copy was
526
+ * written against the repository that was left, and the path it names is
527
+ * about to hold a fresh clone of another one. Held for a person, on every
528
+ * branch, never written and never deleted.
529
+ *
530
+ * And the locks on its branch: each is a claim on a file of the copy that
531
+ * is going. Left standing, a holder still connected keeps one alive with
532
+ * heartbeats, and the same path in the new repository refuses everybody
533
+ * else. Dropped without enqueueing anything, as when change requests are
534
+ * closed as "repository replaced".
535
+ *
536
+ * Throws when either cannot be done, and the caller then leaves the copy
537
+ * where it is. Reaches FORWARD to both services, like `gitService` below:
538
+ * it is only ever called long after this function has returned them.
539
+ */
540
+ const releaseWorkOnSetAsideCopy = async (workspaceId: string): Promise<void> => {
541
+ const held = await pendingCommitsService.markNeedsAttentionInWorkspace(
542
+ workspaceId,
543
+ 'The knowledge-base repository was replaced while this commit was still queued, so it was never ' +
544
+ 'written. The bytes are kept here: the working copy it was meant for belongs to the previous repository.',
545
+ );
546
+ if (held > 0) {
547
+ logger('kb-startup').warn(
548
+ `${held} queued commit(s) for the working copy "${workspaceId}" need attention: the repository was ` +
549
+ 'replaced before they landed, so they were not written to the new one.',
550
+ );
551
+ }
552
+ await fileLockService.releaseAllOnBranch(branchForWorkspaceId(workspaceId));
553
+ };
489
554
  const workspaceService = new WorkspaceService(
490
555
  config.workspacesRoot,
491
556
  () => repositorySource.url(),
492
557
  kb,
493
558
  disk,
494
559
  gitRunner,
560
+ replacedWorkingCopiesRoot,
561
+ // A branch opened onto a clone of another repository sets it aside on
562
+ // the spot, with nobody having stopped the commit worker for it. So it
563
+ // is held here, for the one move, and the copy's work leaves first and
564
+ // is looked for again once the copy has gone: a release that queued its
565
+ // bytes in between was writing to the copy that left.
566
+ //
567
+ // Only the first look may refuse the branch: the copy has not moved yet.
568
+ // The second runs after the move, so its failure is tried once more and
569
+ // then logged, as the startup phase logs the same step. Refusing then
570
+ // would report a move that did happen as one that did not.
571
+ (workspaceId, move) =>
572
+ leased.whileHeld(async () => {
573
+ await releaseWorkOnSetAsideCopy(workspaceId);
574
+ await move();
575
+ await releaseWorkOnSetAsideCopy(workspaceId)
576
+ .catch(() => releaseWorkOnSetAsideCopy(workspaceId))
577
+ .catch((err: unknown) => {
578
+ logger('workspace').error(
579
+ `the working copy "${workspaceId}" was set aside, but what was queued or locked on it meanwhile ` +
580
+ 'could not be looked for again:',
581
+ { err },
582
+ );
583
+ });
584
+ }),
495
585
  );
496
586
  // The KB startup phase: every seeding, scaffolding and migration concern,
497
587
  // run through one runner at the deployment's quiet moments (boot + setup
@@ -529,10 +619,9 @@ export async function createCoreServices(
529
619
  // the first thing that needs them.
530
620
  kbRepoUrl: () => repositorySource.url(),
531
621
  workspacesRoot: config.workspacesRoot,
532
- // Under the backups root, a persistent volume of its own and one nothing
533
- // sweeps: a working copy of a repository that was replaced is kept there,
534
- // never deleted (see the runner's `reconcileClonesWithConfiguredRepository`).
535
- setAsideRoot: path.join(config.backupsRoot, 'replaced-working-copies'),
622
+ // The same folder the workspace service sets aside into — see
623
+ // `replacedWorkingCopiesRoot` above for why the two must agree.
624
+ setAsideRoot: replacedWorkingCopiesRoot,
536
625
  kbDirName,
537
626
  templateDir: config.kbTemplateDir,
538
627
  defaultBranch: () => kb.defaultBranch,
@@ -543,10 +632,48 @@ export async function createCoreServices(
543
632
  steps: kbStartupSteps,
544
633
  buildSeedTree: buildSeedTree(disk, config.kbTemplateDir, extraDirs, [config.adminEmail], kb),
545
634
  gitRunner,
635
+ // The work on a working copy that is about to be set aside leaves first:
636
+ // its queued commits and the locks on its branch (see
637
+ // `releaseWorkOnSetAsideCopy`). A failure here stops the phase before
638
+ // the copy moves: a replacement cloned over an untouched queue is how
639
+ // those bytes would land in the wrong repository.
640
+ beforeCloneSetAside: releaseWorkOnSetAsideCopy,
641
+ // And once it is set aside it must leave the workspace service's cache:
642
+ // on the SAVE that moves the deployment the process is already running,
643
+ // and a cached path to a directory that is gone is how the next reader
644
+ // gets an ENOENT instead of a fresh clone.
645
+ //
646
+ // Then the copy's work is looked for once more. A release that queued
647
+ // its bytes between the first look and the move was writing to the copy
648
+ // that has now gone; nothing can queue against it after this.
649
+ onCloneDiscarded: async (workspaceId) => {
650
+ workspaceService.forgetClone(workspaceId);
651
+ await releaseWorkOnSetAsideCopy(workspaceId);
652
+ },
653
+ // And the replacement it cloned in its place: a fresh clone holds every
654
+ // ref, so the git layer's per-workspace fetch record is told so. Without
655
+ // it that record still holds the FAILED fetch of the repository that was
656
+ // replaced, and a strict branch listing within its TTL refuses the new
657
+ // clone's refs as unproven. Reaches FORWARD to `gitService`, which is
658
+ // built further down: safe because the runner only ever calls this from
659
+ // `runAll`, which the server builder invokes long after this function has
660
+ // returned every service.
661
+ onCloneCreated: (workspaceId) => gitService.noteWorkspaceFetched(workspaceId),
546
662
  });
547
663
  // Shared, workspace-independent store for oversized `call_tool_chain` results,
548
664
  // read back via `read_file`. Sibling of `workspacesRoot`, never committed.
549
665
  const spillStore = new SpillStore(config.spillRoot);
666
+ // The upload route an agent lands files by, so their content never passes
667
+ // through the model. Bytes live BESIDE the workspaces root (never inside
668
+ // one) until the apply commits them or the token expires — checked here
669
+ // rather than assumed, because a root configured inside a workspace would
670
+ // put bytes no gate has judged where the file tools read.
671
+ await assertUploadsRootOutsideWorkspaces(config.agentUploadsRoot, config.workspacesRoot);
672
+ const agentUploadStore = new AgentUploadStore({
673
+ root: config.agentUploadsRoot,
674
+ publicBaseUrl: config.publicBackendUrl,
675
+ tokenPrefix: config.uploadTokenPrefix,
676
+ });
550
677
  // Office-document/PDF text extraction for `read_file`/`grep`, cached by
551
678
  // content hash beside the workspaces root (see `DocExtractionCache`).
552
679
  const docExtractService = new DocExtractService(config.docExtractCacheRoot);
@@ -574,14 +701,10 @@ export async function createCoreServices(
574
701
  // except that new folder at a root (see `access-model/change-gate.ts`).
575
702
  const changeGate = new ChangeReadGate(workspaceService, accessControl, kb, disk);
576
703
 
577
- // Ontology-session boundary: records each agent run's touched ontologies and
578
- // blocks writes once a run has crossed ontologies. Postgres-backed so the
579
- // boundary survives a restart.
580
- const sessionOntologyService = new SessionOntologyService(db, kb);
581
704
  // Per-run write restriction (by file extension). Shared by the workspace tool
582
705
  // surface (which enforces it) and the routine runner (which sets it for
583
- // dashboard-only `watchlist_check` runs). In-memory: a restriction lives only for
584
- // one run, unlike the Postgres-backed ontology touched-set above.
706
+ // dashboard-only `watchlist_check` runs). In-memory: a restriction lives only
707
+ // for one run.
585
708
  const routineWritePolicy = new RoutineWritePolicyService();
586
709
  // Skills: discovered from the default-branch workspace only (global catalog).
587
710
  const skillService = new SkillService(workspaceService, accessControl, kb, disk);
@@ -632,11 +755,11 @@ export async function createCoreServices(
632
755
  // against each other — a backup-reseed races a concurrent commit otherwise.
633
756
  const workspaceMutex = new WorkspaceMutex();
634
757
  // Workflow lifecycle hooks — the ONE registry this composition shares
635
- // between GitService (advisory commit validation), the session-ontology
636
- // gate (blocking preWrite), and the enterprise composition root, which
637
- // registers module-owned handlers on `workflowService.hooks` right after
638
- // this function returns. Core registers none: no commit-time validation
639
- // (advisory anyway) and no ontology write block.
758
+ // between GitService (advisory commit validation), the agent-access gate
759
+ // (the blocking agentRead / preWrite hooks), and the composition root,
760
+ // which registers module-owned handlers on `workflowService.hooks` right
761
+ // after this function returns. Core registers none: no commit-time
762
+ // validation (advisory anyway) and nothing that refuses an agent call.
640
763
  const workflowHooks = new WorkflowHooks();
641
764
  const gitService = new GitService(
642
765
  workspaceService,
@@ -731,7 +854,7 @@ export async function createCoreServices(
731
854
  eventBus,
732
855
  fileChangeNotifier,
733
856
  // Exposed as `workflowService.hooks` — the SAME instance GitService and
734
- // the session-ontology gate consult, so enterprise registrations against
857
+ // the agent-access gate consult, so a deployment's registrations against
735
858
  // it reach every hook point.
736
859
  workflowHooks,
737
860
  );
@@ -1066,6 +1189,9 @@ export async function createCoreServices(
1066
1189
  // For the needs-authorization setup link surfaced to external agents.
1067
1190
  publicFrontendUrl: config.publicFrontendUrl,
1068
1191
  readAgentPreamble: readPreamble,
1192
+ // The guide's name the shared file rules spell, read per request so a name
1193
+ // the setup save applies lands without a restart.
1194
+ kbLayout: () => kb.layout,
1069
1195
  secretsScope,
1070
1196
  },
1071
1197
  // Pre-dispatch per-user credential check: the vault answers "has this caller
@@ -1134,8 +1260,9 @@ export async function createCoreServices(
1134
1260
  creatorAccess,
1135
1261
  loadActiveGroups,
1136
1262
  });
1137
- const toolHandlerFactory = createToolHandlerFactory(resolveToolContext);
1138
- const toolAuthMiddleware = createToolAuthMiddleware(externalApiKeyService, internalTokenService);
1263
+ const writeAccess = ports.writeAccess ?? alwaysWritable;
1264
+ const toolHandlerFactory = createToolHandlerFactory(resolveToolContext, writeAccess);
1265
+ const toolAuthMiddleware = createToolAuthMiddleware(externalApiKeyService, internalTokenService, authService);
1139
1266
  // Read-only manual endpoints accept the above PLUS a browser JWT, so a
1140
1267
  // logged-in user can browse the catalog with their session. Execution routes
1141
1268
  // keep `toolAuthMiddleware` (no JWT), so a session can read but not invoke.
@@ -1187,7 +1314,10 @@ export async function createCoreServices(
1187
1314
  name: recoveryBot.name,
1188
1315
  },
1189
1316
  });
1190
- const leased = withStartupTask(pendingCommitsWorker, reconcileQueue);
1317
+ // Holdable: a move to another repository stops the worker for as long as
1318
+ // working copies are being set aside and cloned again, without giving up
1319
+ // the lease (see `holdable` in `core/lifecycle.ts`).
1320
+ const leased = holdable(withStartupTask(pendingCommitsWorker, reconcileQueue));
1191
1321
 
1192
1322
  // SSO providers. The array REFERENCE is shared with the caller's port — an
1193
1323
  // overlay pushes its own plugins into it after construction (they mount when
@@ -1283,6 +1413,7 @@ export async function createCoreServices(
1283
1413
  db,
1284
1414
  gitRunner,
1285
1415
  commitWorker,
1416
+ whileCommitsHeld: (work) => leased.whileHeld(work),
1286
1417
  startupRetry: null,
1287
1418
  tenantKey,
1288
1419
  secretsScope,
@@ -1298,11 +1429,11 @@ export async function createCoreServices(
1298
1429
  kb,
1299
1430
  kbDirName,
1300
1431
  spillStore,
1432
+ agentUploadStore,
1301
1433
  docExtractService,
1302
1434
  accessControl,
1303
1435
  creatorAccess,
1304
1436
  changeGate,
1305
- sessionOntologyService,
1306
1437
  routineWritePolicy,
1307
1438
  skillService,
1308
1439
  pendingSkillsService,
@@ -1353,6 +1484,7 @@ export async function createCoreServices(
1353
1484
  toolAuthMiddleware,
1354
1485
  manualAuthMiddleware,
1355
1486
  toolHandlerFactory,
1487
+ writeAccess,
1356
1488
  // Server-time seams — defaults here; the enterprise overlay overwrites
1357
1489
  // (or, for the array, pushes into) these after construction.
1358
1490
  sessionSink: ports.sessionSink ?? new UuidSessionSink(),