@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,154 @@
1
+ import { inflateRawSync } from 'node:zlib';
2
+ import { validateFilename } from '@bevel-software/platform-shared';
3
+ import { GitInternalsError } from '../../shared/domain-errors.js';
4
+ import { hasGitInternalsSegment } from '../../shared/git-internals.js';
5
+ /**
6
+ * What an archive's entry NAMES are allowed to be, as one set of rules two
7
+ * surfaces ask: `unzip` (a .zip already in the workspace) and
8
+ * `apply_file_upload` (a .zip the agent sent to the upload route). Both land
9
+ * bytes an agent never typed at paths the archive chose, so both have to judge
10
+ * the same names the same way — and they did not, for as long as the rules
11
+ * lived inside `WorkspaceService.unzipFile` as three inline blocks.
12
+ *
13
+ * NAMES, and how an entry's BYTES are read ({@link readZipEntry}) — the two
14
+ * things decidable from the archive alone. Whether a target sits behind a
15
+ * symbolic link already on disk, whether the caller may write there, and what
16
+ * is already at the path are facts about a workspace, so each surface asks
17
+ * those where its own writes land.
18
+ */
19
+ /**
20
+ * The entries an archive from macOS carries that nobody asked for: the
21
+ * resource-fork sidecar tree and the Finder's own index. SILENTLY dropped
22
+ * rather than reported — they are not the caller's content and a list of
23
+ * refusals about them says nothing.
24
+ */
25
+ export function isZipNoiseEntry(rawName) {
26
+ return (rawName.startsWith('__MACOSX/') ||
27
+ rawName === '__MACOSX' ||
28
+ rawName.endsWith('/.DS_Store') ||
29
+ rawName === '.DS_Store' ||
30
+ /(^|\/)\._/.test(rawName));
31
+ }
32
+ /**
33
+ * An entry's name with separators in the one spelling the rules read. A zip
34
+ * written on Windows may use `\`, which every check below (and every path
35
+ * built from the result) would otherwise read as part of a single segment.
36
+ */
37
+ export function zipEntryName(entryName) {
38
+ return entryName.replace(/\\/g, '/');
39
+ }
40
+ /** The path segments `rawName` names, with a trailing slash and empty parts dropped. */
41
+ export function zipEntrySegments(rawName) {
42
+ return rawName
43
+ .replace(/\/+$/, '')
44
+ .split('/')
45
+ .filter((s) => s.length > 0);
46
+ }
47
+ /**
48
+ * Why `rawName` may not be landed at all — the reason a caller reports beside
49
+ * the entry — or null when the name is one a workspace path can be built from.
50
+ *
51
+ * Three rules, in the order that makes each refusal say the most useful thing:
52
+ * a path that climbs out or is anchored at the root is invalid whatever its
53
+ * segments are; the git folder is refused in every spelling, because an
54
+ * archive must not be a way to write what git reads as its own metadata; and
55
+ * then each segment has to be a name a filesystem on any of the three
56
+ * operating systems keeps intact (`validateFilename`).
57
+ */
58
+ export function zipEntryNameRefusal(rawName) {
59
+ if (!rawName || rawName.startsWith('/') || /(^|\/)\.\.($|\/)/.test(rawName))
60
+ return 'Invalid path';
61
+ if (hasGitInternalsSegment(rawName))
62
+ return new GitInternalsError().message;
63
+ for (const segment of zipEntrySegments(rawName)) {
64
+ const reason = validateFilename(segment);
65
+ if (reason)
66
+ return reason;
67
+ }
68
+ return null;
69
+ }
70
+ /**
71
+ * Whether a zip entry is a symbolic LINK rather than a file or a folder.
72
+ *
73
+ * A zip stores a link as an ordinary member whose bytes are the link's target
74
+ * text and whose unix mode (the high half of the external attributes) carries
75
+ * `S_IFLNK`. A reader that ignores the mode writes the target text out as a
76
+ * regular file — content nobody sent, under a name that was meant to point
77
+ * somewhere. `apply_file_upload` refuses such an entry outright; the entry is
78
+ * not a file, so there are no bytes of the caller's to land.
79
+ */
80
+ export function isSymlinkZipEntry(entry) {
81
+ const attr = entry.header?.attr ?? entry.attr ?? 0;
82
+ if (!Number.isFinite(attr) || attr <= 0)
83
+ return false;
84
+ // The external attributes' high 16 bits are the unix mode when the archive
85
+ // was written on a unix host; `S_IFMT & mode === S_IFLNK` is the link bit.
86
+ return ((attr >>> 16) & 0o170000) === 0o120000;
87
+ }
88
+ /** A zip's compression method for DEFLATE, the only one that can expand. */
89
+ const ZIP_METHOD_DEFLATED = 8;
90
+ /**
91
+ * Read one entry's bytes, inflating NO MORE than `budget` of them.
92
+ *
93
+ * The one read both surfaces use, because the bound is the whole point and it
94
+ * has a hole when each surface writes it itself. An entry's header DECLARES its
95
+ * uncompressed size, and the reader caps the inflation at that — but only when
96
+ * the size it declares is above zero. An entry that declares zero is inflated
97
+ * with no cap at all, so "check the declared size against the budget, then
98
+ * read" passes a 20 KB archive that expands to 20 MB, and a 50 MB one that
99
+ * expands to tens of gigabytes in a single call, before any check on the bytes
100
+ * that arrived has run. A thousandfold is what deflate does to a run of one
101
+ * byte; the header saying "empty" was the only thing standing in front of it.
102
+ *
103
+ * So the declared size is never the only bound:
104
+ *
105
+ * - an entry declaring more than `budget` is refused unread;
106
+ * - a DEFLATED entry declaring ZERO with a stream of its own is inflated here,
107
+ * capped at a single byte. An empty file compressed with deflate is exactly
108
+ * this shape (a two-byte stream that inflates to nothing) and is read as the
109
+ * empty file it is; anything that inflates to a byte or more contradicts its
110
+ * own header and is refused;
111
+ * - every other entry is read by the archive reader, which caps the inflation
112
+ * at the declared size this function has just held against the budget, and
113
+ * the bytes that arrive are measured again.
114
+ *
115
+ * A read that throws — a failed checksum, a stream longer than it declared, an
116
+ * unknown method — is that entry's refusal, not the whole archive's: one bad
117
+ * member must not cost the caller the others.
118
+ */
119
+ export function readZipEntry(entry, budget) {
120
+ const { size: declared, compressedSize, method } = entry.header;
121
+ if (declared > budget)
122
+ return { ok: false, reason: 'too_large' };
123
+ if (declared === 0 && method === ZIP_METHOD_DEFLATED && compressedSize > 0) {
124
+ let inflated;
125
+ try {
126
+ inflated = inflateRawSync(entry.getCompressedData(), { maxOutputLength: 1 });
127
+ }
128
+ catch (err) {
129
+ // Past the one-byte cap, or not a deflate stream at all: either way it
130
+ // is not the empty file its header says it is.
131
+ return { ok: false, reason: 'unreadable', detail: declaresEmptyButIsNot(err) };
132
+ }
133
+ if (inflated.byteLength > 0)
134
+ return { ok: false, reason: 'unreadable', detail: declaresEmptyButIsNot() };
135
+ return { ok: true, data: inflated };
136
+ }
137
+ let data;
138
+ try {
139
+ data = entry.getData();
140
+ }
141
+ catch (err) {
142
+ return { ok: false, reason: 'unreadable', detail: err instanceof Error ? err.message : String(err) };
143
+ }
144
+ if (data.byteLength > budget)
145
+ return { ok: false, reason: 'too_large' };
146
+ return { ok: true, data };
147
+ }
148
+ function declaresEmptyButIsNot(err) {
149
+ const outputLimit = err?.code === 'ERR_BUFFER_TOO_LARGE';
150
+ return err === undefined || outputLimit
151
+ ? 'its header declares an empty file, but it holds content'
152
+ : `its header declares an empty file, and its content could not be read (${err instanceof Error ? err.message : String(err)})`;
153
+ }
154
+ //# sourceMappingURL=zip-entry-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zip-entry-rules.js","sourceRoot":"","sources":["../../../src/modules/workspace/zip-entry-rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import express, { type RequestHandler } from 'express';
2
+ /**
3
+ * Whether the deployment may be CHANGED right now. The seam a host that
4
+ * sells seats needs for a workspace that has more people on it than its plan
5
+ * allows: nobody is removed and everyone can still sign in and read, but
6
+ * nothing can be edited until an admin brings the count down or the plan up
7
+ * — the way Notion handles a lapsed workspace.
8
+ *
9
+ * Core is always writable. The port is asked on every request that would
10
+ * change something (a mutating HTTP route, a write tool), so a host answers
11
+ * from something cheap — its own cached view of the plan.
12
+ */
13
+ export type WriteAccessVerdict = {
14
+ ok: true;
15
+ } | {
16
+ ok: false;
17
+ message: string;
18
+ };
19
+ export interface IWriteAccess {
20
+ canWrite(): Promise<WriteAccessVerdict>;
21
+ /**
22
+ * Paths of the host's own routes that must stay usable while the
23
+ * deployment is read-only — above all, the ones that END it (buying
24
+ * seats, the billing portal). Matched as prefixes of the request path.
25
+ */
26
+ readonly alwaysWritablePaths?: readonly string[];
27
+ }
28
+ /** Core's default: always writable. */
29
+ export declare const alwaysWritable: IWriteAccess;
30
+ /** The `code` a refused write answers with, so a client can tell it from a permission error. */
31
+ export declare const READ_ONLY_CODE = "workspace_read_only";
32
+ /**
33
+ * Whether a request may go ahead while the deployment is read-only, without asking.
34
+ *
35
+ * Judged on the path in LOWER CASE, because that is how the router reads
36
+ * it: routes match whatever the letter case, so `/API/workspace/…` reaches
37
+ * the same handler as `/api/workspace/…`. Judged as written, it would be
38
+ * outside the gated namespace and walk past the gate.
39
+ */
40
+ export declare function isAlwaysWritable(method: string, path: string, hostPaths?: readonly string[]): boolean;
41
+ /**
42
+ * Refuse every change to the deployment while the port says it is
43
+ * read-only, with a 403 the app recognises by {@link READ_ONLY_CODE}.
44
+ * Mounted ahead of every route under `/api`; a request the port is not
45
+ * asked about costs nothing. A port that fails is treated as writable —
46
+ * a billing lookup that is down must not freeze every workspace.
47
+ */
48
+ export declare function createWriteGateMiddleware(writeAccess: IWriteAccess): RequestHandler;
49
+ /**
50
+ * The verdict for a write tool, the tool layer's half of the gate: `null`
51
+ * when it may run, the refusal otherwise. Same failure rule as the HTTP gate.
52
+ */
53
+ export declare function refuseWriteTool(writeAccess: IWriteAccess): Promise<string | null>;
54
+ /**
55
+ * GET /api/write-access — whether the deployment can be changed right now,
56
+ * and if not, the host's words for why. The app reads it to say so once, in
57
+ * a banner, instead of letting every save fail on its own.
58
+ */
59
+ export declare function createWriteAccessRoutes(writeAccess: IWriteAccess): express.Router;
60
+ //# sourceMappingURL=write-access.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-access.d.ts","sourceRoot":"","sources":["../../../src/modules/write-access/write-access.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAE,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAKvD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/E,MAAM,WAAW,YAAY;IAC3B,QAAQ,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClD;AAED,uCAAuC;AACvC,eAAO,MAAM,cAAc,EAAE,YAE5B,CAAC;AAEF,gGAAgG;AAChG,eAAO,MAAM,cAAc,wBAAwB,CAAC;AA8CpD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,SAAS,MAAM,EAAO,GAAG,OAAO,CAQzG;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,YAAY,GAAG,cAAc,CAcnF;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,WAAW,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CASvF;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAWjF"}
@@ -0,0 +1,129 @@
1
+ import express from 'express';
2
+ import { logger } from '../../shared/logging.js';
3
+ const log = logger('write-access');
4
+ /** Core's default: always writable. */
5
+ export const alwaysWritable = {
6
+ canWrite: async () => ({ ok: true }),
7
+ };
8
+ /** The `code` a refused write answers with, so a client can tell it from a permission error. */
9
+ export const READ_ONLY_CODE = 'workspace_read_only';
10
+ const MUTATING_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
11
+ /**
12
+ * The mutating routes that stay open while the deployment is read-only.
13
+ * EVERY OTHER mutating route under `/api` is refused, so a route added
14
+ * later is read-only until someone decides otherwise — the safe default for
15
+ * a list nobody re-reads when adding a route.
16
+ *
17
+ * What stays open, and why:
18
+ * - Signing in and out of everything: sessions, agent connections and
19
+ * their keys. A read-only workspace is still one people read.
20
+ * - Account administration: the way an admin brings the number of people
21
+ * down. Erasing an account with `removeFromAccess` commits to the
22
+ * knowledge base, and that commit is part of the same act.
23
+ * - The tool surface: every tool call is judged by the tool layer itself,
24
+ * which knows a read tool from a write tool (see `toolHandler`); the MCP
25
+ * endpoint only relays to it.
26
+ * - Deployment setup, tool secrets and pulling remote changes in: the
27
+ * deployment's configuration, not its content, and what an admin needs
28
+ * to keep it working.
29
+ * - Requests that are POST only because they carry a body: permission
30
+ * queries, previews, probes, focus and heartbeat signals, fetching
31
+ * remotes, accepting a review baseline. None of them changes content.
32
+ */
33
+ const ALWAYS_WRITABLE = [
34
+ /^\/api\/auth\//,
35
+ /^\/api\/mcp(\/|$)/,
36
+ /^\/api\/agent\//,
37
+ /^\/api\/admin\/accounts(\/|$)/,
38
+ /^\/api\/admin\/connection-keys\//,
39
+ /^\/api\/audit\//,
40
+ /^\/api\/sync(\/|$)/,
41
+ /^\/api\/setup\//,
42
+ /^\/api\/secrets\//,
43
+ /^\/api\/admin\/github-facade\//,
44
+ /^\/api\/workspace\/[^/]+\/access\/batch$/,
45
+ /^\/api\/workspace\/[^/]+\/review\/accept$/,
46
+ /^\/api\/workspace\/[^/]+\/flush$/,
47
+ /^\/api\/workspace\/[^/]+\/workflow\/refresh-remotes$/,
48
+ /^\/api\/workspace\/[^/]+\/workflow\/locks\/heartbeat$/,
49
+ /^\/api\/events\/[^/]+\/focus$/,
50
+ /^\/api\/tools\/preview$/,
51
+ ];
52
+ /**
53
+ * Whether a request may go ahead while the deployment is read-only, without asking.
54
+ *
55
+ * Judged on the path in LOWER CASE, because that is how the router reads
56
+ * it: routes match whatever the letter case, so `/API/workspace/…` reaches
57
+ * the same handler as `/api/workspace/…`. Judged as written, it would be
58
+ * outside the gated namespace and walk past the gate.
59
+ */
60
+ export function isAlwaysWritable(method, path, hostPaths = []) {
61
+ if (!MUTATING_METHODS.has(method.toUpperCase()))
62
+ return true;
63
+ const asRouted = path.toLowerCase();
64
+ if (asRouted !== '/api' && !asRouted.startsWith('/api/'))
65
+ return true;
66
+ return (ALWAYS_WRITABLE.some((rule) => rule.test(asRouted)) ||
67
+ hostPaths.some((prefix) => asRouted.startsWith(prefix.toLowerCase())));
68
+ }
69
+ /**
70
+ * Refuse every change to the deployment while the port says it is
71
+ * read-only, with a 403 the app recognises by {@link READ_ONLY_CODE}.
72
+ * Mounted ahead of every route under `/api`; a request the port is not
73
+ * asked about costs nothing. A port that fails is treated as writable —
74
+ * a billing lookup that is down must not freeze every workspace.
75
+ */
76
+ export function createWriteGateMiddleware(writeAccess) {
77
+ if (writeAccess === alwaysWritable)
78
+ return (_req, _res, next) => next();
79
+ return async (req, res, next) => {
80
+ if (isAlwaysWritable(req.method, req.path, writeAccess.alwaysWritablePaths))
81
+ return next();
82
+ let verdict;
83
+ try {
84
+ verdict = await writeAccess.canWrite();
85
+ }
86
+ catch (err) {
87
+ log.warn('the write-access port failed; letting the request through:', { err });
88
+ return next();
89
+ }
90
+ if (verdict.ok)
91
+ return next();
92
+ res.status(403).json({ error: verdict.message, code: READ_ONLY_CODE });
93
+ };
94
+ }
95
+ /**
96
+ * The verdict for a write tool, the tool layer's half of the gate: `null`
97
+ * when it may run, the refusal otherwise. Same failure rule as the HTTP gate.
98
+ */
99
+ export async function refuseWriteTool(writeAccess) {
100
+ if (writeAccess === alwaysWritable)
101
+ return null;
102
+ try {
103
+ const verdict = await writeAccess.canWrite();
104
+ return verdict.ok ? null : verdict.message;
105
+ }
106
+ catch (err) {
107
+ log.warn('the write-access port failed; letting the tool run:', { err });
108
+ return null;
109
+ }
110
+ }
111
+ /**
112
+ * GET /api/write-access — whether the deployment can be changed right now,
113
+ * and if not, the host's words for why. The app reads it to say so once, in
114
+ * a banner, instead of letting every save fail on its own.
115
+ */
116
+ export function createWriteAccessRoutes(writeAccess) {
117
+ const router = express.Router();
118
+ router.get('/write-access', async (_req, res) => {
119
+ try {
120
+ const verdict = await writeAccess.canWrite();
121
+ res.json(verdict.ok ? { writable: true } : { writable: false, message: verdict.message });
122
+ }
123
+ catch {
124
+ res.json({ writable: true });
125
+ }
126
+ });
127
+ return router;
128
+ }
129
+ //# sourceMappingURL=write-access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-access.js","sourceRoot":"","sources":["../../../src/modules/write-access/write-access.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Application-layer encryption for PII columns. Personal data (emails, names,
3
+ * change-request text) is AES-256-GCM ciphertext in Postgres, so a leaked
4
+ * dump, an injected query, or a compromised DB credential yields no personal
5
+ * data — the key lives only in the app's environment. Disk-level encryption
6
+ * still carries the blanket at-rest claim (git refs, WAL, logs); this layer is
7
+ * the DB-specific control on top.
8
+ *
9
+ * THE KEY BELONGS TO THE DATABASE HANDLE, not to the process. A process that
10
+ * serves several knowledge bases holds one handle per tenant, each built with
11
+ * that tenant's own `SECRETS_ENC_KEY` (`createDb(url, { piiKey })`), so a
12
+ * tenant's rows are sealed under the same key as its stored credentials and
13
+ * its dump opens, whole, with that one key.
14
+ *
15
+ * A drizzle column type is a module-level object and is told nothing about
16
+ * the handle a statement runs on, so it cannot hold a key. It only MARKS a
17
+ * value ({@link SealOnWrite}, {@link IndexOnWrite}); the handle's own
18
+ * connection replaces the mark with ciphertext or a blind index on the way
19
+ * in, and opens every sealed value on the way out (see `connection.ts`). A
20
+ * mark that reaches a connection with no key refuses to be written.
21
+ *
22
+ * Both keys are HKDF-derived from the handle's key with distinct info
23
+ * strings, so PII ciphertext and blind indexes are domain-separated from the
24
+ * secrets-vault key without asking operators to provision a second variable.
25
+ */
26
+ /** What a database handle seals, opens and indexes personal data with. */
27
+ export interface PiiKeys {
28
+ /**
29
+ * Encrypt a value for storage (fresh random IV — NOT equality-comparable).
30
+ *
31
+ * The empty string is stored as itself: it carries no personal data, GCM of
32
+ * an empty plaintext would produce an empty ciphertext segment the blob
33
+ * parser cannot represent, and columns with a DB-level `DEFAULT ''` then
34
+ * hold exactly the same representation as an app-written empty value.
35
+ */
36
+ seal(plain: string): string;
37
+ /**
38
+ * Open a stored value, and SAY whether it opened: a value that is not a
39
+ * blob is its own plaintext; a blob this key does not open is `ok: false`.
40
+ * For the callers that must tell "the key does not open this" from a value
41
+ * — the shape of what {@link read} returns proves nothing, since a
42
+ * plaintext may itself be shaped like a blob.
43
+ */
44
+ open(value: string): {
45
+ ok: true;
46
+ plain: string;
47
+ } | {
48
+ ok: false;
49
+ };
50
+ /**
51
+ * The lenient read every query result goes through: a blob this key does
52
+ * not open is handed back as it is, as is anything that is not a blob —
53
+ * rows written before the encryption release stay readable until the
54
+ * backfill at start rewrites them.
55
+ */
56
+ read(value: string): string;
57
+ /**
58
+ * Blind index for equality on an encrypted column: HMAC-SHA256 of the
59
+ * trimmed, lower-cased value, hex-encoded. Deterministic, so a `*_bidx`
60
+ * column can carry the unique constraints and lookups that randomized
61
+ * ciphertext cannot. Reveals only equality, never content.
62
+ */
63
+ index(value: string): string;
64
+ }
65
+ /** Derive the personal-data keys from a deployment's (or a tenant's) `SECRETS_ENC_KEY`. */
66
+ export declare function derivePiiKeys(secretsEncKey: string): PiiKeys;
67
+ /**
68
+ * Every PII ciphertext starts with this marker. An explicit prefix — rather
69
+ * than recognising ciphertext by its `iv:tag:ct` shape — means legacy
70
+ * plaintext can never be mistaken for ciphertext (and silently skipped by the
71
+ * backfill), and lets the backfill find unsealed rows with a plain SQL
72
+ * predicate instead of scanning every row in the app. Bump the version
73
+ * segment if the format ever changes.
74
+ */
75
+ export declare const PII_CIPHERTEXT_PREFIX = "pii:v1:";
76
+ /**
77
+ * The shape of a PII ciphertext blob, as ONE pattern both Postgres and this
78
+ * process read: the version prefix followed by base64 segments of the exact
79
+ * widths GCM produces (12-byte IV → 16 chars, 16-byte tag → 22 chars + `==`).
80
+ * Lets the backfill find unsealed rows with a `!~` predicate instead of
81
+ * scanning every row in the app.
82
+ *
83
+ * {@link isEncryptedBlob} is built FROM it rather than written beside it. The
84
+ * two used to be separate spellings of one idea, and they disagreed: the
85
+ * JavaScript one decoded each part and so took an unpadded tag for a 16-byte
86
+ * one, which this pattern does not. A value the two disagreed about was
87
+ * selected by the backfill's SQL as unsealed and then skipped by its
88
+ * JavaScript as sealed, on every start, and stayed in clear for good. The
89
+ * pattern uses nothing POSIX and JavaScript read differently.
90
+ */
91
+ export declare const PII_SEALED_SHAPE_SQL_REGEX = "^pii:v1:[A-Za-z0-9+/]{16}:[A-Za-z0-9+/]{22}==:[A-Za-z0-9+/]+={0,2}$";
92
+ /**
93
+ * Whether `value` has the shape of a PII ciphertext blob — exactly the values
94
+ * {@link PII_SEALED_SHAPE_SQL_REGEX} matches in the database, no more and no
95
+ * fewer. Shape only: whether the configured key OPENS it is `PiiKeys.open`'s
96
+ * answer, and a plaintext somebody typed in this shape passes here.
97
+ */
98
+ export declare function isEncryptedBlob(value: string): boolean;
99
+ /**
100
+ * A value on its way to the database that the handle's connection still has
101
+ * to turn into what is stored. It is never a storable value itself: `pg`
102
+ * asks an object how to serialise itself through `toPostgres`, and this one
103
+ * refuses, so a statement that reaches a connection which did not replace it
104
+ * — a pool built without `createDb`, or a handle built without a key — fails
105
+ * instead of writing the plaintext.
106
+ */
107
+ export declare abstract class PiiParam {
108
+ readonly plain: string;
109
+ constructor(plain: string);
110
+ /** What the handle's keys make of it. */
111
+ abstract stored(keys: PiiKeys): string;
112
+ toPostgres(): never;
113
+ }
114
+ /** Plaintext of an {@link encryptedText} column: stored as ciphertext. */
115
+ export declare class SealOnWrite extends PiiParam {
116
+ stored(keys: PiiKeys): string;
117
+ }
118
+ /** The address a {@link blindIndexText} column is written or compared with: stored as its blind index. */
119
+ export declare class IndexOnWrite extends PiiParam {
120
+ stored(keys: PiiKeys): string;
121
+ }
122
+ /**
123
+ * Drizzle column type for encrypted PII text: services read and write
124
+ * plaintext, the database only ever sees ciphertext. NEVER use an
125
+ * `encryptedText` column in a WHERE clause or conflict target — the fresh IV
126
+ * per write means `eq(column, plaintext)` silently matches nothing. Equality
127
+ * goes through the column's `*_bidx` companion ({@link blindIndexText}).
128
+ */
129
+ export declare const encryptedText: {
130
+ (): import("drizzle-orm/pg-core").PgCustomColumnBuilder<{
131
+ name: "";
132
+ dataType: "custom";
133
+ columnType: "PgCustomColumn";
134
+ data: string;
135
+ driverParam: string;
136
+ enumValues: undefined;
137
+ }>;
138
+ <TConfig extends Record<string, any>>(fieldConfig?: TConfig | undefined): import("drizzle-orm/pg-core").PgCustomColumnBuilder<{
139
+ name: "";
140
+ dataType: "custom";
141
+ columnType: "PgCustomColumn";
142
+ data: string;
143
+ driverParam: string;
144
+ enumValues: undefined;
145
+ }>;
146
+ <TName extends string>(dbName: TName, fieldConfig?: unknown): import("drizzle-orm/pg-core").PgCustomColumnBuilder<{
147
+ name: TName;
148
+ dataType: "custom";
149
+ columnType: "PgCustomColumn";
150
+ data: string;
151
+ driverParam: string;
152
+ enumValues: undefined;
153
+ }>;
154
+ };
155
+ /**
156
+ * Drizzle column type for the blind index beside an encrypted column. WRITE
157
+ * AND COMPARE IT WITH THE VALUE ITSELF — `emailBidx: email`,
158
+ * `eq(users.emailBidx, email)`, `inArray(users.emailBidx, emails)` — and the
159
+ * handle's connection stores or binds its index. Reading the column gives the
160
+ * stored index, which is good for nothing but SQL: writing it back would
161
+ * index the index, so that is refused. A row copied from another carries the
162
+ * value its index was made from, not the index.
163
+ *
164
+ * Only through drizzle's operators, which bind through the column: a value
165
+ * interpolated into a raw `sql` template is compared as it is and matches
166
+ * nothing.
167
+ */
168
+ export declare const blindIndexText: {
169
+ (): import("drizzle-orm/pg-core").PgCustomColumnBuilder<{
170
+ name: "";
171
+ dataType: "custom";
172
+ columnType: "PgCustomColumn";
173
+ data: string;
174
+ driverParam: string;
175
+ enumValues: undefined;
176
+ }>;
177
+ <TConfig extends Record<string, any>>(fieldConfig?: TConfig | undefined): import("drizzle-orm/pg-core").PgCustomColumnBuilder<{
178
+ name: "";
179
+ dataType: "custom";
180
+ columnType: "PgCustomColumn";
181
+ data: string;
182
+ driverParam: string;
183
+ enumValues: undefined;
184
+ }>;
185
+ <TName extends string>(dbName: TName, fieldConfig?: unknown): import("drizzle-orm/pg-core").PgCustomColumnBuilder<{
186
+ name: TName;
187
+ dataType: "custom";
188
+ columnType: "PgCustomColumn";
189
+ data: string;
190
+ driverParam: string;
191
+ enumValues: undefined;
192
+ }>;
193
+ };
194
+ //# sourceMappingURL=column-crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"column-crypto.d.ts","sourceRoot":"","sources":["../../src/shared/column-crypto.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,0EAA0E;AAC1E,MAAM,WAAW,OAAO;IACtB;;;;;;;OAOG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B;;;;;;OAMG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAA;KAAE,CAAC;IACjE;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CAC9B;AAED,2FAA2F;AAC3F,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAsB5D;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,YAAY,CAAC;AAE/C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,0BAA0B,wEAAyF,CAAC;AAIjI;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;;;;GAOG;AACH,8BAAsB,QAAQ;IAChB,QAAQ,CAAC,KAAK,EAAE,MAAM;gBAAb,KAAK,EAAE,MAAM;IAElC,yCAAyC;IACzC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM;IAEtC,UAAU,IAAI,KAAK;CAMpB;AAED,0EAA0E;AAC1E,qBAAa,WAAY,SAAQ,QAAQ;IACvC,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM;CAG9B;AAED,0GAA0G;AAC1G,qBAAa,YAAa,SAAQ,QAAQ;IACxC,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM;CAG9B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;CAaxB,CAAC;AAIH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;CAezB,CAAC"}