@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
@@ -2,7 +2,7 @@ import { createHash, randomBytes } from 'node:crypto';
2
2
  import { logger } from '../../shared/logging.js';
3
3
 
4
4
  const log = logger('external-api-key');
5
- import { and, asc, desc, eq, isNotNull, isNull, type SQL } from 'drizzle-orm';
5
+ import { and, desc, eq, isNotNull, isNull, type SQL } from 'drizzle-orm';
6
6
  import type { AuthUser } from '@bevel-software/platform-shared';
7
7
  import type { Database } from '../database/connection.js';
8
8
  import { externalApiKeys, users } from '../database/schema.js';
@@ -108,7 +108,9 @@ export class ExternalApiKeyService implements IExternalApiKeyService {
108
108
  // Join users so a single round-trip resolves both "token is valid" and
109
109
  // "load the user it belongs to". The unique index on token_hash makes
110
110
  // this a point-lookup. `isNull(revokedAt)` is what enforces revocation
111
- // — the row remains for audit.
111
+ // — the row remains for audit — and `isNull(deactivatedAt)` is what
112
+ // stops a switched-off account's keys without revoking them, so
113
+ // switching it back on restores them.
112
114
  const [row] = await this.db
113
115
  .select({
114
116
  tokenId: externalApiKeys.id,
@@ -119,7 +121,7 @@ export class ExternalApiKeyService implements IExternalApiKeyService {
119
121
  })
120
122
  .from(externalApiKeys)
121
123
  .innerJoin(users, eq(externalApiKeys.userId, users.id))
122
- .where(and(eq(externalApiKeys.tokenHash, tokenHash), isNull(externalApiKeys.revokedAt)))
124
+ .where(and(eq(externalApiKeys.tokenHash, tokenHash), isNull(externalApiKeys.revokedAt), isNull(users.deactivatedAt)))
123
125
  .limit(1);
124
126
 
125
127
  if (!row) return null;
@@ -167,6 +169,11 @@ export class ExternalApiKeyService implements IExternalApiKeyService {
167
169
  // Owner joined in so the admin overview is one round-trip; ordered by
168
170
  // owner email so per-account grouping is a linear pass, newest key
169
171
  // first within an account (same order the owner sees on their own page).
172
+ // The email half of that order is applied in-process: the column is
173
+ // ciphertext in the database, where ORDER BY would sort by IV noise. The
174
+ // sort is stable, so the database's newest-first survives within an owner.
175
+ // By code unit, not by locale: a collation can call two different
176
+ // addresses equal, and equal here means "the same owner's group".
170
177
  const rows = await this.db
171
178
  .select({
172
179
  key: externalApiKeys,
@@ -176,7 +183,8 @@ export class ExternalApiKeyService implements IExternalApiKeyService {
176
183
  })
177
184
  .from(externalApiKeys)
178
185
  .innerJoin(users, eq(externalApiKeys.userId, users.id))
179
- .orderBy(asc(users.email), desc(externalApiKeys.createdAt));
186
+ .orderBy(desc(externalApiKeys.createdAt));
187
+ rows.sort((a, b) => (a.email < b.email ? -1 : a.email > b.email ? 1 : 0));
180
188
  return rows.map((row) => ({
181
189
  ...toSummary(row.key),
182
190
  user: { id: row.userId, email: row.email, name: row.name },
@@ -12,9 +12,9 @@ export interface InternalTokenClaim {
12
12
  /**
13
13
  * The agent run / conversation id (the Mastra thread id) this token is scoped
14
14
  * to, when minted per-run. Surfaced as `ToolContext.sessionId` so the
15
- * ontology-session boundary can scope to one run for the in-process agent
16
- * (the external/MCP path carries `sessionId` on the tool body instead).
17
- * Absent for identity-only tokens.
15
+ * agent-access hooks can tell the in-process agent's run apart (the
16
+ * external/MCP path carries `sessionId` on the tool body instead). Absent
17
+ * for identity-only tokens.
18
18
  */
19
19
  sessionId?: string;
20
20
  /**
@@ -4,6 +4,7 @@ import { logger } from '../../shared/logging.js';
4
4
  const log = logger('tools');
5
5
  import type { IExternalApiKeyService } from './external-api-key.interface.js';
6
6
  import type { AuthService } from '../auth/auth.service.js';
7
+ import { ACCOUNT_DEACTIVATED_MESSAGE, AuthBackendError } from '../auth/account-admission.js';
7
8
  import { InternalTokenService } from './internal-token.service.js';
8
9
  import { INVALID_CONNECTION_KEY_CHALLENGE, INVALID_CONNECTION_KEY_MESSAGE } from './connection-key-rejection.js';
9
10
 
@@ -79,10 +80,10 @@ export const requireInternalSource: RequestHandler = (req, res, next) => {
79
80
 
80
81
  /**
81
82
  * Mirror of `requireInternalSource` for surfaces that must admit ONLY external
82
- * callers. `start_session` mints a fresh chat thread as the caller's
83
- * ontology-session id; an internal (in-process agent) token already carries
84
- * its run's `sessionId`, so letting it mint a new thread mid-run would
85
- * silently reset the one-ontology-per-conversation boundary.
83
+ * callers. `start_session` mints a fresh chat thread as the caller's session
84
+ * id; an internal (in-process agent) token already carries its run's
85
+ * `sessionId`, so letting it mint a new thread mid-run would silently split
86
+ * one conversation in two.
86
87
  *
87
88
  * "External" is decided by the VERIFIER, not by which credential shape
88
89
  * authenticated: a `bevel_…` connection key resolves external, and so does an
@@ -122,6 +123,15 @@ export type VerifyResult =
122
123
  export function createTokenVerifier(
123
124
  externalApiKeyService: IExternalApiKeyService,
124
125
  internalTokenService: InternalTokenService,
126
+ /**
127
+ * Whether the account an internal token was minted for is still on. An
128
+ * internal token carries only a user id and lives up to an hour (longer
129
+ * for the MCP loopback), so without this a switched-off account's running
130
+ * agent would keep working until it expired. Connection keys need no
131
+ * second look: their lookup reads the account. Every deployment passes
132
+ * it; absent, internal tokens are judged on their signature alone.
133
+ */
134
+ accounts?: Pick<AuthService, 'isActive'>,
125
135
  ): (token: string | undefined) => Promise<VerifyResult> {
126
136
  const missing = { ok: false as const, status: 401, message: 'A connection key or internal token is required' };
127
137
  return async (token) => {
@@ -136,6 +146,9 @@ export function createTokenVerifier(
136
146
  if (internalTokenService.looksLikeInternalToken(token)) {
137
147
  const claim = internalTokenService.verify(token);
138
148
  if (!claim) return { ok: false, status: 401, message: 'Invalid or expired internal token' };
149
+ if (accounts && !(await accounts.isActive(claim.userId))) {
150
+ return { ok: false, status: 401, message: ACCOUNT_DEACTIVATED_MESSAGE };
151
+ }
139
152
  return claim.externalProxy
140
153
  ? {
141
154
  ok: true,
@@ -170,8 +183,9 @@ export function createTokenVerifier(
170
183
  export function createToolAuthMiddleware(
171
184
  externalApiKeyService: IExternalApiKeyService,
172
185
  internalTokenService: InternalTokenService,
186
+ accounts?: Pick<AuthService, 'isActive'>,
173
187
  ): RequestHandler {
174
- const verify = createTokenVerifier(externalApiKeyService, internalTokenService);
188
+ const verify = createTokenVerifier(externalApiKeyService, internalTokenService, accounts);
175
189
  return async (req: Request, res: Response, next: NextFunction) => {
176
190
  const header = req.headers.authorization;
177
191
  if (!header || !header.toLowerCase().startsWith('bearer ')) {
@@ -210,9 +224,9 @@ export function createToolAuthMiddleware(
210
224
  export function createManualAuthMiddleware(
211
225
  externalApiKeyService: IExternalApiKeyService,
212
226
  internalTokenService: InternalTokenService,
213
- authService: AuthService,
227
+ authService: Pick<AuthService, 'isActive' | 'resolveSession'>,
214
228
  ): RequestHandler {
215
- const verify = createTokenVerifier(externalApiKeyService, internalTokenService);
229
+ const verify = createTokenVerifier(externalApiKeyService, internalTokenService, authService);
216
230
  return async (req: Request, res: Response, next: NextFunction) => {
217
231
  const header = req.headers.authorization;
218
232
  if (!header || !header.toLowerCase().startsWith('bearer ')) {
@@ -241,13 +255,22 @@ export function createManualAuthMiddleware(
241
255
  }
242
256
 
243
257
  // Otherwise treat the bearer as a browser session JWT.
258
+ let userId: string;
244
259
  try {
245
- const { userId } = authService.verifyToken(token);
246
- req.toolAuth = { source: 'session', userId, scope: 'write' };
247
- next();
248
- } catch {
260
+ ({ userId } = await authService.resolveSession(token));
261
+ } catch (err) {
262
+ // The account could not be looked up: an outage, not a verdict on the
263
+ // session, and a 401 would sign a valid caller out over it.
264
+ if (err instanceof AuthBackendError) {
265
+ log.error('session account lookup failed:', { err: err.cause });
266
+ res.status(500).json({ error: 'Authentication backend unavailable' });
267
+ return;
268
+ }
249
269
  res.setHeader('WWW-Authenticate', WWW_AUTH);
250
270
  res.status(401).json({ error: 'Invalid or expired token' });
271
+ return;
251
272
  }
273
+ req.toolAuth = { source: 'session', userId, scope: 'write' };
274
+ next();
252
275
  };
253
276
  }
@@ -12,6 +12,7 @@ import { createToolHandlerFactory } from '../tool-handler.js';
12
12
  import { registerWorkspaceTools } from '../../workspace/workspace.tools.js';
13
13
  import { RoutineWritePolicyService } from '../../workspace/routine-write-policy.js';
14
14
  import { WorkflowHooks } from '../../workflow/workflow-hooks.js';
15
+ import { ToolDescriptionNotes } from '../../workspace/agent-access.gate.js';
15
16
  import { SpillStore } from '../../workspace/spill-store.js';
16
17
  import { DocExtractService } from '../../workspace/file-readers/doc-extract.service.js';
17
18
  import { NEW_ROLE_GUIDANCE } from '../../access-model/roles-yaml-guard.js';
@@ -68,13 +69,14 @@ async function start(): Promise<string> {
68
69
  canOwner: async () => true,
69
70
  canWriteBatchAtRef: async () => null,
70
71
  canReadBatch: async (_w: string, _u: string, paths: string[]) => new Map(paths.map((p) => [p, true])),
72
+ // The `after` half of a move's or copy's preview — allowing everything, like the gates above.
73
+ previewAccessAfterRelocation: async () => ({ read: true, write: true, download: true, owner: true }),
71
74
  } as never;
72
75
  registerWorkspaceTools(registry, router, toolAuth, createToolHandlerFactory(resolve), new SpillStore(path.join(os.tmpdir(), 'bevel-test-spills')), new DocExtractService(docCache), allowAll, testKbContext({ kbDirName: KB }), {
73
- service: {} as never,
74
- enabled: false, // ontology boundary not under test here
75
- kb: testKbContext({ kbDirName: KB }),
76
+ // No hook registered: the agent-access gate refuses nothing here.
76
77
  recoveryBotEmail: 'recovery-bot@bevel.local',
77
78
  hooks: new WorkflowHooks(),
79
+ notes: new ToolDescriptionNotes(),
78
80
  }, new RoutineWritePolicyService(), {} as never);
79
81
  const app = express();
80
82
  app.use(express.json());
@@ -10,6 +10,7 @@ import { createManualRoutes } from '../../tool-registry/manual.routes.js';
10
10
  import { registerWorkspaceTools } from '../../workspace/workspace.tools.js';
11
11
  import { RoutineWritePolicyService } from '../../workspace/routine-write-policy.js';
12
12
  import { WorkflowHooks } from '../../workflow/workflow-hooks.js';
13
+ import { ToolDescriptionNotes } from '../../workspace/agent-access.gate.js';
13
14
  import { SpillStore } from '../../workspace/spill-store.js';
14
15
  import { DocExtractService } from '../../workspace/file-readers/doc-extract.service.js';
15
16
  import { testKbContext } from '../../../__tests__/kb-context.js';
@@ -65,11 +66,10 @@ async function start(): Promise<string> {
65
66
  // and their web_search / submit_feedback / cite_kb_node cases — moved to
66
67
  // the enterprise repo with their modules; core registers only core tools.
67
68
  registerWorkspaceTools(registry, router, toolAuth, th, new SpillStore('/tmp/bevel-test-spills'), new DocExtractService('/tmp/bevel-test-doc-extract'), accessControl, testKbContext(), {
68
- service: {} as never,
69
- enabled: false, // ontology boundary not under test here
70
- kb: testKbContext(),
69
+ // No hook registered: the agent-access gate refuses nothing here.
71
70
  recoveryBotEmail: 'recovery-bot@bevel.local',
72
71
  hooks: new WorkflowHooks(),
72
+ notes: new ToolDescriptionNotes(),
73
73
  }, new RoutineWritePolicyService(), {} as never /* sessionSink — start_session not exercised here */);
74
74
  router.use(createManualRoutes(registry, toolAuth));
75
75
 
@@ -18,7 +18,11 @@ const externalApiKeyService = {
18
18
  verifyAndLoadToken: async (t: string) =>
19
19
  t === 'bevel_ok' ? { user: { id: 'u-ext', email: 'e@x', name: 'Ext' }, tokenId: 'tk-1' } : null,
20
20
  } as never;
21
- const authService = { getUserById: async (id: string) => ({ id, email: 'e@x', name: 'N' }) } as never;
21
+ // Every account is on except `u-off`, which an admin switched off.
22
+ const authService = {
23
+ getUserById: async (id: string) => ({ id, email: 'e@x', name: 'N' }),
24
+ isActive: async (id: string) => id !== 'u-off',
25
+ } as never;
22
26
 
23
27
  let tempDir = '';
24
28
  const workspaceService = {
@@ -66,6 +70,12 @@ describe('validateToken (tool-author SDK entry)', () => {
66
70
  expect((await fs.readFile('a.md')).toString()).toBe('hi');
67
71
  });
68
72
 
73
+ it('throws a 401 ToolError for a valid internal token whose account was switched off', async () => {
74
+ const err = await validateToken(internalToken.mint({ userId: 'u-off' })).catch((e) => e);
75
+ expect(hasHttpStatus(err) && err.status).toBe(401);
76
+ expect((err as Error).message).toContain('switched off');
77
+ });
78
+
69
79
  it('throws a 401 ToolError on a bad token and an empty token', async () => {
70
80
  for (const bad of ['garbage', 'bevel_nope', '', undefined]) {
71
81
  const err = await validateToken(bad as string).catch((e) => e);
@@ -6,6 +6,7 @@ import { hasHttpStatus, ToolError, type ToolHandler } from './tool.contract.js';
6
6
  import { WorkflowDomainError } from '../../shared/domain-errors.js';
7
7
  import { domainErrorBody } from '../../shared/http-errors.js';
8
8
  import type { ResolveToolContext } from './tool-context.js';
9
+ import { alwaysWritable, READ_ONLY_CODE, refuseWriteTool, type IWriteAccess } from '../write-access/write-access.js';
9
10
  import '../tool-auth/tool-auth.middleware.js'; // Express Request.toolAuth augmentation
10
11
 
11
12
  function isAsyncIterable(v: unknown): v is AsyncIterable<unknown> {
@@ -29,7 +30,7 @@ export interface ToolHandlerOptions {
29
30
  * The handler receives `req.body` as the flat args (UTCP's `body_field` already
30
31
  * delivered the inner body as the request body).
31
32
  */
32
- export function createToolHandlerFactory(resolve: ResolveToolContext) {
33
+ export function createToolHandlerFactory(resolve: ResolveToolContext, writeAccess: IWriteAccess = alwaysWritable) {
33
34
  return function toolHandler(handler: ToolHandler, opts: ToolHandlerOptions = {}) {
34
35
  return async (req: Request, res: Response): Promise<void> => {
35
36
  const auth = req.toolAuth;
@@ -41,10 +42,29 @@ export function createToolHandlerFactory(resolve: ResolveToolContext) {
41
42
  res.status(403).json({ error: 'This tool requires write access.' });
42
43
  return;
43
44
  }
45
+ // Before anything is awaited: a client that goes away during the
46
+ // write-access check below must still abort the call.
47
+ //
48
+ // Asked of the RESPONSE. The request closes as soon as its body has
49
+ // been read, with the client still there and waiting, so its `close`
50
+ // says nothing about the client; the response closes when it has been
51
+ // sent or the connection is gone, and `writableEnded` tells the two
52
+ // apart.
44
53
  const abort = new AbortController();
45
- req.on('close', () => {
54
+ res.on('close', () => {
46
55
  if (!res.writableEnded) abort.abort();
47
56
  });
57
+ // The tool layer's half of the read-only gate: the HTTP gate lets every
58
+ // tool call through, since only here is a write tool told from a read.
59
+ if (opts.write) {
60
+ const refusal = await refuseWriteTool(writeAccess);
61
+ if (refusal !== null) {
62
+ res.status(403).json({ error: refusal, code: READ_ONLY_CODE });
63
+ return;
64
+ }
65
+ // The client left while the verdict was awaited: nobody to answer.
66
+ if (abort.signal.aborted) return;
67
+ }
48
68
  const body: unknown = req.body;
49
69
  if (body !== undefined && body !== null && (typeof body !== 'object' || Array.isArray(body))) {
50
70
  res.status(400).json({ error: 'Request body must be a JSON object.' });
@@ -53,8 +73,8 @@ export function createToolHandlerFactory(resolve: ResolveToolContext) {
53
73
  const args = (body ?? {}) as Record<string, unknown>;
54
74
  // `sessionId` rides the tool body like `branch` does: the external MCP
55
75
  // proxy injects it (ask-tool continuity convention) and the in-process
56
- // agent passes its thread id. Surfaced on the context so the ontology
57
- // gate can scope to one run without each handler re-reading args.
76
+ // agent passes its thread id. Surfaced on the context so the
77
+ // agent-access gate can name the run without each handler re-reading args.
58
78
  const sessionId = typeof args.sessionId === 'string' && args.sessionId.length > 0
59
79
  ? args.sessionId
60
80
  : undefined;
@@ -34,8 +34,9 @@ export interface ToolContext {
34
34
  * The agent run / conversation id this call belongs to (the agent thread id).
35
35
  * Supplied as a `sessionId` field on the tool's input body — the external MCP
36
36
  * proxy injects it via the `ask`-tool continuity convention, and the
37
- * in-process agent passes its thread id. Used to scope the ontology-session
38
- * boundary to one run. Absent for non-agent or unthreaded calls.
37
+ * in-process agent passes its thread id. Handed to the agent-access hooks,
38
+ * which is how a deployment scopes a rule to one conversation. Absent for
39
+ * non-agent or unthreaded calls.
39
40
  */
40
41
  sessionId?: string;
41
42
  /**
@@ -35,7 +35,7 @@ export type ValidateToken = (
35
35
  ) => Promise<ToolContext>;
36
36
 
37
37
  export function createToolValidator(deps: ToolValidatorDeps): ValidateToken {
38
- const verify = createTokenVerifier(deps.externalApiKeyService, deps.internalTokenService);
38
+ const verify = createTokenVerifier(deps.externalApiKeyService, deps.internalTokenService, deps.authService);
39
39
  const resolve = createToolContextResolver(deps);
40
40
  return async (token, signal) => {
41
41
  let result;
@@ -65,32 +65,24 @@ export function registerToolManualsTools(
65
65
 
66
66
  const listSetupDef = toolDef({
67
67
  name: 'list_tool_setup',
68
+ // What each FIELD means is documented on the field, in `outputs` below:
69
+ // `setup.kind`, `variables` and `invalid` each carried a paragraph here,
70
+ // which made this description two thousand characters and so the first
71
+ // thing a client cut. The description says what the tool answers and the
72
+ // three things an agent cannot read off a field.
68
73
  description:
69
- 'Configuration status of every `.tool` the current user can access: what each tool needs set up and ' +
70
- 'what is already configured. Results are scoped to the caller — a `.tool` the caller cannot READ is ' +
71
- 'absent entirely, and all status flags reflect the caller\'s own state. Per tool: `setup` describes ' +
72
- 'an MCP server\'s sign-in requirement (`open` = none; `oauth-auto` = sign-in was configured ' +
73
- 'automatically; `oauth-manual` = the sign-in needs an OAuth app the owner registers with the provider: ' +
74
- 'a writer declares its client id on a `user`-scoped variable with an `oauth` block — in the plugin.json ' +
75
- 'extensions entry for an mcp.json server (endpoints are discovered from the server; PKCE is on by ' +
76
- 'default), or in the `.tool` file with explicit URLs — and pastes the client secret on the tool\'s page. ' +
77
- '`setup.reason` is present only while something still blocks the sign-in and says what). ' +
78
- 'Per variable: whether the shared (admin) value is set, whether the CURRENT user has ' +
79
- 'set/authorized their own, and whether it is an OAuth sign-in (users authorize those on the /connect ' +
80
- 'page, never by typing a value). `canWrite` = the caller may write THAT `.tool` FILE (per-file access ' +
81
- 'from its frontmatter `write:`/`owner:` verbs and the access.md chain — NOT a platform role), which ' +
82
- 'is exactly what gates setting its shared secrets: the people who manage the file configure the tool. ' +
83
- 'Secret VALUES are never returned and can never be set through a tool — an admin enters them in the ' +
84
- 'tool editor; users sign in on /connect. ' +
85
- '`invalid` names any `.tool` file the scan REFUSED, with the reason and its location: those files ' +
86
- 'are the only ones missing — every other tool is listed and callable, and a refused file is listed ' +
87
- 'again as a normal tool on the next call once it is fixed (or removed), with nothing to restart or ' +
88
- 'reconnect. ' +
89
- 'The listing is the RELEASED catalog, built from the default branch only: a server or `.tool` you ' +
90
- 'declared on a draft is not listed, not callable and not signed-in-able until that draft is merged. ' +
91
- 'Pass `branch` (the draft you wrote the declaration on) and `onBranchOnly` names every tool declared ' +
92
- 'there that the default branch does not serve yet — open a change request, then ask the user to ' +
93
- 'review and merge it in the app to activate it.',
74
+ 'Configuration status of every `.tool` the current user can access: what each tool needs set up and what is ' +
75
+ 'already configured, as `{ tools, invalid, onBranchOnly, note? }`. Scoped to the CALLER — a `.tool` it cannot ' +
76
+ 'READ is absent entirely, and every flag is the caller\'s own state. ' +
77
+ 'Secret VALUES are never returned and can never be set through a tool: an admin enters them in the tool editor, ' +
78
+ 'and users sign in on /connect rather than typing a value. ' +
79
+ 'What gates setting a tool\'s shared secrets is `canWrite` on the `.tool` FILE — per-file access from its ' +
80
+ 'frontmatter `write:`/`owner:` verbs and the access.md chain, NOT a platform role: the people who manage the ' +
81
+ 'file configure the tool. ' +
82
+ 'The listing is the RELEASED catalog, built from the default branch only: a server or `.tool` you declared on a ' +
83
+ 'draft is not listed, not callable and not signed-in-able until that draft is merged. Pass `branch` (the draft ' +
84
+ 'you wrote the declaration on) and `onBranchOnly` names every tool declared there that the default branch does ' +
85
+ 'not serve yet — open a change request, then ask the user to review and merge it in the app to activate it.',
94
86
  path: '/api/agent/tools/list_tool_setup',
95
87
  inputs: {
96
88
  type: 'object',
@@ -119,12 +111,32 @@ export function registerToolManualsTools(
119
111
  type: { type: 'string' },
120
112
  setup: {
121
113
  type: ['object', 'null'],
122
- description: 'MCP auto-discovery setup requirement; null for non-mcp tools.',
123
- properties: { kind: { type: 'string' }, reason: { type: 'string' } },
114
+ description: "An MCP server's sign-in requirement; null for non-mcp tools.",
115
+ properties: {
116
+ kind: {
117
+ type: 'string',
118
+ description:
119
+ '`open` = no sign-in; `oauth-auto` = sign-in was configured automatically; `oauth-manual` = the ' +
120
+ 'sign-in needs an OAuth app the owner registers with the provider: a writer declares its client ' +
121
+ 'id on a `user`-scoped variable with an `oauth` block — in the plugin.json extensions entry for ' +
122
+ 'an mcp.json server (endpoints are discovered from the server; PKCE is on by default), or in the ' +
123
+ '`.tool` file with explicit URLs — and pastes the client secret on the tool\'s page.',
124
+ },
125
+ reason: {
126
+ type: 'string',
127
+ description: 'Present only while something still blocks the sign-in, and says what.',
128
+ },
129
+ },
130
+ },
131
+ canWrite: {
132
+ type: 'boolean',
133
+ description: 'You may write this `.tool` FILE, which is what gates setting its shared secrets.',
124
134
  },
125
- canWrite: { type: 'boolean' },
126
135
  variables: {
127
136
  type: 'array',
137
+ description:
138
+ 'What this tool needs configured, and by whom: per variable, whether the shared (admin) value is ' +
139
+ 'set and whether the CURRENT user has set or authorized their own.',
128
140
  items: {
129
141
  type: 'object',
130
142
  properties: {
@@ -150,9 +162,10 @@ export function registerToolManualsTools(
150
162
  invalid: {
151
163
  type: 'array',
152
164
  description:
153
- '`.tool` files the scan refused — the ONLY tools missing from `tools`. Each names the file and ' +
154
- 'why it was refused, with the line/column or field where the validation failed. Fix the file (or ' +
155
- 'delete it) and the next call lists it as a normal tool. Never contains a secret value.',
165
+ '`.tool` files the scan refused — the ONLY tools missing from `tools`; every other tool is listed and ' +
166
+ 'callable. Each names the file and why it was refused, with the line/column or field where the validation ' +
167
+ 'failed. Fix the file (or delete it) and the next call lists it as a normal tool, with nothing to restart ' +
168
+ 'or reconnect. Never contains a secret value.',
156
169
  items: {
157
170
  type: 'object',
158
171
  properties: {